Compare commits

...

616 Commits

Author SHA1 Message Date
nicktrn e714f3262a Release 4.0.0-v4-beta.0 (#1907)
* fix package versions in references

* update lockfile
2025-04-09 16:12:36 +01:00
github-actions[bot] b949b5d257 chore: Update version for release (v4-beta) (#1755)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-09 16:07:14 +01:00
Eric Allam bc8901dbf1 chore: configure changesets to enter prerelease mode on v4-beta tag (#1869)
* Add major v4 changeset

* Enter v4 prerelease mode in changesets

* Use v4-beta tag instead of v4
2025-04-09 15:55:34 +01:00
nicktrn 98a3cfbfa1 Clear runner state of previous executions before warm starts (#1905)
* attach all run controller logs to the run

* make controller-level pre-suspend wait duration configurable

* snapshot status should remain EXECUTING for short retry delays

* add suspended process error

* ensure clean slate before waiting for next run

* treat immediate retries as warm starts

* fix for finished runs waiting forever
2025-04-09 15:53:42 +01:00
Matt Aitken db97f80646 Fix for queue indexing not updating concurrencyLimit/removing it (#1906) 2025-04-09 15:53:21 +01:00
James Ritchie 8382274402 Fixes a padding issue on the org menu dropdown (#1903) 2025-04-09 14:00:57 +01:00
nicktrn ebb71932f9 snapshot status should remain EXECUTING for short retry delays (#1902) 2025-04-09 13:23:39 +01:00
James Ritchie f12141b307 Removes hard coded “5” from “5 related runs” in the waitpoint token inspector (#1901) 2025-04-09 12:21:11 +01:00
Matt Aitken 3ce6144339 Added the Attempt span error back (and properties) (#1900) 2025-04-08 22:45:11 +01:00
Matt Aitken 3cbbece473 Run completed at fix and engine API rate limit whitelist (#1899)
* Use completedAt instead of updatedAt

* Disable verbose SSE logs

* Engine rate limiter: whitelist engine worker-actions

* Show the compute column in the runs table

* The compute column should be invocation + compute cost
2025-04-08 20:55:35 +01:00
Matt Aitken 79d6044dd0 Added engine worker immediatePollIntervalMs and limit env vars (#1898) 2025-04-08 15:39:44 +01:00
nicktrn 72d98778eb Fix node options and OOM retries (#1897)
* don't hardcode max old space size in deployment images

* flags: treat underscores as hyphens

* append attempt number to runner name if >1

* improve retry spans for oom errors
2025-04-08 13:07:45 +01:00
Matt Aitken a51b9b9f98 Fix: custom queue releaseConcurrencyOnWaitpoint. (#1896)
* Make sure releaseConcurrencyOnWaitpoint is set on taskQueue

* Allow task queue to have releaseConcurrencyOnWaitpoint (required to fix custom queues)

* Test for releaseConcurrencyOnWaitpoint

* Added a warning if you use the same queue twice with different settings

* Improved the error some more
2025-04-08 12:32:22 +01:00
Logan Mimaroglu 9d261f4986 Update atomic-deployment.mdx (#1895) 2025-04-08 10:08:31 +01:00
nicktrn fa0e2bf17c Fix v4 build workflow (#1894) 2025-04-07 17:34:07 +01:00
Matt Aitken 56a880f1bd Fix dequeue snapshot batch ids (#1893)
* Pass the batchId through from previous snapshots when dequeuing

* Fix and test for resuming batch after checkpoint
2025-04-07 17:22:56 +01:00
James Ritchie b322cf1eda Fix for long file names wrapping instead of truncating on the RunTimeline (#1891) 2025-04-07 16:36:22 +01:00
James Ritchie 86e56144c0 Adds selected state for waitpoint table rows (#1892) 2025-04-07 16:35:58 +01:00
Matt Aitken fc0a331c42 We weren’t copying the batch info when suspending a run (#1890) 2025-04-07 16:00:07 +01:00
nicktrn a9d5646d75 Very short waits count towards compute usage (#1888)
* add duration wait threshold

* improve wording

* Updates the wait overview, wait.for and wait.until pages copy to better explain compute costs

---------

Co-authored-by: James Ritchie <james@trigger.dev>
2025-04-07 14:52:33 +01:00
Matt Aitken 32d2992802 Security update of nanoid from 3.3.4 to 3.3.8 (#1887) 2025-04-07 13:56:37 +01:00
Himanshu Sharma e546028e9b upgrade nanoid to latest version (#1886) 2025-04-07 11:31:06 +01:00
nicktrn 58567bfe44 Add v4 timeline metrics to prod runs (#1884)
* set correct worker name on create

* dev: kill run process on failed cancel attempt

* add missing prod timeline events

* add more debug logs after api call failures

* import events are always visible

* set dequeued at on new runners

* skip fork event test

* fix timeline test for import event
2025-04-04 18:39:13 +01:00
Eric Allam e7bca6fa1b fix: release concurrency system only consumes tokens when releasings are executed successfully (#1883)
* fix: release concurrency system only consumes tokens when releasings are executed successfully

* Add release concurrency metrics sending to otel
2025-04-04 17:20:33 +01:00
Matt Aitken 195c5d758b Update the path for the subcription portal (#1885) 2025-04-04 17:15:07 +01:00
Matt Aitken 36159be544 Warm start UI (#1882)
* Better trace icon

* Better Waitpoint token icon

* Fix for bad jsx

* Warm and cold start icons

* Tooltips now use a <Portal> so they appear on top

* Warm start components

* Added warm start markers to the Run page and inspector

* Fix for getting the correct value from the metadata

* Better trace icon, with fallback to a passed in one

* Removed unused isWarmStart function
2025-04-04 14:20:28 +01:00
nicktrn 7644d92efa Fix restored runner ID and various small enhancements (#1880)
* make worker nodetype configurable

* make max dequeue count configurable from supervisor

* deterministic runner ids

* update runner id on restore

* disable resource monitor

* explicit run controller type imports

* enhance debug logs
2025-04-04 12:35:23 +01:00
Eric Allam e6efb3c61b realtime: allow realtime streams to have it's own redis env vars (#1876) 2025-04-04 12:30:29 +01:00
Eric Allam 7b9ab1781a re2: add ability to selectively disable release concurrency queue consumers via env var (#1881)
* run engine: add ability to selectively disable release concurrency queue consumers via env var

Also added some additional logging

* actually pass the disableConsumers option through to the release concurrency system

* Fixed CLI e2e failing tests
2025-04-04 12:28:39 +01:00
Eric Allam 25c1abac8e v4: eagerly fork child processes before warm start initiates (#1879)
* v4: eagerly fork child process before warm start

Also fixes an issue where the attempt span events weren't coming through in the partial spans

* Making flushing a safer operation

* Add changeset

* Improve attempt metrics when in a warm start, and pass the STYLE_VARIANT for a warm or cold start on attempt spans
2025-04-04 11:07:36 +01:00
Dan 179a9c95c7 Added satori and improved react email (#1877)
* Improved the react email docs and added a video

* Copy

* Added a link to the cursor rules

* Added satori example

* Copy updates

* Added react pdf link
2025-04-03 16:42:54 +01:00
Eric Allam 18ad897171 fix: silence erroneous prisma package.json warnings (#1875) 2025-04-02 22:39:29 +01:00
Matt Aitken 5bdbf47fb3 Run page improvements (#1874)
* When selecting spans, don’t add to the browser history

* Only non-debug events can extend the total duration of the run timeline
2025-04-02 19:51:09 +01:00
nicktrn 7591b6e619 Fix v4 restore race condition (#1870) 2025-04-02 18:05:43 +01:00
Eric Allam cb6d697e6d chore: improve blank state copy (#1872) 2025-04-02 17:44:18 +01:00
Eric Allam 8e5d5cf690 fix: only pass engine version header when inside a task (#1871)
This will allow migrating to the v4 SDK in your application backend before deploying v4 to trigger, and everything will just work
2025-04-02 17:40:07 +01:00
nicktrn dcacc3a9e2 Fixes for v4 waits and restores (#1868)
* remove dummy metrics from heartbeat

* fix heartbeat timeouts..

* reset clock when resolving waitpoints

* optionally set metadata url when scheduling run

* don't use global vitest as it's outdated

* don't run test files in parallel

* handle special graceful shutdown code

* exit codes for success and failure can now be set and overridden

* ensure immediate cleanup in tests

* update lockfile after conflict
2025-04-02 15:27:01 +01:00
Eric Allam 6d819c6726 chore: publish docker even if the unit tests fail for now (#1867) 2025-04-02 14:20:16 +01:00
Eric Allam 3e341701ae v4 engine version selection logic updated (#1866) 2025-04-02 14:04:01 +01:00
Eric Allam 51bb3ee464 v4 ai improvements (#1863)
* AI SDK sql generator and approval example WIP

* Create some nice spans and extract the tools out so we can get the type of the streams

* Enable AI SDK telemetry

* Adding ai.tool and removing toolTask, 3rd party telemetry spans now wil create partials, better ai SDK telemetry icons

* Created a separate d3-chat example, split out from the openai-agents example

* Fixed the tool options being passed to metadata in `ai.tool`

* Add a link to the run

* Slightly improved design

* Add a crawler task using crawl4ai

* Use a tool to get the userId

* Couple of tweaks

* Adding markdown rendering to assistant messages and added an e2b based chart rendering task

* Backup to anthropic

* Add changeset
2025-04-02 12:00:02 +01:00
Eric Allam e539c7c941 v4: allow disabling of the run engine worker via the RUN_ENGINE_WORKER_ENABLED env var (#1865) 2025-04-02 11:39:55 +01:00
Eric Allam 31bef8d7cd fix: update migration with the "Drop/Add" index of SecretStore key (#1864) 2025-04-02 10:43:02 +01:00
Matt Aitken 7ce8bd1acd Queues page: fix for upgrade state, and upgrade docs link (#1862) 2025-04-01 19:20:23 +01:00
James Ritchie 49c067d755 Fixes the avatar squishing if the project name is too long (#1861) 2025-04-01 17:39:10 +01:00
James Ritchie 551b7947f2 New waitpoint token docs page + updates the URL and description for the waitpoint blank state in the app (#1860)
* Delete old wait-for pages & create new wait-for-token page

* Link to new wait-for-token page and update description

* Adds an upgrade to v4 coming soon docs page

* Removes unncessary property from Note component
2025-04-01 17:15:59 +01:00
James Ritchie a5b9151e3a Adds a "dev connected" banner to the top of the Run and Runs list pages (#1855)
* Adds DevPresenceBanner to the Run page

* Adds DevPresenceBanner to the Run list page

* Makes the DevConnection component reusable and moves components to the DevPresence.tsx file

* SideMenu: only show dev presence when project engine === V2

* WIP on disconnected banner on v3 and v4

* v3 dev connection working. Made it slightly red

* Only show the disconnected banner on v3 if the run is not completed

---------

Co-authored-by: Matt Aitken <matt@mattaitken.com>
2025-04-01 14:34:01 +01:00
Himanshu Sharma 28121249bb fix: prevent enqueuing into closed ReadableStream (#1781)
* fix: prevent enqueuing into closed ReadableStream

* Create smart-coins-hammer.md

---------

Co-authored-by: Eric Allam <eallam@icloud.com>
2025-04-01 11:52:41 +01:00
James Ritchie 81941c8910 Fixes a docs link and tweaks some wording for the triggering task blank state (#1856) 2025-04-01 11:33:07 +01:00
nicktrn bed834b589 Misc v4 checkpoint fixes (#1859)
* logs for optional services

* print env vars on startup in debug mode

* routes need to explicitly ask to keep connection alive

* log indicators for now

* make workload api listen host configurable

* expose supervisor metrics and make more configurable

* configurable pull secrets, no defaults

* remove restore route

* run controller to handle queued executing

* fix v3 deploys in v4 project

* update admin worker route

* only start pod cleaner et al in k8s mode

* set new worker group as default if none yet

* make image ref optional

* checkpoint image ref is optional for output as well

* export feature flag const

* one last image ref type fix

* make runner intervals configurable

* ability to set arbitrary env vars on new runners

* set default runtime back to node 21

* move all runner env vars to the same section
2025-04-01 10:08:15 +01:00
Eric Allam 9daa3977fa fix: safe upserting of task queues during worker indexing (#1858) 2025-03-31 16:28:08 +01:00
Eric Allam f320e14f38 cli: add dev lock file to prevent 2 dev processes running at the same time in the same dir (#1854)
* cli: add dev lock file to prevent 2 dev processes running at the same time in the same dir

* Make sure the .trigger dir exists before creating the dev.lock file
2025-03-31 16:27:11 +01:00
James Ritchie 4d5080d097 Fixes missing truncation on the project name text (#1857) 2025-03-31 14:49:37 +01:00
Matt Aitken 2b34dbe4b0 Fix for TaskRunErrorCodes.MAX_DURATION_EXCEEDED error saying ms when it’s seconds (#1848) 2025-03-31 14:48:03 +01:00
nicktrn 394f1decd3 Misc v4 fixes and improved logging (#1831)
* logs for optional services

* print env vars on startup in debug mode

* routes need to explicitly ask to keep connection alive

* log indicators for now

* make workload api listen host configurable

* expose supervisor metrics and make more configurable

* configurable pull secrets, no defaults

* remove restore route

* run controller to handle queued executing

* fix v3 deploys in v4 project

* update admin worker route

* only start pod cleaner et al in k8s mode

* set new worker group as default if none yet
2025-03-31 14:06:17 +01:00
Eric Allam c963dcda57 fix: use default retry settings when no catchError handlers are defined (#1852) 2025-03-31 13:57:02 +01:00
Eric Allam 0e77747d45 fix: runs.retrieve now uses the output stored on the TaskRun table instead of deprecated attempts table (#1853)
* fix: runs.retrieve now uses the output stored on the TaskRun table instead of deprecated attempts table

* Fixed typecheck error
2025-03-31 13:54:46 +01:00
Eric Allam 8977d2546c fix: custom queues defined on tasks on v3 now get correctly created (#1846) 2025-03-28 17:56:26 +00:00
Eric Allam af45e8397a fix: ensure task list and test action use correct dev env (#1845)
* Ensure the task list uses the dev environment of the logged in user

* Ensure the test task action uses the correct dev environment

* Fixed issue with missing case statement scope, wrapping in block
2025-03-28 17:55:05 +00:00
Dan 15b426d5b6 Add realtime / react hooks examples snippet (#1770)
* Added realtime / react hooks examples snippet and added it to all the relevant pages

* Fix link
2025-03-28 15:57:41 +00:00
James Ritchie b29f3ffb2d Updates the onboarding copy now we have multiple example projects (#1842) 2025-03-28 15:56:26 +00:00
James Ritchie f3efdad797 "Dev connecting" icon update + text color fix for the purple login buttons (#1835)
* Updates the large login buttons to the new purple style

* Updates the dev checking connection icon to include 3 dots on the screen

* Fixes svg property names
2025-03-28 15:56:00 +00:00
James Ritchie 182f5bc706 Fixes the disabled state for the Switch component (#1841) 2025-03-28 15:55:03 +00:00
James Ritchie 94c574bfef Fixes svg icon property for React (#1837) 2025-03-28 15:54:39 +00:00
Matt Aitken 5640467ce5 Task queues weren’t setting concurrencyLimit or releaseConcurrencyOnWaitpoint (#1844) 2025-03-28 15:53:56 +00:00
James Ritchie 73b41c28bf Support solo buttons in table row (#1840)
* Removed old condition that handled old v2 tables

* Adds support for a single button to be shown on hover in a table cell

* Makes the “hiddenButtons” support a single button instead of adding a new property

* Format the docs link for Schedules like the other for consistency
2025-03-28 15:53:15 +00:00
Eric Allam f4ef008e2c feat: Python otel support, enriching spans, OpenAI Agents SDK example (#1839)
* D3 demo WIP

* Agent working and completing token successfully

* Sending OpenAI Agent SDK spans through to the platform now works

* A couple of perf tweaks for enriching events
2025-03-28 13:01:59 +00:00
Dan 2a6d8251b2 Added react email example (#1802)
* Added react email docs

* Added jsx note

* Removed troubleshooting
2025-03-28 11:56:02 +00:00
Dan fc7e196a09 Added supabase auth page and updated examples (#1838)
* Added supabase auth page

* Added auth info snippet

* Added snippet and updated examples
2025-03-28 11:55:47 +00:00
James Ritchie 49c43a128b Adds a python logo icon (#1834) 2025-03-28 11:04:16 +00:00
Matt Aitken 174484fb32 Added default time period to Runs, Waitpoint and Batch list pages. Improved dev presence (#1832)
* New time period filter (permanently displayed)

* Batches, and fix for blank state

* Waitpoint token filtering

* Tags query: remove things we’re not using

* Run tag and waitpoint tags use startsWith for faster search

* Fix for run page on wrong env. Added schedule last triggered column

* Removed the Redis pubsub, just use the presence key

* Improve the dev presence responsiveness

* The CLI presence connection recovers when the webapp is restarted

* Dev schedules are now working for v4

* Refactored to make the dev presence stuff

* Got rid of stupid extra /dev, added a connecting state with icon

* Remove unused Redis client
2025-03-28 09:32:42 +00:00
Eric Allam 589f7c3bcc v4: Fix HandleErrorFunction type (#1829)
* v4: Fix HandleErrorFunction type

* Only show the catchError span if there are any catchError hooks
2025-03-28 09:15:33 +00:00
James Ritchie ff6359caf2 Buttons: Updates the primary button style to fit the UI style better (#1828)
* Move to indigo primary button to better fit in with the app UI style

* Updates Pause queue button to secondary

* Updates some blank state CTA buttons to primary
2025-03-27 13:25:34 +00:00
Eric Allam 0e82698548 v4: Waitpoint PAT completion, fix dequeuing without retry config, fixed metadata (#1826)
* WIP

* Completing waitpoints using public access tokens

* fixed example

* adding agent-loops reference, adding useWaitToken react hook, adding a way to throw an error to output from the run, adding an icon option to logger.trace

* Fixed metadata system

* Check if the waitpoint is COMPLETED already in the complete endpoint and return true
2025-03-27 10:04:12 +00:00
James Ritchie be02439d1d Icons: updates and new icons for the side menu, run page and general improvements to the way they work (#1825)
* New format for the functions instead of “onWait() task”

* Make the icon sizes match the hero icons

* Updating icons for the new task hooks

* Adds new icons to the RunIcon.tsx component

* Improves the size of the menu icons

* Fixes the missaligned environment dropdown and dev connected button

* Changes the button colours so they are all unique

* More icon updates

* Adds new variables for main page icons

* Improves icons sizes

* Using new color variables in the side menu and blank states

* Adds preview environment color variable

* Align the env icon in the menu

* Updates the env icons to be the correct size and padding to match hero icons

* Label uses new colors, removes unused cases and new env icons

* Lower case env label

* Removes unused code

* Use full env title

* Organize imports

* More variants of the runs icon to work at smaller sizes

* small padding adjustment

* reformat init function span

* Fix last init formatting

* New colour for settings

* Organize switch statement for icons nicely

* Use new color variable

* Update icon svgs

* Renamed lifecycle hook icon to Function icon

* Renamed function icon file name
2025-03-26 19:12:12 +00:00
James Ritchie d745bb63ae Cursor rules: Copy improvements and reordering sections (#1813)
* Re-ordered some rules for the video

* Adds description back in
2025-03-26 18:38:01 +00:00
Matt Aitken 00586ffaaf Waitpoint tokens page, wait.listTokens() and wait.retrieveToken() (#1824)
* Added waitpoints/tokens to the sidebar

* Added indexes to the Waitpoint time for filtering

* Begun work on `WaitpointTokenListPresenter`, the pag is a copy of the Queues page for now

* MVP of waitpoint token page

* Added status

* Expiry of timeout/ttl

* Improvements to the waitpoint table

* Improved columns and icon

* Changes from the RunTag copy on hover branch

* Fix for nested button error

* Added waitpoint tags to the DB/table

* Applied Eric’s task run tag fix (it’s live on prod in the legacy run engine branch)

* Added tags to waitpoints

* Removed todos that have been done

* Added token support for releaseConcurrency. Also added a ton of JSDocs

* Added releaseConcurrency to the API token endpoint…

* WIP on waitpoint page filters

* Fix for tags filtering

* Waitpoint filters working

* Fix for badly named function

* WaitpointPresenter used from SpanPresenter

* Waitpoint detail panel WIP

* Fix for server client hydration issue with CodeBlock

* Selected waitpoint panel

* Added a blank state

* Added waitpoint docs link

* Fix for animated number going past the target

* Fix for the queue list pagination and upgrade status

* Engine version error for waitpoint token list

* RunTag component doesn’t get squished and hover behaviour is nicer

* Associating runs with waitpoints

* Added triggered icon

* Link directly to the waitpoint

* Fix for TS error on waitpoint retrieve

* Added CopyableText component, used for waitpoint id in the table

* Removed the confetti 🎊

* Deleted some old images

* Moved some schemas/types to core. Use `id` instead of `friendlyId`

* Added wait.listTokens() function. Made some changes to the types to make it nicer

* WIP wait.retrieveToken()

* wait.retrieveToken working

* Added data to retrieve token

* Separate ApiWaitpointPresenter completely

* Added completed time to the waitpoint detail panel

* Fix for the Avatar component having SSR issues. Specify the size in rems and removed the useLayoutEffect

* Fix for applied idempotency key filter dropdown showing the id field

* Use parentheses to make sure the token list query respects idempotency key correctly

* Use the proper logger, and have a decent message with info to track the bug down

* Pass the org title into the Avatar

* Better error when failing to creating a manual waitpoint after X attempts
2025-03-26 18:05:26 +00:00
nicktrn c08c3b488c Update lockfile (#1822) 2025-03-26 10:55:59 +00:00
nicktrn 8c3542cfd6 Small package release tweaks (#1821)
* set core version for redis-worker

* add empty changelog
2025-03-26 10:39:11 +00:00
Eric Allam d6fd7f559e Run engine: Using root queue timestamp to prioritize completing runs (#1818)
* Improve the new run engine Trigger/Batch trigger service class names

* centralize queue timestamp logic in EnqueueSystem, adding queueTimestamp support and propagation

* Fixing the create tags in the new run engine trigger task service
2025-03-26 10:25:49 +00:00
nicktrn 4fe1d49f94 Add v4 pod lifecycle handlers (#1819)
* patch k8s client to allow field selector on informers

* add pod cleaner and tests

* add failed pod handler and tests

* remove supervisor test script for now
2025-03-26 10:22:24 +00:00
nicktrn 5f48b9c7f9 Fix worker deployment engine version (#1820)
* enable re2 cli flag by default

* background worker engine determines deployment type
2025-03-26 10:22:13 +00:00
Eric Allam 82251e21f2 Merge pull request #1817 from triggerdotdev/revamped-lifecycle-hooks
v4: New lifecycle hooks
2025-03-25 16:46:34 +00:00
Eric Allam 6d5d10a45a Support other init file names 2025-03-25 16:33:12 +00:00
Eric Allam 1def8d06c2 Use allSettled so onWait/onResume errors don't break anything 2025-03-25 16:32:17 +00:00
Eric Allam e745fcb663 Remove console.log 2025-03-25 15:58:50 +00:00
Eric Allam 887c5cac5c Add changeset 2025-03-25 15:56:55 +00:00
Eric Allam 268c9f6898 implement onWait/onResume 2025-03-25 15:55:42 +00:00
Eric Allam 3ca5985277 Hooks now all use the new types, and adding some spans 2025-03-25 15:55:42 +00:00
Eric Allam df9cad6667 Get it all working 2025-03-25 15:55:42 +00:00
Eric Allam ba520fcdd8 Make sure and register all the config hooks 2025-03-25 15:55:42 +00:00
Eric Allam 23ae77afff handle max duration timeout errors better 2025-03-25 15:55:42 +00:00
Eric Allam 30705fd59b implement cleanup 2025-03-25 15:55:42 +00:00
Eric Allam d0a5c16c63 Add cleanup hook 2025-03-25 15:55:42 +00:00
Eric Allam 6ba2b0476b more tests 2025-03-25 15:55:42 +00:00
Eric Allam 5edbb77dfe use tryCatch in TaskExecutor 2025-03-25 15:55:42 +00:00
Eric Allam 7f804f381c middleware working and tests passing 2025-03-25 15:55:42 +00:00
Eric Allam 6ab74c47d6 implement and test handleError (now catchError) 2025-03-25 15:55:42 +00:00
Eric Allam f84ddcd6ea test oncomplete 2025-03-25 15:55:42 +00:00
Eric Allam 87824a8ce3 dry up some stuff 2025-03-25 15:55:42 +00:00
Eric Allam 91225da813 test on failure 2025-03-25 15:55:42 +00:00
Eric Allam 2262a42632 more tests and fixes 2025-03-25 15:55:42 +00:00
Eric Allam ef871b0434 starting test executor 2025-03-25 15:55:42 +00:00
Eric Allam 4a8d6932c8 a couple tweaks 2025-03-25 15:55:42 +00:00
Eric Allam 307309f4f7 implement onComplete 2025-03-25 15:55:42 +00:00
Eric Allam b974d8297d use new onFailure 2025-03-25 15:55:42 +00:00
Eric Allam 116788912b use new onStart 2025-03-25 15:55:42 +00:00
Eric Allam 2e6d0d8783 share the hook registration code 2025-03-25 15:55:42 +00:00
Eric Allam 2d16d664ef Add middleware hooks 2025-03-25 15:55:42 +00:00
Eric Allam 6001dfa7de Add middleware types 2025-03-25 15:55:42 +00:00
Eric Allam dc6e659e94 new locals API 2025-03-25 15:55:42 +00:00
Eric Allam e34e520057 more hooks 2025-03-25 15:55:42 +00:00
Eric Allam 597b7baaaa adding imports 2025-03-25 15:55:42 +00:00
Eric Allam e77e8d48d6 handleError 2025-03-25 15:55:42 +00:00
Eric Allam aaf2ed8a20 onWait and onResume 2025-03-25 15:55:42 +00:00
Eric Allam e816ba4382 onComplete 2025-03-25 15:55:42 +00:00
Eric Allam 06f6a04056 onStart 2025-03-25 15:55:42 +00:00
Eric Allam be676fdb33 onFailure 2025-03-25 15:55:42 +00:00
Eric Allam 1777ff4cfb move onStart to the new lifecycle hook system 2025-03-25 15:55:42 +00:00
Eric Allam 17e162310d Improve init lifecycle hook types and fix tabler icons on spans 2025-03-25 15:55:42 +00:00
Eric Allam 2c44e59d07 init.ts at the root of the trigger dir is now automatically loaded 2025-03-25 15:55:42 +00:00
Eric Allam 0215a8cfa8 vibes 2025-03-25 15:55:42 +00:00
Eric Allam f59bad9227 Revamping the lifecycle hooks, starting with init 2025-03-25 15:55:42 +00:00
James Ritchie 2ef60ce51f Side menu: style fixes, small improvements and better Info Panel component props (#1816)
* Adds “plan” to the plan name if it’s a paid plan so they all format correctly

* Fixes menu item having incorrect height

* Adds gap between switch org menu items

* Moves main action button to right side in blank state

* Simpler way to add “plan” to the plan name

* Styled the plan badge a bit nicer

* Project page now matches the other settings page layouts

* Fixes colour of queue icon

* Fixes table blank state not spanning enough cols

* Organize imports

* Unified the blank state panels and improved some copy in them

* Improved the copy in the no deployed tasks blank state

* Make the upgrade icon solid and indigo

* Transition the hover state on the side menu section

* Replaces many props with a single ‘accessory’ React node on the Info Panel component

* Organize imports
2025-03-24 14:44:08 +00:00
Eric Allam c4e1364cf9 Merge pull request #1808 from triggerdotdev/re2-queue-indexing
re2: Queue indexing, queue trigger changes
2025-03-21 16:09:15 +00:00
Eric Allam f1d5886c11 Another attempt at getting CI to pass tests 2025-03-21 15:50:51 +00:00
Eric Allam b72b7c2c52 Fixed typechecks 2025-03-21 15:22:53 +00:00
Eric Allam f46a409f0e Fixed typescript error 2025-03-21 15:06:55 +00:00
Eric Allam 5a725675d3 Fix the unit tests CI action 2025-03-21 15:06:55 +00:00
Eric Allam 671ff26d50 Removing more exportName stuff 2025-03-21 15:06:55 +00:00
Eric Allam 109bfaca00 Remove exportName from the trigger span names 2025-03-21 15:06:55 +00:00
Eric Allam b65f2256e0 Tasks are indexed even if you don't export them 🤩 2025-03-21 15:06:55 +00:00
Eric Allam c87ff2c634 Various fixes for no longer having the exportName 2025-03-21 15:06:55 +00:00
Eric Allam 233a22d59e Move runs sidebar menu item below tasks and alerts into the manage section 2025-03-21 15:06:55 +00:00
Eric Allam e53eed1753 Fixed the types on pauseQueue service 2025-03-21 15:06:55 +00:00
Eric Allam ad08756001 Run tests sequentially in CI, and pass "--run" to vitest to disable watch mode 2025-03-21 15:06:55 +00:00
Eric Allam 8d514b902d Use orderableName on the TaskQueue and add the releaseConcurrencyOnWaitpoint to the SDK and the dashboard 2025-03-21 15:06:55 +00:00
Eric Allam f58dee1aa1 Fix the task run status reason UI 2025-03-21 15:06:55 +00:00
Eric Allam de5e0c8e8b try to fix flaky tests 2025-03-21 15:06:55 +00:00
Eric Allam bee5e75931 Fixed cli e2e tests 2025-03-21 15:06:55 +00:00
Eric Allam f4a1eb1914 Fixed changesets and added one 2025-03-21 15:06:55 +00:00
Eric Allam 02eeefd647 Increase maxRetriesPerRequest in CI 2025-03-21 15:06:24 +00:00
Eric Allam cbe651e7e4 Fix useTaskTrigger queue option params and remove exportName from e2e tests 2025-03-21 15:06:24 +00:00
Eric Allam 941e03b3eb Using the new PENDING_VERSION status now in the UI 2025-03-21 15:06:24 +00:00
Eric Allam 1bec4e1432 Getting the queues indexing to work by passing them to the create background worker request bodies 2025-03-21 15:06:24 +00:00
Eric Allam 7e411ac162 New PENDING_VERSION system which now requires queues to exist at dequeue time 2025-03-21 15:06:24 +00:00
Eric Allam 7a58439728 WIP queue indexing 2025-03-21 15:06:24 +00:00
nicktrn 49a3f72e13 Publish redis-worker and add graceful shutdown manager (#1810)
* add shutdown manager

* update ai test instructions

* add shutdown timeout to redis-worker

* move redis worker to packages

* add unregister method

* prep for publishing package

* fix types

* update ai files

* fix cursor terminal links

* prevent overly friendly ids

* use structured logger

* use unique shutdown handler names

* rework suspend completion

* add trycatch util

* rework suspend restore

* add http server metrics

* add missing prom-client to core

* add prom metrics to redis worker

* bundle redis-worker

* fix esm/cjs interop

* remove proxy from changeset ignore and add supervisor

* add pause to prerelease script for any manual edits

* unregister the correct handler and add early detection

* small change to http handler return

* fix worker tests

* fix shutdown manager tests
2025-03-21 14:53:27 +00:00
Dan adca1997af Added turborepo and prisma docs (#1807)
* Added python links in guides intro

* Added example 1

* Updated intro

* Added example 2

* Typo

* Improved intro
2025-03-20 17:29:06 +00:00
Niels 1eb3fb34e1 Enhance RunTag parsing with robust key-value tag splitting (#1782)
* Enhance RunTag parsing with robust key-value tag splitting

* Clarify RunTag parsing documentation
2025-03-20 13:19:26 +00:00
Eric Allam a8b3c7003f Merge pull request #1804 from triggerdotdev/re2-reserve-concurrency
re2: New release concurrency system
2025-03-19 18:01:01 +00:00
Eric Allam 38e1887495 Fixed failing redis worker tests 2025-03-19 17:58:53 +00:00
Eric Allam e5ea9cb2d7 move the release concurrency queue into the release concurrency system, make it disabled by default, configure the run engine in the webapp with env vars 2025-03-19 17:55:29 +00:00
Eric Allam 28b3ed0496 Implement release concurrency system 2025-03-19 17:55:29 +00:00
Eric Allam 7eaf81abbe Use releaserId in case we don't end up using run IDs 2025-03-19 17:54:47 +00:00
Eric Allam f9c7e95981 Added updated execution states chart and updated readme 2025-03-19 17:54:47 +00:00
Eric Allam b170a62671 fixed the create checkpoint valid snapshot logic 2025-03-19 17:54:47 +00:00
Eric Allam e7c8f94447 implement the QUEUED_EXECUTING dequeuing, and creating a checkpoint while the run is in QUEUED_EXECUTING state by saving the EXECUTING_WITH_WAITPOINTS snapshotId as the previousSnapshotId on the QUEUED_EXECUTING snapshot 2025-03-19 17:54:47 +00:00
Eric Allam 29371e950b Implement checkpoint tests, handle dequeuing QUEUED_EXECUTING runs 2025-03-19 17:54:47 +00:00
Eric Allam 42fb5d05ff Fix more tests 2025-03-19 17:54:47 +00:00
Eric Allam 67d74f066c More tests passing, fixed the heartbeat issue 2025-03-19 17:54:47 +00:00
Eric Allam 7866e95636 waiting for worker system 2025-03-19 17:54:47 +00:00
Eric Allam a4581f1597 ttl system 2025-03-19 17:54:47 +00:00
Eric Allam 363f0668b5 Delayed run system 2025-03-19 17:54:47 +00:00
Eric Allam 63b43ff357 more system work 2025-03-19 17:54:47 +00:00
Eric Allam 717cec879b move startRunAttempt to RunAttemptSystem 2025-03-19 17:54:47 +00:00
Eric Allam 1b61b95c57 WIP run engine systems 2025-03-19 17:54:47 +00:00
Eric Allam 7d11e827d9 Remove reserve concurrency system from run engine 2025-03-19 17:54:47 +00:00
Eric Allam 8c66ec3189 Remove reserve concurrency system from run queue 2025-03-19 17:54:47 +00:00
Eric Allam 063651c7b8 Upgrade vitest in the run-engine package 2025-03-19 17:54:47 +00:00
Eric Allam 2048b72288 go to QUEUE_EXECUTING state if reacquiring concurrency doesn't work 2025-03-19 17:54:47 +00:00
Eric Allam 61c0834aae Implement reserve concurrency clearing when the child run is acked 2025-03-19 17:54:47 +00:00
Eric Allam fd9b0bf676 WIP new reacquire concurrency system 2025-03-19 17:54:47 +00:00
Eric Allam cf3b23862e remove project and task current concurrency tracking 2025-03-19 17:54:47 +00:00
Eric Allam de9e296551 Add configuration for the release concurrency queue 2025-03-19 17:54:47 +00:00
Eric Allam cce402dfe7 If max tokens is 0, then don't do releasings 2025-03-19 17:54:47 +00:00
Eric Allam b7ddf20d62 Correctly use the new release concurrency queue in the run engine 2025-03-19 17:54:47 +00:00
Eric Allam bf41703dfd improve the consumer of the concurrency queue 2025-03-19 17:54:47 +00:00
Eric Allam c8b99728b9 Get all the release concurrency queue tests passing 2025-03-19 17:54:47 +00:00
Eric Allam d11d491215 WIP release concurrency queue 2025-03-19 17:54:47 +00:00
Eric Allam 1362d8296d Add batchId to the delayed enqueueRun call 2025-03-19 17:54:47 +00:00
Eric Allam 2b6ce169be remove unused method 2025-03-19 17:54:47 +00:00
Eric Allam e9fa4ce3a2 Update delays to use a redis worker and work with the new reserve concurrency system 2025-03-19 17:54:47 +00:00
Eric Allam 1a233efd42 the run engine now works with the new reserve concurrency system 2025-03-19 17:54:47 +00:00
Eric Allam 555d9912ec fixed some key producer tests 2025-03-19 17:54:47 +00:00
Eric Allam 880538ff92 improve the dead letter queue stuff 2025-03-19 17:54:47 +00:00
Eric Allam a5b2b39201 ack 2025-03-19 17:54:47 +00:00
Eric Allam 16546f144f dequeue message 2025-03-19 17:54:47 +00:00
Eric Allam 8b3551c9e2 wip reserve concurrency system 2025-03-19 17:54:47 +00:00
Eric Allam 71c7b53891 Merge pull request #1805 from triggerdotdev/queues-page
Queues page and pausing the environment/queues
2025-03-19 17:52:24 +00:00
James Ritchie 4b6f51a110 Adds js to file paths and fixes typos (#1803) 2025-03-19 17:32:03 +00:00
Matt Aitken 4e3ef9f199 Blank state if you have no queues 2025-03-19 17:21:01 +00:00
Matt Aitken 16ff502605 Better scrolling on the Queues page 2025-03-19 17:11:58 +00:00
Matt Aitken 99e722c5c9 Do all the environment concurrency lookups in parallel 2025-03-19 17:01:26 +00:00
Matt Aitken 1d5dadbc24 Remove unused number-flow package 2025-03-19 16:44:34 +00:00
Matt Aitken 110548c354 Don’t use defer, it causes a horrible UI flash with revalidate 2025-03-19 16:42:10 +00:00
Matt Aitken 5c9a305e6b Auto-reload the queue page every 10 seconds 2025-03-19 16:34:06 +00:00
Matt Aitken 46eed03743 Added pause/resume functions to the SDK 2025-03-19 14:59:37 +00:00
Matt Aitken 03fb6e2890 Redirect to the correct page, to keep your place 2025-03-19 14:39:24 +00:00
Matt Aitken 42089a4691 Pausing individual queues working 2025-03-19 14:36:53 +00:00
Matt Aitken 34a178f169 Added upgrade panel 2025-03-19 13:22:31 +00:00
Matt Aitken 8ddaa626b6 WIP on upgrade panel 2025-03-19 13:13:52 +00:00
Matt Aitken 57a57b4b87 InfoBox storybook file 2025-03-19 13:07:19 +00:00
Matt Aitken aa787c2dbb Queues page promise fix 2025-03-19 11:49:20 +00:00
Matt Aitken d8b114465f use TypedAwait 2025-03-19 11:41:46 +00:00
Matt Aitken c74d57f9fd Fix for type issue 2025-03-19 11:41:34 +00:00
Matt Aitken c2038b51a8 Retrieving a queue is working well 2025-03-19 11:18:19 +00:00
Matt Aitken e2da1810b8 WIP on retrieving a queue using the SDK/API 2025-03-19 11:09:16 +00:00
Matt Aitken 4ee85cbe8c Git ignore the react hooks src/package.json 2025-03-19 11:08:56 +00:00
Matt Aitken 2b8c77d8bf Added some JSDocs 2025-03-18 19:53:56 +00:00
Matt Aitken b4a20909f7 Don’t return more than 100 queues per page 2025-03-18 19:53:49 +00:00
Matt Aitken 714e4ecb39 Added queues.list() SDK function 2025-03-18 19:50:15 +00:00
Matt Aitken 323a12499d Merge remote-tracking branch 'origin/main' into queues-page
# Conflicts:
#	apps/webapp/app/components/SetupCommands.tsx
#	apps/webapp/app/components/navigation/SideMenu.tsx
2025-03-18 19:08:25 +00:00
Matt Aitken 50593eb24f Transform the queue type and added tooltips 2025-03-18 19:07:33 +00:00
James Ritchie a6e85f00b2 Side menu and Org dropdown improvements (#1801)
* Organize imports

* Removes animation on the dropdown menu icon

* Improves Settings and Usage button states

* Side menu sections animate with easeInOut

* Incorporates docs button into the text and adds title to the clipboard field

* Icons look better as the small variant

* Improved the typography layout of the org name and small links

* Made the divide line show when the side menu scrolls

* Made the impersonating icon amber so it stands out more

* Adds an optional amber border to the org dropdown
2025-03-18 19:04:51 +00:00
Matt Aitken 629393334d Show 25 queues per page 2025-03-18 18:58:29 +00:00
Matt Aitken 3a4991dd45 Loading improvements 2025-03-18 18:55:56 +00:00
Dan f713b1e056 Added 2 more python examples (#1800)
* Added python image processing and updated docs.json

* Edits to crawl4ai

* Updated commands

* Added pdf form extractor
2025-03-18 16:50:43 +00:00
Matt Aitken d6beb0c745 Separate the presenters out, useful for the SDK 2025-03-18 16:38:59 +00:00
Matt Aitken df53715ee5 Added app version to the org setting menu 2025-03-18 13:31:20 +00:00
Matt Aitken 343e7ec90b Merge remote-tracking branch 'origin/main' into queues-page 2025-03-18 13:16:01 +00:00
Matt Aitken 0173406999 Pausing the environment is working 2025-03-18 13:14:29 +00:00
James Ritchie ec1cec5767 Centering account pages and making them consistent (#1799)
* Small updates to the Settings page

* Remove max-w-md from Members page

* Removing max width from Profile page
2025-03-18 12:57:23 +00:00
Matt Aitken a51f8c56a4 WIP pausing 2025-03-18 09:28:14 +00:00
Matt Aitken 43413c1b41 Reduce the env data that determineEngineVersion needs 2025-03-18 09:12:05 +00:00
Matt Aitken ec4511b629 Add paused status to RuntimeEnvironment and TaskQueue 2025-03-18 09:11:39 +00:00
Matt Aitken bbe30ea625 Move increase limit button to the new spot 2025-03-17 18:54:38 +00:00
Matt Aitken d5ee760b9d Get values for the queue and running 2025-03-17 18:49:19 +00:00
Matt Aitken c6fd1816ba Current badge changed 2025-03-17 17:33:13 +00:00
Matt Aitken 6539c4a267 Added pagination 2025-03-17 16:32:02 +00:00
Matt Aitken a871137e09 WIP on queues page 2025-03-17 15:52:31 +00:00
Matt Aitken fabfb5051b WIP on queues page 2025-03-17 15:07:47 +00:00
Matt Aitken 3fd4470e89 Fix for SSE error when trying to send when the controller has been aborted 2025-03-17 14:29:51 +00:00
Matt Aitken d5561b5400 Disable vscode warning about vitest 2025-03-17 14:29:25 +00:00
Matt Aitken 2ec76d6a35 Ignore .husky 2025-03-17 14:29:01 +00:00
Matt Aitken 9a777099ee Changed CLI clipboard fields to secondary 2025-03-17 13:36:05 +00:00
Matt Aitken 7b862b9438 Major dashboard improvements (environment centric) (#1796)
* Delete the proxy app (was v2)

* Delete RunPresenterElectric

* Select the best proj/org/env

* Storing current proj/env in DB. Initial selection logic working with tasks page

* 2sm needed to be in the Tailwind merge list

* Move the task stream route (although we don’t actually use the env for now)

* Alerts moved from /v3

* API keys page moved from /v3

* Concurrency page moved from /v3

* WIP on side menu sections

* Improved the accordion animation

* Moved schedules from /v3

* More pages moved

* Move pages working

* Run page working

* Schedules working

* Moved deployments

* Alert pages moved

* Delete electric hooks, not used

* Started setting up blank states

* Test page working

* Removed “Select task” from the test page

* Some work on deployment page

* Style tweaks

* Redirect from project root to approriate env

* Improved env selector styling

* Fix for jsx errors

* Better min width on env selector

* Improved the env switching logic

* Added deployments to env routing

* Redirect deployments to the correct env

* Redirect run from proj to env

* JSX icon fix

* Only allow single env schedules from now on

* Remove env var count from the API keys page

* Move improvements and redirects

* Project settings moved

* Fix for scroll area on test page

* Tweaked the test design

* Made recent payloads column narrower

* Improved the test layout some more

* Added org icon, new project selector menu

* WIP on org switching menu

* Org switching is working

* New menu working well, removed old side menu items

* Buttons can now have a component name or an actual component for their icons

* Removed the Projects page, instead redirect appropriately

* Fix for broken blank states

* Minor run table improvements

* Removed unused switcher log and logic

* Concurrency page fix for invalid html, improved layout

* Minor improvements

* Moved the side menu to the project level

* Improved account styling

* Moved org settings pages (with redirects)

* Add current plan to billing side menu link

* Upgrade to get staging from env dropdown

* New env badge on concurrency limits page

* Show Run Engine version in span presenter

* New promote icon

* Concurrency limits page is the sum of engine v1 + v2 queues

* Fix for missing batch import

* Added currentConcurrencyOfEnvQueue function

* Basic avatar setting working

* Avatar setting is working

* You can change the color of your icon

* Avatar improvements

* Bugfix for mising prop

* Removed some old env badges

* Fixed replaying

* Removed EnvironmentLabel

* Old env badge deleted, changed everywhere to the new one

* Fix for Slack integration paths

* Fix for waitpoint completion form moving

* Bulk replay/cancel env fix

* Fix for alert webhook path

* Redirect projects/v3/* to project/*

* Fixes for CLI redirect routes

* Remove welcome email (unused)

* Change how we count schedules towards your limits

* Use new schedules limits when checking a schedule

* Added projectId back in to task queries (indexes)

* WIP dev presence

* CLI modal

* Moved things around and use Context

* Fix for p inside p

* Dev connected status on run page

* Correct dev env (not a teammates)

* Show disconnected message at the end

* Minor tweak on project dropdown icon padding

* Fix for inconsistent date format for presence

* Added a message when pushing to the billing page

* Center the team page

* Project settings page centered

* Improvements to the dev presence
2025-03-17 13:32:31 +00:00
Erin Allison 2a51ae86a2 Suppress external instrumentations for fetch calls from ApiClient (#1788)
Signed-off-by: Erin Allison <erin@eallison.us>
Co-authored-by: Matt Aitken <matt@mattaitken.com>
2025-03-16 16:45:48 +00:00
nicktrn 4fda8a5ee1 Fix prerelease script (#1794)
* clean before building

* add main branch commit protection
2025-03-15 12:20:54 +00:00
nicktrn 7842e9dff0 Warm start and restore improvements (#1793)
* handle warm start service failure on supervisor side

* export zodfetch wrapper

* add warm start client

* rename to keepalive

* add missing warm start header

* make heartbeat and snapshot poll interval configurable

* create warm start client in constructor

* add warm start run debug log

* re-enable checkpoints and improve error messages

* reduce run pod container name cardinality

* move runner id generator into core

* fix runner id import

* log when no checkpoint client and we try to restore

* move run controller constructor to the top

* fix import

* support env overrides after restore
2025-03-14 18:37:53 +00:00
Eric Allam 58961658d8 Merge pull request #1791 from triggerdotdev/cursor-rules-writing-tasks 2025-03-12 18:46:03 +00:00
James Ritchie 9d06c114b6 Fixed some copy 2025-03-12 17:56:38 +00:00
nicktrn 7f2569721e Fix supervisor builds (#1790)
* fix and restructure dockerignore

* switch to using pnpm deploy

* pass webapp node image as build arg

* ensure pnpm is downloaded at build, not runtime
2025-03-12 16:10:37 +00:00
Eric Allam 3a0475930f Merge pull request #1785 from myftija/mcp-server
Add support for running a local MCP server via the `dev` CLI command
2025-03-12 10:53:47 +00:00
Eric Allam 80e981c9f7 Merge pull request #1787 from triggerdotdev/docs/crawl-docs-updates 2025-03-11 21:32:28 +00:00
D-K-P 60c1e2c16e Updated crawl4ai docs 2025-03-11 20:42:08 +00:00
saadi 7d65aa9299 Describe the MCP server feature in the readme 2025-03-11 19:17:46 +01:00
saadi 603c903fbe Add MCP tool to list all available tasks and enable fuzzy task matching
This enables LLMs to figure out which task you are referring to, without needing to specify the full task ID
2025-03-11 18:34:17 +01:00
saadi 2e2632d5cc Add new MCP tool to list the logs for a run
This enables some basic level of debugging capabilities in combinatio with the other MCP tools
2025-03-11 16:20:56 +01:00
saadi ed1f4cea12 Expose a new API endpoint to list events for a given task run 2025-03-11 16:20:12 +01:00
Dan 282cc0629f Added Python crawling example (#1786)
* Added Python crawling (slithering?) example

* Fixed link
2025-03-11 14:16:55 +00:00
saadi b6b25b05f5 Add MCP tool to cancel task runs 2025-03-11 15:11:54 +01:00
saadi 9cd6d40acc Shut down the MCP server when the dev session stops 2025-03-11 14:53:55 +01:00
saadi 2e7b1a7b1c Make the MCP server port configurable via a flag in the dev command 2025-03-11 14:47:11 +01:00
saadi af3af9ca17 Add MCP tool to get a single run
This can be used in combination with the trigger-task tool from LLMs to show details about the run after triggering the task.
2025-03-11 14:33:51 +01:00
saadi 1d7224d2ee Add MCP tool to list runs 2025-03-11 14:22:42 +01:00
saadi ce3e49b4e3 Enable passing custom payload in the trigger-task MCP tool 2025-03-11 14:05:07 +01:00
James Ritchie 25c2fdb30a Adds writing-tasks.mdc for cursor rules 2025-03-11 12:56:54 +00:00
saadi e00afc1332 Use the v2 run engine by default in the SDK API client
V2 is the way to go for the future.
2025-03-11 12:58:11 +01:00
saadi 5c0158e98b Expose a basic version of the MCP server as an option for the dev CLI command
This just adds the main components for the MCP server. It hooks on the
existing `dev` command and can be enabled by passing the `--mcp` flag.

Currently only the `trigger-task` tool is exposed, which enables users
trigger tasks via MCP and see the resulting run.

Still WIP :)
2025-03-11 12:11:33 +01:00
Eric Allam 67115ca880 Contributing guide updates (#1784) 2025-03-11 07:54:03 +00:00
Eric Allam de4ee19d15 Merge pull request #1783 from triggerdotdev/cursor-repo-rules-and-docs
Added initial repo cursor rules and other docs for AI
2025-03-11 06:20:10 +00:00
Eric Allam 5db995856e Added repo cursor rules and other docs for AI 2025-03-10 23:03:27 +00:00
Eric Allam 70ff5de7b9 WIP 2025-03-10 13:26:13 +00:00
Matt Aitken 6cf86d5916 Deleting v2 dashboard/api (#1628)
* Delete v2 Stripe routes

* Delete v2 billing/usage pages

* Delete v2 integration pages

* Delete v2 project pages

* Deleted a load of components and services

* Deleted a load more components, presenters and services

* Deleted another 100 files or so…

* Removed old v2 paths

* Removed named icons from form titles

* Removed more string icons

* Delete NamedIcon

* Fixed some type errors

* Delete endpointApi

* Removed v2 from core/sdk

* Post merge fixes

* added explicit return types

* using the new sdk export without v3

* Delete old v2 file

* Added explicit return types because TS was complaining…

* Don’t export RuntimeEnvironmentType from two core files. Was causing TS issue

* Fix for removal of NamedIcon in new route

* Removed strange eslintrc rule

* Use the new redis client

---------

Co-authored-by: James Ritchie <james@trigger.dev>
2025-03-08 14:53:06 +00:00
Eric Allam d855d55ea0 re2: env based queue selection algo (#1775)
* re2: fix @trigger.dev/core exports

* re2: WIP env based queue selection algo

* more wip

* WIP

* Get run engine tests to pass

* Adding tests for the fair dequeueing strat in the run engine

* Configure the new queue selection strategy in the webapp and get it all building and typechecks passing

* webapp now uses built packages, building redis-worker, run-engine, database, using better tsconfig setups for tests, moving isomorphic code into core/v3/isomorphic

* Fixed webapp typechecks

* dev now depends on build, fixed supervisor typecheck

* Fixed run engine tests

* Fixed e2e tests
2025-03-07 14:30:19 +00:00
nicktrn 38ddd830d7 Add supervisor split service controls (#1774)
* dockerignore node_modules in subdirectories

* image tag action should handle re2 tags

* add supervisor containerfile

* add publish worker re2 workflow

* fix copypasta

* require branch check

* add more granular service control to supervisor session

* fix supervisor api domain for split setups

* remove default workload api domain

* option to disable workload api

* fix bool env var coercion
2025-03-07 12:40:11 +00:00
Matt Aitken 4dbf11aed1 Run Engine 2: More robust attempt failing/retrying (inc. OOM retrying) (#1773)
* Added describe to tests that were missing it

* Added a function to get the maxOldSpaceSize

* Make it easy to take `NODE_OPTIONS` and set the old space flag

* Added a zed task to rebuild the packages

* Moved isOOMRunError and added SIBABRT condition

* Deduplication flags function with tests

* Export flags file

* On TaskRunProcess, set max old space and deduplicate the flags with priority order

* Move retrying logic to a separate function, it was getting very messy

* Created new test file for attempt failures

* Allow setting retry settings for tests

* Some retrying tests, including OOM

* More failure condition tests

* Fix for OOM retrying

* Complete the attempt span if it was an OOM error

* Remove old broken import

* Fixed order of exports

* Fix for docker-provider checkpoints import
2025-03-07 09:07:49 +00:00
nicktrn e297c7f7f0 Add supervisor build pipeline (#1772)
* dockerignore node_modules in subdirectories

* image tag action should handle re2 tags

* add supervisor containerfile

* add publish worker re2 workflow

* fix copypasta

* require branch check
2025-03-06 18:22:36 +00:00
Luca D'Alessandro 3089caff0c docs: suggest updated vercel-wait action for Atomic deploys (#1771) 2025-03-06 17:22:22 +00:00
nicktrn 94fb532f5e Add RE2 supervisor (#1769)
* add new core server only export

* exit after run if warm starts disabled

* add supervisor
2025-03-06 13:20:45 +00:00
James Ritchie 78194e1a36 Corrects the number of schedules on Free tier (#1768) 2025-03-06 13:06:26 +00:00
Eric Allam cf4c4d7b65 Add attempt metrics in dev (prod WIP). Added max concurrent runs setting to dev using p-limit (#1766) 2025-03-06 11:57:01 +00:00
Matt Aitken 722fae566c Reschedule heartbeats and added a test to check it works (#1765) 2025-03-06 10:44:53 +00:00
Matt Aitken 983bb411cc Redis client factory (error handling and good defaults) (#1761)
* @internal/redis package for creating Redis clients with sensible defaults and error handling

* Added @internal/redis to webapp
2025-03-05 18:32:46 +00:00
James Ritchie 4358e2d2ef Clarifies only executing runs count towards concurrency (#1760) 2025-03-05 17:57:42 +00:00
James Ritchie 31c2a23ffc Docs improvements: clearing build cache, concurrently running next + trigger, trouble shooting pino (#1758)
* Adds “Explore by build extension” to the intro page

* Adds instructions for clearing the build cache

* Add a tip for running trigger dev + next dev concurrently

* left align table headers

* Adds trouble shooting for pino

* Adds corepack bug and workaround

* Removes reference to undici as it messes with spans

* Left align table headers

* Adds example for exporting logs to Axiom
2025-03-05 17:35:23 +00:00
Eric Allam 7b10d0b0ee re2: fixed cli e2e tests (#1757) 2025-03-05 16:58:28 +00:00
Eric Allam e89fb92532 re2: dev runs work without worker groups, fixed some type issues (#1756)
* In dev, the worker group is optional when triggering tasks (the master queue is defined by the environment). Also deprecated the TaskEvent.isDebug column and using TaskEventKind.LOG instead for debug events

* Fixed a couple of type issues

* More type fixes
2025-03-05 16:29:41 +00:00
nicktrn e97704d904 Run Engine 2.0 (WIP) (#1575)
* bump worker version

* Suggested glossary for the RunEngine, TBC

* Removed BatchTaskRun changes from this branch, they were done in main

* Set the BatchTaskRun status to completed when all runs are completed

* When dequeuing respect passed in maxResources

* Ported over the new run props: idempotencyKeyExpiresAt, versions, oneTimeUseToken, maxDurationInSeconds

* Didn’t hit save… the new props when triggering tasks passed through

* Idempotency expiration + waitpoint edge case

* WIP on creating checkpoint, parking for now

* fix worker routes

* upgrade webapp node types to support generic event emitter

* separate event bus handler singleton and run failure alerts

* duration waits

* fix execution snapshot debug spans

* task waits

* fix event bus types

* temporary fix for react hook run handle type

* disable run notifications for now

* convert any typecasts to expect errors to more easily fix later

* fix webapp types after node types upgrade

* updateEnvConcurrencyLimits across marqs and the runqueue

* Pass proper values into the run engine

* RunQueue settings and removed unused rebalancing workers

* Remove rebalancing prop

* Tidied more things up

* Update/remove queue limits for MARQS and RunQueue

* taskQueue/concurrencyLimit changes ported back into the RunEngine

* Reworked completing waitpoints to improve performance and reduce race conditions

* Improved test robustness

* Down to a single run lock only when a run is totally unblocked and ready to continue

* warm starts, worker notifications, wait fixes

* Fix for Run Engine poll interval env var

* Expect the waitpoint to be completed quickly

* If a run is locked then it’s too late to expire it

* Added VALKEY_ env vars and plugged them into the run engine

* Extracted and updated the guard queue function so it can be used when batching

* Added logging and universal concurrency changes to trigger task v1

* Added notes back in

* Bump @trigger.dev/worker to 3.3.7

* reportInvocationUsage for the runAttemptStarted event

* improve execution snapshot span debug span start times

* Unfriendly IDs

* update lockfile

* Created a shared determineEngineVersion function

* disable unfinished commands

* save new cli config to different location, misc fixes

* add basic engine version check via current deploy

* new run engine will default to node 22 runtime

* block some actions for projects on previous run engine

* fix worker group tests

* fix triggerAndWait test

* one typescript version to rule them all

* redlock type patch

* fix type issues caused by ts-reset

* improve cleanup scripts

* add missing socket.io dep

* fix run notification handler type

* fix worker group test again

* generate prisma client for e2e tests

* remove worker group tests for now

* prevent image pull rate limits during unit tests

* increase timeout for queue concurrency limit test

* generate prisma client for preview release

* same node types everywhere

* Updated engine readme, removed legacy system notes

* use default machine preset from platform package

* worker instances plural in schema

* disable pnpm update notifications

* return worker group details from connect call

* add workers admin route

* fix heartbeat route return type

* move deployment labels to core apps

* refactor run controller env schema

* Add firstAttemptStartedAt to TaskRun

* RunEngine 2.0 batch trigger support (#1581)

* Make it clear when BatchTriggerV2Service is used

* Copy of BatchTriggerV2Service

* WIP batch triggering

* Allow blocking a run with multiple waitpoints at once. Made it atomic

* Removed unused param

* New batch service

* Pass through the parentRunId and resumeParentOnCompletion

* Use the new batch service, and correct trigger task version

* Force V1 engine if using BatchTriggerV2Service, we’ve already done the check at this point

* Removed the $transaction and early exit if nothing changed

* Adedd a simple batch task to the hello world reference catalog

* Fix for batch waits not working

* Added parentRunId in a couple more places

* Removed waitForBatch log

* Added another parentRunId

* Expanded the example to include all the different triggers

* More changes to blocking to support continuing after idempotent completed runs

* Fix for the wrong type when blocking a run

* remove @map

* optimise worker auth query

* add engine version header to core api client requests

* remove unique constraint for default group id

* consolidate migrations

* the first managed worker becomes the global default

* Debug events off by default, added an admin toggle to show them

* worker group name can't be an empty string

* add exec helper to core

* move machine resources to core

* add pre-dequeue callback to determine max resources

* optionally skip dequeue

* bump worker package

* move worker to core

* fix ReadableStream type error

* fix another type issue

* update a few more tsconfigs

* add metadata changes introduced in #1563

* Run Engine 2.0 trigger idempotency (#1613)

* Return isCached from the trigger API endpoint

* Fix for the wrong type when blocking a run

* Render the idempotent run in the inspector

* Event repository for idempotency

* Debug events off by default, added an admin toggle to show them

* triggerAndWait idempotency span

* Some improvements to the reference idempotency task

* Removed the cached tracing from the SDK

* Server-side creating cached span

* Improved idempotency test task

* Create cached task spans in a better way

* Idempotency span support inc batch trigger

* Simplified how the spans are done, using more of the existing code

* Improved the idempotency test task

* Added Waitpoint Batch type, add to TaskRunWaitpoint with order

* Pass batch ids through to the run engine when triggering

* Added batchIndex

* Better batch support in the run engine

* Added settings to batch trigger service, before major overhaul

* Allow the longer run/batch ids in the filters

* Changed how batching works, includes breaking changes in CLI

* Removed batch idempotency because it gets put on the runs instead

* Added `runs` to the batch.retrieve call/API

* Set firstAttemptStartedAt when creating the first attempt

* Do nothing when receiving a BATCH waitpoint

* Some fixes in the new batch trigger service… mostly just passing missing optional params through

* Tweaked the idempotency test task for more situations

* Only block with a batch if it’s a batchTriggerAndWait… 🤦‍♂️

* Added another case to the idempotency test task: multiple of the same idempotencyKey in a single batch

* Support for the same run multiple times in the same batch

* Small tweaks

* Make sure to complete batches, even if they’re not andWait ones

* Export RunDuplicateIdempotencyKeyError from the run engine

* Latest lockfile

* Trigger with a machine (old run engine)

* RE2, allow setting machine when triggering

* Fix for new glob patterns

* add max run count to dequeue from version route

* add worker instance name env var and header

* queue consumer pre skip callback

* poll for more runs after final execution errors

* fix dequeue search param schema

* add shortcut to debug switch

* expose run engine timeouts as env vars

* make warm start durations configurable

* add optional status to json reply helper

* fix preSkip hook, add debug logs

* BLOCKED_BY_WAITPOINTS -> SUSPENDED

* exit controller when run suspended

* check if already replied before http reply

* run controller will wait for next run after the current one is suspended

* cancel run button shortcut

* minimal event repository environment type

* fix update metadata call

* run suspension and misc fixes wip

* change debug shortcut to shift + D

* Started work on the Dev supervisor

* Formatting

* Fix for bad imports

* Before rebuilding SSE

* Presence updating from the CLI working via SSE

* add worker notification debug logs

* send run:stop when exiting run phase

* skip current snapshot poll on worker notification

* add more logs and route to submit run debug logs

* add worker and runner ids to snapshots

* improve run notification debug logs

* add workload debug log route

* misc run controller fixes and refactor

* prevent parallel execution of critical functions

* update bun to 1.2.1

* WIP with dev dequeuing

* Method to convert friendlyIds to non-friendly, do nothing with actual ids

* Set the engine on BackgroundWorker, lazily upgrade projects to engine V2

* Runs with ttls were getting immediately expired… oops.

* Pass the Waiting for deploy reason through, so we have it on the execution snapshots

* Fixed the logic for getting the right background worker for a run

* Use the correct ID when dequeuing…

* determineEngineVersion is now fully functional

* Rate limiter ignores the dev endpoints

* Retrieving a batch gives you the runIds

* Set a unique version for the RE2 BatchTaskRun

* add provisional changeset

* The start of dev run execution is working

* First dev run working

* Moved the dev run controller closer to what Nick did with the managed one

* export exec output type

* Heartbeat fix: don’t heartbeat if _isHeartbeating == false

* Dev runs get notifications, some dev bug fixes

* Improved logging or dequeuing

* We need to dequeue runs from the latest version too, for triggerAndWait

* Ported Eric’s validateWorkerManifest with nicer errors

* When flattening an idempotency key if part is undefined, return undefined

* Dev logging fixes

* Remove sigterm listener

* Deprecating workers. Don’t specify a BackgroundWorker when dequeuing an environment

* Deleted some old files. Renamed “managed” to “deploy”

* When a build finishes, always copy the build dir (otherwise the first one gets trampled on by the 2nd)

* Dev master queues should work differently

* Deleting old workers

* Added debounce function to core

* Improvement to canceling

* WIP on debounce canceling on socket disconnection

* Added environment data to execution snapshots

* Dev runs that have stalled get “Canceled” with a reason explaining why

* Show CLI messaged when a connection to the platform is lost/restored

* Fix TriggerTask after merge

* Add trigger task v2 max attempts, replace some findUniques

* Port the new queue logic to the run engine

* More fixes post-merge

* We weren’t setting a `retryConfig` up for the tests… it’s now required

* Start the Redis worker inside the Run Engine… 🤦‍♂️

* Trying to make the testcontainers more reliable

* Added keyPrefix: "engine:”

* Badly placed bracket in trigger task

* Better Redis namespacing

* Fix for expired run not getting removed from the queue

* Don’t create a redis client in the testcontainers, return the redisOptions instead

* Cleanup redis client in the run lock tests

* Fix for the RunQueue not supporting keyPrefix

* Updated more of the RunQueue scripts rebalancing

* Trying to make Redis more robust in the tests…

* Improved test resiliciency more

* Fix for delays (checkpoint check)

* Increase the timeout slightly to fix ttl test

* Added priority support when triggering

* More wip trying to make test containers more reliable

* batchTriggerAndWait test is still failing… some wip to try fix it

* Fixed redis tests now we’re not providing a client

* Separate Redis clients for the run engine worker/queue/runlock

* Made the wait for duration test more resilient

* Added idempotencyKeyExpiresAt to Waitpoints

* Waitpoint timeouts and idempotency expiry

* Use finishWaitpoint, removed extra worker job

* Added waitpoint idempotency tests

* Creating resume tokens is working

* Some improvements to the resume tokens

* Moved resumeTokens to just be wait functions 🥳

* Delete old RuntimeManagers

* Wait for token is working

* Better test for the wait tokens

* Improved the test task some more

* Hide the accessories in the span inspector

* WIP on waitpoint inspector

* WIP on complete waitpoint form

* Span overview panel can be changed based on the entity type

* Improved the waitpoint display

* WIP on completing waitpoint form

* Use the existing CodeBlock for the tip

* Style improvements

* Complete waitpoint

* All waitpoint sidebar variants

* Waits now use a pause icon

* Durations waits use the API to create/block with a waitpoint, not the runtime

* Fix for engine.blockRunWithWaitpoint required org id

* Removed old wait code from the run controllers/task run process

* Form action for skipping a datetime waitpoint

* Move testDockerCheckpoint to a separate core package export (it can’t be bundled on the client)

* Fix for glitchy hourglass animation

* Completed waitpoints display better

* Increase Redis maxRetriesPerRequest to 20 (default)

* Completing and skipping waitpoints is working

* Remove the database prisma dev command, since we need to use create only now. Updated docs

* Added skip timeout, reworked the UI

* Tweaked spacing

* Added payload limit to waitpoint token completion from dashboard

* Test idempotency works on wait.for and wait.until

* Moved the worker-actions to /engine/ from /api/

* Moved dev engine endpoints to /engine/ from /api/

* Separate /engine/ rate limiter

* Added parallel wait prevention, it’s working for duration waits but not well for triggerAndWait yet

* WIP post-merge conflicts

* Set taskEventStore column in the new engine

* Remove duplicate keys

* Post-merge fixes

* Fix for span merge layout

* Use executedAt instead of firstAttemptStartedAt

---------

Co-authored-by: Matt Aitken <matt@mattaitken.com>
2025-03-05 14:40:14 +00:00
Matt Aitken c519a5afc7 Convert the number part of the version to a number, from a string (#1754)
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 0s
🚀 Publish Trigger.dev Docker / units (push) Failing after 4s
🚀 Publish Trigger.dev Docker / publish-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker (push) Has been skipped
⚒️ Publish Worker / build (coordinator) (push) Has been cancelled
⚒️ Publish Worker / build (docker-provider) (push) Has been cancelled
⚒️ Publish Worker / build (kubernetes-provider) (push) Has been cancelled
2025-03-05 08:29:16 +00:00
Eric Allam 5730faea00 Release 3.3.17 (#1752)
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 0s
🚀 Publish Trigger.dev Docker / units (push) Failing after 10s
🚀 Publish Trigger.dev Docker / publish-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker (push) Has been skipped
2025-03-04 12:47:31 +00:00
github-actions[bot] 58f5955c66 chore: Update version for release (#1710)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-03-04 12:40:41 +00:00
James Ritchie 7162f07216 Updates sveltekit community plugin links (#1746) 2025-03-04 12:30:50 +00:00
Eric Allam 8f3a28effb pythonExtension and python runtime improvements (#1734)
* pythonExtension and python runtime improvements

* Adding streaming support

* Use writeFileSync

* Restructure extension docs and add python extension docs

* Fix broken link

* Update docs/config/extensions/overview.mdx

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update docs/config/extensions/aptGet.mdx

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update docs/config/extensions/custom.mdx

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Add environment variable support

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-03-04 12:30:26 +00:00
Eric Allam bbd82adbda docs: improve deployment docs and add atomic deploy guide (#1748)
* docs: improve deployment docs and add atomic deploy guide

* Various docs fixes
2025-03-04 12:00:06 +00:00
Eric Allam edf0c1024f Publish docker image when internal-packages change (#1750) 2025-03-04 11:18:29 +00:00
Eric Allam 457273e4cf Fix email alert failures because of newlines in error messages (fixes #1745) (#1749) 2025-03-04 10:54:39 +00:00
Dan c315e38d76 Updated imports (#1747) 2025-03-03 20:21:28 +00:00
Eric Allam e0241819a4 fix: needsPromotion fixed in deploy github actions (#1741) 2025-03-01 10:29:44 +00:00
Dan 796e710439 Added claude thinking chatbot docs (#1743) 2025-03-01 09:57:22 +00:00
Eric Allam c8732becbd Updates @clack/prompts to 0.10.0 to improve CI output when using the spinner (#1740)
* Updates @clack/prompts to 0.10.0 to improve CI output when using the spinner

* Add changeset
2025-03-01 07:13:58 +00:00
Dan 5164935f9c Added X-Frame-Options and CSP (#1690) 2025-02-28 11:06:27 +00:00
James Ritchie e1db12b8ba Expandable code blocks (#1683)
* Removed unnecessary classes from Dialog

* Display large modal with code and copy button

* Fixes button position if chrome is shown

* Removes the duplication of the Highlight component
2025-02-28 09:17:56 +00:00
Eric Allam 187200a1bc Feat: Improved run start timeline visibility (#1732)
* Record cold start and execution metrics on attempt executions. Add cold start metrics as span events on attempt spans and display them in the run dashboard

* Add deployed tasks run timeline metrics

* Add Dequeued event to run timeline and cleanup the run timeline code

* Adds variants to storybook

* WIP adding new span styles

* Added offset progress bar animation

* More storybook states

* Adds support for the full vertical span to show the same state

* Adds error state to timelineLine

* Added additional state

* Added more line styling

* Added progress state to dequeued

* Added another state to storybook

* Fixed classname error

* Updated styles for the span timeline points

* Fixes alignment of timeline follow cursor indicator

* Adds help text tooltip to timeline span type titles

* Fixes type error

* Tweaked wording of tooltips

* Fixed type error (check this)

* Moved isAdmin to a higher level

* removed unused admin props

* Removed unused Admin filter

* Fixed border styling

* made the opacity of the timeline states 30% less

* Undo type cast

* Added a diminished style that’s used for spans (grey progress bar)

* Adds new storybook state

* Fixed timeline state

* Removed state if span isn’t the first

* Changed the timestamp span icon

---------

Co-authored-by: James Ritchie <james@trigger.dev>
2025-02-27 20:47:48 +00:00
Eric Allam 26f9a1e478 Feat: two phase deployment, version pinning (#1739)
* WIP two-phase deployments

* Fix the help text

* Rename TRIGGER_WORKER_VERSION to TRIGGER_VERSION

* Add changeset

* A few naming fixes
2025-02-27 20:44:23 +00:00
Eric Allam 0e5ec8bfbc Only allow a single dev queue consumer to dequeue at once (#1737)
* Only allow a single dev queue consumer to dequeue at once

* Expire dev queue consumer connections keys

* Add missing return
2025-02-27 15:08:47 +00:00
Eric Allam d2ceb825a7 Fix issue where a typescript file with only types causes indexing to fail (#1735)
* Fix issue where a typescript file with only types causes indexing to fail

* Create ninety-cars-peel.md
2025-02-27 15:03:14 +00:00
Dan 376a0c5d04 Update README.md 2025-02-26 15:37:27 +00:00
Victor Duarte 06f60f2720 feat(build): Add support for Python scripts via pythonExtension (#1686)
* add PythonExtension

* remove potential shell injection risk

* Filter out blank lines or comment lines

* fix spelling

* add pythonExtension's `runInline`

* changes to requirements don’t invalidate the entire install layer

* copy script files on-demand

* improve PythonExtension types and logging

* add changeset

* fix broken imports

* Improve security of inline script execution

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Add file existence check for requirementsFile

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* update lock file

* Enhance error handling with detailed error information

* Add portable type annotation

* fix error TS18046: 'e' is of type 'unknown'

* export the python extension

* add `pythonExtension` to the catalog

* fix `Cannot find module '@trigger.dev/build/extensions/core' (TS2307)

* replace execa by tinyexec

* Update pnpm-lock.yaml

* add custom traces instead of logging

* The cleanup in the finally block does not fail silently anymore

* move python runtime/extension to independent package

* fix build package readme

* update lock file

* add documentation to python's package

* add missing dependency

* Update little-trains-begin.md

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Matt Aitken <matt@mattaitken.com>
Co-authored-by: Eric Allam <eallam@icloud.com>
2025-02-26 13:39:28 +00:00
Gustavo Fior ca05a544d5 add missing imports to overview.mdx (#1731)
added missing imports:

import { auth } from "@trigger.dev/sdk/v3";
import { tasks } from "@trigger.dev/sdk/v3";
2025-02-25 21:00:38 +00:00
Dmitrii Malakhov 8044433320 Typo fix cli-commands-deploy.mdx (#1728) 2025-02-25 08:42:26 +00:00
James Ritchie 412eb81ee1 Explain TTL even better so the LLMs hopefully understand it (#1726) 2025-02-21 16:11:16 +00:00
Eric Allam 62e3238ecc Add configurable redis TTL on realtime streams (#1725) 2025-02-21 16:07:12 +00:00
James Ritchie 5a250cf92d Adds help and fix for “No loader is configured” (#1724) 2025-02-21 10:09:41 +00:00
nicktrn 80f2fc8bbe Add scheduled at timestamp to created run containers (#1722)
* add scheduled at timestamp to k8s provider

* add scheduled at timestamp to docker provider
2025-02-20 17:27:38 +00:00
Eric Allam bf43fd4f8e Use priority offsets in MarQS and schedule future messages using redis worker (#1720)
* Improve the MarQS priority system by moving future messages into the LRE worker and using a priority timestamp offset to define priority in messages

* Add a test to make sure priority offsets don't unfairly favor environments

* requeuing should clear concurrency sets

* Heartbeats should only reschedule existing heartbeat jobs

* Fix type error
2025-02-20 14:06:32 +00:00
nicktrn 7186b1e428 Add support for deferred checkpoints (#1721)
* add ability to delay checkpoints

* optional checkpoint delays for dependency waits

* prevent checkpoint creation for resumed batches

* unpause after checkpoint rejected by platform

* prevent checkpoint creation for resumed task waits

* fix checkpoint cancellation
2025-02-20 12:47:09 +00:00
James Ritchie 99f2caf5c2 Docs/new reference section (#1719)
* Moves API reference to a new dropdown section

* Move toubleshooting higher up

* Separates the reference Overview page into new pages

* Removed Projects api and redirects old page to overview

* typo
2025-02-20 11:30:32 +00:00
James Ritchie e19bf2abaf Docs/more docs improvements (#1718)
* Format the machine override example code

* Adds note to add `fluent-ffmpeg` to external

* Updates onFailure with more clarification

* Adds debugging in vs code to Troubleshooting

* Removed link
2025-02-19 16:06:42 +00:00
Eric Allam 23b4c2a214 Add MarQS requeueMessage, an atomic version of replaceMessage (#1717)
* New MarQS method requeueMessage, an atomic version of replace message

* Remove redundant call to remove the message from the env queue in requeueMessage
2025-02-19 15:11:43 +00:00
Eric Allam 7b1159eb45 MarQS reserve concurrency system & queue priority for resuming/retrying (#1715)
* run engine v1: orgs are no longer considered for concurrency

* Add reserve concurrency concept to allow waiting to resume parent tasks to release concurrency at the env level for child tasks to use (or else there is a deadlock). WIP recursive tasks

* child tasks inherit the queue timestamp from their parent tasks to prioritize completing child tasks based on when their parent started

* handle reserve concurrency with recursive deadlocks

* Finish docs update for concurrency

* Some fixes from badge conflict resolution

* WIP priority queues

* Implement MarQS priority queues

* Fix the migrations
2025-02-19 11:43:34 +00:00
James Ritchie c9f7ea87c3 Docs/various docs improvements (#1716)
* Adds more Prisma documentation

* Adds more info and a diagram to improve idempotencyKeyTTLs

* Adds more clarity to the run with TTL docs section

* Limits page copy improvements

* Adds schema properties to the alert webhooks
2025-02-19 11:13:29 +00:00
James Ritchie d6c869eb8f Docs/community packages (#1714)
* typo

* Adds feature list to the sveltekit plugin page

* Removed Migrations section in side menu

* Tidy up the table links

* Creates a community packages section

* Small tweak

* Adds info and links to community packages

* Updated mintlify broken links version in workflow file

* fixed broken link
2025-02-18 13:13:07 +00:00
James Ritchie 31a43273df Latest lock file 2025-02-18 11:01:14 +00:00
Erin Allison b4d1619732 Remove V2 countdown from sidebar (#1712)
Signed-off-by: Erin Allison <erin@eallison.us>
Signed-off-by: Erin Allison <eallison@andrettikarting.com>
Co-authored-by: James Ritchie <james@trigger.dev>
2025-02-18 10:59:12 +00:00
Vinny ferddy-ogo lagueule 1e194a53e5 Bump next from 14.2.15 to 14.2.21 in /references/nextjs-realtime (#1583)
Bumps [next](https://github.com/vercel/next.js) from 14.2.15 to 14.2.21.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/compare/v14.2.15...v14.2.21)

---
updated-dependencies:
- dependency-name: next
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-17 18:40:09 +00:00
nicktrn 8f3f373002 Improve reliability of completion submission (#1711)
* add timeout support to sendWithAck

* coordinator will retry completion submission

* actually retry

* increase default retries

* something went wrong there, add this back in

* add changeset
2025-02-14 15:14:42 +00:00
nicktrn 36fea7d645 Add support for manual checkpoints (#1709)
* don't crash run on lazy attempt errors and count on retry

* remove abort controller after checkpoint in all cases

* duration checkpoints

* manual checkpoints

* add busybox to coordinator image

* add async toggle to process in background

* remove early returns

* add changeset for manual checkpoint schema
2025-02-14 11:35:10 +00:00
James Ritchie 37723344cd Adds SvelteKit to the frameworks (#1707) 2025-02-13 17:30:38 +00:00
Matt Aitken e61b29ae52 Release 3.3.16 2025-02-13 15:31:55 +00:00
github-actions[bot] 4dbf217edb chore: Update version for release (#1705)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-02-13 15:25:08 +00:00
James Ritchie a62cb3e60b Agent docs examples (#1706)
* Uses image cards for the frameworks

* Removes old snippets

* New AI agents side menu section

* WIP adding new ai agent pages

* Better overview page

* More copy added to the agent example pages

* Copy improvements

* Removes “Creating a project” page and side menu section

* Fixes broken links

* Updates to the latest Mintlify version, fixes issues, changes theme

* Adds descriptions to the main dropdown menu items

* Reformatted Introduction docs ‘landing page’

* Retry heartbeat timeouts by putting back in the queue (#1689)

* If there’s a heartbeat error and no attempts we put it back in the queue to try again

* When nacking, return whether it was put back in the queue or not

* Try and nack, if it fails then fail the run

* Consolidated switch statement

* Fail executing/retrying runs

* OOM retrying on larger machines (#1691)

* OOM retrying on larger machines

* Create forty-windows-shop.md

* Update forty-windows-shop.md

* Only retry again if the machine is different from the original

* Kubernetes OOMs appear as non-zero sigkills, adding support for treating these as OOMs

* Complete the original attempt span if retrying due to an OOM

* Revert "Complete the original attempt span if retrying due to an OOM"

This reverts commit 5f652c6212.

* chore: Update version for release (#1666)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* Release 3.3.14

* Set machine when triggering docs

* Batch queue runs that are waiting for deploy (#1693)

* Detect ffmpeg OOM errors, added manual OutOfMemoryError (#1694)

* Detect ffmpeg OOM errors, added manual OutOfMemoryError

* Create eighty-spies-knock.md

* Improved the machines docs, including the new OutOfMemoryError

* chore: Update version for release (#1695)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* Release 3.3.15

* Create new partitioned TaskEvent table, and switch to it gradually as new runs are created (#1696)

* Create new partitioned TaskEvent table, and switch to it gradually as new runs are created

* Add env var for partition window in seconds

* Make startCreatedAt required in task event store

* Don't create an attempt if the run is final, batchTriggerAndWait bad continue fix (#1698)

* WIP fix for ResumeAttemptService selecting the wrong attempt (which has no error or output)

* Don’t create an attempt if the run is already in a final status

* Don’t get all the columns for the query. Improved the logging.

* Added a log to the batch example

* Filter out the undefined values

* Fix missing logs on child runs by using the root task run createdAt if it exists (#1697)

* Provider changes to support image cache (#1700)

* add env var for additional pull secrets

* make static images configurable

* optional image prefixes

* optional labels with sample rates

* add missing core paths

* remove excessive logs

* Fix run container exits after OOM retries (#1701)

* remove unused imports

* tell run to exit before force requeue

* handle exit for case where we already retried after oom

* improve retry span and add machine props

* don't try to exit run in dev

* Upgrade local dev to use electric beta.15 (#1699)

* Text fixes

* Removed pnpm files

---------

Co-authored-by: Matt Aitken <matt@mattaitken.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Eric Allam <eric@trigger.dev>
Co-authored-by: nicktrn <55853254+nicktrn@users.noreply.github.com>
2025-02-13 14:51:43 +00:00
Matt Aitken 440d413ce8 Alert Webhook improvements (#1703)
* WIP with webhook SDK function and types

* JSDocs added to the schema

* Webhooks are working

* Expanded the alert docs

* Remove duplicate export of waitUntil.js

* Use uncrypto

* Don’t rate limit webhooks

* Create slow-olives-fix.md
2025-02-13 13:44:36 +00:00
Eric Allam a2c70b450a Upgrade local dev to use electric beta.15 (#1699) 2025-02-13 11:02:01 +00:00
nicktrn a38f713e32 Fix run container exits after OOM retries (#1701)
* remove unused imports

* tell run to exit before force requeue

* handle exit for case where we already retried after oom

* improve retry span and add machine props

* don't try to exit run in dev
2025-02-12 18:32:58 +00:00
nicktrn 0924b37283 Provider changes to support image cache (#1700)
* add env var for additional pull secrets

* make static images configurable

* optional image prefixes

* optional labels with sample rates

* add missing core paths

* remove excessive logs
2025-02-12 16:57:55 +00:00
Eric Allam d622aba86d Fix missing logs on child runs by using the root task run createdAt if it exists (#1697) 2025-02-12 14:03:48 +00:00
Matt Aitken d214aa9093 Don't create an attempt if the run is final, batchTriggerAndWait bad continue fix (#1698)
* WIP fix for ResumeAttemptService selecting the wrong attempt (which has no error or output)

* Don’t create an attempt if the run is already in a final status

* Don’t get all the columns for the query. Improved the logging.

* Added a log to the batch example

* Filter out the undefined values
2025-02-12 14:02:29 +00:00
Eric Allam bd0cc541c5 Create new partitioned TaskEvent table, and switch to it gradually as new runs are created (#1696)
* Create new partitioned TaskEvent table, and switch to it gradually as new runs are created

* Add env var for partition window in seconds

* Make startCreatedAt required in task event store
2025-02-12 09:12:08 +00:00
Matt Aitken f0726affa2 Release 3.3.15 2025-02-12 00:42:27 +00:00
github-actions[bot] 0ceb37f0e8 chore: Update version for release (#1695)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-02-12 00:37:38 +00:00
Matt Aitken 252bf8d806 Improved the machines docs, including the new OutOfMemoryError 2025-02-12 00:25:03 +00:00
Matt Aitken efd2d21762 Detect ffmpeg OOM errors, added manual OutOfMemoryError (#1694)
* Detect ffmpeg OOM errors, added manual OutOfMemoryError

* Create eighty-spies-knock.md
2025-02-12 00:02:01 +00:00
Matt Aitken 6017c522e1 Batch queue runs that are waiting for deploy (#1693) 2025-02-11 19:06:38 +00:00
Matt Aitken a50b93faa4 Set machine when triggering docs 2025-02-11 12:43:45 +00:00
Matt Aitken d4043fc258 Release 3.3.14 2025-02-11 12:29:54 +00:00
github-actions[bot] 6ec786c7b2 chore: Update version for release (#1666)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-02-11 12:28:35 +00:00
Matt Aitken d2ee232fae Revert "Complete the original attempt span if retrying due to an OOM"
This reverts commit 5f652c6212.
2025-02-11 11:33:07 +00:00
Matt Aitken 5f652c6212 Complete the original attempt span if retrying due to an OOM 2025-02-11 11:01:21 +00:00
Matt Aitken 036a5061e1 Kubernetes OOMs appear as non-zero sigkills, adding support for treating these as OOMs 2025-02-11 10:32:01 +00:00
Matt Aitken bb65b2614a OOM retrying on larger machines (#1691)
* OOM retrying on larger machines

* Create forty-windows-shop.md

* Update forty-windows-shop.md

* Only retry again if the machine is different from the original
2025-02-10 17:43:59 +00:00
Matt Aitken 4dd42cd6d4 Retry heartbeat timeouts by putting back in the queue (#1689)
* If there’s a heartbeat error and no attempts we put it back in the queue to try again

* When nacking, return whether it was put back in the queue or not

* Try and nack, if it fails then fail the run

* Consolidated switch statement

* Fail executing/retrying runs
2025-02-10 13:41:32 +00:00
Matt Aitken bc7d44582d If there’s no queue concurrency limit, default to the env concurrency limit (not 1m) (#1687) 2025-02-09 21:34:40 +00:00
Victor Duarte 2771fd7408 Missing construction option in AudioWaveformExtension (#1684)
* missing construction option in `AudioWaveformExtension`

* Create lemon-fireants-repair.md

---------

Co-authored-by: Matt Aitken <matt@mattaitken.com>
2025-02-09 15:52:46 +00:00
James Ritchie 1f1a6b0405 Fixed runs page filter bug (#1681) 2025-02-07 17:32:30 +00:00
Eric Allam 9f843d0a0f Remove v2 event list (#1680) 2025-02-07 13:51:34 +00:00
Eric Allam 9dfd6a5a0a Rate limit alerts by channel for task run alerts using generic cell rate algo (#1679) 2025-02-07 13:36:37 +00:00
Eric Allam 5210d3a8bb Add internal spans to event repo (#1678) 2025-02-07 13:36:15 +00:00
Eric Allam b5867627be Use idempotency key index when finding existing runs by idempotency key in batch (#1677) 2025-02-07 13:36:03 +00:00
Eric Allam 0b555faf01 Force upgrade v1 batches to v3 (#1676)
* Force upgrade v1 batches to v3

* Actually lets process v1 batches async
2025-02-06 22:37:26 +00:00
Eric Allam dcf1ab6f38 Remove schedule constraints from TaskRun to prevent db load issues when a schedule or instance is deleted (#1674) 2025-02-06 19:16:48 +00:00
Matt Aitken c49af774ba Retry batch item completion (#1675)
* Added isPrismaRetriableError()

* Retry completeBatchTaskRunItem if they fail because of a retriable Prisma error

* Retry using Redis worker

* Handle more retriable errors. Add special condition in for race condition error

* Added Postgres connection_timeout with default 20s

* Added a simple batchTriggerAndWait example
2025-02-06 19:16:30 +00:00
Eric Allam f00ed9b13e Add the sha256 digest to the deployment image reference (#1673) 2025-02-06 19:14:34 +00:00
Eric Allam 3ec6983e45 Move run ttl and delays from graphile to redis worker (#1672) 2025-02-06 14:25:37 +00:00
Eric Allam a2c2d920b7 Use redis worker for run heartbeats and alerts (#1669)
* Move the task run heartbeats to RedisWorker

* Move alerts to redis worker, improving redis worker

* Fix typecheck errors

* Use single threaded tests for redis worker

* Enable/disable the redis workers independently

* Remove preview release from PR checks
2025-02-06 10:21:57 +00:00
nicktrn b946b9f38e Add optional env var and label to disable image pre-pulls (#1667)
* add env var to disable pre-pulls

* add label to disable pre-pulls on specific nodes
2025-02-05 16:49:57 +00:00
Eric Allam 99d38151f6 Auto-fix config.dirs of /trigger and /src/trigger (#1665) 2025-02-05 13:00:18 +00:00
Eric Allam e35b6f539a Release 3.3.13 (#1664)
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 0s
🚀 Publish Trigger.dev Docker / units (push) Failing after 0s
🚀 Publish Trigger.dev Docker / publish-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker (push) Has been skipped
2025-02-04 16:32:26 +00:00
github-actions[bot] 20d983e08a chore: Update version for release (#1654)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-02-04 16:31:00 +00:00
Eric Allam 3887c4a621 Fix "no tasks defined" issue (fix #1681) (#1663) 2025-02-04 16:08:57 +00:00
Eric Allam e61573600b Reduce contention on batchTaskRun when setting expected count (#1662) 2025-02-04 13:43:50 +00:00
Eric Allam 53e6d478ed Fix deploy timeout issues (#1661)
* deploy v2 streaming WIP

* finalize deployment now SSE and won't timeout

* handle zodfetchSSE connection errors

* Create tender-cycles-melt.md
2025-02-04 12:44:14 +00:00
nicktrn 7fa45ad337 Fix pre-pulling of deployment images onto worker nodes (#1660)
* lint

* get pre-pull data from finalized deployment
2025-02-04 10:07:07 +00:00
Eric Allam 42c5f4d9fa Add text_pattern_ops index on SecretKey.key to improve getSecrets perf (#1659) 2025-02-03 22:52:16 +00:00
Eric Allam 6f52b00560 Set taskRunAttemptId on batch task run items on completion (#1658) 2025-02-03 15:25:12 +00:00
D-K-P d285f7930e Added guides and examples links and snippet 2025-02-03 14:30:03 +00:00
Eric Allam b377b577ca db efficiency improvements (#1657)
- remove calls to findUnique, especially when including/selecting relations
- add some missing indexes
- add spans to $transaction calls to help track down long running txs
2025-02-03 13:59:58 +00:00
Eric Allam b67ea9a2f5 improve batch completion system for run engine v1 (#1656)
* Automatically retry TriggerTaskService when hitting a unique constraint error on idempotency key

* improve batch completion system for run engine v1

* Rename batch stuff to v3 so it's not confusing

* Handle unique constraint error on BatchTaskRunItem creation and allow different limits for batchTrigger and batchTriggerAndWait
2025-02-03 11:29:51 +00:00
D-K-P de31220820 Docs correction 2025-01-31 15:15:23 +00:00
Eric Allam 3641647add Add the keyPrefix option when redis is not in cluster mode (#1655) 2025-01-31 10:50:52 +00:00
D-K-P 664bce561d Added vercel ai sdk image generator example to the docs 2025-01-30 15:39:28 +00:00
Eric Allam 73471589ef Fixed issue with asResponse and withResponse not working on runs.retrieve (#1648) 2025-01-30 14:01:38 +00:00
Eric Allam 813d73d1a3 Fix issue with heavy contention on TaskQueue updating concurrency limit (#1653)
* Fix issue with heavy contention on TaskQueue updating concurrency limit

* Remove concurrency limit when creating background worker tasks
2025-01-30 14:01:11 +00:00
nicktrn 9ad396bdb1 Fix custom queue docs 2025-01-30 13:53:48 +00:00
James Ritchie 4e7c55920d Docs maxDuration page now states it’s a required param 2025-01-29 19:30:25 +00:00
Eric Allam 3eede3c339 Better handle redis error events (#1652) 2025-01-29 17:27:52 +00:00
Eric Allam b3afbcf558 Correctly configure redis cluster for aws elasticache (#1651) 2025-01-29 16:56:51 +00:00
Eric Allam f43de6ab74 Support redis/valkey cluster mode (#1650) 2025-01-29 16:28:51 +00:00
mintlify[bot] 3127df5b67 Add new Bulk Actions section to documentation (#1649)
* Documentation edits made through Mintlify web editor

* Moves files to correct path and improves copy

* Corrects video props to camel case and removes deprecated prop

* Link to the new bulk actions page from the replaying page

---------

Co-authored-by: mintlify[bot] <109931778+mintlify[bot]@users.noreply.github.com>
Co-authored-by: James Ritchie <james@trigger.dev>
2025-01-29 16:01:01 +00:00
Eric Allam 9fd175e860 Prevent findUnique from bringing down our database (#1633) 2025-01-29 13:57:16 +00:00
Eric Allam ef0fc6b0b4 Support for logically separated redis instances (#1647)
* Remove unnecessary disabled org check

* Add support for separate redis servers for caching, rate limiter, marqs, and the pub/sub stuff (they all default to the previously used env vars for redis)
2025-01-29 13:53:58 +00:00
Eric Allam a24b2a7732 More v1 run engine fixes (#1644)
* Remove the socket count from shared queue consumer cause it can take up to 5s to complete

* Add support for re-using queue snapshots across dequeues

* Only consider the top N orgs when dequeuing, to help mitigate large spikes in queues (like around the hour and half hour marks)
2025-01-28 16:16:37 +00:00
Eric Allam 6da5e7a4cf Various fixes for run engine v1 (#1643)
* Various fixes for run engine v1

- Make sure there are connected providers before sending a scheduled attempt message, nack and retry if there are not
- Fail runs that fail task heartbeats when pending and locked
- More and better logging around shared queue consumer
- Fix bug when failing a task run with no attempt

* Prevent findUnique from bringing down our database
2025-01-27 17:15:17 +00:00
Eric Allam 75753ec5ad Release 3.3.12 2025-01-24 19:19:55 +00:00
github-actions[bot] 40e0d2f7e9 chore: Update version for release (#1626)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-24 19:18:39 +00:00
Eric Allam 246eb4361e If a deployment is already deployed, just return the deployment so the request succeeds (#1639) 2025-01-24 18:47:26 +00:00
nicktrn ab6e6afbf5 switch to github builders 2025-01-24 18:15:40 +00:00
Eric Allam 7445d9bf35 install ca-certificates in docker image (#1638) 2025-01-24 18:01:42 +00:00
Eric Allam f6eeaa1001 Fix broken cloud deploys by using depot ephemeral registry, skip the registry proxy (#1637) 2025-01-24 17:22:35 +00:00
Eric Allam eaf46bac58 engine v1 improved dequeue selection algorithm (#1632)
* engine v1 fairness improvements (env based queue selection)

* Add ability to bias env selection by concurrency limit and capacity

* Add some randomization to the list of queues within each evn

* Configure biases using env vars

* Typo

* revert v3 catalog project ref change

* Fix v2 marqs
2025-01-23 15:01:42 +00:00
James Ritchie 16cf357380 Focus visible style refinements (#1577)
* reduces the thickness of the focus-visible

* More subtle Input style

* Updates focus-visible styles for the SimpleSelect

* Tables tab through rows and action cells only

* Refined focus visible style

* Adds the custom focus visible to the TextLink

* Moves the onClick handling to the table row rather than the cell

* Makes table row heights consistent

* Adds gap between task search bar and button

* Prevents long tag values from wrapping

* Removes unnecessary rows from table header

* Added gap between search and filters

* Fixes the schedules table pagination staying fixed to the bottom of the page

* Removed more unnecessary header table rows to prevent them being selectable

* Removed isSelected styles (not working)

* Added <tr> back to the main Table compontent

* Table row handles modifier keys

* Adds to={path} to the TableRow only

* Revert "Adds to={path} to the TableRow only"

This reverts commit 8a814d434ff85b8ab9c2305d336074f659d86094.

* Revert "Table row handles modifier keys"

This reverts commit b2394741fa2ed6b908e00673b1a395ae89ca7146.

* Table reverted to use linked cells rather than rows

* Set the tab index of a cell and style the table row when tabbed

* Tabbed row style applied to the sticky cells

* Adds isTabbableCell to each table

* Improves the spcificity of the row highlighting

* Reduces the height of the task rows to match the other tables

* Adds tab styles to fill in row dividers top and bottom

* Removed old row onClick and to props

* Removed duplicate table header row

* Creates table style variants so tables look good in the inspector panels

* Full width tables on the schedules inspector panel

* Reduce padding on Alerts page table rows

* Updates the Deploy page inspector table to the new style

* Removes the duplicate Table Row from the table headers in v2

* Adds TableRow to the EventsTable

* Adds TableRow to more tables

* Adds TableRow to more tables

* Adds TableRow to more tables

* Adds TableRow to more tables

* Fix for showing correct cursor for linked table rows

* Removed variants from some of the table components
2025-01-21 21:11:59 +00:00
Eric Allam f96bf7210b Engine v1 improvements (#1627)
* Efficiency improvements to SharedQueueConsumer and MarQS for run engine v1

* Reliabily resolve task queue for a run and ack runs where we can't find the queue

* engine v1: stop nacking after reaching the max nack count, ack to remove the message

* Handle division by 0 possibility in distributeQueues
2025-01-21 14:24:02 +00:00
James Ritchie c855e4a96c v3 pages have page titles (#1582)
* Adds tasks page title

* Adds page titles to the <head>
2025-01-21 12:07:32 +00:00
Eric Allam e1497c802d Update config docs for new telemetry property (#1610) 2025-01-20 10:29:57 +00:00
Eric Allam e2612224b4 Fix "crypto is not defined error" on Node.js 18 in dev (#1623)
* Add --experimental-global-webcrypto node option fix "crypto is not defined error" on Node.js 18 in dev

* Only add flag when on node 18

* Missed the function
2025-01-20 10:29:26 +00:00
Matt Aitken 0d38ea0feb Updated the batch REST API docs to the new endpoint 2025-01-17 13:28:02 +00:00
Eric Allam 0687ce722c Release 3.3.11 (#1621)
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 4s
🚀 Publish Trigger.dev Docker / units (push) Failing after 4s
🚀 Publish Trigger.dev Docker / publish-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker (push) Has been skipped
2025-01-17 12:06:41 +00:00
github-actions[bot] d627fa718e chore: Update version for release (#1611)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-17 12:05:15 +00:00
Matt Aitken 5798024a5b Require max duration in trigger.config. Validate TRIGGER_ACCESS_TOKEN is a PAT. (#1620)
* Show a good error if TRIGGER_ACCESS_TOKEN isn’t a PAT

* Require maxDuration in trigger.config from now on

* Changeset

* Update the trigger.config templates copy, and set the default to 1hr

* Set the maxDuration in the trigger.config files inside the repo

* Fixed the e2e tests by setting the package to 0.0.0-prerelease-20250116195421
2025-01-17 11:32:55 +00:00
Eric Allam 5750f35add Shared queue consumer telemetry improvements and perf changes (#1619)
* Shared queue consumer telemetry improvements

* findUnique -> findFirst to stop the prisma dataloader issue on api key auth

* Added action stats, audited all actions and improved reasons

* Better debug logging filtering
2025-01-17 09:15:21 +00:00
Matt Aitken 11f2832ae5 Fix for detecting internal errors 2025-01-16 17:03:39 +00:00
Matt Aitken fd04ed50ef Prevent multiple waits callback delayed by one tick
To ensure the first wait doesn't checkpoint before the second is called
2025-01-16 16:23:56 +00:00
Matt Aitken 6d17443e16 Changeset: Detect parallel waits and show a useful error message 2025-01-16 15:48:55 +00:00
Matt Aitken dd321e3616 Throw an error for concurrent waits (with nice docs link) (#1618)
* WIP preventing concurrent waits, throw an error

* Added ConcurrentWaitError (not retryable)

* Move preventMultipleWaits out of the RuntimeAPI

* Added preventMultipleWaits to the devRuntimeManager

* Added throwable InternalError. Plus new TASK_DID_CONCURRENT_WAIT code

* Docs link for troubleshooting concurrent waits

* Docs for troubleshooting concurrent waits

* preventMultipleWaits function

* Added TASK_DID_CONCURRENT_WAIT code

* Deal with InternalErrors that skipRetrying

* Added preventMultipleWaits to prod
2025-01-16 14:33:14 +00:00
Gabe O'Leary 2ad664d17d Add note about prisma-json-types-generator (#1617) 2025-01-15 23:18:42 +00:00
Matt Aitken 5eb30d026d Improve worker deployment queries (#1616)
* workerDeployment change findUnique to findFirst

* Changed the worker deployment queries to only select what they need, some of the columns are huge

* Only get the bare minimum data for tasks too
2025-01-15 20:34:28 +00:00
nicktrn 719ae83057 Add support for specifying machine preset at trigger time (#1608)
* add machine preset trigger option

* add changeset

* add examples to changeset

* machinePreset -> machine

* task options now also accept machine as simple string

* update openapi spec
2025-01-15 13:47:13 +00:00
Eric Allam 7eda22851e Prevent v2 batches from being resumed before all items have been created (#1614) 2025-01-14 14:37:04 +00:00
Eric Allam b3aa87ee26 Add custom telemetry exporter support (#1602)
* WIP langsmith & AI SDK integration

* Add changeset

* Add exporter support to deployed tasks

* Better support for external exporters and group exporters and instrumentations under the telemetry config property

* Missing changes
2025-01-13 22:43:34 +00:00
Eric Allam 14ce599218 Add reason to MarQS acknowledgeMessage to track why it's called (#1607) 2025-01-13 17:29:09 +00:00
Eric Allam 0a18e1d919 Additional dev queue consumer logging (#1606) 2025-01-13 16:37:19 +00:00
Eric Allam ca51f65394 Truncate ingested log messages to 4096, and truncate message to 256 in the summary tree view (#1604) 2025-01-13 11:35:39 +00:00
Eric Allam 46a37fdc49 Release 3.3.10 (#1601)
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 0s
🚀 Publish Trigger.dev Docker / units (push) Failing after 4s
🚀 Publish Trigger.dev Docker / publish-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker (push) Has been skipped
2025-01-10 19:52:30 +00:00
github-actions[bot] 24383dc65d chore: Update version for release (#1600)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-10 19:50:41 +00:00
Eric Allam e13edd3a20 Handle errors thrown by requests in Realtime react hooks (#1599) 2025-01-10 19:40:34 +00:00
Eric Allam 9e0f03623d Support ignoring test/spec files inside trigger dirs (fixes #1593) (#1596)
* Support ignoring test/spec files inside trigger dirs (fixes #1593)

* Add changeset
2025-01-10 19:40:08 +00:00
Eric Allam 914ceaf3c5 dev CLI: cleanup deprecated background worker files (fixes #1572) (#1595)
* dev CLI: cleanup deprecated background worker files (fixes #1572)

* Add changeset
2025-01-10 19:39:34 +00:00
nicktrn 472e5f93e1 Fix: Prevent failed resumes for very large batches (#1598)
* findUnique -> findFirst

* remove tx

* make query more legible

* cut resume payload queries in half

* remove the additional status checks

* tweak the catalog batch parent task

* fix error message
2025-01-10 18:33:25 +00:00
D-K-P 51a054f534 Added Realtime CSV importer to the docs 2025-01-09 17:13:55 +00:00
Eric Allam efd61f1374 Release 3.3.9 (#1591)
🚀 Publish Trigger.dev Docker / units (push) Failing after 3s
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 10s
🚀 Publish Trigger.dev Docker / publish-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker (push) Has been skipped
2025-01-09 11:35:16 +00:00
github-actions[bot] 2c7f7b1d5e chore: Update version for release (#1586)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-09 11:34:03 +00:00
Eric Allam c92edd61af New metadata system doc updates (#1590) 2025-01-09 11:13:48 +00:00
Eric Allam ab69b60348 New metadata deployed task fix (#1588)
* Add more logging around new metadata system

* Make the authenticated env optional when updating run metadata
2025-01-08 22:42:34 +00:00
Dan d52e3d7d57 Added LLM evaluator to example projects (#1589)
* Added repo links

* Added links to remix / nextjs webhooks examples

* Added a Realtime learn more snippet

* Added the batch llm evaluator docs

* Improved snippet

* Updated intro
2025-01-08 21:16:56 +00:00
Eric Allam 3ca0c15161 Add ability to disable metadata flushing and logging (#1587) 2025-01-08 12:37:43 +00:00
James Ritchie 0aa597104c Shortcut improvements (#1573)
* Added shortcuts (with tooltip) to the pagination

* WIP fixing the disabled hover state

* Styled the tooltip

* Added a shortcuts panel to the help menu

* Adds new shortcut to list

* Changes “meta” for “mod”

* Adds more shortcuts to the list

* Adding shortcut to open the shortcuts panel

* tweak gap between shortcut letters

* button component now has icon spacing adjustment (for lucide icons)

* Fixed some ilegal markup

* Pagination uses disabled prop rather than a disabled wrapper

* Improved the Switch styles

* Makes the shortcut modifier optional

* Added new icon based shortcut keys for mac and win

* Updated PC modifier shortcuts

* Adds a new windows key icon

* Allows variants and react nodes to be used as the modifier key

* Adds more shortcuts to the storybook

* Adds missing focus-visible styles to the pagination

* Removed test modifier keys

* number style is tabular

* Update apps/webapp/app/components/primitives/ShortcutKey.tsx

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Tooltip now just 1 prop on the button component

---------

Co-authored-by: Matt Aitken <matt@mattaitken.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-01-08 11:58:18 +00:00
Eric Allam 34f8bd588e Add ability to update parent and root run metadata from children (#1563)
* Increase the number of active streams from 2 to 5

* WIP parent metadata updates

* Fix noop metadata manager

* Implement run metadata updates from ancestor tasks

* Add changeset

* Add ability to stream into parent and root task runs

* Don't duplicate exporting run metadata types

* Add ability to fetch streams through metadata

* Couple of fixes from CodeRabbit

* Fix metadata tests

* Make sure streams are subscribed in the "background"

* Move the stream subscription stuff to the API client, expose it through `runs.fetchStream`

* Fixed run stream tests
2025-01-08 11:29:24 +00:00
Eric Allam 4243cb2451 Release 3.3.8
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 0s
🚀 Publish Trigger.dev Docker / units (push) Failing after 0s
🚀 Publish Trigger.dev Docker / publish-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker (push) Has been skipped
2025-01-08 10:29:06 +00:00
github-actions[bot] 7e8a0d96d2 chore: Update version for release (#1570)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-08 10:26:37 +00:00
Willow (GHOST) 4d19dec71a fix: add user-agent to the websocket request (#1584) 2025-01-07 22:21:32 +00:00
Eric Allam 76a5ac293e Realtime fixes: safari, timezones, and TIMED_OUT status (#1585)
* Fix realtime safari bug because of missing ReadableStream async iterable support
Limit to only safari

* Fix missing TIMED_OUT run status

* When coercing realtime date strings, make sure they are set to UTC
2025-01-07 20:47:49 +00:00
Dan 668b34d25e Adding example projects (#1564)
* Added new side menu section and updated intro

* Updated the fal ai realtime project

* Copy update

* Added example project

* Swapped walkthrough and repo

* Restore original fal task page

* Added links back to the intro
2025-01-03 20:00:40 +00:00
James Ritchie 6a2d033f32 Project setup improvements (#1579)
* Adds ‘an existing project’ to step 1

* When you click pnpm or yarn, the other tabs change too
2024-12-30 18:07:07 +00:00
James Ritchie e71df640a9 Adds more org names to the alert emails (#1561)
Co-authored-by: Matt Aitken <matt@mattaitken.com>
2024-12-30 17:54:38 +00:00
James Ritchie 330f6c548e Softened the language in the Pro upgrade modal (#1574) 2024-12-30 17:49:00 +00:00
Erin Allison 4d2412a0fe Add Support for Alternate Email Transports (#1580)
* Add support for AWS SES and SMTP email transports

Signed-off-by: Erin Allison <eallison@andrettikarting.com>

* Correct environment variable names for new email settings

Signed-off-by: Erin Allison <eallison@andrettikarting.com>

* Correct option names being passed to nodemailer for SMTP

Signed-off-by: Erin Allison <eallison@andrettikarting.com>

* Remove use of AWS SDK synthetic default export

It apparently causes issues when transpiled/bundled

Signed-off-by: Erin Allison <eallison@andrettikarting.com>

* Add documentation for new email settings

Signed-off-by: Erin Allison <eallison@andrettikarting.com>

* Move nodemailer types to devDependencies

Signed-off-by: Erin Allison <eallison@andrettikarting.com>

* Adjust mail transport error handling

Gotta keep the linter happy :)

Signed-off-by: Erin Allison <eallison@andrettikarting.com>

* Fix typecheck error on MailTransportOptions

Signed-off-by: Erin Allison <eallison@andrettikarting.com>

* Correct environment variable usage for SMTP email transport

Signed-off-by: Erin Allison <eallison@andrettikarting.com>

---------

Signed-off-by: Erin Allison <eallison@andrettikarting.com>
2024-12-30 17:38:17 +00:00
Matt Aitken 21a4fabc28 Docs update batch limits to 5MB 2024-12-18 11:47:50 +00:00
Eric Allam 3418aa614b Don't expire enqueued runs if they have lockedAt set (#1571) 2024-12-16 19:00:18 +00:00
Eric Allam 88a0754ae0 Support scopes packages in externals when found in a transitive dep #1552 (#1569)
* Support scopes packages in externals when found in a transitive dep

* fixed typescript errro

* Add changeset
2024-12-16 17:55:44 +00:00
Eric Allam f3e4597ef4 Update scrape-hacker-news.mdx 2024-12-14 21:11:37 +00:00
Eric Allam 2bf04ece54 Release 3.3.7 (#1562)
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 0s
🚀 Publish Trigger.dev Docker / units (push) Failing after 0s
🚀 Publish Trigger.dev Docker / publish-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker (push) Has been skipped
2024-12-13 15:49:52 +00:00
github-actions[bot] 295b2eb3b6 chore: Update version for release (#1558)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-12-13 15:48:04 +00:00
James Ritchie f0182abc83 Alert emails now contain the Org name (#1555)
* Alert emails now contain the Org name

* Adds org title to the alert parameters
2024-12-13 13:32:34 +00:00
James Ritchie 1105b9b71a 3 small improvements (#1560)
* Updated login page logos

* Adds an isSelected state to the Table

* Toast style now matches the design

* Adds a space between the upgrade panel and the list of users
2024-12-13 13:32:19 +00:00
Eric Allam 7077dbf65e @trigger.dev/react-hooks now works with React 19 (#1559) 2024-12-13 13:31:47 +00:00
Eric Allam 6b355ab9ad Upgrades and fixes to Realtime and Realtime streams (#1549)
* Fix streaming splits in realtime streams v2

* Add changeset

* Skip all flaky tests 😡

* Improve the way we stream from tasks to the server

* Improve the v1 realtime streams (Redis)

* Turn on the relay realtime stream service

* Improved the relay realtime cleanup

* Fixed consuming realtime runs w/streams after the run is already finished

* Remove some logs

* Update changeset

* Fixed runStream tests
2024-12-13 11:42:50 +00:00
Niloy 68d7139ebe Update deploy-environment-variables.mdx (#1509)
base64 with -i for input file
2024-12-13 11:16:47 +00:00
James Ritchie c58f6f7a68 Upgrading from Hobby to Pro displays a dialog to confirm (#1545)
* Upgrading from Hobby to Pro displays a dialog to confirm

* Improved the logic for upgrading to pro

* Added the spinner component to storybook

* Improved the dark style spinner
2024-12-13 11:14:47 +00:00
James Ritchie 6516e15bd8 Changes frozen to waiting (#1551)
* Offset the tooltip in more

* The Select component’s shortcut has a better hover state

* “Frozen” changed to “Waiting”
2024-12-13 11:13:34 +00:00
James Ritchie 7f2365f4e0 Adds a tooltip to the Status header showing deployment status descriptions (#1554) 2024-12-13 11:11:14 +00:00
James Ritchie f560594610 Tabs horizontally overflow scroll in the side panel inspectors (#1556) 2024-12-13 11:08:04 +00:00
Matt Aitken ebd9767926 When replaying, using the concurrencyLimit from the original run’s queue 2024-12-12 16:48:22 +00:00
Matt Aitken 1c631a1b6f Skip the RealtimeClient tests for now in CI… flaky 2024-12-12 14:39:35 +00:00
Matt Aitken 110a8ea48c Added queue updating logging. For Triggering changed the concurrency limit calculation to match deploying 2024-12-12 13:49:20 +00:00
James Ritchie 10f0ef3301 Adds v2 countdown eol text in side menu (#1546) 2024-12-11 12:24:23 +00:00
nicktrn 2e4a6307b4 Fix: Prevent some attempt creation retries from causing system failures (#1550) 2024-12-11 12:01:18 +00:00
Eric Allam 30ea5eb13a Release 3.3.6 (#1544)
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 4s
🚀 Publish Trigger.dev Docker / units (push) Failing after 19s
🚀 Publish Trigger.dev Docker / publish-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker (push) Has been skipped
2024-12-10 15:53:45 +00:00
github-actions[bot] 5846f30228 chore: Update version for release (#1537)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-12-10 15:52:05 +00:00
Eric Allam 3afa42c209 Make TRIGGER_REALTIME_STREAM_VERSION overridable by the project env vars (#1543) 2024-12-10 14:54:50 +00:00
Eric Allam 86b1628953 Add run metadata to runs.list endpoint (#1542) 2024-12-10 12:52:42 +00:00
Matt Aitken ea23dbd297 Throw an error if Resend responds with an error (#1540) 2024-12-10 12:37:36 +00:00
Matt Aitken 9065e64be8 Added logging when we remove a queue’s concurrency limit 2024-12-10 09:57:41 +00:00
Eric Allam 9970b9b68e Realtime streams now powered by electric (#1541)
* Realtime streams now powered by electric, and fix the streaming update duplicate issues by converting the electric Shape materialized view into a ReadableStream of changes

* Ensure realtime subscription stops when runs are finished, and add an onComplete handle to use realtime hooks

* Fix tests
2024-12-09 22:09:30 +00:00
Matt Aitken b4113134ad Fix: test form: update the callback when the task changes 2024-12-08 22:07:42 +00:00
Eric Allam 2a07ea42f1 Optionally trigger batched items sequentially to preserve order (#1536)
* Optionally trigger batched items sequentially to preserve order

* Fix infinite v3.processBatchTaskRun enqueuings by checking the attemptCount
2024-12-05 15:16:06 +00:00
Eric Allam 91afa5ebbf Release 3.3.5 (#1535)
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 3s
🚀 Publish Trigger.dev Docker / units (push) Failing after 5s
🚀 Publish Trigger.dev Docker / publish-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker (push) Has been skipped
2024-12-03 18:13:54 +00:00
github-actions[bot] 65262dc3d7 chore: Update version for release (#1534)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-12-03 18:12:28 +00:00
Eric Allam 9105701ae0 Fix cancelled runs breaking realtime subscriptions (#1533) 2024-12-03 16:55:27 +00:00
nicktrn 9b35cc484b Release 3.3.4
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 5s
🚀 Publish Trigger.dev Docker / units (push) Failing after 20s
🚀 Publish Trigger.dev Docker / publish-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker (push) Has been skipped
2024-12-03 12:16:56 +00:00
github-actions[bot] 30a04a5a06 chore: Update version for release (#1531)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-12-03 12:16:08 +00:00
nicktrn 493315af48 Hide batch actions for dev runs 2024-12-03 11:18:40 +00:00
Eric Allam 8db1da69e9 Triggering docs and adding options to all trigger hooks (#1528) 2024-12-03 10:15:49 +00:00
nicktrn cd7a45101e Fix: Extract the correct SDK version during build (#1530)
* fix build manifest sdk version

* new cli version prop in deployment tab

* changeset
2024-12-03 10:00:55 +00:00
nicktrn 29d107dc0a Feat: Add button to try and resume pending batches (#1529)
* add try and resume action for in-progress batches

* don't reinvent the wheel
2024-12-03 09:50:55 +00:00
Eric Allam cf7dc8d719 Release 3.3.3 (#1527)
🚀 Publish Trigger.dev Docker / units (push) Failing after 0s
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 20s
🚀 Publish Trigger.dev Docker / publish-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker (push) Has been skipped
2024-12-02 20:02:34 +00:00
github-actions[bot] 9ced599b19 chore: Update version for release (#1526)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-12-02 20:01:09 +00:00
Eric Allam 67592ec2b4 Multiple streams can now be consumed at the same time (#1522)
* Mutliple streams can be now consumed simultaneously

* Update prerelease script

* Add changeset

* Make it core

* Handle API error responses when streaming
2024-12-02 17:58:10 +00:00
Eric Allam f7bf7bc268 Release 3.3.2 (#1521)
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 3s
🚀 Publish Trigger.dev Docker / units (push) Failing after 21s
🚀 Publish Trigger.dev Docker / publish-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker (push) Has been skipped
2024-12-02 14:14:14 +00:00
github-actions[bot] bb57426a0d chore: Update version for release (#1516)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-12-02 14:12:27 +00:00
Eric Allam 3ab7eb9c7a Add unique indexes for the oneTimeUseToken columns (#1517) 2024-12-02 13:45:38 +00:00
Eric Allam 2892efad04 Fix: actually wait for realtime streams to finished (#1520)
* Realtime streams fix waiting for streams to finished

* Add changeset
2024-12-02 13:40:18 +00:00
nicktrn 979ba51d2f Feat: configurable deploy timeouts (#1519)
* make deploy timeout configurable

* increase default deploy timeout to 8 minutes
2024-12-02 11:02:16 +00:00
Eric Allam 364ea565ed Use one-time use tokens when triggering or batch triggering from the frontend (#1515)
* Use one-time use tokens when triggering or batch triggering from the frontend

* Add changeset
2024-11-29 20:16:37 +00:00
Eric Allam 58252728f6 Release 3.3.1 (#1513)
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 4s
🚀 Publish Trigger.dev Docker / units (push) Failing after 4s
🚀 Publish Trigger.dev Docker / publish-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker (push) Has been skipped
2024-11-29 13:00:10 +00:00
github-actions[bot] 5eaad0577e chore: Update version for release (#1508)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-11-29 12:56:50 +00:00
Matt Aitken 00f1103deb Show "root" runs by default, save the preference to a cookie (#1512)
* “Root only” toggle, off by default. Disabled when filtering by tasks

* Remove text transition on Switch because it didn’t match the container
2024-11-29 12:40:15 +00:00
Eric Allam c37622e7b6 Realtime: improve scope access to runs with tags and batches (#1511)
* JWT scopes for tags and batches can now access runs that have the tag or are in the batch

- useTaskTrigger can now submit options
- auto-generated batch trigger public access tokens no longer need each individual run ID scope

* Add changeset

* Added task scopes to work like tags and batches

Also removed scopes for tags when auto-generating a public access token as that could be dangerous.
2024-11-29 11:37:11 +00:00
Eric Allam d67023aa8f Fixed the missing icons in trigger spans (#1506) 2024-11-28 19:09:33 +00:00
Matt Aitken d9bfe55a8c Run filters: fix for “Batch ID” that should say “Schedule ID” 2024-11-28 17:00:17 +00:00
Eric Allam f4a18feca0 Docs: Batch Trigger upgrades (#1505)
* WIP batch docs

* More batch trigger v2 docs
2024-11-28 15:55:45 +00:00
Matt Aitken 0acd052061 Release 3.3.0
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 0s
🚀 Publish Trigger.dev Docker / units (push) Failing after 4s
🚀 Publish Trigger.dev Docker / publish-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker (push) Has been skipped
2024-11-28 15:27:20 +00:00
github-actions[bot] 6bf3bbcfd7 chore: Update version for release (#1504)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-11-28 15:26:11 +00:00
James Ritchie 1077709e15 Side help panel (#1488)
* WIP adding a side help panel to the tasks page

* Optionally display the shortcut before the trailing icon in the button

* Updated the close icon

* WIP adding a new side help panel

* WIP adding content to the side help panel

* WIP new side help panel content

* Removed images as not needed any more

* Added content to the side help menu

* Help panel open/closed state stored as cookie

* Removed the icons from the docs and examples links
2024-11-28 11:49:52 +00:00
Willow (GHOST) 54017cbffa feat: always show alerts tab and lock email alerts when keys are missing (#1478) 2024-11-28 11:43:34 +00:00
James Ritchie 39ef733a34 Deploy page table rows stay selected when the side menu is open (#1492) 2024-11-28 11:35:08 +00:00
Fernando Coelho 736f577c25 Update Infisical instructions to new SDK (#1493)
* Update Infisical instructions to new SDK

Current documentation is using old Infisical SDK implementation.

New SDK has a different name, and authenticates on a method, not in the class constructor.

* Fix import and client secret env
2024-11-28 11:34:27 +00:00
James Ritchie b7de02ec14 Improved the text selection style (#1497) 2024-11-28 11:33:27 +00:00
James Ritchie 90db0f6e01 Adds realtime limits to the billing page pricing tiers (#1500) 2024-11-28 11:32:46 +00:00
Eric Allam 6df8069c0e Batch Trigger upgrades (#1502)
* WIP batch trigger v2

* Fix for the DateField being one month out… getUTCMonth() is zero indexed 🤦‍♂️

* Added a custom date range filter

* Deal with closing the custom date range

* Child runs filter

* Fix for the clear button untoggling the child runs

* WIP batchTriggerV2

* Finished removing rate limit from the webapp

* Added an index TaskRun to make useRealtimeBatch performant

* Renamed the period filter labels to be “Last X mins”

* Denormalize background worker columns into TaskRun

* Use the runTags column on TaskRun

* Add TaskRun ("projectId", "id" DESC) index

* Improved the v2 batch trigger endpoint to process items in parallel and also added a threshold, below which the processing of items is async

* Added a runId filter, and WIP for batchId filter

* WIP triggerAll

* Add new batch methods for triggering multiple different tasks in a single batch

* Disabled switch styling

* Batch filtering, force child runs to show if filtering by batch/run

* Added schedule ID filtering

* Force child runs to show when filtering by scheduleId, for consistency

* realtime: allow setting enabled: false on useApiClient

* Batches page

* Always complete batches, not only batchTriggerAndWait in deployed tasks

* Add batch.retrieve and allow filtering by batch in runs.list

* Renamed pending to “In progress”

* Tidied up the table a bit

* Deal with old batches: “Legacy batch”

* Added the Batch to the run inspector

* Fixed the migration that created the new idempotency key index on BatchTaskRun

* Fixed the name of the idempotencyKeyExpiresAt option and now default idempotency key TTL is 30 days, not 24 hours

* Timezone fix: wrong month in Usage page dropdown

* The DateField now defaults to local time, but can be overriden to use utc with an option

* Don’t allow the task icon to get squished

* BatchFilters removed unused imports

* In the batch filtering, use `id` instead of `batchId` in the URL

* BatchFilters: we don’t need a child tasks hidden input field

* Creates some common filter components/functions

* Fix for batchVersion check when filtering by batch status

* Add additional logging around telemetry and more attributes for trigger spans

* Show clear button for specific id filters

* Batch list: only allow environments that are part of this project

* Unnecessary optional chain

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Add JSDocs

---------

Co-authored-by: Matt Aitken <matt@mattaitken.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2024-11-28 11:25:00 +00:00
nicktrn b714d6fabf Release 3.2.2
🚀 Publish Trigger.dev Docker / units (push) Failing after 0s
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 4s
🚀 Publish Trigger.dev Docker / publish-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker (push) Has been skipped
2024-11-27 16:31:01 +00:00
github-actions[bot] 576715374e chore: Update version for release (#1503)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-11-27 16:29:30 +00:00
nicktrn 9046ea4f23 Fix: Prevent hung worker process on flush exceptions (#1501)
* deprecate cli logger warn and error methods

* replace broken error logger in deployed workers

* changeset
2024-11-27 16:22:26 +00:00
Eric Allam 7e209e0771 Stop using Response errors when validating API Keys (#1498)
* Stop using Response errors when validating API Keys, instead introduce a new "Result" type that has success and failure conditions. Adding in a way to progressively adopt because this touches everything.

* Make sure authenticateApiRequestWithFailure always returns a result
2024-11-25 15:19:05 +00:00
Eric Allam 3792394e5c Always await wrapResponse since it returns a Promise, to protect against UnhandledRejection errors (#1494) 2024-11-22 22:16:50 +00:00
Eric Allam 4305a23668 Fix: trigger queue updates (#1491)
* Fixed task trigger queue update logic to only update an existing queue if the concurrency limit changes, instead of on every single trigger task call

* Fix bad update from another branch
2024-11-22 10:39:34 +00:00
James Ritchie e7fc592cd9 Reinstate pagination on schedules page 2024-11-21 09:53:39 +00:00
James Ritchie cb12a73db6 Fix for the last task in a long list wasn’t on screen 2024-11-20 18:10:05 +00:00
James Ritchie 31fc9e12b7 Show the delete alerts button in the popover 2024-11-20 17:40:37 +00:00
James Ritchie 2bdbedcdd8 Fixed layout bug in the alerts page if you don’t have any alerts set 2024-11-20 14:55:22 +00:00
Eric Allam 332166a098 Release 3.2.1
🚀 Publish Trigger.dev Docker / units (push) Failing after 0s
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 3s
🚀 Publish Trigger.dev Docker / publish-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker (push) Has been skipped
2024-11-20 14:14:56 +00:00
github-actions[bot] 14f19066b5 chore: Update version for release (#1481)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-11-20 14:13:53 +00:00
Eric Allam af7a7681cf Update realtime docs for streaming and a couple tweaks to the SDK (#1486) 2024-11-20 14:10:38 +00:00
James Ritchie c044cb125e Fixed the scrolling on the JSON editor on the test page 2024-11-19 16:06:51 +00:00
Eric Allam 8ba226b99a Add TRIGGER_STREAMS_URL to auto-populated dev env vars (#1485) 2024-11-19 16:06:41 +00:00
Eric Allam 7d4708839f Upgrade zod to 3.23.8 (#1484)
* Upgrade to zod 3.23.8

* Add changeset
2024-11-19 15:51:37 +00:00
James Ritchie 532fdcad47 Fix to keep the Alerts progress bar at the bottom of the screen when you have reached the max 2024-11-19 13:34:29 +00:00
Eric Allam 186942de5d minor update should be a patch (#1483) 2024-11-19 13:16:13 +00:00
Eric Allam 23b43be952 feat/realtime-streams (#1470)
* WIP realtime streams

* Handle realtime with large payloads or outputs #1451

* feat: optimize Redis stream handling with batching

Add STREAM_ORIGIN to environment schema. Improve performance in
RealtimeStreams by using TextDecoderStream for simpler text
decoding and implementing batching of XADD commands for Redis
streams. Limit stream size using MAXLEN option. Update
environment variable repository with new variable type. Adjust
import statements for Redis key and value types.

* 🔧 chore: add dev dependencies for bundle analysis

* add metadata tests and a few more utilties

* Add stream tests and improve streaming

* Added AI tool tasks, descriptions to tasks

* Use the config file path to determine the workingDir, then the package.json path

* Remove stream test files

* useTaskTrigger react hook that allows triggering a task from the client

* Add streaming support for the realtime react hooks

* Add ability to stream results after useTaskTrigger

* Improve the stream throttling

* Use the runId as the ID key to bust the cache after triggering

* Upgrade to to the latest electric sql client and server

* Make realtime server backwards compat with 3.1.2 release

* Pass the runId into useRealtimeRun

* Fix scopes when specifiying reading all runs

* WIP @trigger.dev/rsc package

* Various fixes and accepted recommendations by CodeRabbit

* Regenerate pnpm lock file

* A couple tweaks to rsc and give up on rendering react in tasks for now

* Add changeset

* Remove triggerRequest from the useEffect deps

* Improve realtime & frontend authentication errors

* Fixed authorization tests

* Remove unnecessary log

* Add metadata.stream limits and improve the metadata streams structure

* Streams can now have up to 2500 entries

* Various coderabbit fixes

* additional react-hooks jsdocs
2024-11-19 13:14:25 +00:00
James Ritchie ea0956464b Added an ellipsis to the tooltip text 2024-11-19 13:07:11 +00:00
James Ritchie eca79afced Added some padding around the Setup Your Dev callout on Task page 2024-11-19 13:03:20 +00:00
James Ritchie 58861a0983 Added the new Help & Feedback popover to the account side menu (#1482) 2024-11-19 10:08:12 +00:00
James Ritchie 1b3a2f2e5b Full width tables (#1474)
* Updated styling

* Updated the main Table component styles to the new design

* Run page table layout style updates

* Updated row styling and new scrolling behaviour

* Fixed bulk action bar no appearing

* Improved table component styles

* Pagination arrows now chevrons

* Added link to Runs docs page

* Small popover tweak

* Moved the extra Test button out of Tables and into the RunsTable

* WIP refactoring the sticky table cell

* Support for the existing way we have menus in the sticky table cells

* Created a specific Docs style button

* Improved dialog trigger button styles

* Improved docs button styles

* Updated Docs buttons to the new style in the tooltips

* Adding a Test button on hover

* Removed the page padding and made table scroll

* Fixed table scrolling

* cleaned up imports

* WIP adding a test button that links to the test page

* added a docs button to the header

* Much nicer display bar for showing how many schedules you have (and it works with the updated table)

* Fixed the button types in the schedules info panel

* Fixed button icon colour

* Removed the pagination as it’s not being used

* Delete modal button position updated

* Fixed padding on recent payloads panel

* Fixed button icon colours

* Better style for active table row

* Table scrolling now behaves properly

* environment buttons now have a hover state

* Bigger Run test bar to match the Run page

* Small layout fixes to the schedule side panel

* Test page now has link to docs

* fixed text alignment with table headers

* API keys page now has full width tables

* Fix typo and modal layout

* Table uses new sticky button type

* Tidy imports

* Env var table now full width

* Form buttons have a border top

* Updated button styles

* Updated blank state message style to be consistent

* Removed unnecessary form button border

* Alerts page tables now full width

* Allowing the Detail cell to be styled

* Updated the page structure to be the same as the scheduled page – with better progress indicator

* Concurrency limits page now full width

* Fixed function name label extending too far

* Improved the button sizes on the replay modal

* Added a minimal style to the info panels

* Make sure you can always see the info and unlock staging info when the table scrolls

* Info panel style updated to match the others

* Removed the duplicate header

* Usage page tables now full width

* Personal access token page using full width tables

* Small CSS fixes

* Deployments page table now full width

* Deploy page scrolls properly and pagination stays on screen if table is scrollable

* TableCellMenu using the correct popover

* Added missing button variant

* Button group is now named so it doesn’t cause conflicts

* Added docs link to the Run page

* Typo

* Added a link to the test page if you have no runs

* Make the table sticky cell buttons more obvious

* Env var page now using the new popover menu

* Improved the menu items

* Copy tweak

* Made the icons bright on hover

* Updated the cancel run dialog

* Improved the API keys sticky menu behaviour

* Alerts table menu using the new popover

* Correct sized buttons in the Env Var page table menu

* Fixed weird Dialog text styling

* Schedules page blank state using correct docs button style

* Copy tweak

* Menu buttons styled correctly

* Added close buttons to the deploy dialogs

* Made the Dialog footer match the Form footer style

* Fixed feedback form buttons

* Fixed cancel run dialog

* Test page uses the taskIdentifier in the path

* Test task button working

* Added Spline component for 404 logo to remove spline logo

* Removed todo comment

* Added a docs button to the PAT page

---------

Co-authored-by: Matt Aitken <matt@mattaitken.com>
2024-11-18 17:11:06 +00:00
Willow (GHOST) 653d974da7 feat: exit if docker buildx can't be found for self-hosted builds (#1475)
* feat: exit if docker buildx can't be found for self-hosted builds

* chore: tweak debug log message

* chore: improve error message based on rabbit's suggestion
2024-11-18 12:17:48 +00:00
nicktrn 332854bbcb Release 3.1.3
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 19s
🚀 Publish Trigger.dev Docker / publish-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker (push) Has been skipped
🚀 Publish Trigger.dev Docker / units (push) Failing after 4s
2024-11-15 11:40:00 +00:00
github-actions[bot] 911447304c chore: Update version for release (#1469)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-11-15 11:39:24 +00:00
amuwal 2be1062f1e fix: unintended project initialization at absolute path /trigger during project initialization (#1412)
* Fix parsing of project path for cli init command

* Update development instructions for cli-v3

* Create gentle-mails-sip.md

---------

Co-authored-by: Eric Allam <eallam@icloud.com>
2024-11-15 11:34:51 +00:00
James Ritchie 64be7b04e8 Linking to more examples in the Next.js guide 2024-11-14 10:04:48 +00:00
Dan 925c463fe5 Added libreoffice example (#1472)
* Added Libreoffice example docs

* typo
2024-11-13 15:03:23 +00:00
James Ritchie e1bd6b3355 Fixed broken link 2024-11-13 13:55:06 +00:00
James Ritchie fd90f79ede Added realtime example link to Introduction page 2024-11-13 13:51:40 +00:00
James Ritchie 5d303ce6f2 New Realtime example added to docs 2024-11-13 13:45:47 +00:00
James Ritchie d98221ba7f Added the new quickstart video to the docs 2024-11-13 11:31:28 +00:00
Diego Valenzuela 29026a379b Fix: Typo on overview.mdx (#1460)
* Fix: Typo on overview.mdx

the import helloWorldTask wasn't called correctly at an example

* Fix: Typo update on overview.mdx

Now the import is called `helloWorld` instead of `helloWorldTask` to be consistent with the filename as requested
2024-11-12 18:29:33 +00:00
D-K-P 0706482232 Fix vercelSynEnvVars export 2024-11-12 12:37:21 +00:00
D-K-P 2e273e8fe2 Updated vercel env vars instructions and naming 2024-11-12 11:28:17 +00:00
Nico a5d59de39e Fix: vercelSyncEnvVars teamId (#1463)
* Update vercelSyncEnvVars.ts

* Update vercelSyncEnvVars.ts

* Update vercelSyncEnvVars.ts

* Update vercelSyncEnvVars.ts

* Update docs

* Add changeset
2024-11-12 11:19:25 +00:00
D-K-P 71808fafd1 Improved maxDuration comments in the templates 2024-11-07 14:02:56 +00:00
Matt Aitken 82ebfc7908 Made it clearer that tasks need to be exported 2024-11-06 15:17:12 +00:00
Matt Aitken f0996686e9 Added a db index to make the schedule list page faster
There was a slow query to get the latest run for a list of schedules
2024-11-06 11:56:28 +00:00
Matt Aitken e422fb5e4d Added the ability to switch database volumes locally without losing data, with docs 2024-11-06 10:55:19 +00:00
nicktrn ceac10b098 Release 3.1.2
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 4s
🚀 Publish Trigger.dev Docker / units (push) Failing after 21s
🚀 Publish Trigger.dev Docker / publish-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker (push) Has been skipped
2024-11-05 15:25:20 +00:00
github-actions[bot] 365fabf9a0 chore: Update version for release (#1461)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-11-05 15:24:01 +00:00
nicktrn 21e1375760 Better logs for payload retrieval issues 2024-11-05 12:19:49 +00:00
nicktrn d7656314d6 Prevent stuck runs after duplicate dependency resume messages (#1459)
* ignore duplicate dependency resume messages

* add changeset
2024-11-05 12:00:02 +00:00
nicktrn 5368bcf6bd Improve resume reliability of restored runs (#1458)
* replay ready for resume

* clear replay state between attempts

* add changeset
2024-11-05 11:59:44 +00:00
D-K-P adf5970245 Shortened sidebar title to Vercel sync env vars 2024-11-04 14:58:01 +00:00
Dan aca2f54cb7 Docs - sync vercel env vars (#1426)
* Added sync env vars docs example

* Added links in the config + deploy files

* Updated copy

* Added vercel docs cards and added them to all of the relevant docs pages

* Added sync env vars to the intro page

* Added automatically sync env vars section to nextjs guide

* Added ‘Manually’

* Removed code block and updated title

* updated formatting and added vercelsyncenvvars to the config file

* Added missing comment

* Updated imports to /core

* Added sync env var docs link and note

* Improved the VERCEL_ACCESS_TOKEN note

* Added link to vercel

* Updated docs and improved formatting

* Copy tweaks

* Added space

* Updated deploy docs
2024-11-04 14:55:10 +00:00
Chibuike Chijioke 6313db698a fix(docs): Correct typo in '/idempotency' documentation (#1456) 2024-11-02 23:56:56 +00:00
Eric Allam c9811beae2 Release 3.1.1
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 0s
🚀 Publish Trigger.dev Docker / units (push) Failing after 0s
🚀 Publish Trigger.dev Docker / publish-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker (push) Has been skipped
2024-11-01 21:33:00 +00:00
github-actions[bot] 97533cba2e chore: Update version for release (#1442)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-11-01 21:32:04 +00:00
Dan e92459bf1e Added revalidation docs (#1444)
* Added revalidation docs

* Typo

* Copy tweak
2024-11-01 21:25:44 +00:00
Eric Allam cc74cae393 chore: remove unnecessary browser export condition (#1455) 2024-11-01 21:11:45 +00:00
nicktrn 58def055d0 Add new internal error for very outdated SDK versions (#1453)
* add outdated sdk error and crash outdated runs instead of staying queued forever

* add changeset
2024-11-01 15:41:31 +00:00
D-K-P 41f5261fe5 Added drizzle docs and updated prisma 2024-11-01 14:40:25 +00:00
Eric Allam ce1a54eb3b Renamed useBatch to useRealtimeBatch (#1447)
* Renamed useBatch to useRealtimeBatch

* Use the new hook name in the nextjs-realtime ref
2024-11-01 13:23:02 +00:00
Eric Allam 25285488f8 Add individual run ids to auto-generated public access token when calling batchTrigger (#1449) 2024-11-01 13:22:34 +00:00
Eric Allam befdcef1f0 Add known issue for realtime (#1452) 2024-11-01 13:21:01 +00:00
nicktrn fa18e6c012 Increase maximum retry count for deploy run controller (#1450)
* increase max retries for all deploy run controller operations

* add changeset
2024-11-01 11:23:11 +00:00
nicktrn 4194bce4a3 Forked worker processes now inherit PATH from parent (#1448)
* set PATH when forking

* add changeset
2024-11-01 11:23:00 +00:00
nicktrn da08e5015f Pass init output to all error handlers (#1441)
* pass init output to all error handlers

* add changeset
2024-10-31 11:23:47 +00:00
D-K-P 9b7472844d Updated deploy docs 2024-10-31 09:49:59 +00:00
Dan eace78fa04 Added fal AI example (#1439) 2024-10-30 17:30:23 +00:00
Dan e81b2a7839 Firecrawl example docs (#1438)
* Added firecrawl example docs

* Added Firecrawl and scrape hn to the intro page table
2024-10-30 16:41:04 +00:00
Eric Allam cdcfc81aca Add API_ORIGIN env var to be able to split dashboard & API servers 2024-10-29 16:43:22 +00:00
Eric Allam 816b0f9e3d Add some more react hooks & realtime docs 2024-10-29 16:41:57 +00:00
Eric Allam 6fb073672d Add realtime API docs 2024-10-29 13:57:02 +00:00
Eric Allam 9203bd8e67 Add realtime walkthrough video to docs 2024-10-29 13:23:57 +00:00
Eric Allam 3252d9ec31 docs: realtime, react hooks, and frontend usage (#1434)
* WIP realtime/frontend docs

* WIP

* Deleted old examples

* Improved styling

* Improved the generated example styles

* More style improvements

* More realtime docs

* Couple doc tweeks

* runs-and-attempts -> runs

* Remove the prerelease package version

---------

Co-authored-by: James Ritchie <james@trigger.dev>
2024-10-29 12:31:04 +00:00
Eric Allam 45fdf35e20 Release 3.1.0
🚀 Publish Trigger.dev Docker / units (push) Failing after 0s
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 5s
🚀 Publish Trigger.dev Docker / publish-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker (push) Has been skipped
2024-10-29 12:25:27 +00:00
github-actions[bot] 53841d7bf0 chore: Update version for release (#1423)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-10-29 12:24:21 +00:00
nicktrn d2c779eb0f Fixes for internal error reattempts (#1436)
* magic links on span event errors

* prevent task monitor from processing errors handled elsewhere

* exclusively use internal error code enum for completion data

* add complete attempt service opts

* reattempts need to go via the queue for task controllers that may have exited

* only infer retry config if completed via crash or system failure

* enhance error before deciding if retriable

* retry on SIGTERM

* enable retry config helper for latest sdk

* don't retry heartbeat timeouts for now

* enable task monitor to update fatal errors

* add missing service

* update retry config since package version

* don't alter completion time when updating existing error
2024-10-28 19:15:47 +00:00
Dan cbe51707a5 Added Sentry example (#1435)
* Added sentry docs

* Removed additionalPackages

* Updated description

* Copy updates
2024-10-28 15:57:41 +00:00
Eric Allam 3d7a6d8e9d Add missing indexes on Checkpoint table 2024-10-28 13:37:59 +00:00
Valentin Mocanu 72cdb5edc4 Supply correct namespace to TaskMonitor (#1419) 2024-10-27 12:53:43 +00:00
James Ritchie 1f11a8dde0 Added an AI prompt for converting v2 jobs to v3 tasks 2024-10-25 16:06:27 +01:00
Eric Allam af427aa32d Proxy no longer rewrites hostname, instead uses worker origin request forwarding 2024-10-24 17:02:57 +01:00
nicktrn 212f8539c3 Automatically reattempt after internal errors (#1424)
* refactor finalize run service

* refactor complete attempt service

* remove separate graceful exit handling

* refactor task status helpers

* clearly separate statuses in prisma schema

* all non-final statuses should be failable

* new import payload error code

* store default retry config if none set on task

* failed run service now respects retries

* fix merged task retry config indexing

* some errors should never be retried

* finalize run service takes care of acks now

* execution payload helper now with single object arg

* internal error code enum export

* unify failed and crashed run retries

* Prevent uncaught socket ack exceptions (#1415)

* catch all the remaining socket acks that could possibly throw

* wrap the remaining handlers in try catch

* New onboarding question (#1404)

* Updated “Twitter” to be “X (Twitter)”

* added Textarea to storybook

* Updated textarea styling to match input field

* WIP adding new text field to org creation page

* Added description to field

* Submit feedback to Plain when an org signs up

* Formatting improvement

* type improvement

* removed userId

* Moved submitting to Plain into its own file

* Change orgName with name

* use sendToPlain function for the help & feedback email form

* use name not orgName

* import cleanup

* Downgrading plan form uses sendToPlain

* Get the userId from requireUser only

* Added whitespace-pre-wrap to the message property on the run page

* use requireUserId

* Removed old Plain submit code

* Added a new Context page for the docs (#1416)

* Added a new context page with task context properties

* Removed code comments

* Added more crosslinks

* Fix updating many environment variables at once (#1413)

* Move code example to the side menu

* New docs example for creating a HN email summary

* doc: add instructions to create new reference project and run it locally (#1417)

* doc: add instructions to create new reference project and run it locally

* doc: Add instruction for running tunnel

* minor language improvement

* Fix several restore and resume bugs (#1418)

* try to correct resume messages with missing checkpoint

* prevent creating checkpoints for outdated task waits

* prevent creating checkpoints for outdated batch waits

* use heartbeats to check for and clean up any leftover containers

* lint

* improve exec logging

* improve resume attempt logs

* fix for resuming parents of canceled child runs

* separate SIGTERM from maybe OOM errors

* pretty errors can have magic dashboard links

* prevent uncancellable checkpoints

* simplify task run error code enum export

* grab the last, not the first child run

* Revert "prevent creating checkpoints for outdated batch waits"

This reverts commit f2b5c2ac42.

* Revert "grab the last, not the first child run"

This reverts commit 89ec5c8bfd.

* Revert "prevent creating checkpoints for outdated task waits"

This reverts commit 11066b4e74.

* more logs for resume message handling

* add magic error link comment

* add changeset

* chore: Update version for release (#1410)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* Release 3.0.13

* capture ffmpeg oom errors

* respect maxAttempts=1 when failing before first attempt creation

* request worker exit on fatal errors

* fix error code merge

* add new error code to should retry

* pretty segfault errors

* pretty internal errors for attempt spans

* decrease oom false positives

* fix timeline event color for failed runs

* auto-retry packet import and export

* add sdk version check and complete event while completing attempt

* all internal errors become crashes by default

* use pretty error helpers exclusively

* error to debug log

* zodfetch fixes

* rename import payload to task input error

* fix true non-zero exit error display

* fix retry config parsing

* correctly mark crashes as crashed

* add changeset

* remove non-zero exit comment

* pretend we don't support default default retry configs yet

---------

Co-authored-by: James Ritchie <james@trigger.dev>
Co-authored-by: shubham yadav <126192924+yadavshubham01@users.noreply.github.com>
Co-authored-by: Tarun Pratap Singh <101409098+Wackyator@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-10-24 11:12:24 +01:00
shubham yadav 9a5e6e58be Fix: Handle circular references in flattenAttributes function (#1433)
* Fix: Handle circular references in flattenAttributes function

* Update flattenAttributes.test.ts

* Update packages/core/src/v3/utils/flattenAttributes.ts

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update packages/core/src/v3/utils/flattenAttributes.ts

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Create brown-laws-rest.md

---------

Co-authored-by: Eric Allam <eallam@icloud.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Eric Allam <eric@trigger.dev>
2024-10-24 10:44:04 +01:00
Eric Allam c31700ae5f Fix issue with lost built trigger.dev files after prisma generate (#1429)
* Fix issue with lost built trigger.dev files after prisma generate on deploy

* Add changeset
2024-10-24 10:43:26 +01:00
Eric Allam cfb96859b3 fix: removing schedule instance no longer crashes (#1430) 2024-10-24 10:41:49 +01:00
James Ritchie 9bc641d15e An improved 404 page design (#1427)
* WIP improving the 404 page

* Improved 404 page layout

* Added a shortcut key

* Removed old assets and code
2024-10-24 10:37:24 +01:00
Matt Aitken a79075908e Unflattening array fix for very large arrays (#1432)
* fix unflattening of array indices

* Get tests passing

---------

Co-authored-by: nicktrn <55853254+nicktrn@users.noreply.github.com>
Co-authored-by: Eric Allam <eallam@icloud.com>
2024-10-23 20:58:02 +01:00
Eric Allam 7f9091f205 fix metadata system not updating because of dual package hazard (#1428) 2024-10-23 14:02:13 +01:00
James Ritchie 9f6887b048 Embed the hacker news youtube video instead 2024-10-23 13:13:18 +01:00
Dan 982906cbad Vercel sync env vars extension (#1425)
* Added vercelSyncEnvVars extension

* Updated extension and added try catch

* VercelEnvironment fix

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Added changeset

* Moved vercelSynvEnvVars to code and updated core.ts

* Fixed import

* removed type

---------

Co-authored-by: Matt Aitken <matt@mattaitken.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2024-10-23 13:01:30 +01:00
James Ritchie 90514a73bb Added video to hacker news example docs 2024-10-23 12:21:14 +01:00
Eric Allam 2d63c5db50 Set the api rate limiter redis config 2024-10-22 22:22:13 +01:00
Eric Allam 768036a223 Revert the changes to the provider tsconfigs 2024-10-21 15:19:10 +01:00
Eric Allam 2d8a41b18b feat: realtime (#1402)
* Denormalize run tags, increase character limit to 128

* WIP realtime subscribing to runs

* extracted the stream stuff into core, made it more reusable

* WIP tags

* Remove tags for now because it’s not support in electric

* Support async iterables, readable stream, and callback style subscription styles

* Remove tags streaming endpoint

* Add realtime rate limits and scope them to the /realtime path

* WIP rate limt per org

* Introduce per org rate limits

* WIP JWT auth

* Move migrations into new internal db package

* Resolve pnpm lock file

* Authenticating to the realtime API with JWTs are working

* realtime in the client

* Created react-hooks package and starting to move stuff in there

* Improve types for hooks

* schema tasks

* Added useBatch hook

* build uploadthing/fal demo and change how run metadata is synced to the server

* tweaks

* WIL realtime concurrency tracking

* Implement test for realtime client using testcontainers

also updated electric to latest version

* Allow customizing the expiration time of the automatic JWT created after triggering a task

* Add support for subscribing to run tags

* Improve auth types and API

* finalize the realtime API

* Fixed some example stuff

* Allow up to 10 run tags

* Remove core from docker-provider tsconfig paths to prevent it from being typechecked

* do the same for the kubernetes provider

* Fixing some typecheck errors

* Fix webapp type errors

* Update @trigger.dev/platform to 1.0.13

* Fix attw error

* Remove from/to in subscribeToRuns query params

* Add tests for the rate limit middleware and add custom JWT rate limits

* turn off webapp test parallelism

* Finish renaming jwt -> publicAccessToken and automatically give the JWT read access to the tags when using trigger

* Add changeset

* Attempt to fix unit tests in CI

* Skip running the auth rate limit middleware tests for now

* Try a beefier machine

* Try and run webapp tests separately

* Setup env vars

* Make sliding window test more reliabile
2024-10-21 15:07:08 +01:00
nicktrn 67542a54e8 Release 3.0.13
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 4s
🚀 Publish Trigger.dev Docker / units (push) Failing after 19s
🚀 Publish Trigger.dev Docker / publish-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker (push) Has been skipped
2024-10-18 17:26:03 +01:00
github-actions[bot] f1d89c413b chore: Update version for release (#1410)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-10-18 17:24:53 +01:00
nicktrn 90593adea5 Fix several restore and resume bugs (#1418)
* try to correct resume messages with missing checkpoint

* prevent creating checkpoints for outdated task waits

* prevent creating checkpoints for outdated batch waits

* use heartbeats to check for and clean up any leftover containers

* lint

* improve exec logging

* improve resume attempt logs

* fix for resuming parents of canceled child runs

* separate SIGTERM from maybe OOM errors

* pretty errors can have magic dashboard links

* prevent uncancellable checkpoints

* simplify task run error code enum export

* grab the last, not the first child run

* Revert "prevent creating checkpoints for outdated batch waits"

This reverts commit f2b5c2ac42.

* Revert "grab the last, not the first child run"

This reverts commit 89ec5c8bfd.

* Revert "prevent creating checkpoints for outdated task waits"

This reverts commit 11066b4e74.

* more logs for resume message handling

* add magic error link comment

* add changeset
2024-10-18 16:46:38 +01:00
Tarun Pratap Singh 235ab90c3d doc: add instructions to create new reference project and run it locally (#1417)
* doc: add instructions to create new reference project and run it locally

* doc: Add instruction for running tunnel

* minor language improvement
2024-10-18 16:03:18 +01:00
James Ritchie ef7f112227 New docs example for creating a HN email summary 2024-10-17 13:56:34 +01:00
James Ritchie db70faa4c7 Move code example to the side menu 2024-10-17 13:56:34 +01:00
shubham yadav 1d87b5ea43 Fix updating many environment variables at once (#1413) 2024-10-17 12:24:50 +01:00
James Ritchie d3f10941fd Added a new Context page for the docs (#1416)
* Added a new context page with task context properties

* Removed code comments

* Added more crosslinks
2024-10-17 11:17:17 +01:00
James Ritchie 6dc556b704 New onboarding question (#1404)
* Updated “Twitter” to be “X (Twitter)”

* added Textarea to storybook

* Updated textarea styling to match input field

* WIP adding new text field to org creation page

* Added description to field

* Submit feedback to Plain when an org signs up

* Formatting improvement

* type improvement

* removed userId

* Moved submitting to Plain into its own file

* Change orgName with name

* use sendToPlain function for the help & feedback email form

* use name not orgName

* import cleanup

* Downgrading plan form uses sendToPlain

* Get the userId from requireUser only

* Added whitespace-pre-wrap to the message property on the run page

* use requireUserId

* Removed old Plain submit code
2024-10-17 10:54:38 +01:00
nicktrn b6d1e0d868 Prevent uncaught socket ack exceptions (#1415)
* catch all the remaining socket acks that could possibly throw

* wrap the remaining handlers in try catch
2024-10-16 22:42:27 +01:00
D-K-P f2c5243905 Copy updates 2024-10-16 17:16:52 +01:00
D-K-P 803350ae6e Updated supabase docs 2024-10-16 15:23:48 +01:00
James Ritchie b225423ce0 docs typo 2024-10-16 15:09:18 +01:00
Niels e36e3d54f2 Make ephemeral storage defaults configurable via env vars (#1407)
* Added support for the env vars `POD_EPHEMERAL_STORAGE_SIZE_LIMIT` and `POD_EPHEMERAL_STORAGE_SIZE_REQUEST`

* Reverted pnpm lockfile

* Made sure there is no diff between the main lockfile & PR's lockfile

* Not touching the storage for the index containers

* Not touching the storage for the prePull container
2024-10-16 13:26:48 +01:00
James Ritchie be7790bbdf Added log size info to the limits page 2024-10-15 16:58:36 +01:00
nicktrn feb4fcdac6 Fix bun runtime by downgrading to 1.1.24 (#1409)
* pin bun to 1.1.24 and use slim image

* add changeset

* add warning about bun version to docs
2024-10-14 18:00:10 +01:00
Dan a6de3f0ff3 README updates (#1408)
* Main README edits

* SDK readme edits

* CLI readme edits

* Create cool-keys-sit.md

---------

Co-authored-by: Eric Allam <eric@trigger.dev>
2024-10-14 16:39:19 +01:00
Matt Aitken 1c80a74885 Added a note to scheduled task docs about the delay option. 2024-10-11 18:58:39 +01:00
Dan 4c6f27e978 Updated sharp docs (#1403)
* Added prerequisites to ffmpeg and puppeteer

* Improved the sharp docs

* Updated sharp task code
2024-10-11 17:11:07 +01:00
nicktrn 367d7bdb4c Add more metadata to checkpoint creation logs 2024-10-11 15:55:31 +01:00
James Ritchie d998a2064e New runs fundamentals page (#1401)
* WIP runs guide

* WIP updating the runs and attempts page

* Added more diagrams to Runs page

* Fixed broken anchors

* More updates and diagram improvements

* Improved diagrams and copy

* More diagram improvements

* copy tweak

---------

Co-authored-by: Eric Allam <eallam@icloud.com>
2024-10-11 14:28:01 +01:00
James Ritchie ba96397f53 Updated the limits page 2024-10-10 15:21:53 +01:00
James Ritchie 244988e212 New Help & Feedback menu and tab-selected styles (#1374)
* Added a new dropdown help and feedback menu to the side menu

* Added a shortcut to the popover menu

* Removed dev cli connected button for now

* Contact us form uses original Feedback component to prevent broken links

* Improved the messaging when selecting different options in the email form

* buttons style tweak

* SideMenuItem supports the trailingIconClassName

* Adding a consistent focus-visible states

* Removing tooltips for now

* Squashed commit of the following:

commit 7d11123c0a
Author: Eric Goldman <eric@sequin.io>
Date:   Mon Sep 30 17:54:06 2024 -0700

    Add sequin guide (#1368)

    Co-authored-by: James Ritchie <james@trigger.dev>

commit 8da495ac00
Author: nicktrn <55853254+nicktrn@users.noreply.github.com>
Date:   Mon Sep 30 13:42:22 2024 +0100

    Improve checkpoint reliability and cleanup of temp files (#1367)

    * improve cleanup reliability

    * improve logging

    * bye-bye execa

    * fix for trailing newlines

    * prettier errors

    * trim args and log output by default

    * fix archive cleanup

    * prevent potential memleak

    * more cleanup debug logs

    * ignore abort during cleanup

    * rename checkpoint dir env var and move to helper

    * add global never throw override

    * add tmp cleaner

    * also clean up checkpoint dir by default

    * split by any whitespace, not just tabs

    * only create tmp cleaner if paths to clean

commit 69ec68ee31
Author: Eric Allam <eallam@icloud.com>
Date:   Sun Sep 29 19:18:39 2024 -0700

    Release 3.0.9

commit a6ea8444c9
Author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Date:   Sun Sep 29 19:17:26 2024 -0700

    chore: Update version for release (#1366)

    Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

commit 4c1ee3d6ea
Author: Eric Allam <eallam@icloud.com>
Date:   Sun Sep 29 19:09:38 2024 -0700

    fix: run metadata not working when using npx/pnpm dlx

* More support for custom-focus

* More custom focus styles added

* Support for focus-visible style for the Segmented control

* Fixed table triple dot menu z-index issue

* Improved help menu wording

* When you submit the help form, close the modal

* focus-visible style for radio buttons

* button prop is now optional in the SideMenu component

* focus styling for a text link

* Deleted unused sequin files
2024-10-10 14:28:18 +01:00
Eric Allam b1319fb88c Update limits.mdx 2024-10-10 14:19:40 +01:00
James Ritchie cff2ebba49 Change plan feedback modal (#1393)
* Checkbox component can have its label styles

* Improved the dialog footer

* Handle sending feedback to Slack using Plain

* WIP making the modal conditional

* Moved the Plain form action into the select plan file

* removed comment

* Show a confirmation diaglog if you’re downgrading from Pro to Hobby

* Downgrading to Hobby works

* Use redirectWithErrorMessage instead of throw error

* The cancel form now submits the data correctly

* Modals don’t trigger when you upgrade

* Copy improvements

* Added a tooltip to explain the link to the pricing page

* Squashed commit of the following:

commit 886429b0ed
Author: Matt Aitken <matt@mattaitken.com>
Date:   Tue Oct 8 18:36:46 2024 +0100

    Removed emails, @trigger.dev/database and @trigger.dev/otlp-importer from changesets config

commit f65157a14f
Author: Matt Aitken <matt@mattaitken.com>
Date:   Tue Oct 8 18:31:27 2024 +0100

    Lockfile with run-engine removed

commit 3d67bb81e9
Author: Matt Aitken <matt@mattaitken.com>
Date:   Tue Oct 8 18:24:31 2024 +0100

    Removed run-engine from the webapp package.json/tsconfig

commit d30e9713f4
Author: Matt Aitken <matt@mattaitken.com>
Date:   Tue Oct 8 18:06:04 2024 +0100

    Dockerfile fix because the database package has been moved

commit f2babbf637
Author: Matt Aitken <matt@mattaitken.com>
Date:   Tue Oct 8 09:41:22 2024 -0700

    Internal packages (testcontainers, redis-worker and zod-worker) (#1392)

    * Some notes on the new run engine

    * lockfile with setup for the run engine

    * Documenting where TaskRun is currently mutated, to try figure out the shape of the new system

    * Added notes about how triggering currently works

    * Details about when triggering happens

    * Lots of notes about waitpoints

    * Started scaffolding the RunEngine

    * Sketch of Prisma waitpoint schema while it’s fresh in my mind

    * Got Prisma working with testcontainers

    * Use beforeEach/afterEach

    * Simple Prisma and Redis test

    * Return Redis options instead of a client

    * Simplified things

    * A very simple FIFO pull-based queue to check the tests working properly

    * Use vitest extend

    * Separate redis, postgres and combined tests for faster testing

    * Some fixes and test improvements

    * Pass a logger into the queue

    * A queue processor that processes items from the given queue as fast as it can

    * Test for retrying an item that wasn’t processed

    * First draft of waitpoints in the Prisma schema

    * Remove the custom logger from the test

    * Added a completedAt to Waitpoint

    * Notes on the flow for an execution starting

    * Added redlock, moved some files around

    * Starting point for the TaskRunExecutionSnapshot table

    * Added relationships to TaskRunExecutionSnapshot

    * Change some tsconfig

    * Moved some things around

    * Added some packages

    * WIP on the RunQueue

    * Fix for some imports

    * Key producer with some tests

    * Removed the nv type from the keys… it’s not useful to do global queries

    * Passing unit tests for all the public key producer functions

    * Some basic tests passing for the RunQueue

    * Simple enqueue test working

    * Enqueue and dequeue for dev is working

    * Don’t log everything during the tests

    * Enqueuing/dequeuing from the shared queue is working

    * Tests for getting a shared queue

    * The key producer sharedQueue can now be named, to allow multiple separate queues

    * The key producer uses the name of the queue as the input

    * Extra info in the Prisma schema

    * Dequeuing a message gets the payload and sets the task concurrency all in one Lua script

    * Adding more keys so we can read the concurrency from the queue

    * Setting the concurrency with dequeue and enquque is working

    * Improved the tests and fixed some bugs

    * Acking is resetting the concurrencies

    * Check the key has been removed after acking

    * Nacking is working

    * Changed the package to CommonJS + Node10 so it works with Redlock

    * Moved the database, otel and emails packages to be in internal-packages

    * Moved some Prisma code to the database package

    * Started using the RunEngine for triggering

    * Progress on run engine triggering, first waitpoint code

    * Create a delay waitpoint

    * Moved ZodWorker to an internal package so it can be used in the run engine as well as the webapp

    * Web app now uses the zod worker package

    * Added parseNaturalLanguageDuration to core/apps

    * internal-packages/zod-worker in the lockfile

    * Pass in the master queue, remove old rebalance workers code

    * Add masterQueue to TaskRun

    * Fixed the tests

    * Moved waitpoint code into the run engine, also the zod worker

    * Completing waitpoints

    * An experiment to create a new test container with environment

    * More changes to triggering

    * Started testing triggering

    * Test for a run getting triggered and being enqueued

    * Removed dequeueMessageInEnv

    * Update dev queue tests to use the shared queue function

    * Schema changes for TaskRunExecutionSnapshot

    * First execution snapshot when the run is created. Dequeue run function added to the engine

    * Separate internal package for testcontainers so they can be used elsewhere

    * Remove the simple queue and testcontainers from the run-engine. They’re going to be separate

    * Fix for the wrong path to the Prisma schem,a

    * Added the testcontainers package to the run-engine

    * redis-worker package, just a copy of the simple queue for now

    * The queue now uses Lua to enqueue dequeue

    * The queue now has a catalog and an invisible period after dequeuing

    * Added a visibility timeout and acking, with tests

    * Added more Redis connection logging, deleted todos

    * Visibility timeouts are now defined on the catalog and can be overridden when enqueuing

    * Dequeue multiple items at once

    * Test for dequeuing multiple items

    * Export some types to be used elsewhere

    * Partial refactor of the processor

    * First stab at a worker with concurrency and NodeWorkers

    * Don’t have a default visibility timeout in the queue

    * Worker setup and processing items in a simple test

    * Process jobs in parallel with retrying

    * Get the attempt when dequeuing

    * Workers do exponential backoff

    * Moved todos

    * DLQ functionality

    * DLQ tests

    * Same cluster for all keys in the same queue

    * Added DLQ tests

    * Whitespace

    * Redis pubsub to redrive from the worker

    * Fixed database paths

    * Fix for path to zod-worker

    * Fixes for typecheck errors, mostly with TS versions and module resolution

    * Redlock required a patch

    * Moved the new DB migrations to the new database package folder

    * Remove the run-engine package

    * Remove the RunEngine prisma schema changes

    * Delete triggerTaskV2

    * Remove zodworker test script (no tests)

    * Update test-containers readme

    * Generate the client first

    * Use a specific version of the prisma package

    * Generate the prisma client before running the unit tests

commit fc609470c4
Author: Dan <8297864+D-K-P@users.noreply.github.com>
Date:   Tue Oct 8 14:36:03 2024 +0100

    Supabase database webhook example upgrade (#1386)

    * Added overview for guides and examples section and split them all out

    * New supabase guide wip

    * Updated images and improved docs

    * Trimmed the supabase prereqs

    * Supabase guide wip

    * more updates

    * Replaced old database webhook guide

    * Created one intro page and removed snippets

    * Updated guide sidebar titles

    * Code updates

    * More improvements

    * Updates and added images

    * Compressed image

    * Updated guides descriptions and edge function basic

    * Removed bold

    * Updated redirects

    * Fixed broken links

    * Updated intro

commit 07f82eae54
Author: nicktrn <55853254+nicktrn@users.noreply.github.com>
Date:   Tue Oct 8 13:28:54 2024 +0100

    Release 3.0.11

commit 13ebfcc5b9
Author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Date:   Tue Oct 8 13:24:38 2024 +0100

    chore: Update version for release (#1381)

    Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

commit 2a04d17e1f
Author: nicktrn <55853254+nicktrn@users.noreply.github.com>
Date:   Tue Oct 8 09:24:23 2024 +0100

    Simplify showLogs expression

commit 002ae4bc0d
Author: nicktrn <55853254+nicktrn@users.noreply.github.com>
Date:   Tue Oct 8 09:22:29 2024 +0100

    Fix dotenv overrides for dev runs (#1388)

    * override dashboard dev env vars with local .env

    * add changeset

    * add simple task for testing env vars

commit 047cb00417
Author: nicktrn <55853254+nicktrn@users.noreply.github.com>
Date:   Tue Oct 8 09:22:05 2024 +0100

    Disable schedules for deleted orgs on next tick (#1383)

    * disable schedules for deleted orgs

    * add debug logs

commit 2c014f725a
Author: James Ritchie <james@trigger.dev>
Date:   Sun Oct 6 13:02:00 2024 -0700

    Override log retention (#1385)

    * set full log retention as admin

    * If run.logsDeletedAt is set, don’t bother getting the trace

commit a69e04f03a
Author: nicktrn <55853254+nicktrn@users.noreply.github.com>
Date:   Sat Oct 5 14:18:58 2024 +0100

    Include push output in logs for self-hosted deploys (#1382)

    * include push output in logs

    * changeset

commit c5488df8ca
Author: nicktrn <55853254+nicktrn@users.noreply.github.com>
Date:   Sat Oct 5 13:12:47 2024 +0100

    Fix CLI downgrade check (#1380)

    * fix downgrade detection

    * remove unused semver package from webapp

    * add changeset

commit 1caec27f72
Author: Eric Allam <eric@trigger.dev>
Date:   Fri Oct 4 15:33:35 2024 -0700

    docs: Max duration (#1379)

    * maxDuration docs

    * Update the init command to set the maxDuration and include a commented out maxDuration in the config file

commit e14c954e12
Author: Eric Allam <eallam@icloud.com>
Date:   Fri Oct 4 15:02:05 2024 -0700

    Release 3.0.10

commit 8e61f5d631
Author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Date:   Fri Oct 4 14:59:07 2024 -0700

    chore: Update version for release (#1378)

    Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

commit 08db565a72
Author: Eric Allam <eallam@icloud.com>
Date:   Thu Oct 3 12:38:25 2024 -0700

    improve the timed out description

commit 6d0884254e
Author: Eric Allam <eric@trigger.dev>
Date:   Thu Oct 3 12:43:26 2024 -0700

    feat: Add maxDuration to tasks (#1377)

    * WIP

    * Get max duration working on deployed runs

    * Actually set the timed out runs to status = TIMED_OUT

    * The client status for TIMED_OUT is now MAX_DURATION_EXCEEDED

    * New TimedOutIcon

    * Added new timedout icon

    * Add ability to opt-out of maxDuration with timeout.None

    * MAX_DURATION_EXCEEDED -> TIMED_OUT

    * changeset

    * Improved styling for the status tooltip content

    ---------

    Co-authored-by: James Ritchie <james@trigger.dev>

commit 665ccf88b7
Author: nicktrn <55853254+nicktrn@users.noreply.github.com>
Date:   Thu Oct 3 12:33:18 2024 +0100

    Update github actions and self-hosting docs

commit 1ff7b861ca
Author: Eric Allam <eric@trigger.dev>
Date:   Wed Oct 2 18:26:36 2024 -0700

    Add max queue depth limits (#1376)

    * Add runs to an env queue, as well as the actual queue

    * Add queue size limit guard on triggering tasks

commit c531a9d986
Author: Eric Allam <eric@trigger.dev>
Date:   Wed Oct 2 15:30:39 2024 -0700

    fix: cleanup ttl expire run graphile jobs (#1373)

    * fix: remove ttl expire run graphile jobs when a run is started or completed

    * Update expireEnqueuedRun.server.ts

commit 0bf500fa95
Author: Matt Aitken <matt@mattaitken.com>
Date:   Wed Oct 2 15:30:16 2024 -0700

    Prioritize finishing waited runs (#1375)

    * If a tree node is missing, estimate the size as zero

    * Task to test prioritizing finishing existing runs after triggerAndWaits

    * When requeuing a run with a checkpoint, put it in the queue with the parent run time so it’s correctly prioritized

    * The same change but if there’s no checkpoint

* Squashed commit of the following:

commit 886429b0ed
Author: Matt Aitken <matt@mattaitken.com>
Date:   Tue Oct 8 18:36:46 2024 +0100

    Removed emails, @trigger.dev/database and @trigger.dev/otlp-importer from changesets config

commit f65157a14f
Author: Matt Aitken <matt@mattaitken.com>
Date:   Tue Oct 8 18:31:27 2024 +0100

    Lockfile with run-engine removed

commit 3d67bb81e9
Author: Matt Aitken <matt@mattaitken.com>
Date:   Tue Oct 8 18:24:31 2024 +0100

    Removed run-engine from the webapp package.json/tsconfig

commit d30e9713f4
Author: Matt Aitken <matt@mattaitken.com>
Date:   Tue Oct 8 18:06:04 2024 +0100

    Dockerfile fix because the database package has been moved

commit f2babbf637
Author: Matt Aitken <matt@mattaitken.com>
Date:   Tue Oct 8 09:41:22 2024 -0700

    Internal packages (testcontainers, redis-worker and zod-worker) (#1392)

    * Some notes on the new run engine

    * lockfile with setup for the run engine

    * Documenting where TaskRun is currently mutated, to try figure out the shape of the new system

    * Added notes about how triggering currently works

    * Details about when triggering happens

    * Lots of notes about waitpoints

    * Started scaffolding the RunEngine

    * Sketch of Prisma waitpoint schema while it’s fresh in my mind

    * Got Prisma working with testcontainers

    * Use beforeEach/afterEach

    * Simple Prisma and Redis test

    * Return Redis options instead of a client

    * Simplified things

    * A very simple FIFO pull-based queue to check the tests working properly

    * Use vitest extend

    * Separate redis, postgres and combined tests for faster testing

    * Some fixes and test improvements

    * Pass a logger into the queue

    * A queue processor that processes items from the given queue as fast as it can

    * Test for retrying an item that wasn’t processed

    * First draft of waitpoints in the Prisma schema

    * Remove the custom logger from the test

    * Added a completedAt to Waitpoint

    * Notes on the flow for an execution starting

    * Added redlock, moved some files around

    * Starting point for the TaskRunExecutionSnapshot table

    * Added relationships to TaskRunExecutionSnapshot

    * Change some tsconfig

    * Moved some things around

    * Added some packages

    * WIP on the RunQueue

    * Fix for some imports

    * Key producer with some tests

    * Removed the nv type from the keys… it’s not useful to do global queries

    * Passing unit tests for all the public key producer functions

    * Some basic tests passing for the RunQueue

    * Simple enqueue test working

    * Enqueue and dequeue for dev is working

    * Don’t log everything during the tests

    * Enqueuing/dequeuing from the shared queue is working

    * Tests for getting a shared queue

    * The key producer sharedQueue can now be named, to allow multiple separate queues

    * The key producer uses the name of the queue as the input

    * Extra info in the Prisma schema

    * Dequeuing a message gets the payload and sets the task concurrency all in one Lua script

    * Adding more keys so we can read the concurrency from the queue

    * Setting the concurrency with dequeue and enquque is working

    * Improved the tests and fixed some bugs

    * Acking is resetting the concurrencies

    * Check the key has been removed after acking

    * Nacking is working

    * Changed the package to CommonJS + Node10 so it works with Redlock

    * Moved the database, otel and emails packages to be in internal-packages

    * Moved some Prisma code to the database package

    * Started using the RunEngine for triggering

    * Progress on run engine triggering, first waitpoint code

    * Create a delay waitpoint

    * Moved ZodWorker to an internal package so it can be used in the run engine as well as the webapp

    * Web app now uses the zod worker package

    * Added parseNaturalLanguageDuration to core/apps

    * internal-packages/zod-worker in the lockfile

    * Pass in the master queue, remove old rebalance workers code

    * Add masterQueue to TaskRun

    * Fixed the tests

    * Moved waitpoint code into the run engine, also the zod worker

    * Completing waitpoints

    * An experiment to create a new test container with environment

    * More changes to triggering

    * Started testing triggering

    * Test for a run getting triggered and being enqueued

    * Removed dequeueMessageInEnv

    * Update dev queue tests to use the shared queue function

    * Schema changes for TaskRunExecutionSnapshot

    * First execution snapshot when the run is created. Dequeue run function added to the engine

    * Separate internal package for testcontainers so they can be used elsewhere

    * Remove the simple queue and testcontainers from the run-engine. They’re going to be separate

    * Fix for the wrong path to the Prisma schem,a

    * Added the testcontainers package to the run-engine

    * redis-worker package, just a copy of the simple queue for now

    * The queue now uses Lua to enqueue dequeue

    * The queue now has a catalog and an invisible period after dequeuing

    * Added a visibility timeout and acking, with tests

    * Added more Redis connection logging, deleted todos

    * Visibility timeouts are now defined on the catalog and can be overridden when enqueuing

    * Dequeue multiple items at once

    * Test for dequeuing multiple items

    * Export some types to be used elsewhere

    * Partial refactor of the processor

    * First stab at a worker with concurrency and NodeWorkers

    * Don’t have a default visibility timeout in the queue

    * Worker setup and processing items in a simple test

    * Process jobs in parallel with retrying

    * Get the attempt when dequeuing

    * Workers do exponential backoff

    * Moved todos

    * DLQ functionality

    * DLQ tests

    * Same cluster for all keys in the same queue

    * Added DLQ tests

    * Whitespace

    * Redis pubsub to redrive from the worker

    * Fixed database paths

    * Fix for path to zod-worker

    * Fixes for typecheck errors, mostly with TS versions and module resolution

    * Redlock required a patch

    * Moved the new DB migrations to the new database package folder

    * Remove the run-engine package

    * Remove the RunEngine prisma schema changes

    * Delete triggerTaskV2

    * Remove zodworker test script (no tests)

    * Update test-containers readme

    * Generate the client first

    * Use a specific version of the prisma package

    * Generate the prisma client before running the unit tests

commit fc609470c4
Author: Dan <8297864+D-K-P@users.noreply.github.com>
Date:   Tue Oct 8 14:36:03 2024 +0100

    Supabase database webhook example upgrade (#1386)

    * Added overview for guides and examples section and split them all out

    * New supabase guide wip

    * Updated images and improved docs

    * Trimmed the supabase prereqs

    * Supabase guide wip

    * more updates

    * Replaced old database webhook guide

    * Created one intro page and removed snippets

    * Updated guide sidebar titles

    * Code updates

    * More improvements

    * Updates and added images

    * Compressed image

    * Updated guides descriptions and edge function basic

    * Removed bold

    * Updated redirects

    * Fixed broken links

    * Updated intro

commit 07f82eae54
Author: nicktrn <55853254+nicktrn@users.noreply.github.com>
Date:   Tue Oct 8 13:28:54 2024 +0100

    Release 3.0.11

commit 13ebfcc5b9
Author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Date:   Tue Oct 8 13:24:38 2024 +0100

    chore: Update version for release (#1381)

    Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

commit 2a04d17e1f
Author: nicktrn <55853254+nicktrn@users.noreply.github.com>
Date:   Tue Oct 8 09:24:23 2024 +0100

    Simplify showLogs expression

commit 002ae4bc0d
Author: nicktrn <55853254+nicktrn@users.noreply.github.com>
Date:   Tue Oct 8 09:22:29 2024 +0100

    Fix dotenv overrides for dev runs (#1388)

    * override dashboard dev env vars with local .env

    * add changeset

    * add simple task for testing env vars

commit 047cb00417
Author: nicktrn <55853254+nicktrn@users.noreply.github.com>
Date:   Tue Oct 8 09:22:05 2024 +0100

    Disable schedules for deleted orgs on next tick (#1383)

    * disable schedules for deleted orgs

    * add debug logs

commit 2c014f725a
Author: James Ritchie <james@trigger.dev>
Date:   Sun Oct 6 13:02:00 2024 -0700

    Override log retention (#1385)

    * set full log retention as admin

    * If run.logsDeletedAt is set, don’t bother getting the trace

commit a69e04f03a
Author: nicktrn <55853254+nicktrn@users.noreply.github.com>
Date:   Sat Oct 5 14:18:58 2024 +0100

    Include push output in logs for self-hosted deploys (#1382)

    * include push output in logs

    * changeset

commit c5488df8ca
Author: nicktrn <55853254+nicktrn@users.noreply.github.com>
Date:   Sat Oct 5 13:12:47 2024 +0100

    Fix CLI downgrade check (#1380)

    * fix downgrade detection

    * remove unused semver package from webapp

    * add changeset

commit 1caec27f72
Author: Eric Allam <eric@trigger.dev>
Date:   Fri Oct 4 15:33:35 2024 -0700

    docs: Max duration (#1379)

    * maxDuration docs

    * Update the init command to set the maxDuration and include a commented out maxDuration in the config file

commit e14c954e12
Author: Eric Allam <eallam@icloud.com>
Date:   Fri Oct 4 15:02:05 2024 -0700

    Release 3.0.10

commit 8e61f5d631
Author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Date:   Fri Oct 4 14:59:07 2024 -0700

    chore: Update version for release (#1378)

    Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

commit 08db565a72
Author: Eric Allam <eallam@icloud.com>
Date:   Thu Oct 3 12:38:25 2024 -0700

    improve the timed out description

commit 6d0884254e
Author: Eric Allam <eric@trigger.dev>
Date:   Thu Oct 3 12:43:26 2024 -0700

    feat: Add maxDuration to tasks (#1377)

    * WIP

    * Get max duration working on deployed runs

    * Actually set the timed out runs to status = TIMED_OUT

    * The client status for TIMED_OUT is now MAX_DURATION_EXCEEDED

    * New TimedOutIcon

    * Added new timedout icon

    * Add ability to opt-out of maxDuration with timeout.None

    * MAX_DURATION_EXCEEDED -> TIMED_OUT

    * changeset

    * Improved styling for the status tooltip content

    ---------

    Co-authored-by: James Ritchie <james@trigger.dev>

commit 665ccf88b7
Author: nicktrn <55853254+nicktrn@users.noreply.github.com>
Date:   Thu Oct 3 12:33:18 2024 +0100

    Update github actions and self-hosting docs

commit 1ff7b861ca
Author: Eric Allam <eric@trigger.dev>
Date:   Wed Oct 2 18:26:36 2024 -0700

    Add max queue depth limits (#1376)

    * Add runs to an env queue, as well as the actual queue

    * Add queue size limit guard on triggering tasks

commit c531a9d986
Author: Eric Allam <eric@trigger.dev>
Date:   Wed Oct 2 15:30:39 2024 -0700

    fix: cleanup ttl expire run graphile jobs (#1373)

    * fix: remove ttl expire run graphile jobs when a run is started or completed

    * Update expireEnqueuedRun.server.ts

commit 0bf500fa95
Author: Matt Aitken <matt@mattaitken.com>
Date:   Wed Oct 2 15:30:16 2024 -0700

    Prioritize finishing waited runs (#1375)

    * If a tree node is missing, estimate the size as zero

    * Task to test prioritizing finishing existing runs after triggerAndWaits

    * When requeuing a run with a checkpoint, put it in the queue with the parent run time so it’s correctly prioritized

    * The same change but if there’s no checkpoint

* Revert "Squashed commit of the following:"

This reverts commit b837b5a558dc5187d89b09135276d16f6856f455.

* Removed console logs

* cleaned up conditionals

* Unlock free plan state

* Fixed subscribe button if you’re already github verified

* Simplified the downgrade reasons logic

* made periodEnd required

---------

Co-authored-by: nicktrn <55853254+nicktrn@users.noreply.github.com>
2024-10-10 14:12:15 +01:00
Dan 25c7ff173f Updated guides intro and side menu (#1398)
* Updated intro and moved stripe webhooks into the webhooks section

* Added additional resources links to nextjs and remix

* Updated intro page

* Changed formatting to improve layout

* Removed Supabase card from frameworks
2024-10-10 12:55:03 +01:00
Dan a6dbf8433c Webhook guides (#1396)
* Added nextjs webhook guide

* Updated intro page

* Copy improvement

* Added repo links to both supabase guides

* Updated structure and added an overview page

* Updated guides and simplified example code

* Added webhook handler for pages and split the docs

* Updated links
2024-10-10 10:18:28 +01:00
1929 changed files with 147045 additions and 82947 deletions
+10
View File
@@ -0,0 +1,10 @@
---
"@trigger.dev/react-hooks": patch
"@trigger.dev/sdk": patch
"trigger.dev": patch
"@trigger.dev/build": patch
"@trigger.dev/core": patch
"@trigger.dev/rsc": patch
---
Run Engine 2.0 (alpha)
+7 -1
View File
@@ -12,7 +12,13 @@
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": ["webapp", "proxy", "coordinator", "docker-provider", "kubernetes-provider"],
"ignore": [
"webapp",
"coordinator",
"docker-provider",
"kubernetes-provider",
"supervisor"
],
"___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
"onlyUpdatePeerDependentsWhenOutOfRange": true
}
+12
View File
@@ -0,0 +1,12 @@
---
"@trigger.dev/redis-worker": major
"@trigger.dev/react-hooks": major
"@trigger.dev/sdk": major
"trigger.dev": major
"@trigger.dev/python": major
"@trigger.dev/build": major
"@trigger.dev/core": major
"@trigger.dev/rsc": major
---
Trigger.dev v4 release. Please see our upgrade to v4 docs to view the full changelog: https://trigger.dev/docs/upgrade-to-v4
+5
View File
@@ -0,0 +1,5 @@
---
"@trigger.dev/sdk": patch
---
Deprecate toolTask and replace with `ai.tool(mySchemaTask)`
+5
View File
@@ -0,0 +1,5 @@
---
"trigger.dev": patch
---
Improve warm start times by eagerly creating the child TaskRunProcess when a previous run as completed
+28
View File
@@ -0,0 +1,28 @@
{
"mode": "pre",
"tag": "v4-beta",
"initialVersions": {
"coordinator": "0.0.1",
"docker-provider": "0.0.1",
"kubernetes-provider": "0.0.1",
"supervisor": "0.0.1",
"webapp": "1.0.0",
"@trigger.dev/build": "3.3.17",
"trigger.dev": "3.3.17",
"@trigger.dev/core": "3.3.17",
"@trigger.dev/python": "3.3.17",
"@trigger.dev/react-hooks": "3.3.17",
"@trigger.dev/redis-worker": "3.3.17",
"@trigger.dev/rsc": "3.3.17",
"@trigger.dev/sdk": "3.3.17"
},
"changesets": [
"breezy-turtles-talk",
"four-needles-add",
"honest-files-decide",
"nice-colts-boil",
"red-wasps-cover",
"smart-coins-hammer",
"weak-jobs-hide"
]
}
+5
View File
@@ -0,0 +1,5 @@
---
"@trigger.dev/core": patch
---
Suppress external instrumentation for fetch calls from ApiClient
+5
View File
@@ -0,0 +1,5 @@
---
"@trigger.dev/core": patch
---
fix: Realtime streams: prevent enqueuing into closed ReadableStream
+7
View File
@@ -0,0 +1,7 @@
---
"@trigger.dev/sdk": patch
"trigger.dev": patch
"@trigger.dev/core": patch
---
v4: New lifecycle hooks
+1 -1
View File
@@ -1,7 +1,7 @@
{
"compilerOptions": {
"target": "es2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"lib": ["ES2022", "DOM", "DOM.Iterable", "DOM.AsyncIterable"],
"module": "NodeNext",
"moduleResolution": "NodeNext",
"moduleDetection": "force",
+7
View File
@@ -0,0 +1,7 @@
{
"mcpServers": {
"trigger.dev": {
"url": "http://localhost:3333/sse"
}
}
}
+24
View File
@@ -0,0 +1,24 @@
---
description: how to run commands in the monorepo
globs:
alwaysApply: true
---
Almost all commands in the monorepo should be executed when `pnpm run ...` from the root of the monorepo. For example, running tests for the `@internal/run-engine` internal package:
```
pnpm run dev --filter webapp
```
But often, when running tests, it's better to `cd` into the directory and then run tests:
```
cd apps/webapp
pnpm run test --run
```
This way you can run for a single file easily:
```
cd internal-packages/run-engine
pnpm run test ./src/engine/tests/ttl.test.ts --run
```
+6
View File
@@ -0,0 +1,6 @@
---
description: understanding the structure of the monorepo
globs:
alwaysApply: true
---
We've documented the structure of our monorepo here: [repo.md](mdc:ai/references/repo.md)
+37
View File
@@ -0,0 +1,37 @@
---
description: Making updates to the main trigger.dev remix webapp
globs: apps/webapp/**/*.tsx,apps/webapp/**/*.ts
alwaysApply: false
---
The main trigger.dev webapp, which powers it's API and dashboard and makes up the docker image that is produced as an OSS image, is a Remix 2.1.0 app that uses an express server, written in TypeScript. The following subsystems are either included in the webapp or are used by the webapp in another part of the monorepo:
- `@trigger.dev/database` exports a Prisma 5.4.1 client that is used extensively in the webapp to access a PostgreSQL instance. The schema file is [schema.prisma](mdc:internal-packages/database/prisma/schema.prisma)
- `@trigger.dev/core` is a published package and is used to share code between the `@trigger.dev/sdk` and the webapp. It includes functionality but also a load of Zod schemas for data validation. When importing from `@trigger.dev/core` in the webapp, we never import the root `@trigger.dev/core` path, instead we favor one of the subpath exports that you can find in [package.json](mdc:packages/core/package.json)
- `@internal/run-engine` has all the code needed to trigger a run and take it through it's lifecycle to completion.
- `@trigger.dev/redis-worker` is a custom redis based background job/worker system that's used in the webapp and also used inside the run engine.
## Environment variables and testing
In the webapp, all environment variables are accessed through the `env` export of [env.server.ts](mdc:apps/webapp/app/env.server.ts), instead of directly accessing `process.env`.
Ideally, the `env.server.ts` file would never be imported into a test file, either directly or indirectly. Tests should only imported classes and functions from a file matching `app/**/*.ts` of the webapp, and that file should not use environment variables, everything should be passed through as options instead. This "service/configuration" separation is important, and can be seen in a few places in the code for examples:
- [realtimeClient.server.ts](mdc:apps/webapp/app/services/realtimeClient.server.ts) is the testable service, and [realtimeClientGlobal.server.ts](mdc:apps/webapp/app/services/realtimeClientGlobal.server.ts) is the configuration
Also for writing tests in the webapp, checkout our [tests.md](mdc:ai/references/tests.md) guide
## Legacy run engine vs Run Engine 2.0
We originally the Trigger.dev "Run Engine" not as a single system, but just spread out all over the codebase, with no real separate or encapsulation. And we didn't even call it a "Run Engine". With Run Engine 2.0, we've completely rewritten big parts of the way the system works, and moved it over to an internal package called `@internal/run-engine`. So we've retroactively named the previous run engine "Legacy run engine". We're focused almost exclusively now on moving to Run Engine 2.0 and will be deprecating and removing the legacy run engine code eventually.
## Where to look for code
- The trigger API endpoint is [api.v1.tasks.$taskId.trigger.ts](mdc:apps/webapp/app/routes/api.v1.tasks.$taskId.trigger.ts)
- The batch trigger API endpoint is [api.v1.tasks.batch.ts](mdc:apps/webapp/app/routes/api.v1.tasks.batch.ts)
- Setup code for the prisma client is in [db.server.ts](mdc:apps/webapp/app/db.server.ts)
- The run engine is configured in [runEngine.server.ts](mdc:apps/webapp/app/v3/runEngine.server.ts)
- All the "services" that are found in app/v3/services/**/*.server.ts
- The code for the TaskEvent data, which is the otel data sent from tasks to our servers, is in both the [eventRepository.server.ts](mdc:apps/webapp/app/v3/eventRepository.server.ts) and also the [otlpExporter.server.ts](mdc:apps/webapp/app/v3/otlpExporter.server.ts). The otel endpoints which are hit from production and development otel exporters is [otel.v1.logs.ts](mdc:apps/webapp/app/routes/otel.v1.logs.ts) and [otel.v1.traces.ts](mdc:apps/webapp/app/routes/otel.v1.traces.ts)
- We use "presenters" to move more complex loader code into a class, and you can find those are app/v3/presenters/**/*.server.ts
File diff suppressed because it is too large Load Diff
+6
View File
@@ -0,0 +1,6 @@
---
description: How to write tests in the monorepo
globs:
alwaysApply: true
---
Follow our [tests.md](mdc:ai/references/tests.md) guide for how to write tests in the monorepo.
+7
View File
@@ -0,0 +1,7 @@
apps/docker-provider/
apps/kubernetes-provider/
apps/proxy/
apps/coordinator/
packages/rsc/
.changeset
.zed
+31 -39
View File
@@ -1,44 +1,23 @@
\*.log
.git
.github
**/*.log
**/*.pem
**/*.tsbuildinfo
# editor
**/.cache
**/.env
**/.next
**/.output
**/.trigger
**/.tshy
**/.tshy-build
**/.turbo
**/.vercel
**/.wrangler
.idea
.vscode
**/dist
**/node_modules
# dependencies
node_modules
.pnp
.pnp.js
# testing
coverage
# next.js
.next/
build
# packages
build
dist
packages/\*\*/dist
# misc
.DS_Store
\*.pem
.turbo
.vercel
.cache
.output
.trigger
apps/\*\*/public/build
apps/webapp/build
apps/webapp/public/build
cypress/screenshots
cypress/videos
@@ -47,8 +26,21 @@ apps/**/styles/tailwind.css
packages/**/styles/tailwind.css
.changeset
references
.DS_Store
.git
.github
.idea
.pnp
.pnp.js
.vscode
coverage
build
docs
examples
out
references
CHANGESETS.md
CONTRIBUTING.md
README.md
+25 -3
View File
@@ -31,10 +31,32 @@ DEV_OTEL_BATCH_PROCESSING_ENABLED="0"
# AUTH_GITHUB_CLIENT_ID=
# AUTH_GITHUB_CLIENT_SECRET=
# Resend is an email service used for signing in to Trigger.dev via a Magic Link.
# Emails will print to the console if you leave these commented out
# Configure an email transport to allow users to sign in to Trigger.dev via a Magic Link.
# If none are configured, emails will print to the console instead.
# Uncomment one of the following blocks to allow delivery of
# Resend
### Visit https://resend.com, create an account and get your API key. Then insert it below along with your From and Reply To email addresses. Visit https://resend.com/docs for more information.
# RESEND_API_KEY=<api_key>
# EMAIL_TRANSPORT=resend
# FROM_EMAIL=
# REPLY_TO_EMAIL=
# RESEND_API_KEY=
# Generic SMTP
### Enter the configuration provided by your mail provider. Visit https://nodemailer.com/smtp/ for more information
### SMTP_SECURE = false will use STARTTLS when connecting to a server that supports it (usually port 587)
# EMAIL_TRANSPORT=smtp
# FROM_EMAIL=
# REPLY_TO_EMAIL=
# SMTP_HOST=
# SMTP_PORT=587
# SMTP_SECURE=false
# SMTP_USER=
# SMTP_PASSWORD=
# AWS Simple Email Service
### Authentication is configured using the default Node.JS credentials provider chain (https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-aws-sdk-credential-providers/#fromnodeproviderchain)
# EMAIL_TRANSPORT=aws-ses
# FROM_EMAIL=
# REPLY_TO_EMAIL=
-14
View File
@@ -1,14 +0,0 @@
module.exports = {
root: true,
// This tells ESLint to load the config from the package `eslint-config-custom`
extends: ["custom"],
settings: {
next: {
rootDir: ["apps/*/"],
},
},
parserOptions: {
sourceType: "module",
ecmaVersion: 2020,
},
};
+5
View File
@@ -31,6 +31,11 @@ runs:
sha=$(echo ${{ github.sha }} | head -c7)
ts=$(date +%s)
tag=${env}-${sha}-${ts}
elif [[ "${{ github.ref_name }}" == re2-*-* ]]; then
env=$(echo ${{ github.ref_name }} | cut -d- -f2)
sha=$(echo ${{ github.sha }} | head -c7)
ts=$(date +%s)
tag=${env}-${sha}-${ts}
elif [[ "${{ github.ref_name }}" == v.docker.* ]]; then
version="${GITHUB_REF_NAME#v.docker.}"
tag="v${version}"
+1
View File
@@ -0,0 +1 @@
This is the repo for Trigger.dev, a background jobs platform written in TypeScript. Our webapp at apps/webapp is a Remix 2.1 app that uses Node.js v20. Our SDK is an isomorphic TypeScript SDK at packages/trigger-sdk. Always prefer using isomorphic code like fetch, ReadableStream, etc. instead of Node.js specific code. Our tests are all vitest. We use prisma in internal-packages/database for our database interactions using PostgreSQL. For TypeScript, we usually use types over interfaces. We use zod a lot in packages/core and in the webapp. Avoid enums. Use strict mode. No default exports, use function declarations.
+1 -1
View File
@@ -39,4 +39,4 @@ jobs:
${{ runner.os }}-mintlify
- name: 🔗 Check for broken links
run: npx mintlify@4.0.222 broken-links
run: npx mintlify@4.0.393 broken-links
+4 -1
View File
@@ -17,7 +17,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [buildjet-8vcpu-ubuntu-2204, windows-latest]
os: [ubuntu-latest, windows-latest]
package-manager: ["npm", "pnpm"]
steps:
- name: ⬇️ Checkout repo
@@ -38,6 +38,9 @@ jobs:
- name: 📥 Download deps
run: pnpm install --frozen-lockfile --filter trigger.dev...
- name: 📀 Generate Prisma Client
run: pnpm run generate
- name: 🔧 Build v3 cli monorepo dependencies
run: pnpm run build --filter trigger.dev^...
-31
View File
@@ -28,34 +28,3 @@ jobs:
with:
package: cli-v3
secrets: inherit
preview-release:
name: Preview Release
needs: [typecheck, units, e2e]
if: github.repository == 'triggerdotdev/trigger.dev'
runs-on: buildjet-8vcpu-ubuntu-2204
steps:
- name: ⬇️ Checkout repo
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: ⎔ Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 8.15.5
- name: ⎔ Setup node
uses: buildjet/setup-node@v4
with:
node-version: 20.11.1
cache: "pnpm"
- name: 📥 Download deps
run: pnpm install --frozen-lockfile
- name: 🏗️ Build
run: pnpm run build --filter "@trigger.dev/*" --filter "trigger.dev"
- name: ⚡ Publish preview release
run: npx pkg-pr-new publish --no-template $(ls -d ./packages/*)
+95
View File
@@ -0,0 +1,95 @@
name: "⚒️ Publish Worker RE2"
on:
workflow_call:
inputs:
image_tag:
description: The image tag to publish
type: string
required: false
default: ""
push:
tags:
- "re2-test-*"
- "re2-prod-*"
permissions:
packages: write
contents: read
jobs:
# check-branch:
# runs-on: ubuntu-latest
# steps:
# - name: Fail if re2-prod-* is pushed from a non-main branch
# if: startsWith(github.ref_name, 're2-prod-') && github.base_ref != 'main'
# run: |
# echo "🚫 re2-prod-* tags can only be pushed from the main branch."
# exit 1
build:
# needs: check-branch
strategy:
matrix:
package: [supervisor]
runs-on: ubuntu-latest
env:
DOCKER_BUILDKIT: "1"
steps:
- name: ⬇️ Checkout git repo
uses: actions/checkout@v4
- name: 📦 Get image repo
id: get_repository
run: |
if [[ "${{ matrix.package }}" == *-provider ]]; then
provider_type=$(echo "${{ matrix.package }}" | cut -d- -f1)
repo=provider/${provider_type}
else
repo="${{ matrix.package }}"
fi
echo "repo=${repo}" >> "$GITHUB_OUTPUT"
- id: get_tag
uses: ./.github/actions/get-image-tag
with:
tag: ${{ inputs.image_tag }}
- name: 🐋 Set up Docker Buildx
uses: docker/setup-buildx-action@v3
# ..to avoid rate limits when pulling images
- name: 🐳 Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: 🚢 Build Container Image
run: |
docker build -t infra_image -f ./apps/${{ matrix.package }}/Containerfile .
# ..to push image
- name: 🐙 Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: 🐙 Push to GitHub Container Registry
run: |
docker tag infra_image "$REGISTRY/$REPOSITORY:$IMAGE_TAG"
docker push "$REGISTRY/$REPOSITORY:$IMAGE_TAG"
env:
REGISTRY: ghcr.io/triggerdotdev
REPOSITORY: ${{ steps.get_repository.outputs.repo }}
IMAGE_TAG: ${{ steps.get_tag.outputs.tag }}
- name: 🐙 Push 'v3' tag to GitHub Container Registry
if: steps.get_tag.outputs.is_semver == 'true'
run: |
docker tag infra_image "$REGISTRY/$REPOSITORY:v3"
docker push "$REGISTRY/$REPOSITORY:v3"
env:
REGISTRY: ghcr.io/triggerdotdev
REPOSITORY: ${{ steps.get_repository.outputs.repo }}
+1 -1
View File
@@ -23,7 +23,7 @@ jobs:
strategy:
matrix:
package: [coordinator, docker-provider, kubernetes-provider]
runs-on: buildjet-16vcpu-ubuntu-2204
runs-on: ubuntu-latest
env:
DOCKER_BUILDKIT: "1"
steps:
+3 -5
View File
@@ -24,12 +24,10 @@ on:
- "packages/**"
- "!packages/**/*.md"
- "!packages/**/*.eslintrc"
- "internal-packages/**"
- "apps/**"
- "!apps/**/*.md"
- "!apps/**/*.eslintrc"
- "integrations/**"
- "!integrations/**/*.md"
- "!integrations/**/*.eslintrc"
- "pnpm-lock.yaml"
- "pnpm-workspace.yaml"
- "turbo.json"
@@ -58,14 +56,14 @@ jobs:
secrets: inherit
publish-webapp:
needs: [typecheck, units]
needs: [typecheck]
uses: ./.github/workflows/publish-webapp.yml
secrets: inherit
with:
image_tag: ${{ inputs.image_tag }}
publish-worker:
needs: [typecheck, units]
needs: [typecheck]
uses: ./.github/workflows/publish-worker.yml
secrets: inherit
with:
+1 -1
View File
@@ -17,7 +17,7 @@ concurrency:
jobs:
release:
name: 🦋 Changesets Release
runs-on: buildjet-8vcpu-ubuntu-2204
runs-on: ubuntu-latest
if: github.repository == 'triggerdotdev/trigger.dev'
outputs:
published: ${{ steps.changesets.outputs.published }}
+1 -1
View File
@@ -5,7 +5,7 @@ on:
jobs:
typecheck:
runs-on: buildjet-8vcpu-ubuntu-2204
runs-on: ubuntu-latest
steps:
- name: ⬇️ Checkout repo
+22 -3
View File
@@ -6,7 +6,7 @@ on:
jobs:
unitTests:
name: "🧪 Unit Tests"
runs-on: buildjet-8vcpu-ubuntu-2204
runs-on: ubuntu-latest
steps:
- name: ⬇️ Checkout repo
uses: actions/checkout@v4
@@ -24,11 +24,30 @@ jobs:
node-version: 20.11.1
cache: "pnpm"
# ..to avoid rate limits when pulling images
- name: 🐳 Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: 📥 Download deps
run: pnpm install --frozen-lockfile
- name: 📀 Generate Prisma Client
run: pnpm run generate
- name: 🧪 Run Unit Tests
run: pnpm run test
- name: 🧪 Run Webapp Unit Tests
run: pnpm run test:webapp
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/postgres
DIRECT_URL: postgresql://postgres:postgres@localhost:5432/postgres
SESSION_SECRET: "secret"
MAGIC_LINK_SECRET: "secret"
ENCRYPTION_KEY: "secret"
- name: 🧪 Run Package Unit Tests
run: pnpm run test:packages
- name: 🧪 Run Internal Unit Tests
run: pnpm run test:internal
+5
View File
@@ -58,3 +58,8 @@ apps/**/public/build
.yarn
*.tsbuildinfo
/packages/cli-v3/src/package.json
.husky
/packages/react-hooks/src/package.json
/packages/core/src/package.json
/packages/trigger-sdk/src/package.json
/packages/python/src/package.json
+4 -1
View File
@@ -1,2 +1,5 @@
link-workspace-packages=false
public-hoist-pattern[]=*prisma*
public-hoist-pattern[]=*prisma*
prefer-workspace-packages=true
update-notifier=false
side-effects-cache=false
+2 -6
View File
@@ -1,8 +1,4 @@
{
"recommendations": [
"denoland.vscode-deno"
],
"unwantedRecommendations": [
]
"recommendations": ["bierner.comment-tagged-templates"],
"unwantedRecommendations": []
}
+58
View File
@@ -13,6 +13,24 @@
"cwd": "${workspaceFolder}",
"sourceMaps": true
},
{
"type": "node-terminal",
"request": "launch",
"name": "Debug realtimeStreams.test.ts",
"command": "pnpm run test -t RealtimeStreams",
"envFile": "${workspaceFolder}/.env",
"cwd": "${workspaceFolder}/apps/webapp",
"sourceMaps": true
},
{
"type": "node-terminal",
"request": "launch",
"name": "Debug fairDequeuingStrategy.test.ts",
"command": "pnpm run test -t FairDequeuingStrategy",
"envFile": "${workspaceFolder}/.env",
"cwd": "${workspaceFolder}/apps/webapp",
"sourceMaps": true
},
{
"type": "chrome",
"request": "launch",
@@ -28,6 +46,14 @@
"cwd": "${workspaceFolder}/references/init-shell",
"sourceMaps": true
},
{
"type": "node-terminal",
"request": "launch",
"name": "Debug V3 init dev CLI",
"command": "pnpm exec trigger dev",
"cwd": "${workspaceFolder}/references/init-shell",
"sourceMaps": true
},
{
"type": "node-terminal",
"request": "launch",
@@ -36,6 +62,14 @@
"cwd": "${workspaceFolder}/references/v3-catalog",
"sourceMaps": true
},
{
"type": "node-terminal",
"request": "launch",
"name": "Debug Dev Next.js Realtime",
"command": "pnpm exec trigger dev",
"cwd": "${workspaceFolder}/references/nextjs-realtime",
"sourceMaps": true
},
{
"type": "node-terminal",
"request": "launch",
@@ -99,6 +133,30 @@
"command": "pnpm exec trigger dev",
"cwd": "${workspaceFolder}/references/hello-world",
"sourceMaps": true
},
{
"type": "node-terminal",
"request": "launch",
"name": "Debug RunEngine tests",
"command": "pnpm run test ./src/engine/tests/releaseConcurrencyTokenBucketQueue.test.ts -t 'Should retrieve metrics for all queues via getQueueMetrics'",
"cwd": "${workspaceFolder}/internal-packages/run-engine",
"sourceMaps": true
},
{
"type": "node-terminal",
"request": "launch",
"name": "Debug RunQueue tests",
"command": "pnpm run test ./src/run-queue/index.test.ts",
"cwd": "${workspaceFolder}/internal-packages/run-engine",
"sourceMaps": true
},
{
"type": "node-terminal",
"request": "launch",
"name": "Debug d3-demo",
"command": "pnpm exec trigger dev",
"cwd": "${workspaceFolder}/references/d3-demo",
"sourceMaps": true
}
]
}
+2 -1
View File
@@ -5,5 +5,6 @@
"search.exclude": {
"**/node_modules/**": true,
"packages/cli-v3/e2e": true
}
},
"vitest.disableWorkspaceWarning": true
}
+45
View File
@@ -0,0 +1,45 @@
[
{
"label": "Build packages",
"command": "pnpm run build --filter \"@trigger.dev/*\" --filter trigger.dev",
//"args": [],
// Env overrides for the command, will be appended to the terminal's environment from the settings.
"env": { "foo": "bar" },
// Current working directory to spawn the command into, defaults to current project root.
//"cwd": "/path/to/working/directory",
// Whether to use a new terminal tab or reuse the existing one to spawn the process, defaults to `false`.
"use_new_terminal": false,
// Whether to allow multiple instances of the same task to be run, or rather wait for the existing ones to finish, defaults to `false`.
"allow_concurrent_runs": false,
// What to do with the terminal pane and tab, after the command was started:
// * `always` — always show the task's pane, and focus the corresponding tab in it (default)
// * `no_focus` — always show the task's pane, add the task's tab in it, but don't focus it
// * `never` — do not alter focus, but still add/reuse the task's tab in its pane
"reveal": "always",
// What to do with the terminal pane and tab, after the command has finished:
// * `never` — Do nothing when the command finishes (default)
// * `always` — always hide the terminal tab, hide the pane also if it was the last tab in it
// * `on_success` — hide the terminal tab on task success only, otherwise behaves similar to `always`
"hide": "never",
// Which shell to use when running a task inside the terminal.
// May take 3 values:
// 1. (default) Use the system's default terminal configuration in /etc/passwd
// "shell": "system"
// 2. A program:
// "shell": {
// "program": "sh"
// }
// 3. A program with arguments:
// "shell": {
// "with_arguments": {
// "program": "/bin/bash",
// "args": ["--login"]
// }
// }
"shell": "system",
// Whether to show the task line in the output of the spawned task, defaults to `true`.
"show_summary": true,
// Whether to show the command line in the output of the spawned task, defaults to `true`.
"show_output": true
}
]
+26 -18
View File
@@ -135,23 +135,22 @@ The following steps should be followed any time you start working on a new featu
1. Make sure the webapp is running on localhost:3030
2. Open a terminal window and build the CLI and watch for changes
2. Open a terminal window and build the CLI and packages and watch for changes
```sh
pnpm run dev --filter trigger.dev
pnpm run dev --filter trigger.dev --filter "@trigger.dev/*"
```
2. Open a new terminal window, and anytime changes are made to the `@trigger.dev/core` package, you'll need to manually rebuild the CLI:
```sh
pnpm run build --filter trigger.dev
```
Note: You do not need to do the same for `@trigger.dev/sdk`, just core.
3. Open another terminal window, and change into the `<root>/references/v3-catalog` directory.
4. Run the `dev` command, which will register all the local tasks with the platform and allow you to start testing task execution:
4. You'll need to run the following commands to setup prisma and migrate the database:
```sh
pnpm exec prisma migrate deploy
pnpm run generate:prisma
```
5. Run the `dev` command, which will register all the local tasks with the platform and allow you to start testing task execution:
```sh
# in <root>/references/v3-catalog
@@ -165,13 +164,13 @@ If you want additional debug logging, you can use the `--log-level debug` flag:
pnpm exec trigger dev --log-level debug
```
5. If you make any changes in the CLI/Core/SDK, you'll need to `CTRL+C` to exit the `dev` command and restart it to pickup changes. Any changes to the files inside of the `v3-catalog/src/trigger` dir will automatically be rebuilt by the `dev` command.
6. If you make any changes in the CLI/Core/SDK, you'll need to `CTRL+C` to exit the `dev` command and restart it to pickup changes. Any changes to the files inside of the `v3-catalog/src/trigger` dir will automatically be rebuilt by the `dev` command.
6. Navigate to the `v3-catalog` project in your local dashboard at localhost:3030 and you should see the list of tasks.
7. Navigate to the `v3-catalog` project in your local dashboard at localhost:3030 and you should see the list of tasks.
7. Go to the "Test" page in the sidebar and select a task. Then enter a payload and click "Run test". You can tell what the payloads should be by looking at the relevant task file inside the `/references/v3-catalog/src/trigger` folder. Many of them accept an empty payload.
8. Go to the "Test" page in the sidebar and select a task. Then enter a payload and click "Run test". You can tell what the payloads should be by looking at the relevant task file inside the `/references/v3-catalog/src/trigger` folder. Many of them accept an empty payload.
8. Feel free to add additional files in `v3-catalog/src/trigger` to test out specific aspects of the system, or add in edge cases.
9. Feel free to add additional files in `v3-catalog/src/trigger` to test out specific aspects of the system, or add in edge cases.
## Running end-to-end webapp tests (deprecated)
@@ -230,13 +229,22 @@ pnpm run db:studio
cd packages/database
```
3. Create and apply the migrations
3. Create a migration
```
pnpm run db:migrate:dev
pnpm run db:migrate:dev:create
```
This creates a migration file and executes the migrations against your database and applies changes to the database schema(s)
This creates a migration file. Check the migration file does only what you want. If you're adding any database indexes they must use `CONCURRENTLY`, otherwise they'll lock the table when executed.
4. Run the migration.
```
pnpm run db:migrate:deploy
pnpm run generate
```
This executes the migrations against your database and applies changes to the database schema(s), and then regenerates the Prisma client.
4. Commit generated migrations as well as changes to the schema.prisma file
5. If you're using VSCode you may need to restart the Typescript server in the webapp to get updated type inference. Open a TypeScript file, then open the Command Palette (View > Command Palette) and run `TypeScript: Restart TS server`.
+25 -8
View File
@@ -5,7 +5,7 @@
<img alt="Trigger.dev logo" src="https://imagedelivery.net/3TbraffuDZ4aEf8KWOmI_w/a45d1fa2-0ae8-4a39-4409-f4f934bfae00/public">
</picture>
### Open source background jobs with no timeouts
### Open source background jobs and AI infrastructure
[Discord](https://trigger.dev/discord) | [Website](https://trigger.dev) | [Issues](https://github.com/triggerdotdev/trigger.dev/issues) | [Docs](https://trigger.dev/docs)
@@ -15,13 +15,22 @@
## About Trigger.dev
Trigger.dev is an open source platform and SDK which allows you to create long-running background jobs with no timeouts. Write normal async code, deploy, and never hit a timeout.
Trigger.dev is an open source platform and SDK which allows you to create long-running background jobs. Write normal async code, deploy, and never hit a timeout.
#### Features:
### Key features:
- JavaScript and TypeScript SDK
- Write reliable code by default
- No timeouts
- Retries (with exponential backoff)
- Queues and concurrency controls
- Schedules and crons
- Full Observability; logs, live trace views, advanced filtering
- React hooks to interact with the Trigger API from your React app
- Pipe LLM streams straight to your users through the Realtime API
- Trigger tasks and display the run status and metadata anywhere in your app
- Custom alerts, get notified by email, Slack or webhooks
- No infrastructure to manage
- Elastic (scaling)
- Works with your existing tech stack
## In your codebase
@@ -45,7 +54,7 @@ export const helloWorld = task({
## Deployment
Use our SDK to write tasks in your codebase. There's no infrastructure to manage, your tasks automatically scale and connect to our cloud. Or you can always [self-host](https://trigger.dev/docs/v3/open-source-self-hosting#overview).
Use our SDK to write tasks in your codebase. There's no infrastructure to manage, your tasks automatically scale and connect to our cloud. Or you can always self-host.
## Environments
@@ -59,11 +68,19 @@ View every task in every run so you can tell exactly what happened. We provide a
# Getting started
Visit our docs [here](https://trigger.dev/docs/v3/introduction) for a full guide on how to get started with Trigger.dev.
The quickest way to get started is to create an account and project in our [web app](https://cloud.trigger.dev), and follow the instructions in the onboarding. Build and deploy your first task in minutes.
## Self-host
### Useful links:
If you prefer to self-host, you can follow our [self-hosting guide](https://trigger.dev/docs/v3/open-source-self-hosting#overview).
- [Quick start](https://trigger.dev/docs/quick-start) - get up and running in minutes
- [How it works](https://trigger.dev/docs/v3/how-it-works) - understand how Trigger.dev works under the hood
- [Guides and examples](https://trigger.dev/docs/guides/introduction) - walk-through guides and code examples for popular frameworks and use cases
## Self-hosting
If you prefer to self-host Trigger.dev, you can follow our [self-hosting guide](https://trigger.dev/docs/v3/open-source-self-hosting#overview).
We also have a dedicated self-hosting channel in our [Discord server](https://trigger.dev/discord) for support.
## Development
+37
View File
@@ -0,0 +1,37 @@
## Repo Overview
This is a pnpm 8.15.5 monorepo that uses turborepo @turbo.json. The following workspaces are relevant
## Apps
- <root>/apps/webapp is a remix app that is the main API and dashboard for trigger.dev
- <root>/apps/supervisor is a node.js app that handles the execution of built tasks, interaction with the webapp through internal "engine" APIs, as well as interfacing with things like docker or kubernetes, to execute the code.
## Public Packages
- <root>/packages/trigger-sdk is the `@trigger.dev/sdk` main SDK package.
- <root>/packages/cli-v3 is the `trigger.dev` CLI package. See our [CLI dev command](https://trigger.dev/docs/cli-dev.md) and [Deployment](https://trigger.dev/docs/deployment/overview.md) docs for more information.
- <root>/packages/core is the `@trigger.dev/core` package that is shared across the SDK and other packages
- <root>/packages/build defines the types and prebuilt build extensions for trigger.dev. See our [build extensions docs](https://trigger.dev/docs/config/extensions/overview.md) for more information.
- <root>/packages/react-hooks defines some useful react hooks like our realtime hooks. See our [Realtime hooks](https://trigger.dev/docs/frontend/react-hooks/realtime.md) and our [Trigger hooks](https://trigger.dev/docs/frontend/react-hooks/triggering.md) for more information.
- <root>/packages/redis-worker is the `@trigger.dev/redis-worker` package that implements a custom background job/worker sytem powered by redis for offloading work to the background, used in the webapp and also in the Run Engine 2.0.
## Internal Packages
- <root>/internal-packages/\* are packages that are used internally only, not published, and usually they have a tsc build step and are used in the webapp
- <root>/internal-packages/database is the `@trigger.dev/database` package that exports a prisma client, has the schema file, and exports a few other helpers.
- <root>/internal-packages/run-engine is the `@internal/run-engine` package that is "Run Engine 2.0" and handles moving a run all the way through it's lifecycle
- <root>/internal-packages/redis is the `@internal/redis` package that exports Redis types and the `createRedisClient` function to unify how we create redis clients in the repo. It's not used everywhere yet, but it's the preferred way to create redis clients from now on.
- <root>/internal-packages/testcontainers is the `@internal/testcontainers` package that exports a few useful functions for spinning up local testcontainers when writing vitest tests. See our [tests.md](./tests.md) file for more information.
- <root>/internal-packages/zodworker is the `@internal/zodworker` package that implements a wrapper around graphile-worker that allows us to use zod to validate our background jobs. We are moving away from using graphile-worker as our background job system, replacing it with our own redis-worker package.
## References
- <root>/references/\* are test workspaces that we use to write and test the system. Not quite e2e tests or automated, but just a useful place to help develop new features
## Other
- <root>/docs is our trigger.dev/docs mintlify documentation site
- <root>/docker/Dockerfile is the one that creates the main trigger.dev published image
- <root>/docker/docker-compose.yml is the file we run locally to start postgresql, redis, and electric when we are doing local development. You can run it with `pnpm run docker`
- <root>/CONTRIBUTING.md defines the steps it takes for OSS contributors to start contributing.
+86
View File
@@ -0,0 +1,86 @@
## Running Tests
We use vitest exclusively for testing. To execute tests for a particular workspace, run the following command:
```bash
pnpm run test --filter webapp
```
Prefer running tests on a single file (and first cding into the directory):
```bash
cd apps/webapp
pnpm run test ./src/components/Button.test.ts
```
If you are cd'ing into a directory, you may have to build dependencies first:
```bash
pnpm run build --filter webapp
cd apps/webapp
pnpm run test ./src/components/Button.test.ts
```
## Writing Tests
We use vitest for testing. We almost NEVER mock anything. Start with a top-level "describe", and have multiple "it" statements inside of it.
New test files should be placed right next to the file being tested. For example:
- Source file: `./src/services/MyService.ts`
- Test file: `./src/services/MyService.test.ts`
When writing anything that needs redis or postgresql, we have some internal "testcontainers" that are used to spin up a local instance, redis, or both.
redisTest:
```typescript
import { redisTest } from "@internal/testcontainers";
import { createRedisClient } from "@internal/redis";
describe("redisTest", () => {
redisTest("should use redis", async ({ redisOptions }) => {
const redis = createRedisClient(redisOptions);
await redis.set("test", "test");
const result = await redis.get("test");
expect(result).toEqual("test");
});
});
```
postgresTest:
```typescript
import { postgresTest } from "@internal/testcontainers";
describe("postgresTest", () => {
postgresTest("should use postgres", async ({ prisma }) => {
// prisma is an instance of PrismaClient
});
});
```
containerTest:
```typescript
import { containerTest } from "@internal/testcontainers";
describe("containerTest", () => {
containerTest("should use container", async ({ prisma, redisOptions }) => {
// container has both prisma and redis
});
});
```
## Dos and Dont's
- Do not mock anything.
- Do not use mocks in tests.
- Do not use spies in tests.
- Do not use stubs in tests.
- Do not use fakes in tests.
- Do not use sinon in tests.
- Structure each test with a setup, action, and assertion style.
- Feel free to write long test names.
- If there is any randomness in the code under test, use `seedrandom` to make it deterministic by allowing the caller to provide a seed.
+1 -1
View File
@@ -13,7 +13,7 @@ RUN find . -name "node_modules" -type d -prune -exec rm -rf '{}' +
FROM node-20 AS base
RUN apt-get update \
&& apt-get install -y buildah ca-certificates dumb-init docker.io \
&& apt-get install -y buildah ca-certificates dumb-init docker.io busybox \
&& rm -rf /var/lib/apt/lists/*
COPY --chown=node:node .gitignore .gitignore
+1 -3
View File
@@ -23,10 +23,8 @@
"tinyexec": "^0.3.0"
},
"devDependencies": {
"@types/node": "^18",
"dotenv": "^16.4.2",
"esbuild": "^0.19.11",
"tsx": "^4.7.0",
"typescript": "^5.3.3"
"tsx": "^4.7.0"
}
}
+146 -106
View File
@@ -1,5 +1,5 @@
import { ExponentialBackoff } from "@trigger.dev/core/v3/apps";
import { testDockerCheckpoint } from "@trigger.dev/core/v3/apps";
import { testDockerCheckpoint } from "@trigger.dev/core/v3/serverOnly";
import { nanoid } from "nanoid";
import fs from "node:fs/promises";
import { ChaosMonkey } from "./chaosMonkey";
@@ -27,7 +27,7 @@ type CheckpointAndPushResult =
| { success: true; checkpoint: CheckpointData }
| {
success: false;
reason?: "CANCELED" | "DISABLED" | "ERROR" | "IN_PROGRESS" | "NO_SUPPORT" | "SKIP_RETRYING";
reason?: "CANCELED" | "ERROR" | "SKIP_RETRYING";
};
type CheckpointData = {
@@ -87,9 +87,14 @@ export class Checkpointer {
#dockerMode: boolean;
#logger = new SimpleStructuredLogger("checkpointer");
#abortControllers = new Map<string, AbortController>();
#failedCheckpoints = new Map<string, unknown>();
#waitingForRetry = new Set<string>();
// Indexed by run ID
#runAbortControllers = new Map<
string,
{ signal: AbortSignal; abort: AbortController["abort"] }
>();
private registryHost: string;
private registryNamespace: string;
@@ -189,29 +194,80 @@ export class Checkpointer {
}
}
async checkpointAndPush(opts: CheckpointAndPushOptions): Promise<CheckpointData | undefined> {
async checkpointAndPush(
opts: CheckpointAndPushOptions,
delayMs?: number
): Promise<CheckpointData | undefined> {
const start = performance.now();
this.#logger.log(`checkpointAndPush() start`, { start, opts });
let interval: NodeJS.Timer | undefined;
const { runId } = opts;
let interval: NodeJS.Timer | undefined;
if (opts.shouldHeartbeat) {
interval = setInterval(() => {
this.#logger.log("Sending heartbeat", { runId: opts.runId });
this.opts.heartbeat(opts.runId);
this.#logger.log("Sending heartbeat", { runId });
this.opts.heartbeat(runId);
}, 20_000);
}
const controller = new AbortController();
const signal = controller.signal;
const abort = controller.abort.bind(controller);
const onAbort = () => {
this.#logger.error("Checkpoint aborted", { runId, options: opts });
};
signal.addEventListener("abort", onAbort, { once: true });
const removeCurrentAbortController = () => {
const controller = this.#runAbortControllers.get(runId);
// Ensure only the current controller is removed
if (controller && controller.signal === signal) {
this.#runAbortControllers.delete(runId);
}
// Remove the abort listener in case it hasn't fired
signal.removeEventListener("abort", onAbort);
};
if (!this.#dockerMode && !this.#canCheckpoint) {
this.#logger.error("No checkpoint support. Simulation requires docker.");
this.#failCheckpoint(runId, "NO_SUPPORT");
return;
}
if (this.#isRunCheckpointing(runId)) {
this.#logger.error("Checkpoint procedure already in progress", { options: opts });
this.#failCheckpoint(runId, "IN_PROGRESS");
return;
}
// This is a new checkpoint, clear any last failure for this run
this.#clearFailedCheckpoint(runId);
if (this.disableCheckpointSupport) {
this.#logger.error("Checkpoint support disabled", { options: opts });
this.#failCheckpoint(runId, "DISABLED");
return;
}
this.#runAbortControllers.set(runId, { signal, abort });
try {
const result = await this.#checkpointAndPushWithBackoff(opts);
const result = await this.#checkpointAndPushWithBackoff(opts, { delayMs, signal });
const end = performance.now();
this.#logger.log(`checkpointAndPush() end`, {
start,
end,
diff: end - start,
diffWithoutDelay: end - start - (delayMs ?? 0),
opts,
success: result.success,
delayMs,
});
if (!result.success) {
@@ -221,53 +277,69 @@ export class Checkpointer {
return result.checkpoint;
} finally {
if (opts.shouldHeartbeat) {
// @ts-ignore - Some kind of node incompatible type issue
clearInterval(interval);
}
removeCurrentAbortController();
}
}
isCheckpointing(runId: string) {
return this.#abortControllers.has(runId) || this.#waitingForRetry.has(runId);
#isRunCheckpointing(runId: string) {
return this.#runAbortControllers.has(runId);
}
cancelCheckpoint(runId: string): boolean {
cancelAllCheckpointsForRun(runId: string): boolean {
this.#logger.log("cancelAllCheckpointsForRun: call", { runId });
// If the last checkpoint failed, pretend we canceled it
// This ensures tasks don't wait for external resume messages to continue
if (this.#hasFailedCheckpoint(runId)) {
this.#logger.log("cancelAllCheckpointsForRun: hasFailedCheckpoint", { runId });
this.#clearFailedCheckpoint(runId);
return true;
}
if (this.#waitingForRetry.has(runId)) {
this.#waitingForRetry.delete(runId);
return true;
}
const controller = this.#abortControllers.get(runId);
const controller = this.#runAbortControllers.get(runId);
if (!controller) {
this.#logger.debug("Nothing to cancel", { runId });
this.#logger.debug("cancelAllCheckpointsForRun: no abort controller", { runId });
return false;
}
if (controller.signal.aborted) {
this.#logger.debug("Controller already aborted", { runId });
const { abort, signal } = controller;
if (signal.aborted) {
this.#logger.debug("cancelAllCheckpointsForRun: signal already aborted", { runId });
return false;
}
controller.abort("cancelCheckpoint()");
this.#abortControllers.delete(runId);
abort("cancelCheckpoint()");
this.#runAbortControllers.delete(runId);
return true;
}
async #checkpointAndPushWithBackoff({
runId,
leaveRunning = true, // This mirrors kubernetes behaviour more accurately
projectRef,
deploymentVersion,
attemptNumber,
}: CheckpointAndPushOptions): Promise<CheckpointAndPushResult> {
async #checkpointAndPushWithBackoff(
{
runId,
leaveRunning = true, // This mirrors kubernetes behaviour more accurately
projectRef,
deploymentVersion,
attemptNumber,
}: CheckpointAndPushOptions,
{ delayMs, signal }: { delayMs?: number; signal: AbortSignal }
): Promise<CheckpointAndPushResult> {
if (delayMs && delayMs > 0) {
this.#logger.log("Delaying checkpoint", { runId, delayMs });
try {
await setTimeout(delayMs, undefined, { signal });
} catch (error) {
this.#logger.log("Checkpoint canceled during initial delay", { runId });
return { success: false, reason: "CANCELED" };
}
}
this.#logger.log("Checkpointing with backoff", {
runId,
leaveRunning,
@@ -290,24 +362,24 @@ export class Checkpointer {
delay,
});
this.#waitingForRetry.add(runId);
await setTimeout(delay.milliseconds);
if (!this.#waitingForRetry.has(runId)) {
this.#logger.log("Checkpoint canceled while waiting for retry", { runId });
try {
await setTimeout(delay.milliseconds, undefined, { signal });
} catch (error) {
this.#logger.log("Checkpoint canceled during retry delay", { runId });
return { success: false, reason: "CANCELED" };
} else {
this.#waitingForRetry.delete(runId);
}
}
const result = await this.#checkpointAndPush({
runId,
leaveRunning,
projectRef,
deploymentVersion,
attemptNumber,
});
const result = await this.#checkpointAndPush(
{
runId,
leaveRunning,
projectRef,
deploymentVersion,
attemptNumber,
},
{ signal }
);
if (result.success) {
return result;
@@ -319,24 +391,6 @@ export class Checkpointer {
return result;
}
if (result.reason === "IN_PROGRESS") {
this.#logger.log("Checkpoint already in progress, won't retry", { runId });
this.#failCheckpoint(runId, result.reason);
return result;
}
if (result.reason === "NO_SUPPORT") {
this.#logger.log("No checkpoint support, won't retry", { runId });
this.#failCheckpoint(runId, result.reason);
return result;
}
if (result.reason === "DISABLED") {
this.#logger.log("Checkpoint support disabled, won't retry", { runId });
this.#failCheckpoint(runId, result.reason);
return result;
}
if (result.reason === "SKIP_RETRYING") {
this.#logger.log("Skipping retrying", { runId });
return result;
@@ -364,13 +418,16 @@ export class Checkpointer {
return { success: false, reason: "ERROR" };
}
async #checkpointAndPush({
runId,
leaveRunning = true, // This mirrors kubernetes behaviour more accurately
projectRef,
deploymentVersion,
attemptNumber,
}: CheckpointAndPushOptions): Promise<CheckpointAndPushResult> {
async #checkpointAndPush(
{
runId,
leaveRunning = true, // This mirrors kubernetes behaviour more accurately
projectRef,
deploymentVersion,
attemptNumber,
}: CheckpointAndPushOptions,
{ signal }: { signal: AbortSignal }
): Promise<CheckpointAndPushResult> {
await this.init();
const options = {
@@ -381,39 +438,12 @@ export class Checkpointer {
attemptNumber,
};
if (!this.#dockerMode && !this.#canCheckpoint) {
this.#logger.error("No checkpoint support. Simulation requires docker.");
return { success: false, reason: "NO_SUPPORT" };
}
if (this.isCheckpointing(runId)) {
this.#logger.error("Checkpoint procedure already in progress", { options });
return { success: false, reason: "IN_PROGRESS" };
}
// This is a new checkpoint, clear any last failure for this run
this.#clearFailedCheckpoint(runId);
if (this.disableCheckpointSupport) {
this.#logger.error("Checkpoint support disabled", { options });
return { success: false, reason: "DISABLED" };
}
const controller = new AbortController();
this.#abortControllers.set(runId, controller);
const onAbort = () => {
this.#logger.error("Checkpoint aborted", { options });
controller.signal.removeEventListener("abort", onAbort);
};
controller.signal.addEventListener("abort", onAbort);
const shortCode = nanoid(8);
const imageRef = this.#getImageRef(projectRef, deploymentVersion, shortCode);
const exportLocation = this.#getExportLocation(projectRef, deploymentVersion, shortCode);
const buildah = new Buildah({ id: `${runId}-${shortCode}`, abortSignal: controller.signal });
const crictl = new Crictl({ id: `${runId}-${shortCode}`, abortSignal: controller.signal });
const buildah = new Buildah({ id: `${runId}-${shortCode}`, abortSignal: signal });
const crictl = new Crictl({ id: `${runId}-${shortCode}`, abortSignal: signal });
const cleanup = async () => {
const metadata = {
@@ -435,29 +465,26 @@ export class Checkpointer {
} catch (error) {
this.#logger.error("Error during cleanup", { ...metadata, error });
}
this.#abortControllers.delete(runId);
controller.signal.removeEventListener("abort", onAbort);
};
try {
await this.chaosMonkey.call();
this.#logger.log("Checkpointing:", { options });
this.#logger.log("checkpointAndPush: checkpointing", { options });
const containterName = this.#getRunContainerName(runId);
// Create checkpoint (docker)
if (this.#dockerMode) {
await this.#createDockerCheckpoint(
controller.signal,
signal,
runId,
exportLocation,
leaveRunning,
attemptNumber
);
this.#logger.log("checkpoint created:", {
this.#logger.log("checkpointAndPush: checkpoint created", {
runId,
location: exportLocation,
});
@@ -558,13 +585,16 @@ export class Checkpointer {
}
}
this.#logger.error("Unhandled checkpoint error", { options, error });
this.#logger.error("Unhandled checkpoint error", {
options,
error: error instanceof Error ? error.message : error,
});
return { success: false, reason: "ERROR" };
} finally {
await cleanup();
if (controller.signal.aborted) {
if (signal.aborted) {
this.#logger.error("Checkpoint canceled: Cleanup", { options });
// Overrides any prior return value
@@ -573,6 +603,16 @@ export class Checkpointer {
}
}
async unpause(runId: string, attemptNumber?: number): Promise<void> {
try {
const containterNameWithAttempt = this.#getRunContainerName(runId, attemptNumber);
const exec = new Exec({ logger: this.#logger });
await exec.x("docker", ["unpause", containterNameWithAttempt]);
} catch (error) {
this.#logger.error("[Docker] Error during unpause", { runId, attemptNumber, error });
}
}
async #createDockerCheckpoint(
abortSignal: AbortSignal,
runId: string,
+12 -1
View File
@@ -64,7 +64,18 @@ export class Exec {
command,
argsRaw: args,
argsTrimmed,
...output,
globalOpts: {
trimArgs: this.trimArgs,
neverThrow: this.neverThrow,
hasAbortSignal: !!this.abortSignal,
},
localOpts: opts,
stdout: output.stdout,
stderr: output.stderr,
pid: result.pid,
exitCode: result.exitCode,
aborted: result.aborted,
killed: result.killed,
};
if (this.logOutput) {
File diff suppressed because it is too large Load Diff
+12
View File
@@ -17,3 +17,15 @@ export const numFromEnv = (env: string, defaultValue: number): number => {
return parseInt(value, 10);
};
export function safeJsonParse(json?: string): unknown {
if (!json) {
return;
}
try {
return JSON.parse(json);
} catch (e) {
return null;
}
}
+1 -3
View File
@@ -1,8 +1,6 @@
{
"include": ["./src/**/*.ts"],
"exclude": ["node_modules"],
"compilerOptions": {
"target": "es2016",
"target": "es2018",
"module": "commonjs",
"esModuleInterop": true,
"resolveJsonModule": true,
+1 -3
View File
@@ -20,10 +20,8 @@
"execa": "^8.0.1"
},
"devDependencies": {
"@types/node": "^18.19.8",
"dotenv": "^16.4.2",
"esbuild": "^0.19.11",
"tsx": "^4.7.0",
"typescript": "^5.3.3"
"tsx": "^4.7.0"
}
}
+7 -1
View File
@@ -7,7 +7,8 @@ import {
TaskOperationsRestoreOptions,
} from "@trigger.dev/core/v3/apps";
import { SimpleLogger } from "@trigger.dev/core/v3/apps";
import { isExecaChildProcess, testDockerCheckpoint } from "@trigger.dev/core/v3/apps";
import { isExecaChildProcess } from "@trigger.dev/core/v3/apps";
import { testDockerCheckpoint } from "@trigger.dev/core/v3/serverOnly";
import { setTimeout } from "node:timers/promises";
import { PostStartCauses, PreStopCauses } from "@trigger.dev/core/v3";
@@ -122,6 +123,7 @@ class DockerTaskOperations implements TaskOperations {
`--env=POD_NAME=${containerName}`,
`--env=COORDINATOR_HOST=${COORDINATOR_HOST}`,
`--env=COORDINATOR_PORT=${COORDINATOR_PORT}`,
`--env=TRIGGER_POD_SCHEDULED_AT_MS=${Date.now()}`,
`--name=${containerName}`,
];
@@ -129,6 +131,10 @@ class DockerTaskOperations implements TaskOperations {
runArgs.push(`--cpus=${opts.machine.cpu}`, `--memory=${opts.machine.memory}G`);
}
if (opts.dequeuedAt) {
runArgs.push(`--env=TRIGGER_RUN_DEQUEUED_AT_MS=${opts.dequeuedAt}`);
}
runArgs.push(`${opts.image}`);
try {
+1 -2
View File
@@ -23,7 +23,6 @@
"devDependencies": {
"dotenv": "^16.4.2",
"esbuild": "^0.19.11",
"tsx": "^4.7.0",
"typescript": "^5.3.3"
"tsx": "^4.7.0"
}
}
+87 -17
View File
@@ -17,6 +17,8 @@ import {
import { PodCleaner } from "./podCleaner";
import { TaskMonitor } from "./taskMonitor";
import { UptimeHeartbeat } from "./uptimeHeartbeat";
import { assertExhaustive } from "@trigger.dev/core";
import { CustomLabelHelper } from "./labelHelper";
const RUNTIME_ENV = process.env.KUBERNETES_PORT ? "kubernetes" : "local";
const NODE_NAME = process.env.NODE_NAME || "local";
@@ -34,6 +36,18 @@ const UPTIME_MAX_PENDING_RUNS = Number(process.env.UPTIME_MAX_PENDING_RUNS || "2
const UPTIME_MAX_PENDING_INDECES = Number(process.env.UPTIME_MAX_PENDING_INDECES || "10");
const UPTIME_MAX_PENDING_ERRORS = Number(process.env.UPTIME_MAX_PENDING_ERRORS || "10");
const POD_EPHEMERAL_STORAGE_SIZE_LIMIT = process.env.POD_EPHEMERAL_STORAGE_SIZE_LIMIT || "10Gi";
const POD_EPHEMERAL_STORAGE_SIZE_REQUEST = process.env.POD_EPHEMERAL_STORAGE_SIZE_REQUEST || "2Gi";
// Image config
const PRE_PULL_DISABLED = process.env.PRE_PULL_DISABLED === "true";
const ADDITIONAL_PULL_SECRETS = process.env.ADDITIONAL_PULL_SECRETS;
const PAUSE_IMAGE = process.env.PAUSE_IMAGE || "registry.k8s.io/pause:3.9";
const BUSYBOX_IMAGE = process.env.BUSYBOX_IMAGE || "registry.digitalocean.com/trigger/busybox";
const DEPLOYMENT_IMAGE_PREFIX = process.env.DEPLOYMENT_IMAGE_PREFIX;
const RESTORE_IMAGE_PREFIX = process.env.RESTORE_IMAGE_PREFIX;
const UTILITY_IMAGE_PREFIX = process.env.UTILITY_IMAGE_PREFIX;
const logger = new SimpleLogger(`[${NODE_NAME}]`);
logger.log(`running in ${RUNTIME_ENV} mode`);
@@ -60,6 +74,8 @@ class KubernetesTaskOperations implements TaskOperations {
apps: k8s.AppsV1Api;
};
#labelHelper = new CustomLabelHelper();
constructor(opts: { namespace?: string } = {}) {
if (opts.namespace) {
this.#namespace.metadata.name = opts.namespace;
@@ -98,7 +114,7 @@ class KubernetesTaskOperations implements TaskOperations {
containers: [
{
name: this.#getIndexContainerName(opts.shortCode),
image: opts.imageRef,
image: getImageRef("deployment", opts.imageRef),
ports: [
{
containerPort: 8000,
@@ -152,6 +168,7 @@ class KubernetesTaskOperations implements TaskOperations {
name: containerName,
namespace: this.#namespace.metadata.name,
labels: {
...this.#labelHelper.getAdditionalLabels("create"),
...this.#getSharedLabels(opts),
app: "task-run",
"app.kubernetes.io/part-of": "trigger-worker",
@@ -165,7 +182,7 @@ class KubernetesTaskOperations implements TaskOperations {
containers: [
{
name: containerName,
image: opts.image,
image: getImageRef("deployment", opts.image),
ports: [
{
containerPort: 8000,
@@ -185,6 +202,9 @@ class KubernetesTaskOperations implements TaskOperations {
name: "TRIGGER_RUN_ID",
value: opts.runId,
},
...(opts.dequeuedAt
? [{ name: "TRIGGER_RUN_DEQUEUED_AT_MS", value: String(opts.dequeuedAt) }]
: []),
],
volumeMounts: [
{
@@ -213,6 +233,7 @@ class KubernetesTaskOperations implements TaskOperations {
name: `${this.#getRunContainerName(opts.runId)}-${opts.checkpointId.slice(-8)}`,
namespace: this.#namespace.metadata.name,
labels: {
...this.#labelHelper.getAdditionalLabels("restore"),
...this.#getSharedLabels(opts),
app: "task-run",
"app.kubernetes.io/part-of": "trigger-worker",
@@ -226,12 +247,12 @@ class KubernetesTaskOperations implements TaskOperations {
initContainers: [
{
name: "pull-base-image",
image: opts.imageRef,
image: getImageRef("deployment", opts.imageRef),
command: ["sleep", "0"],
},
{
name: "populate-taskinfo",
image: "registry.digitalocean.com/trigger/busybox",
image: getImageRef("utility", BUSYBOX_IMAGE),
imagePullPolicy: "IfNotPresent",
command: ["/bin/sh", "-c"],
args: ["printenv COORDINATOR_HOST | tee /etc/taskinfo/coordinator-host"],
@@ -247,7 +268,7 @@ class KubernetesTaskOperations implements TaskOperations {
containers: [
{
name: this.#getRunContainerName(opts.runId),
image: opts.checkpointRef,
image: getImageRef("restore", opts.checkpointRef),
ports: [
{
containerPort: 8000,
@@ -298,6 +319,11 @@ class KubernetesTaskOperations implements TaskOperations {
}
async prePullDeployment(opts: TaskOperationsPrePullDeploymentOptions) {
if (PRE_PULL_DISABLED) {
logger.debug("Pre-pull is disabled, skipping.", { opts });
return;
}
const metaName = this.#getPrePullContainerName(opts.shortCode);
const metaLabels = {
@@ -329,10 +355,26 @@ class KubernetesTaskOperations implements TaskOperations {
spec: {
...this.#defaultPodSpec,
restartPolicy: "Always",
affinity: {
nodeAffinity: {
requiredDuringSchedulingIgnoredDuringExecution: {
nodeSelectorTerms: [
{
matchExpressions: [
{
key: "trigger.dev/pre-pull-disabled",
operator: "DoesNotExist",
},
],
},
],
},
},
},
initContainers: [
{
name: "prepull",
image: opts.imageRef,
image: getImageRef("deployment", opts.imageRef),
command: ["/usr/bin/true"],
resources: {
limits: {
@@ -346,7 +388,7 @@ class KubernetesTaskOperations implements TaskOperations {
containers: [
{
name: "pause",
image: "registry.k8s.io/pause:3.9",
image: getImageRef("utility", PAUSE_IMAGE),
resources: {
limits: {
cpu: "1m",
@@ -377,17 +419,20 @@ class KubernetesTaskOperations implements TaskOperations {
}
get #defaultPodSpec(): Omit<k8s.V1PodSpec, "containers"> {
const pullSecrets = ["registry-trigger", "registry-trigger-failover"];
if (ADDITIONAL_PULL_SECRETS) {
pullSecrets.push(...ADDITIONAL_PULL_SECRETS.split(","));
}
const imagePullSecrets = pullSecrets.map(
(name) => ({ name }) satisfies k8s.V1LocalObjectReference
);
return {
restartPolicy: "Never",
automountServiceAccountToken: false,
imagePullSecrets: [
{
name: "registry-trigger",
},
{
name: "registry-trigger-failover",
},
],
imagePullSecrets,
nodeSelector: {
nodetype: "worker",
},
@@ -396,13 +441,13 @@ class KubernetesTaskOperations implements TaskOperations {
get #defaultResourceRequests(): ResourceQuantities {
return {
"ephemeral-storage": "2Gi",
"ephemeral-storage": POD_EPHEMERAL_STORAGE_SIZE_REQUEST,
};
}
get #defaultResourceLimits(): ResourceQuantities {
return {
"ephemeral-storage": "10Gi",
"ephemeral-storage": POD_EPHEMERAL_STORAGE_SIZE_LIMIT,
};
}
@@ -475,6 +520,10 @@ class KubernetesTaskOperations implements TaskOperations {
},
},
},
{
name: "TRIGGER_POD_SCHEDULED_AT_MS",
value: Date.now().toString(),
},
...this.#coordinatorEnvVars,
];
}
@@ -647,6 +696,26 @@ class KubernetesTaskOperations implements TaskOperations {
}
}
type ImageType = "deployment" | "restore" | "utility";
function getImagePrefix(type: ImageType) {
switch (type) {
case "deployment":
return DEPLOYMENT_IMAGE_PREFIX;
case "restore":
return RESTORE_IMAGE_PREFIX;
case "utility":
return UTILITY_IMAGE_PREFIX;
default:
assertExhaustive(type);
}
}
function getImageRef(type: ImageType, ref: string) {
const prefix = getImagePrefix(type);
return prefix ? `${prefix}/${ref}` : ref;
}
const provider = new ProviderShell({
tasks: new KubernetesTaskOperations({
namespace: KUBERNETES_NAMESPACE,
@@ -658,6 +727,7 @@ provider.listen();
const taskMonitor = new TaskMonitor({
runtimeEnv: RUNTIME_ENV,
namespace: KUBERNETES_NAMESPACE,
onIndexFailure: async (deploymentId, details) => {
logger.log("Indexing failed", { deploymentId, details });
+153
View File
@@ -0,0 +1,153 @@
import { assertExhaustive } from "@trigger.dev/core";
const CREATE_LABEL_ENV_VAR_PREFIX = "DEPLOYMENT_LABEL_";
const RESTORE_LABEL_ENV_VAR_PREFIX = "RESTORE_LABEL_";
const LABEL_SAMPLE_RATE_POSTFIX = "_SAMPLE_RATE";
type OperationType = "create" | "restore";
type CustomLabel = {
key: string;
value: string;
sampleRate: number;
};
export class CustomLabelHelper {
// Labels and sample rates are defined in environment variables so only need to be computed once
private createLabels?: CustomLabel[];
private restoreLabels?: CustomLabel[];
private getLabelPrefix(type: OperationType) {
const prefix = type === "create" ? CREATE_LABEL_ENV_VAR_PREFIX : RESTORE_LABEL_ENV_VAR_PREFIX;
return prefix.toLowerCase();
}
private getLabelSampleRatePostfix() {
return LABEL_SAMPLE_RATE_POSTFIX.toLowerCase();
}
// Can only range from 0 to 1
private fractionFromPercent(percent: number) {
return Math.min(1, Math.max(0, percent / 100));
}
private isLabelSampleRateEnvVar(key: string) {
return key.toLowerCase().endsWith(this.getLabelSampleRatePostfix());
}
private isLabelEnvVar(type: OperationType, key: string) {
const prefix = this.getLabelPrefix(type);
return key.toLowerCase().startsWith(prefix) && !this.isLabelSampleRateEnvVar(key);
}
private getSampleRateEnvVarKey(type: OperationType, envKey: string) {
return `${envKey.toLowerCase()}${this.getLabelSampleRatePostfix()}`;
}
private getLabelNameFromEnvVarKey(type: OperationType, key: string) {
return key
.slice(this.getLabelPrefix(type).length)
.toLowerCase()
.replace(/___/g, ".")
.replace(/__/g, "/")
.replace(/_/g, "-");
}
private getCaseInsensitiveEnvValue(key: string) {
for (const [envKey, value] of Object.entries(process.env)) {
if (envKey.toLowerCase() === key.toLowerCase()) {
return value;
}
}
}
/** Returns the sample rate for a given label as fraction of 100 */
private getSampleRateFromEnvVarKey(type: OperationType, envKey: string) {
// Apply default: always sample
const DEFAULT_SAMPLE_RATE_PERCENT = 100;
const defaultSampleRateFraction = this.fractionFromPercent(DEFAULT_SAMPLE_RATE_PERCENT);
const value = this.getCaseInsensitiveEnvValue(this.getSampleRateEnvVarKey(type, envKey));
if (!value) {
return defaultSampleRateFraction;
}
const sampleRatePercent = parseFloat(value || String(DEFAULT_SAMPLE_RATE_PERCENT));
if (isNaN(sampleRatePercent)) {
return defaultSampleRateFraction;
}
const fractionalSampleRate = this.fractionFromPercent(sampleRatePercent);
return fractionalSampleRate;
}
private getCustomLabels(type: OperationType): CustomLabel[] {
switch (type) {
case "create":
if (this.createLabels) {
return this.createLabels;
}
break;
case "restore":
if (this.restoreLabels) {
return this.restoreLabels;
}
break;
default:
assertExhaustive(type);
}
const customLabels: CustomLabel[] = [];
for (const [envKey, value] of Object.entries(process.env)) {
const key = envKey.toLowerCase();
// Only process env vars that start with the expected prefix
if (!this.isLabelEnvVar(type, key)) {
continue;
}
// Skip sample rates - deal with them separately
if (this.isLabelSampleRateEnvVar(key)) {
continue;
}
const labelName = this.getLabelNameFromEnvVarKey(type, key);
const sampleRate = this.getSampleRateFromEnvVarKey(type, key);
const label = {
key: labelName,
value: value || "",
sampleRate,
} satisfies CustomLabel;
customLabels.push(label);
}
return customLabels;
}
getAdditionalLabels(type: OperationType): Record<string, string> {
const labels = this.getCustomLabels(type);
const additionalLabels: Record<string, string> = {};
for (const { key, value, sampleRate } of labels) {
// Always apply label if sample rate is 1
if (sampleRate === 1) {
additionalLabels[key] = value;
continue;
}
if (Math.random() <= sampleRate) {
additionalLabels[key] = value;
continue;
}
}
return additionalLabels;
}
}
+7 -6
View File
@@ -160,7 +160,10 @@ export class TaskMonitor {
let reason = rawReason || "Unknown error";
let logs = rawLogs || "";
let overrideCompletion = false;
/** This will only override existing task errors. It will not crash the run. */
let onlyOverrideExistingError = exitCode === EXIT_CODE_CHILD_NONZERO;
let errorCode: TaskRunInternalError["code"] = TaskRunErrorCodes.POD_UNKNOWN_ERROR;
switch (rawReason) {
@@ -185,10 +188,8 @@ export class TaskMonitor {
}
break;
case "OOMKilled":
overrideCompletion = true;
reason = `${
exitCode === EXIT_CODE_CHILD_NONZERO ? "Child process" : "Parent process"
} ran out of memory! Try choosing a machine preset with more memory for this task.`;
reason =
"[TaskMonitor] Your task ran out of memory. Try increasing the machine specs. If this doesn't fix it there might be a memory leak.";
errorCode = TaskRunErrorCodes.TASK_PROCESS_OOM_KILLED;
break;
default:
@@ -199,7 +200,7 @@ export class TaskMonitor {
exitCode,
reason,
logs,
overrideCompletion,
overrideCompletion: onlyOverrideExistingError,
errorCode,
} satisfies FailureDetails;
+3 -1
View File
@@ -1,6 +1,6 @@
{
"compilerOptions": {
"target": "es2016",
"target": "es2018",
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
@@ -8,6 +8,8 @@
"strict": true,
"skipLibCheck": true,
"paths": {
"@trigger.dev/core": ["../../packages/core/src"],
"@trigger.dev/core/*": ["../../packages/core/src/*"],
"@trigger.dev/core/v3": ["../../packages/core/src/v3"],
"@trigger.dev/core/v3/*": ["../../packages/core/src/v3/*"]
}
-7
View File
@@ -1,7 +0,0 @@
REWRITE_HOSTNAME=
AWS_SQS_ACCESS_KEY_ID=
AWS_SQS_SECRET_ACCESS_KEY=
AWS_SQS_QUEUE_URL=
AWS_SQS_REGION=
#optional
#REWRITE_PORT=
-13
View File
@@ -1,13 +0,0 @@
# http://editorconfig.org
root = true
[*]
indent_style = tab
tab_width = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.yml]
indent_style = space
-172
View File
@@ -1,172 +0,0 @@
# Logs
logs
_.log
npm-debug.log_
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# Runtime data
pids
_.pid
_.seed
\*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
\*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
\*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
\*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.\*
# wrangler project
.dev.vars
.wrangler/
-11
View File
@@ -1,11 +0,0 @@
{
"semi": true,
"singleQuote": false,
"jsxSingleQuote": false,
"trailingComma": "es5",
"bracketSpacing": true,
"bracketSameLine": false,
"printWidth": 100,
"tabWidth": 2,
"useTabs": false
}
-72
View File
@@ -1,72 +0,0 @@
# proxy
## 0.0.11
### Patch Changes
- @trigger.dev/core@2.3.5
## 0.0.10
### Patch Changes
- @trigger.dev/core@2.3.4
## 0.0.9
### Patch Changes
- @trigger.dev/core@2.3.3
## 0.0.8
### Patch Changes
- @trigger.dev/core@2.3.2
## 0.0.7
### Patch Changes
- Updated dependencies [f3efcc0c]
- @trigger.dev/core@2.3.1
## 0.0.6
### Patch Changes
- Updated dependencies [17f6f29d]
- @trigger.dev/core@2.3.0
## 0.0.5
### Patch Changes
- @trigger.dev/core@2.2.11
## 0.0.4
### Patch Changes
- @trigger.dev/core@2.2.10
## 0.0.3
### Patch Changes
- Updated dependencies [6ebd435e]
- @trigger.dev/core@2.2.9
## 0.0.2
### Patch Changes
- Updated dependencies [067e19fe]
- @trigger.dev/core@2.2.8
## 0.0.1
### Patch Changes
- Updated dependencies [756024da]
- @trigger.dev/core@2.2.7
-68
View File
@@ -1,68 +0,0 @@
# Trigger.dev proxy
This is an optional module that can be used to proxy and queue requests to the Trigger.dev API.
## Why?
The Trigger.dev API is designed to be fast and reliable. However, if you have a lot of traffic, you may want to use this proxy to queue requests to the API. It intercepts some requests to the API and adds them to an AWS SQS queue, then the webapp can be setup to process the queue.
## Current features
- Intercepts `sendEvent` requests and adds them to an AWS SQS queue. The webapp then reads from the queue and creates the events.
## Setup
### Create an AWS SQS queue
In AWS you should create a new AWS SQS queue with appropriate security settings. You will need the queue URL for the next step.
### Environment variables
#### Cloudflare secrets
Locally you should copy the `.dev.var.example` file to `.dev.var` and fill in the values.
When deploying you should use `wrangler` (the Cloudflare CLI tool) to set secrets. Make sure you set the correct --env ("staging" or "prod")
```bash
wrangler secret put REWRITE_HOSTNAME --env staging
wrangler secret put AWS_SQS_ACCESS_KEY_ID --env staging
wrangler secret put AWS_SQS_SECRET_ACCESS_KEY --env staging
wrangler secret put AWS_SQS_QUEUE_URL --env staging
wrangler secret put AWS_SQS_REGION --env staging
```
You need to set your API CNAME entry to be proxied by Cloudflare. You can do this in the Cloudflare dashboard.
#### Webapp
These env vars also need setting in the webapp.
```bash
AWS_SQS_REGION
AWS_SQS_ACCESS_KEY_ID
AWS_SQS_SECRET_ACCESS_KEY
AWS_SQS_QUEUE_URL
AWS_SQS_BATCH_SIZE
```
## Deployment
Staging:
```bash
npx wrangler@latest deploy --route "<your-api-subdomain>/*" --env staging
```
Prod:
```bash
npx wrangler@latest deploy --route "<your-api-subdomain>/*" --env prod
```
## Development
Set the environment variables as described above.
1. `pnpm install`
2. `pnpm run dev --filter proxy`
-21
View File
@@ -1,21 +0,0 @@
{
"name": "proxy",
"version": "0.0.11",
"private": true,
"scripts": {
"deploy": "wrangler deploy",
"dev": "wrangler dev"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20240512.0",
"typescript": "^5.0.4",
"wrangler": "^3.57.1"
},
"dependencies": {
"@aws-sdk/client-sqs": "^3.445.0",
"@trigger.dev/core": "workspace:*",
"ulidx": "^2.2.1",
"zod": "3.22.3",
"zod-error": "1.5.0"
}
}
-20
View File
@@ -1,20 +0,0 @@
import { z } from "zod";
const AuthorizationHeaderSchema = z.string().regex(/^Bearer .+$/);
export function getApiKeyFromRequest(request: Request) {
const rawAuthorization = request.headers.get("Authorization");
const authorization = AuthorizationHeaderSchema.safeParse(rawAuthorization);
if (!authorization.success) {
return;
}
const apiKey = authorization.data.replace(/^Bearer /, "");
const type = isPrivateApiKey(apiKey) ? ("PRIVATE" as const) : ("PUBLIC" as const);
return { apiKey, type };
}
function isPrivateApiKey(key: string) {
return key.startsWith("tr_");
}
-87
View File
@@ -1,87 +0,0 @@
import { SQSClient, SendMessageCommand } from "@aws-sdk/client-sqs";
import { ApiEventLog, SendEventBodySchema } from "@trigger.dev/core";
import { generateErrorMessage } from "zod-error";
import { Env } from "..";
import { getApiKeyFromRequest } from "../apikey";
import { json } from "../json";
import { calculateDeliverAt } from "./utils";
/** Adds the event to an AWS SQS queue, so it can be consumed from the main Trigger.dev API */
export async function queueEvent(request: Request, env: Env): Promise<Response> {
//check there's a private API key
const apiKeyResult = getApiKeyFromRequest(request);
if (!apiKeyResult || apiKeyResult.type !== "PRIVATE") {
return json(
{ error: "Invalid or Missing API key" },
{
status: 401,
}
);
}
//parse the request body
try {
const anyBody = await request.json();
const body = SendEventBodySchema.safeParse(anyBody);
if (!body.success) {
return json(
{ error: generateErrorMessage(body.error.issues) },
{
status: 422,
}
);
}
// The AWS SDK tries to use crypto from off of the window,
// so we need to trick it into finding it where it expects it
globalThis.global = globalThis;
const client = new SQSClient({
region: env.AWS_SQS_REGION,
credentials: {
accessKeyId: env.AWS_SQS_ACCESS_KEY_ID,
secretAccessKey: env.AWS_SQS_SECRET_ACCESS_KEY,
},
});
const timestamp = body.data.event.timestamp ?? new Date();
//add the event to the queue
const send = new SendMessageCommand({
// use wrangler secrets to provide this global variable
QueueUrl: env.AWS_SQS_QUEUE_URL,
MessageBody: JSON.stringify({
event: { ...body.data.event, timestamp },
options: body.data.options,
apiKey: apiKeyResult.apiKey,
}),
});
const queuedEvent = await client.send(send);
console.log("Queued event", queuedEvent);
//respond with the event
const event: ApiEventLog = {
id: body.data.event.id,
name: body.data.event.name,
payload: body.data.event.payload,
context: body.data.event.context,
timestamp,
deliverAt: calculateDeliverAt(body.data.options),
};
return json(event, {
status: 200,
});
} catch (e) {
console.error("queueEvent error", e);
return json(
{
error: `Failed to send event: ${e instanceof Error ? e.message : JSON.stringify(e)}`,
},
{
status: 422,
}
);
}
}
-112
View File
@@ -1,112 +0,0 @@
import { SQSClient, SendMessageBatchCommand } from "@aws-sdk/client-sqs";
import { ApiEventLog, SendBulkEventsBodySchema } from "@trigger.dev/core";
import { generateErrorMessage } from "zod-error";
import { Env } from "..";
import { getApiKeyFromRequest } from "../apikey";
import { json } from "../json";
import { calculateDeliverAt } from "./utils";
/** Adds the event to an AWS SQS queue, so it can be consumed from the main Trigger.dev API */
export async function queueEvents(request: Request, env: Env): Promise<Response> {
//check there's a private API key
const apiKeyResult = getApiKeyFromRequest(request);
if (!apiKeyResult || apiKeyResult.type !== "PRIVATE") {
return json(
{ error: "Invalid or Missing API key" },
{
status: 401,
}
);
}
//parse the request body
try {
const anyBody = await request.json();
const body = SendBulkEventsBodySchema.safeParse(anyBody);
if (!body.success) {
return json(
{ error: generateErrorMessage(body.error.issues) },
{
status: 422,
}
);
}
// The AWS SDK tries to use crypto from off of the window,
// so we need to trick it into finding it where it expects it
globalThis.global = globalThis;
const client = new SQSClient({
region: env.AWS_SQS_REGION,
credentials: {
accessKeyId: env.AWS_SQS_ACCESS_KEY_ID,
secretAccessKey: env.AWS_SQS_SECRET_ACCESS_KEY,
},
});
const updatedEvents: ApiEventLog[] = body.data.events.map((event) => {
const timestamp = event.timestamp ?? new Date();
return {
...event,
payload: event.payload,
timestamp,
};
});
//divide updatedEvents into multiple batches of 10 (max size SQS accepts)
const batches: ApiEventLog[][] = [];
let currentBatch: ApiEventLog[] = [];
for (let i = 0; i < updatedEvents.length; i++) {
currentBatch.push(updatedEvents[i]);
if (currentBatch.length === 10) {
batches.push(currentBatch);
currentBatch = [];
}
}
if (currentBatch.length > 0) {
batches.push(currentBatch);
}
//loop through the batches and send them
for (let i = 0; i < batches.length; i++) {
const batch = batches[i];
//add the event to the queue
const send = new SendMessageBatchCommand({
// use wrangler secrets to provide this global variable
QueueUrl: env.AWS_SQS_QUEUE_URL,
Entries: batch.map((event, index) => ({
Id: `event-${index}`,
MessageBody: JSON.stringify({
event,
options: body.data.options,
apiKey: apiKeyResult.apiKey,
}),
})),
});
const queuedEvent = await client.send(send);
console.log("Queued events", queuedEvent);
}
//respond with the events
const events: ApiEventLog[] = updatedEvents.map((event) => ({
...event,
payload: event.payload,
deliverAt: calculateDeliverAt(body.data.options),
}));
return json(events, {
status: 200,
});
} catch (e) {
console.error("queueEvents error", e);
return json(
{
error: `Failed to send events: ${e instanceof Error ? e.message : JSON.stringify(e)}`,
},
{
status: 422,
}
);
}
}
-15
View File
@@ -1,15 +0,0 @@
import { SendEventOptions } from "@trigger.dev/core";
export function calculateDeliverAt(options?: SendEventOptions) {
// If deliverAt is a string and a valid date, convert it to a Date object
if (options?.deliverAt) {
return options?.deliverAt;
}
// deliverAfter is the number of seconds to wait before delivering the event
if (options?.deliverAfter) {
return new Date(Date.now() + options.deliverAfter * 1000);
}
return undefined;
}
-71
View File
@@ -1,71 +0,0 @@
import { queueEvent } from "./events/queueEvent";
import { queueEvents } from "./events/queueEvents";
import { applyRateLimit } from "./rateLimit";
import { Ratelimit } from "./rateLimiter";
export interface Env {
/** The hostname needs to be changed to allow requests to pass to the Trigger.dev platform */
REWRITE_HOSTNAME: string;
REWRITE_PORT?: string;
AWS_SQS_ACCESS_KEY_ID: string;
AWS_SQS_SECRET_ACCESS_KEY: string;
AWS_SQS_QUEUE_URL: string;
AWS_SQS_REGION: string;
//rate limiter
API_RATE_LIMITER: Ratelimit;
}
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
if (!env.REWRITE_HOSTNAME) throw new Error("Missing REWRITE_HOSTNAME");
console.log("url", request.url);
if (!queueingIsEnabled(env)) {
console.log("Missing AWS credentials. Passing through to the origin.");
return redirectToOrigin(request, env);
}
const url = new URL(request.url);
switch (url.pathname) {
case "/api/v1/events": {
if (request.method === "POST") {
return applyRateLimit(request, env, () => queueEvent(request, env));
}
break;
}
case "/api/v1/events/bulk": {
if (request.method === "POST") {
return applyRateLimit(request, env, () => queueEvents(request, env));
}
break;
}
}
//the same request but with the hostname (and port) changed
return redirectToOrigin(request, env);
},
};
function redirectToOrigin(request: Request, env: Env) {
const newUrl = new URL(request.url);
newUrl.hostname = env.REWRITE_HOSTNAME;
newUrl.port = env.REWRITE_PORT || newUrl.port;
const requestInit: RequestInit = {
method: request.method,
headers: request.headers,
body: request.body,
};
console.log("rewritten url", newUrl.toString());
return fetch(newUrl.toString(), requestInit);
}
function queueingIsEnabled(env: Env) {
return (
env.AWS_SQS_ACCESS_KEY_ID &&
env.AWS_SQS_SECRET_ACCESS_KEY &&
env.AWS_SQS_QUEUE_URL &&
env.AWS_SQS_REGION
);
}
-13
View File
@@ -1,13 +0,0 @@
export function json(body: any, init?: ResponseInit) {
const headers = {
"content-type": "application/json",
...(init?.headers ?? {}),
};
const responseInit: ResponseInit = {
...(init ?? {}),
headers,
};
return new Response(JSON.stringify(body), responseInit);
}
-46
View File
@@ -1,46 +0,0 @@
import { Env } from "src";
import { getApiKeyFromRequest } from "./apikey";
import { json } from "./json";
export async function applyRateLimit(
request: Request,
env: Env,
fn: () => Promise<Response>
): Promise<Response> {
const apiKey = getApiKeyFromRequest(request);
if (apiKey) {
const result = await env.API_RATE_LIMITER.limit({ key: `apikey-${apiKey.apiKey}` });
const { success } = result;
console.log(`Rate limiter`, {
success,
key: `${apiKey.apiKey.substring(0, 12)}...`,
});
if (!success) {
//60s in the future
const reset = Date.now() + 60 * 1000;
const secondsUntilReset = Math.max(0, (reset - new Date().getTime()) / 1000);
return json(
{
title: "Rate Limit Exceeded",
status: 429,
type: "https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429",
detail: `Rate limit exceeded. Retry in ${secondsUntilReset} seconds.`,
error: `Rate limit exceeded. Retry in ${secondsUntilReset} seconds.`,
reset,
},
{
status: 429,
headers: {
"x-ratelimit-reset": reset.toString(),
},
}
);
}
} else {
console.log(`Rate limiter: no API key for request`);
}
//call the original function
return fn();
}
-23
View File
@@ -1,23 +0,0 @@
export interface Ratelimit {
/*
* The ratelimit function
* @param {RatelimitOptions} options
* @returns {Promise<RatelimitResponse>}
*/
limit: (options: RatelimitOptions) => Promise<RatelimitResponse>;
}
export interface RatelimitOptions {
/*
* The key to identify the user, can be an IP address, user ID, etc.
*/
key: string;
}
export interface RatelimitResponse {
/*
* The ratelimit success status
* @returns {boolean}
*/
success: boolean;
}
-35
View File
@@ -1,35 +0,0 @@
{
"compilerOptions": {
"target": "es2021" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
"lib": [
"es2021"
] /* Specify a set of bundled library declaration files that describe the target runtime environment. */,
"jsx": "react" /* Specify what JSX code is generated. */,
"module": "es2022" /* Specify what module code is generated. */,
"moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */,
"types": [
"@cloudflare/workers-types"
] /* Specify type package names to be included without being referenced in a source file. */,
"resolveJsonModule": true /* Enable importing .json files */,
"allowJs": true /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */,
"checkJs": false /* Enable error reporting in type-checked JavaScript files. */,
"noEmit": true /* Disable emitting files from a compilation. */,
"isolatedModules": true /* Ensure that each file can be safely transpiled without relying on other imports. */,
"allowSyntheticDefaultImports": true /* Allow 'import x from y' when a module doesn't have a default export. */,
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
"strict": true /* Enable all strict type-checking options. */,
"skipLibCheck": true /* Skip type checking all .d.ts files. */,
"baseUrl": ".",
"paths": {
"@trigger.dev/core": ["../../packages/core/src/index"],
"@trigger.dev/core/*": ["../../packages/core/src/*"]
}
}
}
-33
View File
@@ -1,33 +0,0 @@
name = "proxy"
main = "src/index.ts"
compatibility_date = "2024-05-13"
compatibility_flags = [ "nodejs_compat" ]
[env.staging]
# The rate limiting API is in open beta.
[[env.staging.unsafe.bindings]]
name = "API_RATE_LIMITER"
type = "ratelimit"
# An identifier you define, that is unique to your Cloudflare account.
# Must be an integer.
namespace_id = "1"
# Limit: the number of tokens allowed within a given period in a single
# Cloudflare location
# Period: the duration of the period, in seconds. Must be either 10 or 60
simple = { limit = 100, period = 60 }
[env.prod]
# The rate limiting API is in open beta.
[[env.prod.unsafe.bindings]]
name = "API_RATE_LIMITER"
type = "ratelimit"
# An identifier you define, that is unique to your Cloudflare account.
# Must be an integer.
namespace_id = "2"
# Limit: the number of tokens allowed within a given period in a single
# Cloudflare location
# Period: the duration of the period, in seconds. Must be either 10 or 60
simple = { limit = 300, period = 60 }
+18
View File
@@ -0,0 +1,18 @@
# This needs to match the token of the worker group you want to connect to
TRIGGER_WORKER_TOKEN=
# This needs to match the MANAGED_WORKER_SECRET env var on the webapp
MANAGED_WORKER_SECRET=managed-secret
# Point this at the webapp in prod
TRIGGER_API_URL=http://localhost:3030
# Point this at the OTel collector in prod
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:3030/otel
# Use this on macOS
# OTEL_EXPORTER_OTLP_ENDPOINT=http://host.docker.internal:3030/otel
# Optional settings
DEBUG=1
ENFORCE_MACHINE_PRESETS=1
TRIGGER_DEQUEUE_INTERVAL_MS=1000
+1
View File
@@ -0,0 +1 @@
v22.12.0
+53
View File
@@ -0,0 +1,53 @@
FROM node:22-alpine@sha256:9bef0ef1e268f60627da9ba7d7605e8831d5b56ad07487d24d1aa386336d1944 AS node-22-alpine
WORKDIR /app
FROM node-22-alpine AS pruner
COPY --chown=node:node . .
RUN npx -q turbo@1.10.9 prune --scope=supervisor --docker
FROM node-22-alpine AS base
RUN apk add --no-cache dumb-init
COPY --chown=node:node .gitignore .gitignore
COPY --from=pruner --chown=node:node /app/out/json/ .
COPY --from=pruner --chown=node:node /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
COPY --from=pruner --chown=node:node /app/out/pnpm-workspace.yaml ./pnpm-workspace.yaml
RUN corepack enable && corepack prepare --activate
FROM base AS deps-fetcher
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store pnpm fetch --frozen-lockfile
FROM deps-fetcher AS dev-deps
ENV NODE_ENV development
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store pnpm install --frozen-lockfile --offline --ignore-scripts
FROM base AS builder
COPY --from=pruner --chown=node:node /app/out/full/ .
COPY --from=dev-deps --chown=node:node /app/ .
COPY --chown=node:node turbo.json turbo.json
COPY --chown=node:node .configs/tsconfig.base.json .configs/tsconfig.base.json
COPY --chown=node:node scripts/updateVersion.ts scripts/updateVersion.ts
RUN pnpm run generate && \
pnpm run --filter supervisor... build&& \
pnpm deploy --filter=supervisor --prod /prod/supervisor
FROM base AS runner
ENV NODE_ENV production
COPY --from=builder /prod/supervisor /app/apps/supervisor
EXPOSE 8000
USER node
# ensure pnpm is installed during build and not silently downloaded at runtime
RUN pnpm -v
CMD [ "/usr/bin/dumb-init", "--", "pnpm", "run", "--filter", "supervisor", "start"]
+67
View File
@@ -0,0 +1,67 @@
# Supervisor
## Dev setup
1. Create a worker group
```sh
api_url=http://localhost:3030
wg_name=my-worker
# edit this
admin_pat=tr_pat_...
curl -sS \
-X POST \
"$api_url/admin/api/v1/workers" \
-H "Authorization: Bearer $admin_pat" \
-H "Content-Type: application/json" \
-d "{\"name\": \"$wg_name\"}"
```
2. Create `.env` and set the worker token
```sh
cp .env.example .env
# Then edit your .env and set this to the token.plaintext value
TRIGGER_WORKER_TOKEN=tr_wgt_...
```
3. Start the supervisor
```sh
pnpm dev
```
4. Build CLI, then deploy a reference project
```sh
pnpm exec trigger deploy --self-hosted
# The additional network flag is required on linux
pnpm exec trigger deploy --self-hosted --network host
```
## Additional worker groups
When adding more worker groups you might also want to make them the default for a specific project. This will allow you to test it without having to change the global default:
```sh
api_url=http://localhost:3030
wg_name=my-worker
# edit these
admin_pat=tr_pat_...
project_id=clsw6q8wz...
curl -sS \
-X POST \
"$api_url/admin/api/v1/workers" \
-H "Authorization: Bearer $admin_pat" \
-H "Content-Type: application/json" \
-d "{
\"name\": \"$wg_name\",
\"makeDefaultForProjectId\": \"$project_id\"
}"
```
+31
View File
@@ -0,0 +1,31 @@
{
"name": "supervisor",
"private": true,
"version": "0.0.1",
"main": "dist/index.js",
"type": "module",
"scripts": {
"build": "tsc",
"dev": "tsx --require dotenv/config --watch src/index.ts || (echo '!! Remember to run: nvm use'; exit 1)",
"start": "node dist/index.js",
"test:run": "vitest --no-file-parallelism --run",
"test:watch": "vitest --no-file-parallelism",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@kubernetes/client-node": "^1.0.0",
"@trigger.dev/core": "workspace:*",
"dockerode": "^4.0.3",
"nanoid": "^5.0.9",
"prom-client": "^15.1.0",
"socket.io": "4.7.4",
"std-env": "^3.8.0",
"tinyexec": "^0.3.1",
"zod": "3.23.8"
},
"devDependencies": {
"@types/dockerode": "^3.3.33",
"docker-api-ts": "^0.2.2",
"vitest": "^1.4.0"
}
}
+52
View File
@@ -0,0 +1,52 @@
import * as k8s from "@kubernetes/client-node";
import { Informer } from "@kubernetes/client-node";
import { ListPromise } from "@kubernetes/client-node";
import { KubernetesObject } from "@kubernetes/client-node";
import { assertExhaustive } from "@trigger.dev/core/utils";
export const RUNTIME_ENV = process.env.KUBERNETES_PORT ? "kubernetes" : "local";
export function createK8sApi() {
const kubeConfig = getKubeConfig();
function makeInformer<T extends KubernetesObject>(
path: string,
listPromiseFn: ListPromise<T>,
labelSelector?: string,
fieldSelector?: string
): Informer<T> {
return k8s.makeInformer(kubeConfig, path, listPromiseFn, labelSelector, fieldSelector);
}
const api = {
core: kubeConfig.makeApiClient(k8s.CoreV1Api),
batch: kubeConfig.makeApiClient(k8s.BatchV1Api),
apps: kubeConfig.makeApiClient(k8s.AppsV1Api),
makeInformer,
};
return api;
}
export type K8sApi = ReturnType<typeof createK8sApi>;
function getKubeConfig() {
console.log("getKubeConfig()", { RUNTIME_ENV });
const kubeConfig = new k8s.KubeConfig();
switch (RUNTIME_ENV) {
case "local":
kubeConfig.loadFromDefault();
break;
case "kubernetes":
kubeConfig.loadFromCluster();
break;
default:
assertExhaustive(RUNTIME_ENV);
}
return kubeConfig;
}
export { k8s };
+77
View File
@@ -0,0 +1,77 @@
import { randomUUID } from "crypto";
import { env as stdEnv } from "std-env";
import { z } from "zod";
import { AdditionalEnvVars, BoolEnv } from "./envUtil.js";
const Env = z.object({
// This will come from `spec.nodeName` in k8s
TRIGGER_WORKER_INSTANCE_NAME: z.string().default(randomUUID()),
// Required settings
TRIGGER_API_URL: z.string().url(),
TRIGGER_WORKER_TOKEN: z.string(),
MANAGED_WORKER_SECRET: z.string(),
// Workload API settings (coordinator mode) - the workload API is what the run controller connects to
TRIGGER_WORKLOAD_API_ENABLED: BoolEnv.default("true"),
TRIGGER_WORKLOAD_API_PROTOCOL: z
.string()
.transform((s) => z.enum(["http", "https"]).parse(s.toLowerCase()))
.default("http"),
TRIGGER_WORKLOAD_API_DOMAIN: z.string().optional(), // If unset, will use orchestrator-specific default
TRIGGER_WORKLOAD_API_HOST_INTERNAL: z.string().default("0.0.0.0"),
TRIGGER_WORKLOAD_API_PORT_INTERNAL: z.coerce.number().default(8020), // This is the port the workload API listens on
TRIGGER_WORKLOAD_API_PORT_EXTERNAL: z.coerce.number().default(8020), // This is the exposed port passed to the run controller
// Runner settings
RUNNER_HEARTBEAT_INTERVAL_SECONDS: z.coerce.number().optional(),
RUNNER_SNAPSHOT_POLL_INTERVAL_SECONDS: z.coerce.number().optional(),
RUNNER_ADDITIONAL_ENV_VARS: AdditionalEnvVars, // optional (csv)
// Dequeue settings (provider mode)
TRIGGER_DEQUEUE_ENABLED: BoolEnv.default("true"),
TRIGGER_DEQUEUE_INTERVAL_MS: z.coerce.number().int().default(1000),
TRIGGER_DEQUEUE_MAX_RUN_COUNT: z.coerce.number().int().default(10),
// Optional services
TRIGGER_WARM_START_URL: z.string().optional(),
TRIGGER_CHECKPOINT_URL: z.string().optional(),
TRIGGER_METADATA_URL: z.string().optional(),
// Used by the workload manager, e.g docker/k8s
DOCKER_NETWORK: z.string().default("host"),
OTEL_EXPORTER_OTLP_ENDPOINT: z.string().url(),
ENFORCE_MACHINE_PRESETS: z.coerce.boolean().default(false),
KUBERNETES_IMAGE_PULL_SECRETS: z.string().optional(), // csv
// Used by the resource monitor
OVERRIDE_CPU_TOTAL: z.coerce.number().optional(),
OVERRIDE_MEMORY_TOTAL_GB: z.coerce.number().optional(),
// Kubernetes specific settings
KUBERNETES_FORCE_ENABLED: BoolEnv.default(false),
KUBERNETES_NAMESPACE: z.string().default("default"),
KUBERNETES_WORKER_NODETYPE_LABEL: z.string().default("v4-worker"),
EPHEMERAL_STORAGE_SIZE_LIMIT: z.string().default("10Gi"),
EPHEMERAL_STORAGE_SIZE_REQUEST: z.string().default("2Gi"),
// Metrics
METRICS_ENABLED: BoolEnv.default(true),
METRICS_COLLECT_DEFAULTS: BoolEnv.default(true),
METRICS_HOST: z.string().default("127.0.0.1"),
METRICS_PORT: z.coerce.number().int().default(9090),
// Pod cleaner
POD_CLEANER_ENABLED: BoolEnv.default(true),
POD_CLEANER_INTERVAL_MS: z.coerce.number().int().default(10000),
POD_CLEANER_BATCH_SIZE: z.coerce.number().int().default(500),
// Failed pod handler
FAILED_POD_HANDLER_ENABLED: BoolEnv.default(true),
FAILED_POD_HANDLER_RECONNECT_INTERVAL_MS: z.coerce.number().int().default(1000),
// Debug
DEBUG: BoolEnv.default(false),
});
export const env = Env.parse(stdEnv);
+80
View File
@@ -0,0 +1,80 @@
import { describe, it, expect } from "vitest";
import { BoolEnv, AdditionalEnvVars } from "./envUtil.js";
describe("BoolEnv", () => {
it("should parse string 'true' as true", () => {
expect(BoolEnv.parse("true")).toBe(true);
expect(BoolEnv.parse("TRUE")).toBe(true);
expect(BoolEnv.parse("True")).toBe(true);
});
it("should parse string '1' as true", () => {
expect(BoolEnv.parse("1")).toBe(true);
});
it("should parse string 'false' as false", () => {
expect(BoolEnv.parse("false")).toBe(false);
expect(BoolEnv.parse("FALSE")).toBe(false);
expect(BoolEnv.parse("False")).toBe(false);
});
it("should handle whitespace", () => {
expect(BoolEnv.parse(" true ")).toBe(true);
expect(BoolEnv.parse(" 1 ")).toBe(true);
});
it("should pass through boolean values", () => {
expect(BoolEnv.parse(true)).toBe(true);
expect(BoolEnv.parse(false)).toBe(false);
});
it("should return false for invalid inputs", () => {
expect(BoolEnv.parse("invalid")).toBe(false);
expect(BoolEnv.parse("")).toBe(false);
});
});
describe("AdditionalEnvVars", () => {
it("should parse single key-value pair", () => {
expect(AdditionalEnvVars.parse("FOO=bar")).toEqual({ FOO: "bar" });
});
it("should parse multiple key-value pairs", () => {
expect(AdditionalEnvVars.parse("FOO=bar,BAZ=qux")).toEqual({
FOO: "bar",
BAZ: "qux",
});
});
it("should handle whitespace", () => {
expect(AdditionalEnvVars.parse(" FOO = bar , BAZ = qux ")).toEqual({
FOO: "bar",
BAZ: "qux",
});
});
it("should return undefined for empty string", () => {
expect(AdditionalEnvVars.parse("")).toBeUndefined();
});
it("should return undefined for invalid format", () => {
expect(AdditionalEnvVars.parse("invalid")).toBeUndefined();
});
it("should skip invalid pairs but include valid ones", () => {
expect(AdditionalEnvVars.parse("FOO=bar,INVALID,BAZ=qux")).toEqual({
FOO: "bar",
BAZ: "qux",
});
});
it("should pass through undefined", () => {
expect(AdditionalEnvVars.parse(undefined)).toBeUndefined();
});
it("should handle empty values", () => {
expect(AdditionalEnvVars.parse("FOO=,BAR=value")).toEqual({
BAR: "value",
});
});
});
+39
View File
@@ -0,0 +1,39 @@
import { z } from "zod";
export const BoolEnv = z.preprocess((val) => {
if (typeof val !== "string") {
return val;
}
return ["true", "1"].includes(val.toLowerCase().trim());
}, z.boolean());
export const AdditionalEnvVars = z.preprocess((val) => {
if (typeof val !== "string") {
return val;
}
if (!val) {
return undefined;
}
try {
const result = val.split(",").reduce(
(acc, pair) => {
const [key, value] = pair.split("=");
if (!key || !value) {
return acc;
}
acc[key.trim()] = value.trim();
return acc;
},
{} as Record<string, string>
);
// Return undefined if no valid key-value pairs were found
return Object.keys(result).length === 0 ? undefined : result;
} catch (error) {
console.warn("Failed to parse additional env vars", { error, val });
return undefined;
}
}, z.record(z.string(), z.string()).optional());
+363
View File
@@ -0,0 +1,363 @@
import { SupervisorSession } from "@trigger.dev/core/v3/workers";
import { SimpleStructuredLogger } from "@trigger.dev/core/v3/utils/structuredLogger";
import { env } from "./env.js";
import { WorkloadServer } from "./workloadServer/index.js";
import type { WorkloadManagerOptions, WorkloadManager } from "./workloadManager/types.js";
import Docker from "dockerode";
import { z } from "zod";
import { type DequeuedMessage } from "@trigger.dev/core/v3";
import {
DockerResourceMonitor,
KubernetesResourceMonitor,
type ResourceMonitor,
} from "./resourceMonitor.js";
import { KubernetesWorkloadManager } from "./workloadManager/kubernetes.js";
import { DockerWorkloadManager } from "./workloadManager/docker.js";
import {
HttpServer,
CheckpointClient,
isKubernetesEnvironment,
} from "@trigger.dev/core/v3/serverOnly";
import { createK8sApi } from "./clients/kubernetes.js";
import { collectDefaultMetrics } from "prom-client";
import { register } from "./metrics.js";
import { PodCleaner } from "./services/podCleaner.js";
import { FailedPodHandler } from "./services/failedPodHandler.js";
if (env.METRICS_COLLECT_DEFAULTS) {
collectDefaultMetrics({ register });
}
class ManagedSupervisor {
private readonly workerSession: SupervisorSession;
private readonly metricsServer?: HttpServer;
private readonly workloadServer: WorkloadServer;
private readonly workloadManager: WorkloadManager;
private readonly logger = new SimpleStructuredLogger("managed-worker");
private readonly resourceMonitor: ResourceMonitor;
private readonly checkpointClient?: CheckpointClient;
private readonly podCleaner?: PodCleaner;
private readonly failedPodHandler?: FailedPodHandler;
private readonly isKubernetes = isKubernetesEnvironment(env.KUBERNETES_FORCE_ENABLED);
private readonly warmStartUrl = env.TRIGGER_WARM_START_URL;
constructor() {
const { TRIGGER_WORKER_TOKEN, MANAGED_WORKER_SECRET, ...envWithoutSecrets } = env;
if (env.DEBUG) {
console.debug("[ManagedSupervisor] Starting up", { envWithoutSecrets });
}
if (this.warmStartUrl) {
this.logger.log("[ManagedWorker] 🔥 Warm starts enabled", {
warmStartUrl: this.warmStartUrl,
});
}
const workloadManagerOptions = {
workloadApiProtocol: env.TRIGGER_WORKLOAD_API_PROTOCOL,
workloadApiDomain: env.TRIGGER_WORKLOAD_API_DOMAIN,
workloadApiPort: env.TRIGGER_WORKLOAD_API_PORT_EXTERNAL,
warmStartUrl: this.warmStartUrl,
metadataUrl: env.TRIGGER_METADATA_URL,
imagePullSecrets: env.KUBERNETES_IMAGE_PULL_SECRETS?.split(","),
heartbeatIntervalSeconds: env.RUNNER_HEARTBEAT_INTERVAL_SECONDS,
snapshotPollIntervalSeconds: env.RUNNER_SNAPSHOT_POLL_INTERVAL_SECONDS,
additionalEnvVars: env.RUNNER_ADDITIONAL_ENV_VARS,
} satisfies WorkloadManagerOptions;
if (this.isKubernetes) {
if (env.POD_CLEANER_ENABLED) {
this.logger.log("[ManagedWorker] 🧹 Pod cleaner enabled", {
namespace: env.KUBERNETES_NAMESPACE,
batchSize: env.POD_CLEANER_BATCH_SIZE,
intervalMs: env.POD_CLEANER_INTERVAL_MS,
});
this.podCleaner = new PodCleaner({
register,
namespace: env.KUBERNETES_NAMESPACE,
batchSize: env.POD_CLEANER_BATCH_SIZE,
intervalMs: env.POD_CLEANER_INTERVAL_MS,
});
} else {
this.logger.warn("[ManagedWorker] Pod cleaner disabled");
}
if (env.FAILED_POD_HANDLER_ENABLED) {
this.logger.log("[ManagedWorker] 🔁 Failed pod handler enabled", {
namespace: env.KUBERNETES_NAMESPACE,
reconnectIntervalMs: env.FAILED_POD_HANDLER_RECONNECT_INTERVAL_MS,
});
this.failedPodHandler = new FailedPodHandler({
register,
namespace: env.KUBERNETES_NAMESPACE,
reconnectIntervalMs: env.FAILED_POD_HANDLER_RECONNECT_INTERVAL_MS,
});
} else {
this.logger.warn("[ManagedWorker] Failed pod handler disabled");
}
this.resourceMonitor = new KubernetesResourceMonitor(
createK8sApi(),
env.TRIGGER_WORKER_INSTANCE_NAME
);
this.workloadManager = new KubernetesWorkloadManager(workloadManagerOptions);
} else {
this.resourceMonitor = new DockerResourceMonitor(new Docker());
this.workloadManager = new DockerWorkloadManager(workloadManagerOptions);
}
this.workerSession = new SupervisorSession({
workerToken: env.TRIGGER_WORKER_TOKEN,
apiUrl: env.TRIGGER_API_URL,
instanceName: env.TRIGGER_WORKER_INSTANCE_NAME,
managedWorkerSecret: env.MANAGED_WORKER_SECRET,
dequeueIntervalMs: env.TRIGGER_DEQUEUE_INTERVAL_MS,
queueConsumerEnabled: env.TRIGGER_DEQUEUE_ENABLED,
maxRunCount: env.TRIGGER_DEQUEUE_MAX_RUN_COUNT,
runNotificationsEnabled: env.TRIGGER_WORKLOAD_API_ENABLED,
preDequeue: async () => {
if (this.isKubernetes) {
// Not used in k8s for now
return {};
}
const resources = await this.resourceMonitor.getNodeResources();
return {
maxResources: {
cpu: resources.cpuAvailable,
memory: resources.memoryAvailable,
},
skipDequeue: resources.cpuAvailable < 0.25 || resources.memoryAvailable < 0.25,
};
},
preSkip: async () => {
// When the node is full, it should still try to warm start runs
// await this.tryWarmStartAllThisNode();
},
});
if (env.TRIGGER_CHECKPOINT_URL) {
this.logger.log("[ManagedWorker] 🥶 Checkpoints enabled", {
checkpointUrl: env.TRIGGER_CHECKPOINT_URL,
});
this.checkpointClient = new CheckpointClient({
apiUrl: new URL(env.TRIGGER_CHECKPOINT_URL),
workerClient: this.workerSession.httpClient,
orchestrator: this.isKubernetes ? "KUBERNETES" : "DOCKER",
});
}
// setInterval(async () => {
// const resources = await this.resourceMonitor.getNodeResources(true);
// this.logger.debug("[ManagedWorker] Current resources", { resources });
// }, 1000);
this.workerSession.on("runNotification", async ({ time, run }) => {
this.logger.log("[ManagedWorker] runNotification", { time, run });
this.workloadServer.notifyRun({ run });
});
this.workerSession.on("runQueueMessage", async ({ time, message }) => {
this.logger.log(
`[ManagedWorker] Received message with timestamp ${time.toLocaleString()}`,
message
);
if (message.completedWaitpoints.length > 0) {
this.logger.debug("[ManagedWorker] Run has completed waitpoints", {
runId: message.run.id,
completedWaitpoints: message.completedWaitpoints.length,
});
// TODO: Do something with them or if we don't need the data here, maybe we shouldn't even send it
}
if (!message.image) {
this.logger.error("[ManagedWorker] Run has no image", { runId: message.run.id });
return;
}
const { checkpoint, ...rest } = message;
if (checkpoint) {
this.logger.log("[ManagedWorker] Restoring run", { runId: message.run.id });
if (!this.checkpointClient) {
this.logger.error("[ManagedWorker] No checkpoint client", { runId: message.run.id });
return;
}
try {
const didRestore = await this.checkpointClient.restoreRun({
runFriendlyId: message.run.friendlyId,
snapshotFriendlyId: message.snapshot.friendlyId,
body: {
...rest,
checkpoint,
},
});
if (didRestore) {
this.logger.log("[ManagedWorker] Restore successful", { runId: message.run.id });
} else {
this.logger.error("[ManagedWorker] Restore failed", { runId: message.run.id });
}
} catch (error) {
this.logger.error("[ManagedWorker] Failed to restore run", { error });
}
return;
}
this.logger.log("[ManagedWorker] Scheduling run", { runId: message.run.id });
const didWarmStart = await this.tryWarmStart(message);
if (didWarmStart) {
this.logger.log("[ManagedWorker] Warm start successful", { runId: message.run.id });
return;
}
try {
await this.workloadManager.create({
dequeuedAt: message.dequeuedAt,
envId: message.environment.id,
envType: message.environment.type,
image: message.image,
machine: message.run.machine,
orgId: message.organization.id,
projectId: message.project.id,
runId: message.run.id,
runFriendlyId: message.run.friendlyId,
version: message.version,
nextAttemptNumber: message.run.attemptNumber,
snapshotId: message.snapshot.id,
snapshotFriendlyId: message.snapshot.friendlyId,
});
// Disabled for now
// this.resourceMonitor.blockResources({
// cpu: message.run.machine.cpu,
// memory: message.run.machine.memory,
// });
} catch (error) {
this.logger.error("[ManagedWorker] Failed to create workload", { error });
}
});
if (env.METRICS_ENABLED) {
this.metricsServer = new HttpServer({
port: env.METRICS_PORT,
host: env.METRICS_HOST,
metrics: {
register,
expose: true,
},
});
}
// Responds to workload requests only
this.workloadServer = new WorkloadServer({
port: env.TRIGGER_WORKLOAD_API_PORT_INTERNAL,
host: env.TRIGGER_WORKLOAD_API_HOST_INTERNAL,
workerClient: this.workerSession.httpClient,
checkpointClient: this.checkpointClient,
});
this.workloadServer.on("runConnected", this.onRunConnected.bind(this));
this.workloadServer.on("runDisconnected", this.onRunDisconnected.bind(this));
}
async onRunConnected({ run }: { run: { friendlyId: string } }) {
this.logger.debug("[ManagedWorker] Run connected", { run });
this.workerSession.subscribeToRunNotifications([run.friendlyId]);
}
async onRunDisconnected({ run }: { run: { friendlyId: string } }) {
this.logger.debug("[ManagedWorker] Run disconnected", { run });
this.workerSession.unsubscribeFromRunNotifications([run.friendlyId]);
}
private async tryWarmStart(dequeuedMessage: DequeuedMessage): Promise<boolean> {
if (!this.warmStartUrl) {
return false;
}
const warmStartUrlWithPath = new URL("/warm-start", this.warmStartUrl);
try {
const res = await fetch(warmStartUrlWithPath.href, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ dequeuedMessage }),
});
if (!res.ok) {
this.logger.error("[ManagedWorker] Warm start failed", {
runId: dequeuedMessage.run.id,
});
return false;
}
const data = await res.json();
const parsedData = z.object({ didWarmStart: z.boolean() }).safeParse(data);
if (!parsedData.success) {
this.logger.error("[ManagedWorker] Warm start response invalid", {
runId: dequeuedMessage.run.id,
data,
});
return false;
}
return parsedData.data.didWarmStart;
} catch (error) {
this.logger.error("[ManagedWorker] Warm start error", {
runId: dequeuedMessage.run.id,
error,
});
return false;
}
}
async start() {
this.logger.log("[ManagedWorker] Starting up");
// Optional services
await this.podCleaner?.start();
await this.failedPodHandler?.start();
await this.metricsServer?.start();
if (env.TRIGGER_WORKLOAD_API_ENABLED) {
this.logger.log("[ManagedWorker] Workload API enabled", {
protocol: env.TRIGGER_WORKLOAD_API_PROTOCOL,
domain: env.TRIGGER_WORKLOAD_API_DOMAIN,
port: env.TRIGGER_WORKLOAD_API_PORT_INTERNAL,
});
await this.workloadServer.start();
} else {
this.logger.warn("[ManagedWorker] Workload API disabled");
}
await this.workerSession.start();
}
async stop() {
this.logger.log("[ManagedWorker] Shutting down");
await this.workerSession.stop();
// Optional services
await this.podCleaner?.stop();
await this.failedPodHandler?.stop();
await this.metricsServer?.stop();
}
}
const worker = new ManagedSupervisor();
worker.start();
+3
View File
@@ -0,0 +1,3 @@
import { Registry } from "prom-client";
export const register = new Registry();
+246
View File
@@ -0,0 +1,246 @@
import type Docker from "dockerode";
import type * as TDocker from "docker-api-ts";
import type { MachineResources } from "@trigger.dev/core/v3";
import { SimpleStructuredLogger } from "@trigger.dev/core/v3/utils/structuredLogger";
import { env } from "./env.js";
import type { K8sApi } from "./clients/kubernetes.js";
const logger = new SimpleStructuredLogger("resource-monitor");
interface NodeResources {
cpuTotal: number; // in cores
cpuAvailable: number;
memoryTotal: number; // in bytes
memoryAvailable: number;
}
interface ResourceRequest {
cpu: number; // in cores
memory: number; // in bytes
}
export abstract class ResourceMonitor {
protected cacheTimeoutMs = 5_000;
protected lastUpdateMs = 0;
protected cachedResources: NodeResources = {
cpuTotal: 0,
cpuAvailable: 0,
memoryTotal: 0,
memoryAvailable: 0,
};
protected resourceParser: ResourceParser;
constructor(Parser: new () => ResourceParser) {
this.resourceParser = new Parser();
}
abstract getNodeResources(fromCache?: boolean): Promise<NodeResources>;
blockResources(resources: MachineResources): void {
const { cpu, memory } = this.toResourceRequest(resources);
logger.debug("[ResourceMonitor] Blocking resources", {
raw: resources,
converted: { cpu, memory },
});
this.cachedResources.cpuAvailable -= cpu;
this.cachedResources.memoryAvailable -= memory;
}
async wouldFit(request: ResourceRequest): Promise<boolean> {
const resources = await this.getNodeResources();
return resources.cpuAvailable >= request.cpu && resources.memoryAvailable >= request.memory;
}
private toResourceRequest(resources: MachineResources): ResourceRequest {
return {
cpu: resources.cpu ?? 0,
memory: this.gbToBytes(resources.memory ?? 0),
};
}
private gbToBytes(gb: number): number {
return gb * 1024 * 1024 * 1024;
}
protected isCacheValid(): boolean {
return this.cachedResources !== null && Date.now() - this.lastUpdateMs < this.cacheTimeoutMs;
}
protected applyOverrides(resources: NodeResources): NodeResources {
if (!env.OVERRIDE_CPU_TOTAL && !env.OVERRIDE_MEMORY_TOTAL_GB) {
return resources;
}
logger.debug("[ResourceMonitor] 🛡️ Applying resource overrides", {
cpuTotal: env.OVERRIDE_CPU_TOTAL,
memoryTotalGb: env.OVERRIDE_MEMORY_TOTAL_GB,
});
const cpuTotal = env.OVERRIDE_CPU_TOTAL ?? resources.cpuTotal;
const memoryTotal = env.OVERRIDE_MEMORY_TOTAL_GB
? this.gbToBytes(env.OVERRIDE_MEMORY_TOTAL_GB)
: resources.memoryTotal;
const cpuDiff = cpuTotal - resources.cpuTotal;
const memoryDiff = memoryTotal - resources.memoryTotal;
const cpuAvailable = Math.max(0, resources.cpuAvailable + cpuDiff);
const memoryAvailable = Math.max(0, resources.memoryAvailable + memoryDiff);
return {
cpuTotal,
cpuAvailable,
memoryTotal,
memoryAvailable,
};
}
}
export class DockerResourceMonitor extends ResourceMonitor {
private docker: Docker;
constructor(docker: Docker) {
super(DockerResourceParser);
this.docker = docker;
}
async getNodeResources(fromCache?: boolean): Promise<NodeResources> {
if (this.isCacheValid() || fromCache) {
// logger.debug("[ResourceMonitor] Using cached resources");
return this.cachedResources;
}
const info: TDocker.SystemInfo = await this.docker.info();
const stats = await this.docker.listContainers({ all: true });
// Get system-wide resources
const cpuTotal = info.NCPU ?? 0;
const memoryTotal = info.MemTotal ?? 0;
// Calculate used resources from running containers
let cpuUsed = 0;
let memoryUsed = 0;
for (const container of stats) {
if (container.State === "running") {
const c = this.docker.getContainer(container.Id);
const { HostConfig } = await c.inspect();
const cpu = this.resourceParser.cpu(HostConfig.NanoCpus ?? 0);
const memory = this.resourceParser.memory(HostConfig.Memory ?? 0);
cpuUsed += cpu;
memoryUsed += memory;
}
}
this.cachedResources = this.applyOverrides({
cpuTotal,
cpuAvailable: cpuTotal - cpuUsed,
memoryTotal,
memoryAvailable: memoryTotal - memoryUsed,
});
this.lastUpdateMs = Date.now();
return this.cachedResources;
}
}
export class KubernetesResourceMonitor extends ResourceMonitor {
private k8s: K8sApi;
private nodeName: string;
constructor(k8s: K8sApi, nodeName: string) {
super(KubernetesResourceParser);
this.k8s = k8s;
this.nodeName = nodeName;
}
async getNodeResources(fromCache?: boolean): Promise<NodeResources> {
if (this.isCacheValid() || fromCache) {
logger.debug("[ResourceMonitor] Using cached resources");
return this.cachedResources;
}
const node = await this.k8s.core.readNode({ name: this.nodeName });
const pods = await this.k8s.core.listPodForAllNamespaces({
// TODO: ensure this includes all pods that consume resources
fieldSelector: `spec.nodeName=${this.nodeName},status.phase=Running`,
});
const allocatable = node.status?.allocatable;
const cpuTotal = this.resourceParser.cpu(allocatable?.cpu ?? "0");
const memoryTotal = this.resourceParser.memory(allocatable?.memory ?? "0");
// Sum up resources requested by all pods on this node
let cpuRequested = 0;
let memoryRequested = 0;
for (const pod of pods.items) {
if (pod.status?.phase === "Running") {
if (!pod.spec) {
continue;
}
for (const container of pod.spec.containers) {
const resources = container.resources?.requests ?? {};
cpuRequested += this.resourceParser.cpu(resources.cpu ?? "0");
memoryRequested += this.resourceParser.memory(resources.memory ?? "0");
}
}
}
this.cachedResources = this.applyOverrides({
cpuTotal,
cpuAvailable: cpuTotal - cpuRequested,
memoryTotal,
memoryAvailable: memoryTotal - memoryRequested,
});
this.lastUpdateMs = Date.now();
return this.cachedResources;
}
}
abstract class ResourceParser {
abstract cpu(cpu: number | string): number;
abstract memory(memory: number | string): number;
}
class DockerResourceParser extends ResourceParser {
cpu(cpu: number): number {
return cpu / 1e9;
}
memory(memory: number): number {
return memory;
}
}
class KubernetesResourceParser extends ResourceParser {
cpu(cpu: string): number {
if (cpu.endsWith("m")) {
return parseInt(cpu.slice(0, -1)) / 1000;
}
return parseInt(cpu);
}
memory(memory: string): number {
if (memory.endsWith("Ki")) {
return parseInt(memory.slice(0, -2)) * 1024;
}
if (memory.endsWith("Mi")) {
return parseInt(memory.slice(0, -2)) * 1024 * 1024;
}
if (memory.endsWith("Gi")) {
return parseInt(memory.slice(0, -2)) * 1024 * 1024 * 1024;
}
return parseInt(memory);
}
}
@@ -0,0 +1,580 @@
import { describe, it, expect, beforeAll, afterEach } from "vitest";
import { FailedPodHandler } from "./failedPodHandler.js";
import { K8sApi, createK8sApi } from "../clients/kubernetes.js";
import { Registry } from "prom-client";
import { setTimeout } from "timers/promises";
describe("FailedPodHandler Integration Tests", () => {
const k8s = createK8sApi();
const namespace = "integration-test";
const register = new Registry();
beforeAll(async () => {
// Create the test namespace if it doesn't exist
try {
await k8s.core.readNamespace({ name: namespace });
} catch (error) {
await k8s.core.createNamespace({
body: {
metadata: {
name: namespace,
},
},
});
}
// Clear any existing pods in the namespace
await deleteAllPodsInNamespace({ k8sApi: k8s, namespace });
});
afterEach(async () => {
// Clear metrics to avoid conflicts
register.clear();
// Delete any remaining pods in the namespace
await deleteAllPodsInNamespace({ k8sApi: k8s, namespace });
});
it("should process and delete failed pods with app=task-run label", async () => {
const handler = new FailedPodHandler({ namespace, k8s, register });
try {
// Create failed pods with the correct label
const podNames = await createTestPods({
k8sApi: k8s,
namespace,
count: 2,
shouldFail: true,
});
// Wait for pods to reach Failed state
await waitForPodsPhase({
k8sApi: k8s,
namespace,
podNames,
phase: "Failed",
});
// Start the handler
await handler.start();
// Wait for pods to be deleted
await waitForPodsDeletion({
k8sApi: k8s,
namespace,
podNames,
});
// Verify metrics
const metrics = handler.getMetrics();
// Check informer events were recorded
const informerEvents = await metrics.informerEventsTotal.get();
expect(informerEvents.values).toContainEqual(
expect.objectContaining({
labels: expect.objectContaining({
namespace,
verb: "add",
}),
value: 2,
})
);
expect(informerEvents.values).toContainEqual(
expect.objectContaining({
labels: expect.objectContaining({
namespace,
verb: "connect",
}),
value: 1,
})
);
expect(informerEvents.values).not.toContainEqual(
expect.objectContaining({
labels: expect.objectContaining({
namespace,
verb: "error",
}),
})
);
// Check pods were processed
const processedPods = await metrics.processedPodsTotal.get();
expect(processedPods.values).toContainEqual(
expect.objectContaining({
labels: expect.objectContaining({
namespace,
status: "Failed",
}),
value: 2,
})
);
// Check pods were deleted
const deletedPods = await metrics.deletedPodsTotal.get();
expect(deletedPods.values).toContainEqual(
expect.objectContaining({
labels: expect.objectContaining({
namespace,
status: "Failed",
}),
value: 2,
})
);
// Check no deletion errors were recorded
const deletionErrors = await metrics.deletionErrorsTotal.get();
expect(deletionErrors.values).toHaveLength(0);
// Check processing durations were recorded
const durations = await metrics.processingDurationSeconds.get();
const failedDurations = durations.values.filter(
(v) => v.labels.namespace === namespace && v.labels.status === "Failed"
);
expect(failedDurations.length).toBeGreaterThan(0);
} finally {
await handler.stop();
}
}, 30000);
it("should ignore pods without app=task-run label", async () => {
const handler = new FailedPodHandler({ namespace, k8s, register });
try {
// Create failed pods without the task-run label
const podNames = await createTestPods({
k8sApi: k8s,
namespace,
count: 1,
shouldFail: true,
labels: { app: "not-task-run" },
});
// Wait for pod to reach Failed state
await waitForPodsPhase({
k8sApi: k8s,
namespace,
podNames,
phase: "Failed",
});
await handler.start();
// Wait a reasonable time to ensure pod isn't deleted
await setTimeout(5000);
// Verify pod still exists
const exists = await podExists({ k8sApi: k8s, namespace, podName: podNames[0]! });
expect(exists).toBe(true);
// Verify no metrics were recorded
const metrics = handler.getMetrics();
const processedPods = await metrics.processedPodsTotal.get();
expect(processedPods.values).toHaveLength(0);
} finally {
await handler.stop();
}
}, 30000);
it("should not process pods that are being deleted", async () => {
const handler = new FailedPodHandler({ namespace, k8s, register });
try {
// Create a failed pod that we'll mark for deletion
const podNames = await createTestPods({
k8sApi: k8s,
namespace,
count: 1,
shouldFail: true,
command: ["/bin/sh", "-c", "sleep 30"],
});
// Wait for pod to reach Failed state
await waitForPodsPhase({
k8sApi: k8s,
namespace,
podNames,
phase: "Running",
});
// Delete the pod but don't wait for deletion
await k8s.core.deleteNamespacedPod({
namespace,
name: podNames[0]!,
gracePeriodSeconds: 5,
});
// Start the handler
await handler.start();
// Wait for pod to be fully deleted
await waitForPodsDeletion({
k8sApi: k8s,
namespace,
podNames,
});
// Verify metrics show we skipped processing
const metrics = handler.getMetrics();
const processedPods = await metrics.processedPodsTotal.get();
expect(processedPods.values).toHaveLength(0);
} finally {
await handler.stop();
}
}, 30000);
it("should detect and process pods that fail after handler starts", async () => {
const handler = new FailedPodHandler({ namespace, k8s, register });
try {
// Start the handler
await handler.start();
// Create failed pods with the correct label
const podNames = await createTestPods({
k8sApi: k8s,
namespace,
count: 3,
shouldFail: true,
});
// Wait for pods to be deleted
await waitForPodsDeletion({
k8sApi: k8s,
namespace,
podNames,
});
// Verify metrics
const metrics = handler.getMetrics();
// Check informer events were recorded
const informerEvents = await metrics.informerEventsTotal.get();
expect(informerEvents.values).toContainEqual(
expect.objectContaining({
labels: expect.objectContaining({
namespace,
verb: "add",
}),
value: 3,
})
);
expect(informerEvents.values).toContainEqual(
expect.objectContaining({
labels: expect.objectContaining({
namespace,
verb: "connect",
}),
value: 1,
})
);
expect(informerEvents.values).not.toContainEqual(
expect.objectContaining({
labels: expect.objectContaining({
namespace,
verb: "error",
}),
})
);
// Check pods were processed
const processedPods = await metrics.processedPodsTotal.get();
expect(processedPods.values).toContainEqual(
expect.objectContaining({
labels: expect.objectContaining({
namespace,
status: "Failed",
}),
value: 3,
})
);
// Check pods were deleted
const deletedPods = await metrics.deletedPodsTotal.get();
expect(deletedPods.values).toContainEqual(
expect.objectContaining({
labels: expect.objectContaining({
namespace,
status: "Failed",
}),
value: 3,
})
);
// Check no deletion errors were recorded
const deletionErrors = await metrics.deletionErrorsTotal.get();
expect(deletionErrors.values).toHaveLength(0);
// Check processing durations were recorded
const durations = await metrics.processingDurationSeconds.get();
const failedDurations = durations.values.filter(
(v) => v.labels.namespace === namespace && v.labels.status === "Failed"
);
expect(failedDurations.length).toBeGreaterThan(0);
} finally {
await handler.stop();
}
}, 60000);
it("should handle graceful shutdown pods differently", async () => {
const handler = new FailedPodHandler({ namespace, k8s, register });
try {
// Create first batch of pods before starting handler
const firstBatchPodNames = await createTestPods({
k8sApi: k8s,
namespace,
count: 2,
exitCode: FailedPodHandler.GRACEFUL_SHUTDOWN_EXIT_CODE,
});
// Wait for pods to reach Failed state
await waitForPodsPhase({
k8sApi: k8s,
namespace,
podNames: firstBatchPodNames,
phase: "Failed",
});
// Start the handler
await handler.start();
// Wait for first batch to be deleted
await waitForPodsDeletion({
k8sApi: k8s,
namespace,
podNames: firstBatchPodNames,
});
// Create second batch of pods after handler is running
const secondBatchPodNames = await createTestPods({
k8sApi: k8s,
namespace,
count: 3,
exitCode: FailedPodHandler.GRACEFUL_SHUTDOWN_EXIT_CODE,
});
// Wait for second batch to be deleted
await waitForPodsDeletion({
k8sApi: k8s,
namespace,
podNames: secondBatchPodNames,
});
// Verify metrics
const metrics = handler.getMetrics();
// Check informer events were recorded for both batches
const informerEvents = await metrics.informerEventsTotal.get();
expect(informerEvents.values).toContainEqual(
expect.objectContaining({
labels: expect.objectContaining({
namespace,
verb: "add",
}),
value: 5, // 2 from first batch + 3 from second batch
})
);
// Check pods were processed as graceful shutdowns
const processedPods = await metrics.processedPodsTotal.get();
// Should not be marked as Failed
const failedPods = processedPods.values.find(
(v) => v.labels.namespace === namespace && v.labels.status === "Failed"
);
expect(failedPods).toBeUndefined();
// Should be marked as GracefulShutdown
const gracefulShutdowns = processedPods.values.find(
(v) => v.labels.namespace === namespace && v.labels.status === "GracefulShutdown"
);
expect(gracefulShutdowns).toBeDefined();
expect(gracefulShutdowns?.value).toBe(5); // Total from both batches
// Check pods were still deleted
const deletedPods = await metrics.deletedPodsTotal.get();
expect(deletedPods.values).toContainEqual(
expect.objectContaining({
labels: expect.objectContaining({
namespace,
status: "Failed",
}),
value: 5, // Total from both batches
})
);
// Check no deletion errors were recorded
const deletionErrors = await metrics.deletionErrorsTotal.get();
expect(deletionErrors.values).toHaveLength(0);
} finally {
await handler.stop();
}
}, 30000);
});
async function createTestPods({
k8sApi,
namespace,
count,
labels = { app: "task-run" },
shouldFail = false,
namePrefix = "test-pod",
command = ["/bin/sh", "-c", shouldFail ? "exit 1" : "exit 0"],
randomizeName = true,
exitCode,
}: {
k8sApi: K8sApi;
namespace: string;
count: number;
labels?: Record<string, string>;
shouldFail?: boolean;
namePrefix?: string;
command?: string[];
randomizeName?: boolean;
exitCode?: number;
}) {
const createdPods: string[] = [];
// If exitCode is specified, override the command
if (exitCode !== undefined) {
command = ["/bin/sh", "-c", `exit ${exitCode}`];
}
for (let i = 0; i < count; i++) {
const podName = randomizeName
? `${namePrefix}-${i}-${Math.random().toString(36).substring(2, 15)}`
: `${namePrefix}-${i}`;
await k8sApi.core.createNamespacedPod({
namespace,
body: {
metadata: {
name: podName,
labels,
},
spec: {
restartPolicy: "Never",
containers: [
{
name: "run-controller", // Changed to match the name we check in failedPodHandler
image: "busybox:1.37.0",
command,
},
],
},
},
});
createdPods.push(podName);
}
return createdPods;
}
async function waitForPodsDeletion({
k8sApi,
namespace,
podNames,
timeoutMs = 10000,
waitMs = 1000,
}: {
k8sApi: K8sApi;
namespace: string;
podNames: string[];
timeoutMs?: number;
waitMs?: number;
}) {
const startTime = Date.now();
const pendingPods = new Set(podNames);
while (pendingPods.size > 0 && Date.now() - startTime < timeoutMs) {
const pods = await k8sApi.core.listNamespacedPod({ namespace });
const existingPods = new Set(pods.items.map((pod) => pod.metadata?.name ?? ""));
for (const podName of pendingPods) {
if (!existingPods.has(podName)) {
pendingPods.delete(podName);
}
}
if (pendingPods.size > 0) {
await setTimeout(waitMs);
}
}
if (pendingPods.size > 0) {
throw new Error(
`Pods [${Array.from(pendingPods).join(", ")}] were not deleted within ${timeoutMs}ms`
);
}
}
async function podExists({
k8sApi,
namespace,
podName,
}: {
k8sApi: K8sApi;
namespace: string;
podName: string;
}) {
const pods = await k8sApi.core.listNamespacedPod({ namespace });
return pods.items.some((p) => p.metadata?.name === podName);
}
async function waitForPodsPhase({
k8sApi,
namespace,
podNames,
phase,
timeoutMs = 10000,
waitMs = 1000,
}: {
k8sApi: K8sApi;
namespace: string;
podNames: string[];
phase: "Pending" | "Running" | "Succeeded" | "Failed" | "Unknown";
timeoutMs?: number;
waitMs?: number;
}) {
const startTime = Date.now();
const pendingPods = new Set(podNames);
while (pendingPods.size > 0 && Date.now() - startTime < timeoutMs) {
const pods = await k8sApi.core.listNamespacedPod({ namespace });
for (const pod of pods.items) {
if (pendingPods.has(pod.metadata?.name ?? "") && pod.status?.phase === phase) {
pendingPods.delete(pod.metadata?.name ?? "");
}
}
if (pendingPods.size > 0) {
await setTimeout(waitMs);
}
}
if (pendingPods.size > 0) {
throw new Error(
`Pods [${Array.from(pendingPods).join(
", "
)}] did not reach phase ${phase} within ${timeoutMs}ms`
);
}
}
async function deleteAllPodsInNamespace({
k8sApi,
namespace,
}: {
k8sApi: K8sApi;
namespace: string;
}) {
// Get all pods
const pods = await k8sApi.core.listNamespacedPod({ namespace });
const podNames = pods.items.map((p) => p.metadata?.name ?? "");
// Delete all pods
await k8sApi.core.deleteCollectionNamespacedPod({ namespace, gracePeriodSeconds: 0 });
// Wait for all pods to be deleted
await waitForPodsDeletion({ k8sApi, namespace, podNames });
}
@@ -0,0 +1,298 @@
import { LogLevel, SimpleStructuredLogger } from "@trigger.dev/core/v3/utils/structuredLogger";
import { K8sApi } from "../clients/kubernetes.js";
import { createK8sApi } from "../clients/kubernetes.js";
import { Informer, V1Pod } from "@kubernetes/client-node";
import { Counter, Registry, Histogram } from "prom-client";
import { register } from "../metrics.js";
import { setTimeout } from "timers/promises";
type PodStatus = "Pending" | "Running" | "Succeeded" | "Failed" | "Unknown" | "GracefulShutdown";
export type FailedPodHandlerOptions = {
namespace: string;
reconnectIntervalMs?: number;
k8s?: K8sApi;
register?: Registry;
};
export class FailedPodHandler {
private readonly id: string;
private readonly logger: SimpleStructuredLogger;
private readonly k8s: K8sApi;
private readonly namespace: string;
private isRunning = false;
private readonly informer: Informer<V1Pod>;
private readonly reconnectIntervalMs: number;
// Metrics
private readonly register: Registry;
private readonly processedPodsTotal: Counter;
private readonly deletedPodsTotal: Counter;
private readonly deletionErrorsTotal: Counter;
private readonly processingDurationSeconds: Histogram<string>;
private readonly informerEventsTotal: Counter;
static readonly GRACEFUL_SHUTDOWN_EXIT_CODE = 200;
constructor(opts: FailedPodHandlerOptions) {
this.id = Math.random().toString(36).substring(2, 15);
this.logger = new SimpleStructuredLogger("failed-pod-handler", LogLevel.debug, {
id: this.id,
});
this.k8s = opts.k8s ?? createK8sApi();
this.namespace = opts.namespace;
this.reconnectIntervalMs = opts.reconnectIntervalMs ?? 1000;
this.informer = this.k8s.makeInformer(
`/api/v1/namespaces/${this.namespace}/pods`,
() =>
this.k8s.core.listNamespacedPod({
namespace: this.namespace,
labelSelector: "app=task-run",
fieldSelector: "status.phase=Failed",
}),
"app=task-run",
"status.phase=Failed"
);
// Whenever a matching pod is added to the informer cache
this.informer.on("add", this.onPodCompleted.bind(this));
// Informer events
this.informer.on("connect", this.makeOnConnect("failed-pod-informer").bind(this));
this.informer.on("error", this.makeOnError("failed-pod-informer").bind(this));
// Initialize metrics
this.register = opts.register ?? register;
this.processedPodsTotal = new Counter({
name: "failed_pod_handler_processed_pods_total",
help: "Total number of failed pods processed",
labelNames: ["namespace", "status"],
registers: [this.register],
});
this.deletedPodsTotal = new Counter({
name: "failed_pod_handler_deleted_pods_total",
help: "Total number of pods deleted",
labelNames: ["namespace", "status"],
registers: [this.register],
});
this.deletionErrorsTotal = new Counter({
name: "failed_pod_handler_deletion_errors_total",
help: "Total number of errors encountered while deleting pods",
labelNames: ["namespace", "error_type"],
registers: [this.register],
});
this.processingDurationSeconds = new Histogram({
name: "failed_pod_handler_processing_duration_seconds",
help: "The duration of pod processing",
labelNames: ["namespace", "status"],
registers: [this.register],
});
this.informerEventsTotal = new Counter({
name: "failed_pod_handler_informer_events_total",
help: "Total number of informer events",
labelNames: ["namespace", "verb"],
registers: [this.register],
});
}
async start() {
if (this.isRunning) {
this.logger.warn("failed pod handler already running");
return;
}
this.isRunning = true;
this.logger.info("starting failed pod handler");
await this.informer.start();
}
async stop() {
if (!this.isRunning) {
this.logger.warn("failed pod handler not running");
return;
}
this.isRunning = false;
this.logger.info("stopping failed pod handler");
await this.informer.stop();
}
private async withHistogram<T>(
histogram: Histogram<string>,
promise: Promise<T>,
labels?: Record<string, string>
): Promise<T> {
const end = histogram.startTimer({ namespace: this.namespace, ...labels });
try {
return await promise;
} finally {
end();
}
}
/**
* Returns the non-nullable status of a pod
*/
private podStatus(pod: V1Pod): PodStatus {
return (pod.status?.phase ?? "Unknown") as PodStatus;
}
private async onPodCompleted(pod: V1Pod) {
this.logger.info("pod-completed", this.podSummary(pod));
this.informerEventsTotal.inc({ namespace: this.namespace, verb: "add" });
if (!pod.metadata?.name) {
this.logger.error("pod-completed: no name", this.podSummary(pod));
return;
}
if (!pod.status) {
this.logger.error("pod-completed: no status", this.podSummary(pod));
return;
}
if (pod.metadata?.deletionTimestamp) {
this.logger.info("pod-completed: pod is being deleted", this.podSummary(pod));
return;
}
const podStatus = this.podStatus(pod);
switch (podStatus) {
case "Succeeded":
await this.withHistogram(this.processingDurationSeconds, this.onPodSucceeded(pod), {
status: podStatus,
});
break;
case "Failed":
await this.withHistogram(this.processingDurationSeconds, this.onPodFailed(pod), {
status: podStatus,
});
break;
default:
this.logger.error("pod-completed: unknown phase", this.podSummary(pod));
}
}
private async onPodSucceeded(pod: V1Pod) {
this.logger.info("pod-succeeded", this.podSummary(pod));
this.processedPodsTotal.inc({
namespace: this.namespace,
status: this.podStatus(pod),
});
}
private async onPodFailed(pod: V1Pod) {
this.logger.info("pod-failed", this.podSummary(pod));
try {
await this.processFailedPod(pod);
} catch (error) {
this.logger.error("pod-failed: error processing pod", this.podSummary(pod), { error });
} finally {
await this.deletePod(pod);
}
}
private async processFailedPod(pod: V1Pod) {
this.logger.info("pod-failed: processing pod", this.podSummary(pod));
const mainContainer = pod.status?.containerStatuses?.find((c) => c.name === "run-controller");
// If it's our special "graceful shutdown" exit code, don't process it further, just delete it
if (
mainContainer?.state?.terminated?.exitCode === FailedPodHandler.GRACEFUL_SHUTDOWN_EXIT_CODE
) {
this.logger.debug("pod-failed: graceful shutdown detected", this.podSummary(pod));
this.processedPodsTotal.inc({
namespace: this.namespace,
status: "GracefulShutdown",
});
return;
}
this.processedPodsTotal.inc({
namespace: this.namespace,
status: this.podStatus(pod),
});
}
private async deletePod(pod: V1Pod) {
this.logger.info("pod-failed: deleting pod", this.podSummary(pod));
try {
await this.k8s.core.deleteNamespacedPod({
name: pod.metadata!.name!,
namespace: this.namespace,
});
this.deletedPodsTotal.inc({
namespace: this.namespace,
status: this.podStatus(pod),
});
} catch (error) {
this.logger.error("pod-failed: error deleting pod", this.podSummary(pod), { error });
this.deletionErrorsTotal.inc({
namespace: this.namespace,
error_type: error instanceof Error ? error.name : "unknown",
});
}
}
private makeOnError(informerName: string) {
return () => this.onError(informerName);
}
private async onError(informerName: string) {
if (!this.isRunning) {
this.logger.warn("onError: informer not running");
return;
}
this.logger.error("error event fired", { informerName });
this.informerEventsTotal.inc({ namespace: this.namespace, verb: "error" });
// Reconnect on errors
await setTimeout(this.reconnectIntervalMs);
await this.informer.start();
}
private makeOnConnect(informerName: string) {
return () => this.onConnect(informerName);
}
private async onConnect(informerName: string) {
this.logger.info(`informer connected: ${informerName}`);
this.informerEventsTotal.inc({ namespace: this.namespace, verb: "connect" });
}
private podSummary(pod: V1Pod) {
return {
name: pod.metadata?.name,
namespace: pod.metadata?.namespace,
status: pod.status?.phase,
deletionTimestamp: pod.metadata?.deletionTimestamp,
};
}
// Method to expose metrics for testing
public getMetrics() {
return {
processedPodsTotal: this.processedPodsTotal,
deletedPodsTotal: this.deletedPodsTotal,
deletionErrorsTotal: this.deletionErrorsTotal,
informerEventsTotal: this.informerEventsTotal,
processingDurationSeconds: this.processingDurationSeconds,
};
}
}
@@ -0,0 +1,472 @@
import { PodCleaner } from "./podCleaner.js";
import { K8sApi, createK8sApi } from "../clients/kubernetes.js";
import { setTimeout } from "timers/promises";
import { describe, it, expect, beforeAll, afterEach } from "vitest";
import { Registry } from "prom-client";
describe("PodCleaner Integration Tests", () => {
const k8s = createK8sApi();
const namespace = "integration-test";
const register = new Registry();
beforeAll(async () => {
// Create the test namespace, only if it doesn't exist
try {
await k8s.core.readNamespace({ name: namespace });
} catch (error) {
await k8s.core.createNamespace({
body: {
metadata: {
name: namespace,
},
},
});
}
});
afterEach(async () => {
// Clear metrics to avoid conflicts
register.clear();
// Delete all pods in the namespace
await k8s.core.deleteCollectionNamespacedPod({ namespace, gracePeriodSeconds: 0 });
});
it("should clean up succeeded pods", async () => {
const podCleaner = new PodCleaner({ namespace, k8s, register });
try {
// Create a test pod that's in succeeded state
const podNames = await createTestPods({
k8sApi: k8s,
namespace,
count: 1,
namePrefix: "test-succeeded-pod",
});
if (!podNames[0]) {
throw new Error("Failed to create test pod");
}
const podName = podNames[0];
// Wait for pod to complete
await waitForPodPhase({
k8sApi: k8s,
namespace,
podName,
phase: "Succeeded",
});
// Start the pod cleaner
await podCleaner.start();
// Wait for pod to be deleted
await waitForPodDeletion({
k8sApi: k8s,
namespace,
podName,
});
// Verify pod was deleted
expect(await podExists({ k8sApi: k8s, namespace, podName })).toBe(false);
} finally {
await podCleaner.stop();
}
}, 30000);
it("should accurately track deletion metrics", async () => {
const podCleaner = new PodCleaner({ namespace, k8s, register });
try {
// Create a test pod that's in succeeded state
const podNames = await createTestPods({
k8sApi: k8s,
namespace,
count: 1,
namePrefix: "test-succeeded-pod",
});
// Wait for pod to be in succeeded state
await waitForPodsPhase({
k8sApi: k8s,
namespace,
podNames,
phase: "Succeeded",
});
await podCleaner.start();
// Wait for pod to be deleted
await waitForPodsDeletion({
k8sApi: k8s,
namespace,
podNames,
});
const metrics = podCleaner.getMetrics();
const deletionCycles = await metrics.deletionCyclesTotal.get();
const deletionTimestamp = await metrics.lastDeletionTimestamp.get();
expect(deletionCycles?.values[0]?.value).toBeGreaterThan(0);
expect(deletionTimestamp?.values[0]?.value).toBeGreaterThan(0);
} finally {
await podCleaner.stop();
}
}, 30000);
it("should handle different batch sizes - small", async () => {
const podCleaner = new PodCleaner({
namespace,
k8s,
register,
batchSize: 1,
});
try {
// Create some pods that will succeed
const podNames = await createTestPods({
k8sApi: k8s,
namespace,
count: 2,
});
await waitForPodsPhase({
k8sApi: k8s,
namespace,
podNames,
phase: "Succeeded",
});
await podCleaner.start();
await waitForPodsDeletion({
k8sApi: k8s,
namespace,
podNames,
});
const metrics = podCleaner.getMetrics();
const cycles = await metrics.deletionCyclesTotal.get();
expect(cycles?.values[0]?.value).toBe(2);
} finally {
await podCleaner.stop();
}
}, 30000);
it("should handle different batch sizes - large", async () => {
const podCleaner = new PodCleaner({
namespace,
k8s,
register,
batchSize: 5000,
});
try {
// Create some pods that will succeed
const podNames = await createTestPods({
k8sApi: k8s,
namespace,
count: 10,
});
await waitForPodsPhase({
k8sApi: k8s,
namespace,
podNames,
phase: "Succeeded",
});
await podCleaner.start();
await waitForPodsDeletion({
k8sApi: k8s,
namespace,
podNames,
});
const metrics = podCleaner.getMetrics();
const cycles = await metrics.deletionCyclesTotal.get();
expect(cycles?.values[0]?.value).toBe(1);
} finally {
await podCleaner.stop();
}
}, 30000);
it("should not delete pods without app=task-run label", async () => {
const podCleaner = new PodCleaner({ namespace, k8s, register });
try {
// Create a test pod without the task-run label
const podNames = await createTestPods({
k8sApi: k8s,
namespace,
count: 1,
labels: { app: "different-label" },
namePrefix: "non-task-run-pod",
});
if (!podNames[0]) {
throw new Error("Failed to create test pod");
}
const podName = podNames[0];
// Wait for pod to complete
await waitForPodPhase({
k8sApi: k8s,
namespace,
podName,
phase: "Succeeded",
});
await podCleaner.start();
// Wait a reasonable time to ensure pod isn't deleted
await setTimeout(5000);
// Verify pod still exists
expect(await podExists({ k8sApi: k8s, namespace, podName })).toBe(true);
} finally {
await podCleaner.stop();
}
}, 30000);
it("should not delete pods that are still running", async () => {
const podCleaner = new PodCleaner({ namespace, k8s, register });
try {
// Create a test pod with a long-running command
const podNames = await createTestPods({
k8sApi: k8s,
namespace,
count: 1,
namePrefix: "running-pod",
command: ["sleep", "30"], // Will keep pod running
});
if (!podNames[0]) {
throw new Error("Failed to create test pod");
}
const podName = podNames[0];
// Wait for pod to be running
await waitForPodPhase({
k8sApi: k8s,
namespace,
podName,
phase: "Running",
});
await podCleaner.start();
// Wait a reasonable time to ensure pod isn't deleted
await setTimeout(5000);
// Verify pod still exists
expect(await podExists({ k8sApi: k8s, namespace, podName })).toBe(true);
} finally {
await podCleaner.stop();
}
}, 30000);
});
// Helper functions
async function waitForPodPhase({
k8sApi,
namespace,
podName,
phase,
timeoutMs = 10000,
waitMs = 1000,
}: {
k8sApi: K8sApi;
namespace: string;
podName: string;
phase: string;
timeoutMs?: number;
waitMs?: number;
}) {
const startTime = Date.now();
while (Date.now() - startTime < timeoutMs) {
const pod = await k8sApi.core.readNamespacedPod({
namespace,
name: podName,
});
if (pod.status?.phase === phase) {
return;
}
await setTimeout(waitMs);
}
throw new Error(`Pod ${podName} did not reach phase ${phase} within ${timeoutMs}ms`);
}
async function waitForPodDeletion({
k8sApi,
namespace,
podName,
timeoutMs = 10000,
waitMs = 1000,
}: {
k8sApi: K8sApi;
namespace: string;
podName: string;
timeoutMs?: number;
waitMs?: number;
}) {
const startTime = Date.now();
while (Date.now() - startTime < timeoutMs) {
try {
await k8sApi.core.readNamespacedPod({
namespace,
name: podName,
});
await setTimeout(waitMs);
} catch (error) {
// Pod was deleted
return;
}
}
throw new Error(`Pod ${podName} was not deleted within ${timeoutMs}ms`);
}
async function createTestPods({
k8sApi,
namespace,
count,
labels = { app: "task-run" },
shouldFail = false,
namePrefix = "test-pod",
command = ["/bin/sh", "-c", shouldFail ? "exit 1" : "exit 0"],
}: {
k8sApi: K8sApi;
namespace: string;
count: number;
labels?: Record<string, string>;
shouldFail?: boolean;
namePrefix?: string;
command?: string[];
}) {
const createdPods: string[] = [];
for (let i = 0; i < count; i++) {
const podName = `${namePrefix}-${i}`;
await k8sApi.core.createNamespacedPod({
namespace,
body: {
metadata: {
name: podName,
labels,
},
spec: {
restartPolicy: "Never",
containers: [
{
name: "test",
image: "busybox:1.37.0",
command,
},
],
},
},
});
createdPods.push(podName);
}
return createdPods;
}
async function waitForPodsPhase({
k8sApi,
namespace,
podNames,
phase,
timeoutMs = 10000,
waitMs = 1000,
}: {
k8sApi: K8sApi;
namespace: string;
podNames: string[];
phase: "Pending" | "Running" | "Succeeded" | "Failed" | "Unknown";
timeoutMs?: number;
waitMs?: number;
}) {
const startTime = Date.now();
const pendingPods = new Set(podNames);
while (pendingPods.size > 0 && Date.now() - startTime < timeoutMs) {
const pods = await k8sApi.core.listNamespacedPod({ namespace });
for (const pod of pods.items) {
if (pendingPods.has(pod.metadata?.name ?? "") && pod.status?.phase === phase) {
pendingPods.delete(pod.metadata?.name ?? "");
}
}
if (pendingPods.size > 0) {
await setTimeout(waitMs);
}
}
if (pendingPods.size > 0) {
throw new Error(
`Pods [${Array.from(pendingPods).join(
", "
)}] did not reach phase ${phase} within ${timeoutMs}ms`
);
}
}
async function waitForPodsDeletion({
k8sApi,
namespace,
podNames,
timeoutMs = 10000,
waitMs = 1000,
}: {
k8sApi: K8sApi;
namespace: string;
podNames: string[];
timeoutMs?: number;
waitMs?: number;
}) {
const startTime = Date.now();
const pendingPods = new Set(podNames);
while (pendingPods.size > 0 && Date.now() - startTime < timeoutMs) {
const pods = await k8sApi.core.listNamespacedPod({ namespace });
const existingPods = new Set(pods.items.map((pod) => pod.metadata?.name ?? ""));
for (const podName of pendingPods) {
if (!existingPods.has(podName)) {
pendingPods.delete(podName);
}
}
if (pendingPods.size > 0) {
await setTimeout(waitMs);
}
}
if (pendingPods.size > 0) {
throw new Error(
`Pods [${Array.from(pendingPods).join(", ")}] were not deleted within ${timeoutMs}ms`
);
}
}
async function podExists({
k8sApi,
namespace,
podName,
}: {
k8sApi: K8sApi;
namespace: string;
podName: string;
}) {
const pods = await k8sApi.core.listNamespacedPod({ namespace });
return pods.items.some((p) => p.metadata?.name === podName);
}
+118
View File
@@ -0,0 +1,118 @@
import { SimpleStructuredLogger } from "@trigger.dev/core/v3/utils/structuredLogger";
import { K8sApi } from "../clients/kubernetes.js";
import { createK8sApi } from "../clients/kubernetes.js";
import { HeartbeatService } from "@trigger.dev/core/v3";
import { Counter, Gauge, Registry } from "prom-client";
import { register } from "../metrics.js";
export type PodCleanerOptions = {
namespace: string;
k8s?: K8sApi;
register?: Registry;
batchSize?: number;
intervalMs?: number;
};
export class PodCleaner {
private readonly logger = new SimpleStructuredLogger("pod-cleaner");
private readonly k8s: K8sApi;
private readonly namespace: string;
private readonly batchSize: number;
private readonly deletionHeartbeat: HeartbeatService;
// Metrics
private readonly register: Registry;
private readonly deletionCyclesTotal: Counter;
private readonly lastDeletionTimestamp: Gauge;
constructor(opts: PodCleanerOptions) {
this.k8s = opts.k8s ?? createK8sApi();
this.namespace = opts.namespace;
this.batchSize = opts.batchSize ?? 500;
this.deletionHeartbeat = new HeartbeatService({
intervalMs: opts.intervalMs ?? 10000,
leadingEdge: true,
heartbeat: this.deleteCompletedPods.bind(this),
});
// Initialize metrics
this.register = opts.register ?? register;
this.deletionCyclesTotal = new Counter({
name: "pod_cleaner_deletion_cycles_total",
help: "Total number of pod deletion cycles run",
labelNames: ["namespace", "status", "batch_size"],
registers: [this.register],
});
this.lastDeletionTimestamp = new Gauge({
name: "pod_cleaner_last_deletion_timestamp",
help: "Timestamp of the last deletion cycle",
labelNames: ["namespace"],
registers: [this.register],
});
}
async start() {
this.deletionHeartbeat.start();
}
async stop() {
this.deletionHeartbeat.stop();
}
private async deleteCompletedPods() {
let continuationToken: string | undefined;
do {
try {
const result = await this.k8s.core.deleteCollectionNamespacedPod({
namespace: this.namespace,
labelSelector: "app=task-run",
fieldSelector: "status.phase=Succeeded",
limit: this.batchSize,
_continue: continuationToken,
gracePeriodSeconds: 0,
propagationPolicy: "Background",
timeoutSeconds: 30,
});
// Update continuation token for next batch
continuationToken = result.metadata?._continue;
// Increment the deletion cycles counter
this.deletionCyclesTotal.inc({
namespace: this.namespace,
batch_size: this.batchSize,
status: "succeeded",
});
this.logger.info("Deleted batch of pods", { continuationToken });
} catch (err) {
this.logger.error("Failed to delete batch of pods", {
err: err instanceof Error ? err.message : String(err),
});
this.deletionCyclesTotal.inc({
namespace: this.namespace,
batch_size: this.batchSize,
status: "failed",
});
break;
}
} while (continuationToken);
this.lastDeletionTimestamp.set({ namespace: this.namespace }, Date.now());
}
// Method to expose metrics for testing
public getMetrics() {
return {
deletionCyclesTotal: this.deletionCyclesTotal,
lastDeletionTimestamp: this.lastDeletionTimestamp,
};
}
}
+16
View File
@@ -0,0 +1,16 @@
export function getDockerHostDomain() {
const isMacOs = process.platform === "darwin";
const isWindows = process.platform === "win32";
return isMacOs || isWindows ? "host.docker.internal" : "localhost";
}
export function getRunnerId(runId: string, attemptNumber?: number) {
const parts = ["runner", runId.replace("run_", "")];
if (attemptNumber && attemptNumber > 1) {
parts.push(`attempt-${attemptNumber}`);
}
return parts.join("-");
}
@@ -0,0 +1,87 @@
import { SimpleStructuredLogger } from "@trigger.dev/core/v3/utils/structuredLogger";
import {
type WorkloadManager,
type WorkloadManagerCreateOptions,
type WorkloadManagerOptions,
} from "./types.js";
import { x } from "tinyexec";
import { env } from "../env.js";
import { getDockerHostDomain, getRunnerId } from "../util.js";
export class DockerWorkloadManager implements WorkloadManager {
private readonly logger = new SimpleStructuredLogger("docker-workload-provider");
constructor(private opts: WorkloadManagerOptions) {
if (opts.workloadApiDomain) {
this.logger.warn("[DockerWorkloadProvider] ⚠️ Custom workload API domain", {
domain: opts.workloadApiDomain,
});
}
}
async create(opts: WorkloadManagerCreateOptions) {
this.logger.log("[DockerWorkloadProvider] Creating container", { opts });
const runnerId = getRunnerId(opts.runFriendlyId, opts.nextAttemptNumber);
const runArgs = [
"run",
"--detach",
`--network=${env.DOCKER_NETWORK}`,
`--env=TRIGGER_DEQUEUED_AT_MS=${opts.dequeuedAt.getTime()}`,
`--env=TRIGGER_POD_SCHEDULED_AT_MS=${Date.now()}`,
`--env=TRIGGER_ENV_ID=${opts.envId}`,
`--env=TRIGGER_RUN_ID=${opts.runFriendlyId}`,
`--env=TRIGGER_SNAPSHOT_ID=${opts.snapshotFriendlyId}`,
`--env=TRIGGER_SUPERVISOR_API_PROTOCOL=${this.opts.workloadApiProtocol}`,
`--env=TRIGGER_SUPERVISOR_API_PORT=${this.opts.workloadApiPort}`,
`--env=TRIGGER_SUPERVISOR_API_DOMAIN=${this.opts.workloadApiDomain ?? getDockerHostDomain()}`,
`--env=TRIGGER_WORKER_INSTANCE_NAME=${env.TRIGGER_WORKER_INSTANCE_NAME}`,
`--env=OTEL_EXPORTER_OTLP_ENDPOINT=${env.OTEL_EXPORTER_OTLP_ENDPOINT}`,
`--env=TRIGGER_RUNNER_ID=${runnerId}`,
`--hostname=${runnerId}`,
`--name=${runnerId}`,
];
if (this.opts.warmStartUrl) {
runArgs.push(`--env=TRIGGER_WARM_START_URL=${this.opts.warmStartUrl}`);
}
if (this.opts.metadataUrl) {
runArgs.push(`--env=TRIGGER_METADATA_URL=${this.opts.metadataUrl}`);
}
if (this.opts.heartbeatIntervalSeconds) {
runArgs.push(
`--env=TRIGGER_HEARTBEAT_INTERVAL_SECONDS=${this.opts.heartbeatIntervalSeconds}`
);
}
if (this.opts.snapshotPollIntervalSeconds) {
runArgs.push(
`--env=TRIGGER_SNAPSHOT_POLL_INTERVAL_SECONDS=${this.opts.snapshotPollIntervalSeconds}`
);
}
if (this.opts.additionalEnvVars) {
Object.entries(this.opts.additionalEnvVars).forEach(([key, value]) => {
runArgs.push(`--env=${key}=${value}`);
});
}
if (env.ENFORCE_MACHINE_PRESETS) {
runArgs.push(`--cpus=${opts.machine.cpu}`, `--memory=${opts.machine.memory}G`);
runArgs.push(`--env=TRIGGER_MACHINE_CPU=${opts.machine.cpu}`);
runArgs.push(`--env=TRIGGER_MACHINE_MEMORY=${opts.machine.memory}`);
}
runArgs.push(`${opts.image}`);
try {
const { stdout, stderr } = await x("docker", runArgs);
this.logger.debug("[DockerWorkloadProvider] Create succeeded", { stdout, stderr });
} catch (error) {
this.logger.error("[DockerWorkloadProvider] Create failed:", { opts, error });
}
}
}
@@ -0,0 +1,284 @@
import { SimpleStructuredLogger } from "@trigger.dev/core/v3/utils/structuredLogger";
import {
type WorkloadManager,
type WorkloadManagerCreateOptions,
type WorkloadManagerOptions,
} from "./types.js";
import type { EnvironmentType, MachinePreset } from "@trigger.dev/core/v3";
import { env } from "../env.js";
import { type K8sApi, createK8sApi, type k8s } from "../clients/kubernetes.js";
import { getRunnerId } from "../util.js";
type ResourceQuantities = {
[K in "cpu" | "memory" | "ephemeral-storage"]?: string;
};
export class KubernetesWorkloadManager implements WorkloadManager {
private readonly logger = new SimpleStructuredLogger("kubernetes-workload-provider");
private k8s: K8sApi;
private namespace = env.KUBERNETES_NAMESPACE;
constructor(private opts: WorkloadManagerOptions) {
this.k8s = createK8sApi();
if (opts.workloadApiDomain) {
this.logger.warn("[KubernetesWorkloadManager] ⚠️ Custom workload API domain", {
domain: opts.workloadApiDomain,
});
}
}
async create(opts: WorkloadManagerCreateOptions) {
this.logger.log("[KubernetesWorkloadManager] Creating container", { opts });
const runnerId = getRunnerId(opts.runFriendlyId, opts.nextAttemptNumber);
try {
await this.k8s.core.createNamespacedPod({
namespace: this.namespace,
body: {
metadata: {
name: runnerId,
namespace: this.namespace,
labels: {
...this.#getSharedLabels(opts),
app: "task-run",
"app.kubernetes.io/part-of": "trigger-worker",
"app.kubernetes.io/component": "create",
},
},
spec: {
...this.#defaultPodSpec,
terminationGracePeriodSeconds: 60 * 60,
containers: [
{
name: "run-controller",
image: opts.image,
ports: [
{
containerPort: 8000,
},
],
resources: this.#getResourcesForMachine(opts.machine),
env: [
{
name: "TRIGGER_DEQUEUED_AT_MS",
value: opts.dequeuedAt.getTime().toString(),
},
{
name: "TRIGGER_POD_SCHEDULED_AT_MS",
value: Date.now().toString(),
},
{
name: "TRIGGER_RUN_ID",
value: opts.runFriendlyId,
},
{
name: "TRIGGER_ENV_ID",
value: opts.envId,
},
{
name: "TRIGGER_SNAPSHOT_ID",
value: opts.snapshotFriendlyId,
},
{
name: "TRIGGER_SUPERVISOR_API_PROTOCOL",
value: this.opts.workloadApiProtocol,
},
{
name: "TRIGGER_SUPERVISOR_API_PORT",
value: `${this.opts.workloadApiPort}`,
},
{
name: "TRIGGER_SUPERVISOR_API_DOMAIN",
...(this.opts.workloadApiDomain
? {
value: this.opts.workloadApiDomain,
}
: {
valueFrom: {
fieldRef: {
fieldPath: "status.hostIP",
},
},
}),
},
{
name: "TRIGGER_WORKER_INSTANCE_NAME",
valueFrom: {
fieldRef: {
fieldPath: "spec.nodeName",
},
},
},
{
name: "OTEL_EXPORTER_OTLP_ENDPOINT",
value: env.OTEL_EXPORTER_OTLP_ENDPOINT,
},
{
name: "TRIGGER_RUNNER_ID",
value: runnerId,
},
{
name: "TRIGGER_MACHINE_CPU",
value: `${opts.machine.cpu}`,
},
{
name: "TRIGGER_MACHINE_MEMORY",
value: `${opts.machine.memory}`,
},
{
name: "LIMITS_CPU",
valueFrom: {
resourceFieldRef: {
resource: "limits.cpu",
},
},
},
{
name: "LIMITS_MEMORY",
valueFrom: {
resourceFieldRef: {
resource: "limits.memory",
},
},
},
...(this.opts.warmStartUrl
? [{ name: "TRIGGER_WARM_START_URL", value: this.opts.warmStartUrl }]
: []),
...(this.opts.metadataUrl
? [{ name: "TRIGGER_METADATA_URL", value: this.opts.metadataUrl }]
: []),
...(this.opts.heartbeatIntervalSeconds
? [
{
name: "TRIGGER_HEARTBEAT_INTERVAL_SECONDS",
value: `${this.opts.heartbeatIntervalSeconds}`,
},
]
: []),
...(this.opts.snapshotPollIntervalSeconds
? [
{
name: "TRIGGER_SNAPSHOT_POLL_INTERVAL_SECONDS",
value: `${this.opts.snapshotPollIntervalSeconds}`,
},
]
: []),
...(this.opts.additionalEnvVars
? Object.entries(this.opts.additionalEnvVars).map(([key, value]) => ({
name: key,
value: value,
}))
: []),
],
},
],
},
},
});
} catch (err: unknown) {
this.#handleK8sError(err);
}
}
#throwUnlessRecord(candidate: unknown): asserts candidate is Record<string, unknown> {
if (typeof candidate !== "object" || candidate === null) {
throw candidate;
}
}
#handleK8sError(err: unknown) {
this.#throwUnlessRecord(err);
if ("body" in err && err.body) {
this.logger.error("[KubernetesWorkloadManager] Create failed", { rawError: err.body });
this.#throwUnlessRecord(err.body);
if (typeof err.body.message === "string") {
throw new Error(err.body?.message);
} else {
throw err.body;
}
} else {
this.logger.error("[KubernetesWorkloadManager] Create failed", { rawError: err });
throw err;
}
}
#envTypeToLabelValue(type: EnvironmentType) {
switch (type) {
case "PRODUCTION":
return "prod";
case "STAGING":
return "stg";
case "DEVELOPMENT":
return "dev";
case "PREVIEW":
return "preview";
}
}
private getImagePullSecrets(): k8s.V1LocalObjectReference[] | undefined {
return this.opts.imagePullSecrets?.map((name) => ({ name }));
}
get #defaultPodSpec(): Omit<k8s.V1PodSpec, "containers"> {
return {
restartPolicy: "Never",
automountServiceAccountToken: false,
imagePullSecrets: this.getImagePullSecrets(),
nodeSelector: {
nodetype: env.KUBERNETES_WORKER_NODETYPE_LABEL,
},
};
}
get #defaultResourceRequests(): ResourceQuantities {
return {
"ephemeral-storage": env.EPHEMERAL_STORAGE_SIZE_REQUEST,
};
}
get #defaultResourceLimits(): ResourceQuantities {
return {
"ephemeral-storage": env.EPHEMERAL_STORAGE_SIZE_LIMIT,
};
}
#getSharedLabels(opts: WorkloadManagerCreateOptions): Record<string, string> {
return {
env: opts.envId,
envtype: this.#envTypeToLabelValue(opts.envType),
org: opts.orgId,
project: opts.projectId,
};
}
#getResourceRequestsForMachine(preset: MachinePreset): ResourceQuantities {
return {
cpu: `${preset.cpu * 0.75}`,
memory: `${preset.memory}G`,
};
}
#getResourceLimitsForMachine(preset: MachinePreset): ResourceQuantities {
return {
cpu: `${preset.cpu}`,
memory: `${preset.memory}G`,
};
}
#getResourcesForMachine(preset: MachinePreset): k8s.V1ResourceRequirements {
return {
requests: {
...this.#defaultResourceRequests,
...this.#getResourceRequestsForMachine(preset),
},
limits: {
...this.#defaultResourceLimits,
...this.#getResourceLimitsForMachine(preset),
},
};
}
}
@@ -0,0 +1,34 @@
import { type EnvironmentType, type MachinePreset } from "@trigger.dev/core/v3";
export interface WorkloadManagerOptions {
workloadApiProtocol: "http" | "https";
workloadApiDomain?: string; // If unset, will use orchestrator-specific default
workloadApiPort: number;
warmStartUrl?: string;
metadataUrl?: string;
imagePullSecrets?: string[];
heartbeatIntervalSeconds?: number;
snapshotPollIntervalSeconds?: number;
additionalEnvVars?: Record<string, string>;
}
export interface WorkloadManager {
create: (opts: WorkloadManagerCreateOptions) => Promise<unknown>;
}
export interface WorkloadManagerCreateOptions {
image: string;
machine: MachinePreset;
version: string;
nextAttemptNumber?: number;
dequeuedAt: Date;
// identifiers
envId: string;
envType: EnvironmentType;
orgId: string;
projectId: string;
runId: string;
runFriendlyId: string;
snapshotId: string;
snapshotFriendlyId: string;
}
+582
View File
@@ -0,0 +1,582 @@
import { type Namespace, Server, type Socket } from "socket.io";
import { SimpleStructuredLogger } from "@trigger.dev/core/v3/utils/structuredLogger";
import EventEmitter from "node:events";
import { z } from "zod";
import {
type SupervisorHttpClient,
WORKLOAD_HEADERS,
type WorkloadClientSocketData,
type WorkloadClientToServerEvents,
type WorkloadContinueRunExecutionResponseBody,
WorkloadDebugLogRequestBody,
type WorkloadDequeueFromVersionResponseBody,
WorkloadHeartbeatRequestBody,
type WorkloadHeartbeatResponseBody,
WorkloadRunAttemptCompleteRequestBody,
type WorkloadRunAttemptCompleteResponseBody,
WorkloadRunAttemptStartRequestBody,
type WorkloadRunAttemptStartResponseBody,
type WorkloadRunLatestSnapshotResponseBody,
type WorkloadServerToClientEvents,
type WorkloadSuspendRunResponseBody,
} from "@trigger.dev/core/v3/workers";
import { HttpServer, type CheckpointClient } from "@trigger.dev/core/v3/serverOnly";
import { type IncomingMessage } from "node:http";
import { register } from "../metrics.js";
// Use the official export when upgrading to socket.io@4.8.0
interface DefaultEventsMap {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[event: string]: (...args: any[]) => void;
}
const WorkloadActionParams = z.object({
runFriendlyId: z.string(),
snapshotFriendlyId: z.string(),
});
type WorkloadServerEvents = {
runConnected: [
{
run: {
friendlyId: string;
};
},
];
runDisconnected: [
{
run: {
friendlyId: string;
};
},
];
};
type WorkloadServerOptions = {
port: number;
host?: string;
workerClient: SupervisorHttpClient;
checkpointClient?: CheckpointClient;
};
export class WorkloadServer extends EventEmitter<WorkloadServerEvents> {
private checkpointClient?: CheckpointClient;
private readonly httpServer: HttpServer;
private readonly websocketServer: Namespace<
WorkloadClientToServerEvents,
WorkloadServerToClientEvents,
DefaultEventsMap,
WorkloadClientSocketData
>;
private readonly runSockets = new Map<
string,
Socket<
WorkloadClientToServerEvents,
WorkloadServerToClientEvents,
DefaultEventsMap,
WorkloadClientSocketData
>
>();
private readonly workerClient: SupervisorHttpClient;
constructor(opts: WorkloadServerOptions) {
super();
const host = opts.host ?? "0.0.0.0";
const port = opts.port;
this.workerClient = opts.workerClient;
this.checkpointClient = opts.checkpointClient;
this.httpServer = this.createHttpServer({ host, port });
this.websocketServer = this.createWebsocketServer();
}
private headerValueFromRequest(req: IncomingMessage, headerName: string): string | undefined {
const value = req.headers[headerName];
if (Array.isArray(value)) {
return value[0];
}
return value;
}
private runnerIdFromRequest(req: IncomingMessage): string | undefined {
return this.headerValueFromRequest(req, WORKLOAD_HEADERS.RUNNER_ID);
}
private deploymentIdFromRequest(req: IncomingMessage): string | undefined {
return this.headerValueFromRequest(req, WORKLOAD_HEADERS.DEPLOYMENT_ID);
}
private deploymentVersionFromRequest(req: IncomingMessage): string | undefined {
return this.headerValueFromRequest(req, WORKLOAD_HEADERS.DEPLOYMENT_VERSION);
}
private projectRefFromRequest(req: IncomingMessage): string | undefined {
return this.headerValueFromRequest(req, WORKLOAD_HEADERS.PROJECT_REF);
}
private createHttpServer({ host, port }: { host: string; port: number }) {
return new HttpServer({
port,
host,
metrics: {
register,
expose: false,
},
})
.route("/health", "GET", {
handler: async ({ reply }) => {
reply.text("OK");
},
})
.route(
"/api/v1/workload-actions/runs/:runFriendlyId/snapshots/:snapshotFriendlyId/attempts/start",
"POST",
{
paramsSchema: WorkloadActionParams,
bodySchema: WorkloadRunAttemptStartRequestBody,
handler: async ({ req, reply, params, body }) => {
const startResponse = await this.workerClient.startRunAttempt(
params.runFriendlyId,
params.snapshotFriendlyId,
body,
this.runnerIdFromRequest(req)
);
if (!startResponse.success) {
console.error("Failed to start run", {
params,
error: startResponse.error,
});
reply.empty(500);
return;
}
reply.json(startResponse.data satisfies WorkloadRunAttemptStartResponseBody);
return;
},
}
)
.route(
"/api/v1/workload-actions/runs/:runFriendlyId/snapshots/:snapshotFriendlyId/attempts/complete",
"POST",
{
paramsSchema: WorkloadActionParams,
bodySchema: WorkloadRunAttemptCompleteRequestBody,
handler: async ({ req, reply, params, body }) => {
console.log("headers", req.headers);
const completeResponse = await this.workerClient.completeRunAttempt(
params.runFriendlyId,
params.snapshotFriendlyId,
body,
this.runnerIdFromRequest(req)
);
if (!completeResponse.success) {
console.error("Failed to complete run", {
params,
error: completeResponse.error,
});
reply.empty(500);
return;
}
reply.json(completeResponse.data satisfies WorkloadRunAttemptCompleteResponseBody);
return;
},
}
)
.route(
"/api/v1/workload-actions/runs/:runFriendlyId/snapshots/:snapshotFriendlyId/heartbeat",
"POST",
{
paramsSchema: WorkloadActionParams,
bodySchema: WorkloadHeartbeatRequestBody,
handler: async ({ req, reply, params, body }) => {
const heartbeatResponse = await this.workerClient.heartbeatRun(
params.runFriendlyId,
params.snapshotFriendlyId,
body,
this.runnerIdFromRequest(req)
);
if (!heartbeatResponse.success) {
console.error("Failed to heartbeat run", {
params,
error: heartbeatResponse.error,
});
reply.empty(500);
return;
}
reply.json({
ok: true,
} satisfies WorkloadHeartbeatResponseBody);
},
}
)
.route(
"/api/v1/workload-actions/runs/:runFriendlyId/snapshots/:snapshotFriendlyId/suspend",
"GET",
{
paramsSchema: WorkloadActionParams,
handler: async ({ reply, params, req }) => {
console.debug("Suspend request", { params, headers: req.headers });
if (!this.checkpointClient) {
reply.json(
{
ok: false,
error: "Checkpoints disabled",
} satisfies WorkloadSuspendRunResponseBody,
false,
400
);
return;
}
const runnerId = this.runnerIdFromRequest(req);
const deploymentVersion = this.deploymentVersionFromRequest(req);
const projectRef = this.projectRefFromRequest(req);
if (!runnerId || !deploymentVersion || !projectRef) {
console.error("Invalid headers for suspend request", {
...params,
headers: req.headers,
});
reply.json(
{
ok: false,
error: "Invalid headers",
} satisfies WorkloadSuspendRunResponseBody,
false,
400
);
return;
}
reply.json(
{
ok: true,
} satisfies WorkloadSuspendRunResponseBody,
false,
202
);
const suspendResult = await this.checkpointClient.suspendRun({
runFriendlyId: params.runFriendlyId,
snapshotFriendlyId: params.snapshotFriendlyId,
body: {
runnerId,
runId: params.runFriendlyId,
snapshotId: params.snapshotFriendlyId,
projectRef,
deploymentVersion,
},
});
if (!suspendResult) {
console.error("Failed to suspend run", { params });
return;
}
},
}
)
.route(
"/api/v1/workload-actions/runs/:runFriendlyId/snapshots/:snapshotFriendlyId/continue",
"GET",
{
paramsSchema: WorkloadActionParams,
handler: async ({ req, reply, params }) => {
console.debug("Run continuation request", { params });
const continuationResult = await this.workerClient.continueRunExecution(
params.runFriendlyId,
params.snapshotFriendlyId,
this.runnerIdFromRequest(req)
);
if (!continuationResult.success) {
console.error("Failed to continue run execution", { params });
reply.json(
{
ok: false,
error: "Failed to continue run execution",
},
false,
400
);
return;
}
reply.json(continuationResult.data as WorkloadContinueRunExecutionResponseBody);
},
}
)
.route("/api/v1/workload-actions/runs/:runFriendlyId/snapshots/latest", "GET", {
paramsSchema: WorkloadActionParams.pick({ runFriendlyId: true }),
handler: async ({ req, reply, params }) => {
const latestSnapshotResponse = await this.workerClient.getLatestSnapshot(
params.runFriendlyId,
this.runnerIdFromRequest(req)
);
if (!latestSnapshotResponse.success) {
console.error("Failed to get latest snapshot", {
runId: params.runFriendlyId,
error: latestSnapshotResponse.error,
});
reply.empty(500);
return;
}
reply.json({
execution: latestSnapshotResponse.data.execution,
} satisfies WorkloadRunLatestSnapshotResponseBody);
},
})
.route("/api/v1/workload-actions/runs/:runFriendlyId/logs/debug", "POST", {
paramsSchema: WorkloadActionParams.pick({ runFriendlyId: true }),
bodySchema: WorkloadDebugLogRequestBody,
handler: async ({ req, reply, params, body }) => {
reply.empty(204);
await this.workerClient.sendDebugLog(
params.runFriendlyId,
body,
this.runnerIdFromRequest(req)
);
},
})
.route("/api/v1/workload-actions/deployments/:deploymentId/dequeue", "GET", {
paramsSchema: z.object({
deploymentId: z.string(),
}),
handler: async ({ req, reply, params }) => {
const dequeueResponse = await this.workerClient.dequeueFromVersion(
params.deploymentId,
1,
this.runnerIdFromRequest(req)
);
if (!dequeueResponse.success) {
console.error("Failed to get latest snapshot", {
deploymentId: params.deploymentId,
error: dequeueResponse.error,
});
reply.empty(500);
return;
}
reply.json(dequeueResponse.data satisfies WorkloadDequeueFromVersionResponseBody);
},
});
}
private createWebsocketServer() {
const io = new Server(this.httpServer.server);
const websocketServer: Namespace<
WorkloadClientToServerEvents,
WorkloadServerToClientEvents,
DefaultEventsMap,
WorkloadClientSocketData
> = io.of("/workload");
websocketServer.on("disconnect", (socket) => {
console.log("[WorkloadSocket] disconnect", socket.id);
});
websocketServer.use(async (socket, next) => {
function setSocketDataFromHeader(
dataKey: keyof typeof socket.data,
headerName: string,
required: boolean = true
) {
const value = socket.handshake.headers[headerName];
if (value) {
if (Array.isArray(value)) {
if (value[0]) {
socket.data[dataKey] = value[0];
return;
}
} else {
socket.data[dataKey] = value;
return;
}
}
if (required) {
console.error("[WorkloadSocket] missing required header", { headerName });
throw new Error("missing header");
}
}
try {
setSocketDataFromHeader("deploymentId", WORKLOAD_HEADERS.DEPLOYMENT_ID);
setSocketDataFromHeader("runnerId", WORKLOAD_HEADERS.RUNNER_ID);
} catch (error) {
console.error("[WorkloadSocket] setSocketDataFromHeader error", { error });
socket.disconnect(true);
return;
}
console.debug("[WorkloadSocket] auth success", socket.data);
next();
});
websocketServer.on("connection", (socket) => {
const logger = new SimpleStructuredLogger("workload-namespace", undefined, {
namespace: "workload",
socketId: socket.id,
socketData: socket.data,
});
const getSocketMetadata = () => {
return {
deploymentId: socket.data.deploymentId,
runId: socket.data.runFriendlyId,
snapshotId: socket.data.snapshotId,
runnerId: socket.data.runnerId,
};
};
const runConnected = (friendlyId: string) => {
logger.debug("runConnected", { ...getSocketMetadata() });
// If there's already a run ID set, we should "disconnect" it from this socket
if (socket.data.runFriendlyId) {
logger.debug("runConnected: disconnecting existing run", {
...getSocketMetadata(),
newRunId: friendlyId,
oldRunId: socket.data.runFriendlyId,
});
runDisconnected(socket.data.runFriendlyId);
}
this.runSockets.set(friendlyId, socket);
this.emit("runConnected", { run: { friendlyId } });
socket.data.runFriendlyId = friendlyId;
};
const runDisconnected = (friendlyId: string) => {
logger.debug("runDisconnected", { ...getSocketMetadata() });
this.runSockets.delete(friendlyId);
this.emit("runDisconnected", { run: { friendlyId } });
socket.data.runFriendlyId = undefined;
};
logger.log("wsServer socket connected", { ...getSocketMetadata() });
// FIXME: where does this get set?
if (socket.data.runFriendlyId) {
runConnected(socket.data.runFriendlyId);
}
socket.on("disconnecting", (reason, description) => {
logger.log("Socket disconnecting", { ...getSocketMetadata(), reason, description });
if (socket.data.runFriendlyId) {
runDisconnected(socket.data.runFriendlyId);
}
});
socket.on("disconnect", (reason, description) => {
logger.log("Socket disconnected", { ...getSocketMetadata(), reason, description });
});
socket.on("error", (error) => {
logger.error("Socket error", {
...getSocketMetadata(),
error: {
name: error.name,
message: error.message,
stack: error.stack,
},
});
});
socket.on("run:start", async (message) => {
const log = logger.child({
eventName: "run:start",
...getSocketMetadata(),
...message,
});
log.log("Handling run:start");
try {
runConnected(message.run.friendlyId);
} catch (error) {
log.error("run:start error", { error });
}
});
socket.on("run:stop", async (message) => {
const log = logger.child({
eventName: "run:stop",
...getSocketMetadata(),
...message,
});
log.log("Handling run:stop");
try {
runDisconnected(message.run.friendlyId);
} catch (error) {
log.error("run:stop error", { error });
}
});
});
return websocketServer;
}
notifyRun({ run }: { run: { friendlyId: string } }) {
try {
const runSocket = this.runSockets.get(run.friendlyId);
if (!runSocket) {
console.debug("[WorkloadServer] notifyRun: Run socket not found", { run });
this.workerClient.sendDebugLog(run.friendlyId, {
time: new Date(),
message: "run:notify socket not found on supervisor",
});
return;
}
runSocket.emit("run:notify", { version: "1", run });
console.debug("[WorkloadServer] run:notify sent", { run });
this.workerClient.sendDebugLog(run.friendlyId, {
time: new Date(),
message: "run:notify supervisor -> runner",
});
} catch (error) {
console.error("[WorkloadServer] Error in notifyRun", { run, error });
this.workerClient.sendDebugLog(run.friendlyId, {
time: new Date(),
message: "run:notify error on supervisor",
});
}
}
async start() {
await this.httpServer.start();
}
async stop() {
await this.httpServer.stop();
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"extends": "../../.configs/tsconfig.base.json",
"include": ["src/**/*.ts"],
"compilerOptions": {
"rootDir": "src",
"outDir": "dist"
}
}
+2 -7
View File
@@ -1,17 +1,12 @@
{
"plugins": [
"@trigger.dev/eslint-plugin",
"react-hooks",
"@typescript-eslint/eslint-plugin",
"import"
],
"plugins": ["react-hooks", "@typescript-eslint/eslint-plugin", "import"],
"parser": "@typescript-eslint/parser",
"overrides": [
{
"files": ["*.ts", "*.tsx"],
"rules": {
// Autofixes imports from "@trigger.dev/core" to fine grained modules
"@trigger.dev/no-trigger-core-import": "error",
// "@trigger.dev/no-trigger-core-import": "error",
// Normalize `import type {}` and `import { type }`
"@typescript-eslint/consistent-type-imports": [
"warn",
-15
View File
@@ -1,15 +0,0 @@
import { ApiEventLog } from "@trigger.dev/core";
import { EventRecord } from "@trigger.dev/database";
export function eventRecordToApiJson(eventRecord: EventRecord): ApiEventLog {
return {
id: eventRecord.eventId,
name: eventRecord.name,
payload: eventRecord.payload as any,
context: eventRecord.context as any,
timestamp: eventRecord.timestamp,
deliverAt: eventRecord.deliverAt,
deliveredAt: eventRecord.deliveredAt,
cancelledAt: eventRecord.cancelledAt,
};
}
@@ -0,0 +1,27 @@
import { useAnimate } from "framer-motion";
import { HourglassIcon } from "lucide-react";
import { useEffect } from "react";
export function AnimatedHourglassIcon({
className,
delay,
}: {
className?: string;
delay?: number;
}) {
const [scope, animate] = useAnimate();
useEffect(() => {
animate(
[
[scope.current, { rotate: 0 }, { duration: 0.7 }],
[scope.current, { rotate: 180 }, { duration: 0.3 }],
[scope.current, { rotate: 180 }, { duration: 0.7 }],
[scope.current, { rotate: 360 }, { duration: 0.3 }],
],
{ repeat: Infinity, delay }
);
}, []);
return <HourglassIcon ref={scope} className={className} />;
}
+9 -19
View File
@@ -2,28 +2,18 @@ export function AttemptIcon({ className }: { className?: string }) {
return (
<svg
className={className}
width="16"
height="16"
viewBox="0 0 16 16"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g clipPath="url(#clip0_9964_113464)">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M16 0H0V16H16V0ZM7.09906 4.4L4.53906 11.5H6.11906L6.63906 10H9.35906L9.87906 11.5H11.4591L8.89906 4.4H7.09906ZM7.99906 6L8.92906 8.73H7.06906L7.99906 6Z"
fill="currentColor"
/>
</g>
<defs>
<clipPath id="clip0_9964_113464">
<path
d="M0 2C0 0.895431 0.895431 0 2 0H14C15.1046 0 16 0.895431 16 2V14C16 15.1046 15.1046 16 14 16H2C0.895431 16 0 15.1046 0 14V2Z"
fill="white"
/>
</clipPath>
</defs>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M5 2C3.34315 2 2 3.34315 2 5V19C2 20.6569 3.34315 22 5 22H19C20.6569 22 22 20.6569 22 19V5C22 3.34315 20.6569 2 19 2H5ZM7.65398 16.4367C7.64829 16.4871 7.64671 16.5374 7.64905 16.5873C7.66648 16.959 7.90169 17.3021 8.27281 17.4389C8.7645 17.6202 9.31042 17.3713 9.49607 16.8813L10.2088 15H13.8788L14.5915 16.8813C14.7772 17.3713 15.3231 17.6202 15.8148 17.4389C16.3096 17.2565 16.5628 16.7075 16.3804 16.2127L13.2478 7.71625C13.1255 7.30217 12.7423 7 12.2886 7H11.8041C11.5337 7 11.2884 7.10733 11.1084 7.28171C11.0015 7.38343 10.9157 7.51053 10.8613 7.65817L7.70722 16.2126C7.67996 16.2865 7.66243 16.3617 7.65398 16.4367ZM12.0438 10.1564L10.9665 13H13.1211L12.0438 10.1564Z"
fill="currentColor"
/>
</svg>
);
}
@@ -0,0 +1,73 @@
export function ConnectedIcon({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect
x="0.5"
y="-0.5"
width="19"
height="1"
rx="0.5"
transform="matrix(1 0 0 -1 1.99998 20)"
stroke="#878C99"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M14.4187 5L6.00002 5C4.89545 5 4.00002 5.89543 4.00002 7L4.00001 15C4.00002 16.1046 4.89545 17 6.00002 17L18 17C19.1046 17 20 16.1046 20 15L20 7.9816L18.5 9.4816L18.5 15C18.5 15.2761 18.2762 15.5 18 15.5L6.00001 15.5C5.72387 15.5 5.50001 15.2761 5.50001 15L5.50002 7C5.50002 6.72386 5.72387 6.5 6.00002 6.5L12.9187 6.5L14.4187 5Z"
fill="#878C99"
/>
<path
d="M8.50002 9.75L11.5 12.75L18 6"
stroke="#28BF5C"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}
export function DisconnectedIcon({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M14.673 19L2.99998 19C2.44769 19 1.99998 19.4477 1.99998 20C1.99998 20.5523 2.44769 21 2.99998 21L16.673 21L14.673 19Z"
fill="#878C99"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M3.99999 8.32703L3.99999 15C3.99999 16.1046 4.89542 17 5.99999 17L12.673 17L11.173 15.5L5.99999 15.5C5.72385 15.5 5.49999 15.2761 5.49999 15L5.49999 9.82703L3.99999 8.32703ZM18.5 14.2641L18.5 7C18.5 6.72386 18.2761 6.5 18 6.5L10.7358 6.5L9.23585 5L18 5C19.1046 5 20 5.89543 20 7L20 15C20 15.2292 19.9614 15.4495 19.8904 15.6546L18.5 14.2641Z"
fill="#878C99"
/>
<path d="M3.00001 3L21 21" stroke="#E11D48" strokeWidth="1.5" strokeLinecap="round" />
</svg>
);
}
export function CheckingConnectionIcon({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect
x="0.5"
y="-0.5"
width="19"
height="1"
rx="0.5"
transform="matrix(1 0 0 -1 1.99998 20)"
stroke="#878C99"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M18 6.5L6.00001 6.5C5.72387 6.5 5.50001 6.72386 5.50001 7L5.50001 15C5.50001 15.2761 5.72387 15.5 6.00001 15.5L18 15.5C18.2762 15.5 18.5 15.2761 18.5 15V7C18.5 6.72386 18.2762 6.5 18 6.5ZM6.00001 5C4.89545 5 4.00001 5.89543 4.00001 7L4.00001 15C4.00001 16.1046 4.89545 17 6.00001 17L18 17C19.1046 17 20 16.1046 20 15V7C20 5.89543 19.1046 5 18 5L6.00001 5Z"
fill="#878C99"
/>
<circle cx="9" cy="11" r="1" fill="#878C99" />
<circle cx="12" cy="11" r="1" fill="#878C99" />
<circle cx="15" cy="11" r="1" fill="#878C99" />
</svg>
);
}
@@ -0,0 +1,20 @@
export function DropdownIcon({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M7.5 9L12 5L16.5 9"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M7.5 15L12 19L16.5 15"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}

Some files were not shown because too many files have changed in this diff Show More