Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
889d791
feat(stem): add workflow runtime metadata views and manifests
kingwill101 Feb 26, 2026
aa6b9ed
feat(stem_builder): generate typed workflow starters and app helpers
kingwill101 Feb 26, 2026
0c9cede
feat(stem_builder): switch to per-file part generation and concise st…
kingwill101 Feb 26, 2026
818833c
feat(stem_builder): support plain run entrypoint and configurable sta…
kingwill101 Feb 26, 2026
31ac17c
Add task registration to StemWorkflowApp
kingwill101 Mar 17, 2026
d3c403e
Refresh stem_builder docs and demos
kingwill101 Mar 17, 2026
8e7a753
Add ecommerce workflow example
kingwill101 Mar 17, 2026
544c512
Improve workflow logging context
kingwill101 Mar 17, 2026
f289196
Export logging types from stem
kingwill101 Mar 17, 2026
bf345ec
Adopt tasks-first wiring across Stem APIs and examples
kingwill101 Mar 17, 2026
ba8794a
Prepare adapters for Ormed 0.2.0 release
kingwill101 Mar 18, 2026
7fe3835
Remove explicit adapter registration calls
kingwill101 Mar 18, 2026
7f1ba4e
Fix workflow runtime and store regressions
kingwill101 Mar 19, 2026
4a9a290
Clarify workflow checkpoint terminology
kingwill101 Mar 19, 2026
8c441cb
Add dedicated workflows docs section
kingwill101 Mar 19, 2026
c077f35
Move ecommerce ORM registry next to database code
kingwill101 Mar 19, 2026
267dc2b
Refresh .site docs for current APIs
kingwill101 Mar 19, 2026
7e497e2
Fix stale example paths in docs
kingwill101 Mar 19, 2026
de5eb4a
Ignore local workflow and third-party artifacts
kingwill101 Mar 19, 2026
d47056c
Add proxy workflow runtime probe
kingwill101 Mar 19, 2026
acf985d
Update docs favicon to Stem icon
kingwill101 Mar 19, 2026
d692864
Add typed workflow and task definition APIs
kingwill101 Mar 19, 2026
afc51c1
Document typed workflow and task APIs
kingwill101 Mar 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,5 @@ packages/cloud/
# Local test artifacts
/test/
/test_screenshots/
third_party/
packages/stem/workflow.sqlite*
9 changes: 5 additions & 4 deletions .site/docs/brokers/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,12 @@ and limited fanout without SNS.

- **Redis Streams** is the default. Enable persistence (AOF) and replicate to a
hot standby for fault tolerance. Configure namespaces per environment with
ACLs. The `examples/redis_postgres_worker` sample pairs Redis with Postgres
for result storage.
ACLs. The `packages/stem/example/redis_postgres_worker` sample pairs Redis
with Postgres for result storage.
- **Postgres** integrates tightly with the existing result backend for teams
already running Postgres. Leases are implemented via advisory locks; ensure
the connection pool matches expected concurrency.
already running Postgres. Delivery leases are tracked in queue rows (for
example via `locked_until`), so ensure the connection pool matches expected
concurrency.
- **SQLite** is ideal for single-host development and demos. Use separate DB
files for broker and backend; avoid producer writes to the backend.
- **In-memory** adapters mirror the Redis API and are safe for smoke tests.
Expand Down
6 changes: 3 additions & 3 deletions .site/docs/comparisons/stem-vs-bullmq.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ slug: /comparisons/stem-vs-bullmq
This page is the canonical Stem comparison matrix for BullMQ-style features.
It focuses on capability parity, not API-level compatibility.

**As of:** February 24, 2026
**As of:** March 18, 2026

## Status semantics

