stem_postgres 0.2.0 copy "stem_postgres: ^0.2.0" to clipboard
stem_postgres: ^0.2.0 copied to clipboard

Postgres broker, result backend, and scheduler utilities for Stem.

Stem Logo

stem_postgres #

pub package Dart License Coverage Buy Me A Coffee

Postgres broker, result backend, and scheduler helpers for the Stem runtime.

Install #

dart pub add stem_postgres

Add the core runtime if you haven't already:

dart pub add stem

Usage #

Direct enqueue #

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

Future<void> main() async {
  final registry = SimpleTaskRegistry()
    ..register(
      FunctionTaskHandler(
        name: 'demo.pg',
        entrypoint: (context, args) async {
          print('Hello ${(args['name'] as String?) ?? 'world'}');
        },
      ),
    );

  final broker = await PostgresBroker.connect(
    'postgresql://postgres:postgres@localhost:5432/stem',
  );
  final backend = await PostgresResultBackend.connect(
    'postgresql://postgres:postgres@localhost:5432/stem',
  );

  final stem = Stem(broker: broker, registry: registry, backend: backend);
  await stem.enqueue('demo.pg', args: {'name': 'Stem'});
}

Transactional outbox #

Use PostgresTransactionalOutbox when a task must be published atomically with application data in PostgreSQL. The outbox facade is producer-only: enqueue through it inside outbox.transaction, and pass the underlying broker to the relay.

final outbox = await PostgresTransactionalOutbox.connect(
  'postgresql://postgres:postgres@localhost:5432/stem',
);
final broker = await PostgresBroker.connect(
  'postgresql://postgres:postgres@localhost:5432/stem',
);
final producerBroker = outbox.wrap(broker); // accepts any QueueBroker
final stem = Stem(broker: producerBroker, registry: registry);

await outbox.transaction((transaction) async {
  await transaction.context.table('orders').create({
    'id': orderId,
    'state': 'created',
  });
  await stem.enqueue('orders.process', args: {'id': orderId});
});

await outbox.dispatch(broker: broker);

await broker.close();
await outbox.close();

The relay is at least once. A crash after broker publication and before the outbox row is marked dispatched can publish the same envelope again. Stem's Postgres broker deduplicates queue rows by envelope ID, but task handlers and external side effects must still be idempotent. Run migrations when opening the outbox, or apply the stem_task_outbox migration as part of your normal deployment process. The transaction boundary covers application writes and broker publication records; result-backend status writes and unique-task claims remain separate stores and should not be treated as part of the same database commit unless they are made transaction-aware by the application.

Distributed rate limiting #

PostgresRateLimiter shares a token bucket across worker processes. Refill uses PostgreSQL server time, and each acquire locks and updates one bucket row inside a transaction.

final limiter = await PostgresRateLimiter.connect(
  'postgresql://postgres:postgres@localhost:5432/stem',
  namespace: 'billing-worker',
);

final workerConfig = StemWorkerConfig(rateLimiter: limiter);

Opening the limiter runs the package migrations, including the stem_rate_limit_buckets table. A denied acquisition includes retryAfter so the worker can schedule the next attempt. Close the limiter with the worker's other resources.

Typed TaskDefinition #

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

final demoPg = TaskDefinition<PgArgs, void>(
  name: 'demo.pg',
  encodeArgs: (args) => {'name': args.name},
  metadata: TaskMetadata(description: 'Postgres-backed demo task'),
);

class PgArgs {
  const PgArgs({required this.name});
  final String name;
}

Future<void> main() async {
  final registry = SimpleTaskRegistry()
    ..register(
      FunctionTaskHandler<void>(
        name: demoPg.name,
        entrypoint: (context, args) async {
          print('Hello ${(args['name'] as String?) ?? 'world'}');
        },
        metadata: demoPg.metadata,
      ),
    );

  final broker = await PostgresBroker.connect(
    'postgresql://postgres:postgres@localhost:5432/stem',
  );
  final backend = await PostgresResultBackend.connect(
    'postgresql://postgres:postgres@localhost:5432/stem',
  );

  final stem = Stem(broker: broker, registry: registry, backend: backend);
  await stem.enqueueCall(demoPg(const PgArgs(name: 'Stem')));
}

Tests #

Postgres integration suites expect the docker stack provided by stem_cli:

source ../../stem_cli/_init_test_env
dart test

The tests skip automatically if STEM_TEST_POSTGRES_URL is missing.

0
likes
140
points
146
downloads

Documentation

API reference

Publisher

verified publisherglenfordwilliams.com

Weekly Downloads

Postgres broker, result backend, and scheduler utilities for Stem.

Repository (GitHub)
View/report issues
Contributing

License

unknown (license)

Dependencies

artisanal, collection, contextual, ormed, ormed_postgres, path, postgres, stem, uuid

More

Packages that depend on stem_postgres