Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
49 changes: 26 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,48 +29,35 @@
## Quick Start

```dart
import 'dart:async';

import 'package:stem/stem.dart';

// 1. Define a task
class EmailTask extends TaskHandler<String> {
@override
String get name => 'email.send';

@override
TaskOptions get options => const TaskOptions(maxRetries: 3);

@override
Future<String> call(TaskContext ctx, Map<String, Object?> args) async {
final to = args['to'] as String;
// ... send email logic ...
return 'sent to $to';
}
}

// 2. Bootstrap and run
Future<void> main() async {
final app = await StemApp.inMemory(
tasks: [EmailTask()],
workerConfig: const StemWorkerConfig(
queue: 'default',
consumerName: 'my-worker',
concurrency: 4,
),
);

await app.start();
final client = await StemClient.inMemory(tasks: [EmailTask()]);
final worker = await client.createWorker();
unawaited(worker.start());

// 3. Enqueue work
final taskId = await app.stem.enqueue(
final taskId = await client.stem.enqueue(
'email.send',
args: {'to': 'hello@example.com'},
);
final result = await client.stem.waitForTask<String>(taskId);
print('Result: ${result?.value}');

// 4. Wait for result
final result = await app.stem.waitForTask<String>(taskId);
print('Result: ${result?.value}'); // "sent to hello@example.com"

await app.close();
await worker.shutdown();
await client.close();
}
```