Expand All @@ -27,7 +27,7 @@ It focuses on capability parity, not API-level compatibility.
| Group Rate Limit | `✓` | Stem supports group-scoped rate limiting via `TaskOptions.groupRateLimit`, `groupRateKey`, and `groupRateKeyHeader`. See [Rate Limiting](../core-concepts/rate-limiting.md). |
| Group Support | `✓` | Stem provides `Canvas.group` and `Canvas.chord` primitives. See [Canvas Patterns](../core-concepts/canvas.md). |
| Batches Support | `✓` | Stem exposes first-class batch APIs (`submitBatch`, `inspectBatch`) with durable batch lifecycle status. See [Canvas Patterns](../core-concepts/canvas.md). |
| Parent/Child Dependencies | `✓` | Stem supports dependency composition through chains, groups/chords, and workflow steps. See [Canvas Patterns](../core-concepts/canvas.md) and [Workflows](../core-concepts/workflows.md). |
| Parent/Child Dependencies | `✓` | Stem supports dependency composition through chains, groups/chords, and durable workflow stages. See [Canvas Patterns](../core-concepts/canvas.md) and [Workflows](../workflows/index.md). |
| Deduplication (Debouncing) | `~` | `TaskOptions.unique` prevents duplicate enqueue claims, but semantics are lock/TTL-based rather than BullMQ-native dedupe APIs. See [Uniqueness](../core-concepts/uniqueness.md). |
| Deduplication (Throttling) | `~` | `uniqueFor` and lock TTL windows approximate throttling behavior, but are not a direct BullMQ equivalent. See [Uniqueness](../core-concepts/uniqueness.md). |
| Priorities | `✓` | Stem supports task priority and queue priority ranges. See [Tasks](../core-concepts/tasks.md) and [Routing](../core-concepts/routing.md). |
Expand All @@ -41,7 +41,7 @@ It focuses on capability parity, not API-level compatibility.
| Atomic ops | `~` | Stem includes atomic behavior in specific stores/flows, but end-to-end transactional guarantees (for all enqueue/ack/result paths) are not universally built-in. See [Tasks idempotency guidance](../core-concepts/tasks.md#idempotency-checklist) and [Best Practices](../getting-started/best-practices.md). |
| Persistence | `✓` | Stem persists task/workflow/schedule state through pluggable backends/stores. See [Persistence & Stores](../core-concepts/persistence.md). |
| UI | `~` | Stem includes an experimental dashboard, not a fully mature operator UI parity target yet. See [Dashboard](../core-concepts/dashboard.md). |
| Optimized for | `~` | Stem is optimized for jobs/messages plus durable workflow orchestration, not only queue semantics. See [Core Concepts](../core-concepts/index.md) and [Workflows](../core-concepts/workflows.md). |
| Optimized for | `~` | Stem is optimized for jobs/messages plus durable workflow orchestration, not only queue semantics. See [Core Concepts](../core-concepts/index.md) and [Workflows](../workflows/index.md). |

## Update policy

Expand Down
2 changes: 1 addition & 1 deletion .site/docs/core-concepts/canvas.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ This guide walks through Stem's task composition primitives—chains, groups, an
chords—using in-memory brokers and backends. Each snippet references a runnable
file under `packages/stem/example/docs_snippets/` so you can experiment locally
with `dart run`. If you bootstrap with `StemApp`, use `app.canvas` to reuse the
same broker, backend, registry, and encoder registry.
same broker, backend, task handlers, and encoder registry.

## Chains

Expand Down
18 changes: 13 additions & 5 deletions .site/docs/core-concepts/cli-control.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,17 +109,25 @@ stem worker stats --worker worker-a

```

## Registry resolution
## Task registry resolution for CLI commands

Many CLI commands that reference task names need a registry. The default CLI
context does not load one automatically, so wire it via `runStemCli` with a
Many CLI commands that reference task names need task metadata. That is a CLI
concern, not the default application bootstrap path. The default CLI context
does not load a task registry automatically, so wire it via `runStemCli` with a
`contextBuilder` that sets `CliContext.registry`. For multi-binary deployments,
ensure the CLI and workers share the same registry entrypoint so task names,
encoders, and routing rules stay consistent.
ensure the CLI and workers share the same task-definition entrypoint so task
names, encoders, and routing rules stay consistent.

A common pattern is to build that CLI registry from the same shared task list
or generated `stemModule.tasks` your app uses, so task metadata stays consistent
without teaching registry-first bootstrap for normal services.

If a command needs a registry and none is available, it will exit with an error
or fall back to raw task metadata (depending on the subcommand).

For normal app bootstrap, prefer `tasks: [...]` or a generated `stemModule`.
See [Tasks](./tasks.md) and [stem_builder](./stem-builder.md).

## List registered tasks

```bash
Expand Down
5 changes: 3 additions & 2 deletions .site/docs/core-concepts/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,9 @@ behavior before touching production.
- **[Queue Events](./queue-events.md)** – Publish/listen to queue-scoped custom events.
- **[Canvas Patterns](./canvas.md)** – Chains, groups, and chords for composing work.
- **[Observability](./observability.md)** – Metrics, traces, logging, and lifecycle signals.
- **[Persistence & Stores](./persistence.md)** – Result backends, schedule/lock stores, and revocation storage.
- **[Workflows](./workflows.md)** – Durable Flow/Script runtimes with typed results, suspensions, and event watchers.
- **[Persistence & Stores](./persistence.md)** – Result backends, workflow stores, schedule/lock stores, and revocation storage.
- **[Workflows](../workflows/index.md)** – Durable workflow orchestration, suspensions, recovery, and annotated workflow generation.
- **[stem_builder](./stem-builder.md)** – Generate workflow/task helpers, manifests, workflow refs, and typed task helpers from annotations.
- **[CLI & Control](./cli-control.md)** – Quickly inspect queues, workers, and health from the command line.

Continue with the [Workers guide](../workers/index.md) for operational details.
14 changes: 8 additions & 6 deletions .site/docs/core-concepts/observability.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,16 @@ control-plane commands.

## Workflow Introspection

Workflow runtimes can emit step-level events (started/completed/failed/retrying)
through a `WorkflowIntrospectionSink`. Use it to publish step telemetry or
bridge to your own tracing/logging systems.
Workflow runtimes can emit execution events (started/completed/failed/retrying)
for both flow steps and script checkpoints through a
`WorkflowIntrospectionSink`. Use it to publish workflow telemetry or bridge to
your own tracing/logging systems.

```dart
class LoggingWorkflowIntrospectionSink implements WorkflowIntrospectionSink {
@override
Future<void> recordStepEvent(WorkflowStepEvent event) async {
stemLogger.info('workflow.step', {
stemLogger.info('workflow.execution', {
'run': event.runId,
'workflow': event.workflow,
'step': event.stepId,
Expand Down Expand Up @@ -111,5 +112,6 @@ A minimal dashboard typically charts:
- Scheduler drift (`StemSignals.onScheduleEntryDispatched` drift metrics).

Exporters can be mixed—enable console during development and OTLP in staging/
production. For local exploration, run the `examples/otel_metrics` stack to see
metrics in a collector + Jaeger pipeline.
production. For local exploration, run the
`packages/stem/example/otel_metrics` stack to see metrics in a collector +
Jaeger pipeline.
19 changes: 16 additions & 3 deletions .site/docs/core-concepts/persistence.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ sidebar_position: 7
slug: /core-concepts/persistence
---

Use persistence when you need durable task state, shared schedules, or
revocation storage. Stem ships with Redis, Postgres, and SQLite adapters plus
in-memory variants for local development.
Use persistence when you need durable task state, workflow state, shared
schedules, or revocation storage. Stem ships with Redis, Postgres, and SQLite
adapters plus in-memory variants for local development.

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
Expand Down Expand Up @@ -64,6 +64,19 @@ Handlers needing bespoke treatment can override `TaskMetadata.argsEncoder` and
`TaskMetadata.resultEncoder`; the worker ensures only that task uses the custom
encoder while the rest fall back to the global defaults.

## Workflow store

Workflow stores persist:

- workflow runs and status
- flow step results and script checkpoint results
- suspension/watcher records
- due-run scheduling metadata

That store is what allows workflow resumes, run inspection, and recovery across
worker restarts. See the top-level [Workflows](../workflows/index.md) section
for the durable orchestration model and runtime behavior.

## Schedule & lock stores

```dart title="lib/beat_bootstrap.dart" file=<rootDir>/../packages/stem/example/docs_snippets/lib/persistence.dart#persistence-beat-stores
Expand Down
4 changes: 2 additions & 2 deletions .site/docs/core-concepts/rate-limiting.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ import TabItem from '@theme/TabItem';
```

</TabItem>
<TabItem value="registry" label="Bootstrap StemApp">
<TabItem value="app" label="Bootstrap StemApp">

```dart title="lib/rate_limiting.dart" file=<rootDir>/../packages/stem/example/docs_snippets/lib/rate_limiting.dart#rate-limit-demo-registry

Expand Down Expand Up @@ -157,7 +157,7 @@ Group rate limits share a limiter bucket across related tasks.

## Redis-backed limiter example

The `example/rate_limit_delay` demo ships a Redis fixed-window limiter. It:
The `packages/stem/example/rate_limit_delay` demo ships a Redis fixed-window limiter. It:

- shares tokens across multiple workers,
- logs when a token is granted or denied,
Expand Down
1 change: 1 addition & 0 deletions .site/docs/core-concepts/routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ title: Routing Configuration
sidebar_label: Routing
sidebar_position: 3
slug: /core-concepts/routing
description: Load routing config, build worker subscriptions, and resolve queue or broadcast targets in Stem.
---

Stem workers and publishers resolve queue and broadcast targets from the routing
Expand Down
4 changes: 2 additions & 2 deletions .site/docs/core-concepts/signing.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export STEM_SIGNING_ACTIVE_KEY=v1

2) Wire the signer into producers, workers, and schedulers.

