Skip to Content

@loopstack/scheduling-examples

Scheduling workflow examples for the Loopstack  automation framework.

This module demonstrates the scheduling fundamentals — the primitives you use to start workflows programmatically instead of clicking “Run” in Studio. Each fundamental ships as a small workflow (the work) plus the real trigger that fires it (the primitive): a cron schedule, a webhook endpoint, a delayed timeout, and a batch fan-out.

Background reading: Programmatic Execution.

The core primitive: WorkflowRunner

Every trigger boils down to injecting WorkflowRunner (from @loopstack/core, globally available once LoopstackModule.forRoot() is imported) and calling:

  • run(WorkflowClass, args, { appName, userId }) — enqueue on BullMQ, return a workflowId. Used by all triggers here.
  • runSync(WorkflowClass, args, { appName, userId, stateless? }) — execute inline and await the result.

Background triggers (cron, webhooks, timeouts) have no HTTP request, so no user context. The RunUserResolver helper picks the local Studio user to run as — open Studio once so that user exists.

The fundamentals

FundamentalThe primitive (where it lives)The work
CronCronTriggerScheduler@Cron(EVERY_MINUTE)CronTriggerWorkflow posts a message
WebhookWebhookTriggerController@Public @Post('payment')WebhookTriggerWorkflow records a receipt
DelayedDelayedRunControllerSchedulerRegistry one-off timeoutDelayedRunWorkflow sends a follow-up
BatchBatchTriggerControllerPromise.all fan-outBatchTriggerWorkflow emails one recipient

The trigger is where the scheduling primitive actually lives; the work workflow is just what it launches.

The webhook/delayed/batch controllers are marked @Public() so you can curl them without a token. Real integrations should secure them — verify a provider signature on webhooks, require auth on batch/admin endpoints.

Run it all from Studio — no curl needed

Studio exposes three launchable workflows, one per HTTP-triggered fundamental. Each makes the real HTTP POST to its endpoint — the same call an external caller would make — and posts the response:

Run this in StudioWhat it does
CallWebhookWorkflowPOST /webhooks/scheduling-examples/payment → fires WebhookTriggerWorkflow
CallSignupWorkflowPOST /webhooks/scheduling-examples/signup → schedules DelayedRunWorkflow
CallNewsletterWorkflowPOST /webhooks/scheduling-examples/newsletter → fans out BatchTriggerWorkflow

They go over real HTTP (fetch to http://localhost:$PORT), so they prove the whole trigger path, not just the workflow body. The cron fundamental needs no launcher — it fires on its own schedule. The curl commands below do the same thing from a terminal. The launched “work” workflows (WebhookTriggerWorkflow, etc.) are registered but not listed as standalone Studio entries.

Cron — @Cron

CronTriggerScheduler.tick() is a NestJS @Cron(CronExpression.EVERY_MINUTE) method that calls WorkflowRunner.run(CronTriggerWorkflow, …). It is off by default so it doesn’t spam your workspace — enable it with:

SCHEDULING_EXAMPLES_CRON_ENABLED=true
@Cron(CronExpression.EVERY_MINUTE) async tick() { if (this.configService.get('SCHEDULING_EXAMPLES_CRON_ENABLED') !== 'true') return; const userId = await this.runUser.resolve(); if (!userId) return; await this.workflowRunner.run(CronTriggerWorkflow, { message: 'hello world' }, { appName, userId }); }

Swap CronExpression.EVERY_MINUTE for a cron string like '0 9 * * MON' for “every Monday at 9am”.

Webhook — @Public @Post

curl -X POST http://localhost:3000/webhooks/scheduling-examples/payment \ -H 'content-type: application/json' \ -d '{"customerEmail":"grace@example.com","amountCents":4200,"currency":"USD"}'

The controller maps the request body to workflow args and calls run(), returning the workflowId.

Delayed — SchedulerRegistry timeout

curl -X POST http://localhost:3000/webhooks/scheduling-examples/signup \ -H 'content-type: application/json' -d '{"email":"grace@example.com"}'

Registers a one-off timeout (default 10s, override with SCHEDULING_EXAMPLES_FOLLOWUP_DELAY_MS) that fires the follow-up workflow later — the “run this in 24h after signup” pattern, sped up for the demo.

SchedulerRegistry timeouts live in memory: a server restart loses any pending follow-up. For a durable long delay, enqueue a BullMQ delayed job instead.

Batch — Promise.all fan-out

curl -X POST http://localhost:3000/webhooks/scheduling-examples/newsletter \ -H 'content-type: application/json' \ -d '{"recipients":["ada@example.com","grace@example.com"]}'

Launches one workflow run per recipient in parallel and returns all the workflowIds.

Using in your app

import { SchedulingExamplesModule } from '@loopstack/scheduling-examples'; @Module({ imports: [LoopstackModule.forRoot(), SchedulingExamplesModule], }) export class AppModule {}

About

Author: Jakob Klippel 

License: MIT

Last updated on