Expand Down Expand Up @@ -154,6 +141,7 @@ Future<void> main() async {
| [`stem_sqlite`](./packages/stem_sqlite) | SQLite broker and result backend for local dev/testing | [![pub](https://img.shields.io/pub/v/stem_sqlite.svg)](https://pub.dev/packages/stem_sqlite) |
| [`stem_builder`](./packages/stem_builder) | Build-time code generator for annotated tasks and workflows | [![pub](https://img.shields.io/pub/v/stem_builder.svg)](https://pub.dev/packages/stem_builder) |
| [`stem_adapter_tests`](./packages/stem_adapter_tests) | Shared contract test suites for adapter implementations | [![pub](https://img.shields.io/pub/v/stem_adapter_tests.svg)](https://pub.dev/packages/stem_adapter_tests) |
| [`stem_memory`](./packages/stem_memory) | In-memory adapter package (broker/backend/workflow/scheduler factories) | [![pub](https://img.shields.io/pub/v/stem_memory.svg)](https://pub.dev/packages/stem_memory) |
| [`stem_dashboard`](./packages/dashboard) | Hotwire-based operations dashboard (experimental) | — |

---
Expand Down Expand Up @@ -284,8 +272,23 @@ task test

# Run coverage workflow for core adapters/runtime packages
task coverage

# Run targeted adapter suites (auto-bootstraps integration env)
task test:contract
task test:redis
task test:postgres
```

Targeted adapter tasks now bootstrap integration environment automatically.
If bootstrap still fails (for example Docker unavailable), run:

```bash
source ./packages/stem_cli/_init_test_env
```

Capability flags and skip behavior for adapter contract suites are documented in
[`packages/stem_adapter_tests/README.md`](./packages/stem_adapter_tests/README.md).

---

## Contributing
Expand Down
84 changes: 74 additions & 10 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,49 @@ includes:
dir: ./packages/stem_sqlite

tasks:
test:
desc: Run all workspace package tests with integration test env bootstrapped.
test:with-env:
internal: true
requires:
vars: [TASK_NAME, REQUIRED_VARS, RUN]
cmds:
- |
bash -lc '
set -euo pipefail
source ./packages/stem_cli/_init_test_env
task test:no-env

missing=()
for key in {{.REQUIRED_VARS}}; do
[[ -z "${!key:-}" ]] && missing+=("$key")
done

if (( ${#missing[@]} > 0 )); then
source ./packages/stem_cli/_init_test_env
missing=()
for key in {{.REQUIRED_VARS}}; do
[[ -z "${!key:-}" ]] && missing+=("$key")
done
fi

if (( ${#missing[@]} > 0 )); then
echo "DX ERROR: could not bootstrap required integration environment."
echo "Missing vars: ${missing[*]}"
echo "Run manually:"
echo " source ./packages/stem_cli/_init_test_env"
echo "Then rerun: task {{.TASK_NAME}}"
exit 1
fi

{{.RUN}}
'

test:
desc: Run all workspace package tests with integration test env bootstrapped.
cmds:
- task: test:with-env
vars:
TASK_NAME: test
REQUIRED_VARS: "STEM_TEST_REDIS_URL STEM_TEST_POSTGRES_URL"
RUN: task test:no-env

test:no-env:
desc: Run all workspace package tests without bootstrapping integration env.
cmds:
Expand All @@ -53,15 +86,46 @@ tasks:
- task: stem_redis:test
- task: stem_sqlite:test

test:contract:
desc: Run adapter contract-heavy suites with shared integration env bootstrap.
cmds:
- task: test:with-env
vars:
TASK_NAME: test:contract
REQUIRED_VARS: "STEM_TEST_REDIS_URL STEM_TEST_POSTGRES_URL"
RUN: |
task stem_adapter_tests:test
task stem_memory:test
task stem_sqlite:test
task stem_redis:test
task stem_postgres:test

test:redis:
desc: Run Redis package tests with shared integration env bootstrap.
cmds:
- task: test:with-env
vars:
TASK_NAME: test:redis
REQUIRED_VARS: STEM_TEST_REDIS_URL
RUN: task stem_redis:test

test:postgres:
desc: Run Postgres package tests with shared integration env bootstrap.
cmds:
- task: test:with-env
vars:
TASK_NAME: test:postgres
REQUIRED_VARS: STEM_TEST_POSTGRES_URL
RUN: task stem_postgres:test

coverage:
desc: Run coverage workflow with integration test env bootstrapped.
cmds:
- |
bash -lc '
set -euo pipefail
source ./packages/stem_cli/_init_test_env
task coverage:no-env
'
- task: test:with-env
vars:
TASK_NAME: coverage
REQUIRED_VARS: "STEM_TEST_REDIS_URL STEM_TEST_POSTGRES_URL"
RUN: task coverage:no-env

coverage:no-env:
desc: Run coverage workflow for core packages (no env bootstrap).
Expand Down
5 changes: 5 additions & 0 deletions packages/dashboard/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Changelog

## 0.1.0

- Initial release of the `stem_dashboard` package.
13 changes: 12 additions & 1 deletion packages/stem/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
# Changelog

## 0.1.1

- Improved bootstrap DX with explicit fail-fast errors across broker/backend/
workflow/schedule/lock/revoke resolution paths in `StemStack.fromUrl`,
including actionable hints when adapters support a URL but do not implement
the requested store kind.
- Refreshed docs to lead with `StemClient` and document adapter-focused Task
workflows.
- Aligned in-memory broker and result backend semantics with shared adapter
contracts, including broadcast fan-out behavior for in-memory broker tests.
- Added Taskfile support for package-scoped test orchestration.
- Added Taskfile-based workflows for complex examples (microservice, encrypted
payloads, signing key rotation, security profiles, and Postgres TLS),
including secret/certificate bootstrap and binary build/run helpers.
- Added shared logger injection via `setStemLogger` and reusable structured
context helpers for consistent logging metadata across core components.

Expand All @@ -26,7 +37,7 @@
- Added typed workflow, task, and canvas result APIs with customizable encoders
(TaskResultEncoder and payload encoders).
- Added new example suites (progress heartbeat, worker control lab, and the
feature-complete set) plus refreshed docs/Justfiles for running them.
feature-complete set) plus refreshed docs/Taskfiles for running them.
- Added signals registry/configuration for worker, task, scheduler, and
workflow lifecycle events.
- Improved worker runtime (isolate pool, config, heartbeats/autoscaling) plus
Expand Down
46 changes: 36 additions & 10 deletions packages/stem/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ workflow apps.
```dart
import 'dart:async';
import 'package:stem/stem.dart';
import 'package:stem_redis/stem_redis.dart';

class HelloTask implements TaskHandler<void> {
@override
Expand All @@ -60,11 +59,7 @@ class HelloTask implements TaskHandler<void> {
}

Future<void> main() async {
final client = await StemClient.create(
broker: StemBrokerFactory.redis(url: 'redis://localhost:6379'),
backend: StemBackendFactory.redis(url: 'redis://localhost:6379/1'),
tasks: [HelloTask()],
);
final client = await StemClient.inMemory(tasks: [HelloTask()]);

final worker = await client.createWorker();
unawaited(worker.start());
Expand All @@ -77,6 +72,36 @@ Future<void> main() async {
}
```

For persistent adapters, keep `StemClient` as the entrypoint and resolve
broker/backend wiring from a URL:

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

final client = await StemClient.create(
broker: redisBrokerFactory('redis://localhost:6379'),
backend: redisResultBackendFactory('redis://localhost:6379/1'),
tasks: [HelloTask()],
);
```

or use the lower-boilerplate URL helper:

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

final client = await StemClient.fromUrl(
'redis://localhost:6379',
adapters: const [StemRedisAdapter()],
overrides: const StemStoreOverrides(
backend: 'redis://localhost:6379/1',
),
tasks: [HelloTask()],
);
```

### Direct enqueue (map-based)

```dart
Expand Down Expand Up @@ -382,9 +407,10 @@ print('Body results: ${chordResult.values}');
### Task payload encoders

By default Stem stores handler arguments/results exactly as provided (JSON-friendly
structures). Configure default `TaskPayloadEncoder`s when bootstrapping `StemApp`,
`StemWorkflowApp`, or `Canvas` to plug in custom serialization (encryption,
compression, base64 wrappers, etc.) for both task arguments and persisted results:
structures). Configure default `TaskPayloadEncoder`s when bootstrapping
`StemClient`, `StemApp`, `StemWorkflowApp`, or `Canvas` to plug in custom
serialization (encryption, compression, base64 wrappers, etc.) for both task
arguments and persisted results:

```dart
import 'dart:convert';
Expand All @@ -409,7 +435,7 @@ class Base64ResultEncoder extends TaskPayloadEncoder {
}
}

final app = await StemApp.inMemory(
final client = await StemClient.inMemory(
tasks: [...],
resultEncoder: const Base64ResultEncoder(),
argsEncoder: const Base64ResultEncoder(),
Expand Down
5 changes: 5 additions & 0 deletions packages/stem/Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ version: "3"
vars:
COVERAGE_BADGE: ../../tool/coverage/coverage_badge.dart

includes:
examples:
taskfile: ./example/Taskfile.yml
dir: ./example

tasks:
test:
desc: Run stem package tests.
Expand Down
Loading
Loading