These snippets come from the `example/microservice` project so you can see the
These snippets come from the `packages/stem/example/microservice` project so you can see the
full context.

<Tabs>
Expand Down Expand Up @@ -144,7 +144,7 @@ export STEM_SIGNING_ACTIVE_KEY=primary
4) Remove the old key after the backlog drains.

Example: producer logging the active key and enqueuing during rotation (from
`example/signing_key_rotation`):
`packages/stem/example/signing_key_rotation`):

<Tabs>
<TabItem value="active-key" label="Rotation: log active key + signing state">
Expand Down
136 changes: 136 additions & 0 deletions .site/docs/core-concepts/stem-builder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
---
title: stem_builder
sidebar_label: stem_builder
sidebar_position: 15
slug: /core-concepts/stem-builder
---

`stem_builder` generates workflow/task definitions, manifests, helper output,
and typed workflow refs from annotations, so you can avoid stringly-typed
wiring.

This page focuses on the generator itself. For the workflow authoring model and
durable runtime behavior, start with the top-level
[Workflows](../workflows/index.md) section, especially
[Annotated Workflows](../workflows/annotated-workflows.md).

For script workflows, generated checkpoints are introspection metadata. The
actual execution plan still comes from `run(...)`.

## Install

```bash
dart pub add stem
dart pub add --dev build_runner stem_builder
```

## Define Annotated Workflows and Tasks

```dart
import 'package:stem/stem.dart';

part 'workflow_defs.stem.g.dart';

@WorkflowDefn(
name: 'commerce.user_signup',
kind: WorkflowKind.script,
starterName: 'UserSignup',
)
class UserSignupWorkflow {
Future<Map<String, Object?>> run(String email) async {
final user = await createUser(email);
await sendWelcomeEmail(email);
return {'userId': user['id'], 'status': 'done'};
}

@WorkflowStep(name: 'create-user')
Future<Map<String, Object?>> createUser(String email) async {
return {'id': 'usr-$email'};
}

@WorkflowStep(name: 'send-welcome-email')
Future<void> sendWelcomeEmail(String email) async {}
}

@TaskDefn(name: 'commerce.audit.log', runInIsolate: false)
Future<void> logAudit(TaskInvocationContext ctx, String event, String id) async {
ctx.progress(1.0, data: {'event': event, 'id': id});
}
```

## Generate

```bash
dart run build_runner build --delete-conflicting-outputs
```

Generated output (`workflow_defs.stem.g.dart`) includes:

- `stemModule`
- typed workflow refs like `StemWorkflowDefinitions.userSignup`
- typed task definitions, enqueue helpers, and typed result wait helpers

## Wire Into StemWorkflowApp

Use the generated definitions/helpers directly with `StemWorkflowApp`:

```dart
final workflowApp = await StemWorkflowApp.fromUrl(
'memory://',
module: stemModule,
);

await workflowApp.start();
final result = await StemWorkflowDefinitions.userSignup
.call((email: 'user@example.com'))
.startAndWaitWithApp(workflowApp);
```

If you already manage a `StemApp` for a larger service, reuse it instead of
bootstrapping a second app:

```dart
final stemApp = await StemApp.fromUrl(
'redis://localhost:6379',
adapters: const [StemRedisAdapter()],
tasks: stemModule.tasks,
);

final workflowApp = await StemWorkflowApp.create(
stemApp: stemApp,
module: stemModule,
);
```

If you already centralize broker/backend wiring in a `StemClient`, prefer the
shared-client path:

```dart
final client = await StemClient.fromUrl(
'redis://localhost:6379',
adapters: const [StemRedisAdapter()],
);

final workflowApp = await client.createWorkflowApp(module: stemModule);
```

## Parameter and Signature Rules

- Parameters after context must be required positional serializable values.
- Parameters after context must be required positional values that are either
serializable or codec-backed DTOs.
- Script workflow `run(...)` can be plain (no annotation required).
- `@WorkflowRun` is still supported for explicit run entrypoints.
- Step methods use `@WorkflowStep`.
- Plain `run(...)` is best when called step methods only need serializable
parameters.
- Use `@WorkflowRun()` plus `WorkflowScriptContext` when you need to enter a
context-aware script checkpoint that consumes `WorkflowScriptStepContext`.
- DTO classes are supported when they provide:
- `Map<String, Object?> toJson()`
- `factory Type.fromJson(Map<String, Object?> json)` or an equivalent named
`fromJson` constructor
- Typed task results can use the same DTO convention.
- Workflow inputs, checkpoint values, and final workflow results can use the
same DTO convention. The generated `PayloadCodec` persists the JSON form
while workflow code continues to work with typed objects.
18 changes: 10 additions & 8 deletions .site/docs/core-concepts/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ slug: /core-concepts/tasks
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

Tasks are the units of work executed by Stem workers. Each task is represented by
a handler registered in a `TaskRegistry`. Handlers expose metadata through
`TaskOptions`, which control routing, retry behavior, timeouts, and isolation.
Tasks are the units of work executed by Stem workers. In the common path, you
provide handlers directly via `tasks: [...]` on `Stem`, `Worker`, `StemApp`, or
`StemClient`. Handlers expose metadata through `TaskOptions`, which control
routing, retry behavior, timeouts, and isolation.

## Registering Handlers
## Providing Handlers

<Tabs>
<TabItem value="in-memory" label="In-memory (tasks/email_task.dart)">
Expand Down Expand Up @@ -59,7 +60,7 @@ retry cadence by:
- Tuning the broker connection (e.g. Redis `blockTime`, `claimInterval`,
`defaultVisibilityTimeout`) so delayed messages are drained quickly.

See the `examples/retry_task` Compose demo for a runnable setup that prints
See the `packages/stem/example/retry_task` Compose demo for a runnable setup that prints
every retry signal and shows how the strategy interacts with broker timings.

```dart title="lib/retry_backoff.dart" file=<rootDir>/../packages/stem/example/docs_snippets/lib/retry_backoff.dart#retry-backoff-strategy
Expand All @@ -82,9 +83,9 @@ every retry signal and shows how the strategy interacts with broker timings.
Use the context to build idempotent handlers. Re-enqueue work, cancel jobs, or
store audit details in `context.meta`.

See the `example/task_context_mixed` demo for a runnable sample that exercises
See the `packages/stem/example/task_context_mixed` demo for a runnable sample that exercises
inline + isolate enqueue, TaskRetryPolicy overrides, and enqueue options.
The `example/task_usage_patterns.dart` sample shows in-memory TaskContext and
The `packages/stem/example/task_usage_patterns.dart` sample shows in-memory TaskContext and
TaskInvocationContext patterns without external dependencies.

### Enqueue from a running task
Expand Down Expand Up @@ -177,4 +178,5 @@ metadata overrides:

Because encoders are centrally registered inside the
`TaskPayloadEncoderRegistry`, every producer/worker instance that shares the
registry can resolve encoder ids reliably—even across processes or languages.
same encoder configuration can resolve encoder ids reliably, even across
processes or languages.
Loading
Loading