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
28 changes: 22 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,19 @@
# Durable workspace
# Durable

[![CI](https://github.com/wavezync/durable/actions/workflows/ci.yml/badge.svg)](https://github.com/wavezync/durable/actions/workflows/ci.yml)
[![durable on Hex](https://img.shields.io/hexpm/v/durable.svg?label=durable)](https://hex.pm/packages/durable)
[![durable_dashboard on Hex](https://img.shields.io/hexpm/v/durable_dashboard.svg?label=durable_dashboard)](https://hex.pm/packages/durable_dashboard)
[![Docs](https://img.shields.io/badge/docs-durable.wavezync.com-ffc27a)](https://durable.wavezync.com/docs/)
[![License](https://img.shields.io/hexpm/l/durable.svg)](LICENSE)

Durable, reliable, resumable workflows for Elixir. Write workflows as plain
steps; every step is saved, so runs resume after crashes, deploys and long
waits.

- **Docs, guides and interactive examples:** [durable.wavezync.com](https://durable.wavezync.com)
- **API reference:** [durable](https://hexdocs.pm/durable) and [durable_dashboard](https://hexdocs.pm/durable_dashboard) on HexDocs

## Workspace

This is a path-dep monorepo (pnpm-workspaces style) containing the Durable
workflow engine and its surrounding packages. Each subdirectory is an
Expand All @@ -7,11 +22,12 @@ publishing pipeline.

## Packages

| Path | Package | Description |
| --- | --- | --- |
| [`durable/`](durable/) | `:durable` | Core workflow engine — resumable, reliable workflows with PostgreSQL persistence. |
| [`durable_dashboard/`](durable_dashboard/) | `:durable_dashboard` | LiveView-first web dashboard for monitoring and managing Durable workflows. |
| [`examples/phoenix_demo/`](examples/phoenix_demo/) | (unpublished) | Reference Phoenix app wiring up `:durable` and `:durable_dashboard`. |
| Path | Package | Version | Docs | Description |
| --- | --- | --- | --- | --- |
| [`durable/`](durable/) | `:durable` | [![Hex](https://img.shields.io/hexpm/v/durable.svg)](https://hex.pm/packages/durable) | [HexDocs](https://hexdocs.pm/durable) | Core workflow engine — resumable, reliable workflows with PostgreSQL persistence. |
| [`durable_dashboard/`](durable_dashboard/) | `:durable_dashboard` | [![Hex](https://img.shields.io/hexpm/v/durable_dashboard.svg)](https://hex.pm/packages/durable_dashboard) | [HexDocs](https://hexdocs.pm/durable_dashboard) | LiveView-first web dashboard for monitoring and managing Durable workflows. |
| [`examples/phoenix_demo/`](examples/phoenix_demo/) | (unpublished) | | | Reference Phoenix app wiring up `:durable` and `:durable_dashboard`. |
| [`site/`](site/) | (unpublished) | | [durable.wavezync.com](https://durable.wavezync.com) | [durable.wavezync.com](https://durable.wavezync.com): Astro site. Its docs are the packages' own markdown, read in place (`site/src/docs.ts`). `cd site && pnpm install && pnpm dev` |

## Workspace commands

Expand Down
12 changes: 11 additions & 1 deletion durable/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
# Durable

[![Build Status](https://github.com/wavezync/durable/actions/workflows/ci.yml/badge.svg)](https://github.com/wavezync/durable/actions/workflows/ci.yml)
[![CI](https://github.com/wavezync/durable/actions/workflows/ci.yml/badge.svg)](https://github.com/wavezync/durable/actions/workflows/ci.yml)
[![Hex.pm](https://img.shields.io/hexpm/v/durable.svg)](https://hex.pm/packages/durable)
[![HexDocs](https://img.shields.io/badge/hex-docs-8f75f0.svg)](https://hexdocs.pm/durable)
[![Downloads](https://img.shields.io/hexpm/dt/durable.svg)](https://hex.pm/packages/durable)
[![License](https://img.shields.io/hexpm/l/durable.svg)](https://github.com/wavezync/durable/blob/main/LICENSE)

A durable, resumable workflow engine for Elixir. Similar to Temporal/Inngest.

**Docs:** [durable.wavezync.com](https://durable.wavezync.com/docs/) ·
**Dashboard:** [`durable_dashboard`](https://hex.pm/packages/durable_dashboard) ·
**API:** [HexDocs](https://hexdocs.pm/durable)

## Features

- **Pipeline Model** - Context flows from step to step, simple and explicit
Expand Down Expand Up @@ -573,6 +580,9 @@ mix durable.cleanup --older-than 24h --dry-run # preview what would be

## Dashboard

[![Hex.pm](https://img.shields.io/hexpm/v/durable_dashboard.svg)](https://hex.pm/packages/durable_dashboard)
[![HexDocs](https://img.shields.io/badge/hex-docs-8f75f0.svg)](https://hexdocs.pm/durable_dashboard)

`durable_dashboard` is a LiveView-first console for monitoring and managing
running workflows. Mount it into your Phoenix router with one line:

Expand Down
9 changes: 9 additions & 0 deletions durable_dashboard/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# DurableDashboard

[![CI](https://github.com/wavezync/durable/actions/workflows/ci.yml/badge.svg)](https://github.com/wavezync/durable/actions/workflows/ci.yml)
[![Hex.pm](https://img.shields.io/hexpm/v/durable_dashboard.svg)](https://hex.pm/packages/durable_dashboard)
[![HexDocs](https://img.shields.io/badge/hex-docs-8f75f0.svg)](https://hexdocs.pm/durable_dashboard)
[![License](https://img.shields.io/hexpm/l/durable_dashboard.svg)](https://github.com/wavezync/durable/blob/main/LICENSE)

**Docs:** [durable.wavezync.com](https://durable.wavezync.com/docs/dashboard/) ·
**Engine:** [`durable`](https://hex.pm/packages/durable) ·
**API:** [HexDocs](https://hexdocs.pm/durable_dashboard)

LiveView-first web console for the [Durable](https://hex.pm/packages/durable)
workflow engine.

Expand Down
4 changes: 4 additions & 0 deletions site/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules/
dist/
.astro/
.wrangler/
58 changes: 58 additions & 0 deletions site/astro.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { defineConfig } from 'astro/config';
import starlight from '@astrojs/starlight';
import { satteri } from '@astrojs/markdown-satteri';
import { DOCS, REPO, docLinks } from './src/docs.ts';
import { changelog } from './src/changelog.ts';
import { EXAMPLES } from './src/examples.ts';
import { macroCalls, night } from './src/code-theme.ts';

const doc = (d) => ({ label: d.label, slug: d.id });
const group = (name) => DOCS.filter((d) => d.group === name).map(doc);

export default defineConfig({
site: 'https://durable.wavezync.com',
trailingSlash: 'always',
markdown: { processor: satteri({ mdastPlugins: [docLinks, changelog] }) },
integrations: [
starlight({
title: 'Durable',
description:
'Open-source durable workflow engine for Elixir. Retries, sleeps, events, human approvals and sagas, saved step by step so runs resume after crashes.',
logo: { src: './src/assets/logo.svg' },
head: [
{ tag: 'meta', attrs: { property: 'og:image', content: 'https://durable.wavezync.com/og.png' } },
{ tag: 'meta', attrs: { property: 'og:image:width', content: '1200' } },
{ tag: 'meta', attrs: { property: 'og:image:height', content: '630' } },
{ tag: 'meta', attrs: { property: 'og:image:alt', content: 'Resilient, Reliable, Resumable workflows for Elixir' } },
{ tag: 'meta', attrs: { name: 'twitter:image', content: 'https://durable.wavezync.com/og.png' } },
],
favicon: '/favicon.svg',
social: [{ icon: 'github', label: 'GitHub', href: REPO }],
customCss: ['./src/styles/fonts.css', './src/styles/theme.css'],
components: {
Head: './src/components/Head.astro',
PageTitle: './src/components/PageTitle.astro',
SocialIcons: './src/components/HeaderLinks.astro',
},
expressiveCode: {
themes: [night, 'github-light'],
shiki: { transformers: [macroCalls] },
styleOverrides: { borderRadius: '12px', codeFontFamily: 'var(--sl-font-mono)', uiFontFamily: 'var(--sl-font)' },
},
sidebar: [
{ label: 'Start here', items: group('Start here') },
{ label: 'Guides', items: group('Guides') },
{ label: 'Examples', items: [{ label: 'All examples', link: '/examples/' }, ...EXAMPLES.map((e) => ({ label: e.heading, link: `/examples/${e.slug}/` }))] },
{ label: 'Dashboard', items: group('Dashboard') },
{ label: 'Changelog', items: group('Changelog') },
{
label: 'API reference',
items: [
{ label: 'durable on HexDocs', link: 'https://hexdocs.pm/durable' },
{ label: 'durable_dashboard on HexDocs', link: 'https://hexdocs.pm/durable_dashboard' },
],
},
],
}),
],
});
114 changes: 114 additions & 0 deletions site/og/og.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<!doctype html>
<!--
The social card (public/og.png): an agent workflow part way through, with the
headline under it. After editing, regenerate from site/:
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --headless=new --hide-scrollbars \
--force-device-scale-factor=1 --window-size=1200,630 --virtual-time-budget=3000 \
--screenshot=public/og.png "file://$PWD/og/og.html"
-->
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Durable social card</title>
<style>
@font-face { font-family: Sora; font-weight: 100 800; src: url('../node_modules/@fontsource-variable/sora/files/sora-latin-wght-normal.woff2') format('woff2'); }
@font-face { font-family: 'Geist Mono'; font-weight: 400; src: url('../node_modules/@fontsource/geist-mono/files/geist-mono-latin-400-normal.woff2') format('woff2'); }
@font-face { font-family: 'Geist Mono'; font-weight: 500; src: url('../node_modules/@fontsource/geist-mono/files/geist-mono-latin-500-normal.woff2') format('woff2'); }
:root { --night: #0a0f1e; --ink: #f3eef3; --dim: #8994ab; --teal: #2bb3a3; --amber: #ffc27a; --violet: #6997fc; --dawn: linear-gradient(90deg, #8fb0ff, #d6a6ff 55%, #ffb69a); }
* { box-sizing: border-box; }
html, body { margin: 0; width: 1200px; height: 630px; overflow: hidden; }
body {
position: relative; padding: 64px 72px; font-family: Sora, sans-serif; color: var(--ink);
background:
radial-gradient(640px 360px at 64% 34%, rgba(214, 166, 255, 0.16), transparent 70%),
radial-gradient(520px 380px at 100% 110%, rgba(255, 182, 154, 0.26), transparent 70%),
radial-gradient(600px 420px at 0% -20%, rgba(176, 201, 255, 0.18), transparent 70%),
radial-gradient(circle at 1px 1px, rgba(255, 255, 255, 0.05) 1px, transparent 0) 0 0 / 26px 26px,
var(--night);
}
header { display: flex; align-items: center; justify-content: space-between; }
.brand { display: flex; align-items: center; gap: 14px; font-size: 30px; font-weight: 600; letter-spacing: -0.03em; }
.wz { display: flex; align-items: center; gap: 10px; }
.wz .word { filter: brightness(0) invert(1); opacity: 0.9; }

/* plan → (search ‖ read_docs) → draft → review (waiting) → send. Positions are px in a 1056×220 box. */
.graph { position: absolute; left: 72px; top: 150px; width: 1056px; height: 220px; font: 500 19px 'Geist Mono', monospace; }
.graph > * { position: absolute; }
.start { left: 0; top: 100px; width: 20px; height: 20px; border-radius: 50%; background: var(--teal); }
.e { top: 109px; height: 3px; border-radius: 2px; }
.elbow { width: 25px; border: 0 solid; }
.elbow.up { top: 47px; height: 65px; border-top-width: 3px; }
.elbow.down { top: 109px; height: 64px; border-bottom-width: 3px; }
.elbow.fan { border-left-width: 3px; }
.elbow.fan.up { border-top-left-radius: 14px; }
.elbow.fan.down { border-bottom-left-radius: 14px; }
.elbow.join { border-right-width: 3px; }
.elbow.join.up { border-top-right-radius: 14px; }
.elbow.join.down { border-bottom-right-radius: 14px; }
.n {
height: 48px; display: flex; align-items: center; gap: 10px; padding: 0 16px 0 12px; border-radius: 24px;
background: #182a35; border: 2px solid rgba(43, 179, 163, 0.7);
}
.n > i { width: 22px; height: 22px; flex: none; border-radius: 50%; background: var(--teal); display: grid; place-items: center; }
.n > i svg { width: 13px; height: 13px; }
.n.wait { background: #2b2328; border: 2px dashed var(--amber); box-shadow: 0 0 0 6px rgba(255, 194, 122, 0.12), 0 0 44px rgba(255, 194, 122, 0.4); }
.n.next { color: var(--dim); background: #151d2d; border: 2px dashed #65718b; }
.n.next > i { background: none; border: 2px dashed #65718b; }
/* LLM steps carry a sparkle, as on the landing page's agent demo. */
.llm { position: absolute; right: -9px; top: -10px; width: 24px; height: 24px; border-radius: 50%; background: var(--violet); border: 2px solid var(--night); display: grid; place-items: center; }
.says { left: 708px; top: 148px; width: 150px; text-align: center; font-weight: 400; font-size: 16px; color: var(--amber); }

h1 { position: absolute; left: 72px; bottom: 60px; margin: 0; font-size: 62px; font-weight: 600; letter-spacing: -0.05em; line-height: 1.04; }
h1 span { display: block; }
h1 .tail { display: flex; align-items: center; gap: 14px; }
h1 .tail span { padding-bottom: 0.08em; background: var(--dawn); -webkit-background-clip: text; background-clip: text; color: transparent; }
</style>
</head>
<body>
<svg width="0" height="0" style="position: absolute" aria-hidden="true">
<symbol id="check" viewBox="0 0 24 24"><path d="M20 6 9 17l-5-5" fill="none" stroke="#0a0f1e" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/></symbol>
<symbol id="sparkle" viewBox="0 0 24 24"><path fill="#fff" d="M9.94 15.5a2 2 0 0 0-1.44-1.44l-6.13-1.58a.5.5 0 0 1 0-.96L8.5 9.94A2 2 0 0 0 9.94 8.5l1.58-6.14a.5.5 0 0 1 .96 0L14.06 8.5a2 2 0 0 0 1.44 1.44l6.14 1.58a.5.5 0 0 1 0 .96l-6.14 1.58a2 2 0 0 0-1.44 1.44l-1.58 6.14a.5.5 0 0 1-.96 0z"/></symbol>
</svg>

<header>
<div class="brand">
<svg width="44" height="44" viewBox="0 0 26 26" aria-hidden="true"><path d="M3 8H13" stroke="#637294" stroke-width="2" stroke-linecap="round"/><path d="M13 8C17 8 17 18 21 18H23" fill="none" stroke="#6997fc" stroke-width="2" stroke-linecap="round" stroke-dasharray="2 3"/><circle cx="13" cy="8" r="3.5" fill="#f2785b"/><circle cx="22" cy="18" r="3.5" fill="#2bb3a3"/></svg>
Durable
</div>
<div class="wz">
<img src="../src/assets/wavezync-mark.png" width="34" height="31" alt="" />
<img class="word" src="../src/assets/wavezync-wordmark.png" width="115" height="22" alt="WaveZync" />
</div>
</header>

<div class="graph">
<span class="start"></span>
<span class="e" style="left: 28px; width: 50px; background: linear-gradient(90deg, #8fb0ff, #9aaeff)"></span>
<span class="n" style="left: 86px; top: 86px; width: 114px"><i><svg><use href="#check"/></svg></i>plan<span class="llm"><svg width="13" height="13"><use href="#sparkle"/></svg></span></span>

<span class="e" style="left: 208px; width: 28px; border-radius: 0; background: #b6abff"></span>
<span class="elbow fan up" style="left: 233px; border-color: #b6abff"></span>
<span class="elbow fan down" style="left: 233px; border-color: #b6abff"></span>
<span class="n" style="left: 266px; top: 24px; width: 170px"><i><svg><use href="#check"/></svg></i>search</span>
<span class="n" style="left: 266px; top: 148px; width: 170px"><i><svg><use href="#check"/></svg></i>read_docs</span>
<span class="elbow join up" style="left: 444px; border-color: #d6a6ff"></span>
<span class="elbow join down" style="left: 444px; border-color: #d6a6ff"></span>
<span class="e" style="left: 466px; width: 28px; border-radius: 0; background: #d6a6ff"></span>

<span class="n" style="left: 502px; top: 86px; width: 132px"><i><svg><use href="#check"/></svg></i>draft<span class="llm"><svg width="13" height="13"><use href="#sparkle"/></svg></span></span>
<span class="e" style="left: 642px; width: 58px; background: linear-gradient(90deg, #d6a6ff, #ffb69a)"></span>
<span class="n wait" style="left: 708px; top: 86px; width: 150px"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#ffc27a" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" style="flex: none"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>review</span>
<span class="says">waiting for you</span>
<span class="e" style="left: 866px; width: 54px; border-radius: 0; background: repeating-linear-gradient(90deg, rgba(255, 255, 255, 0.2) 0 8px, transparent 8px 16px)"></span>
<span class="n next" style="left: 928px; top: 86px; width: 116px"><i></i>send</span>
</div>

<h1>
<span>Resilient, Reliable, Resumable</span>
<span class="tail">
<span>workflows for Elixir</span>
<svg width="31" height="48" viewBox="0 0 160 245" aria-hidden="true"><path fill="#8847B9" d="M125.504 111.281c-5.677-5.353-12.253-10.842-19.865-16.313-18.3-13.152-27.443-29.06-31.812-43.468 0 0-5.16 8.602-9.827 17.602C56.481 83.602 49.5 107.5 51 127c2.354 5.818 5.048 9.821 8.206 15.883 4.608 8.849 9.872 16.481 15.487 23.062q2.679.37 5.332.557c23.224 1.637 44.394-7.179 51.691-23.619 5.937-13.377-6.212-31.602-6.212-31.602"/><path fill="#B88CD9" d="M159.896 161.988C156.513 88.578 87.14 100.885 87.14 0a152 152 0 0 0-15.563 11.124s-4.4 18.453 2.25 40.376c4.37 14.409 13.512 30.316 31.812 43.468 7.612 5.471 14.188 10.96 19.865 16.313 28.735 27.099 34.392 50.707 34.392 50.707"/><path fill="#6E2E9E" d="M71.577 11.124S53.649 28.173 40.105 59.99a174 174 0 0 0-3.576 9.112S38.626 96.416 51 127c-1.5-19.5 5.481-43.398 13-57.898 4.667-9 9.826-17.602 9.826-17.602-6.648-21.923-2.25-40.376-2.25-40.376"/><path fill="#B88CD9" d="M71.577 11.124c-21.02 17.105-36.483 38.81-47.545 60.844 0 0-4.218 20.93 1.717 48.915 0 0 .663-23.734 10.78-51.781a174 174 0 0 1 3.576-9.112C53.65 28.173 71.577 11.124 71.577 11.124"/><path fill="#8847B9" d="M24.032 71.968C6.572 106.745.076 142.345.001 161.988c0 0 14.733 24.636 53.497 32.921a120 120 0 0 0 15.4 2.237 132 132 0 0 1-5.031-5.074c-4.54-4.831-8.706-10.138-12.438-15.464-10.72-15.302-17.854-30.759-19.953-35.53-.463-.945-.643-1.494-.643-1.494-2.569-6.931-3.735-12.337-5.084-18.701-5.935-27.985-1.717-48.915-1.717-48.915"/><path fill="#6E2E9E" d="M110.056 194.096c36.101-8.969 49.84-32.108 49.84-32.108s-5.657-23.608-34.392-50.707c0 0 12.149 18.225 6.212 31.602-7.297 16.44-28.467 25.256-51.692 23.619a80 80 0 0 1-5.331-.557c11.201 13.13 23.797 22.076 35.363 28.151"/><path fill="#B88CD9" d="M160 165.169a120 120 0 0 0-.104-3.181s-13.739 23.139-49.84 32.108c20.82 10.936 38.302 12.573 38.302 12.573 7.387-12.093 11.642-26.301 11.642-41.5"/><path fill="#8847B9" d="M110.056 194.096c-8.678 2.156-18.649 3.493-30.031 3.493a138 138 0 0 1-11.127-.443c29.683 28.345 60.463 30.858 60.463 30.858a80.3 80.3 0 0 0 18.997-21.335s-17.482-1.637-38.302-12.573"/><path fill="#B88CD9" d="M68.898 197.146a120 120 0 0 1-15.4-2.237c.462 3.07.964 5.947 1.502 8.591 5.712 28.089 25.025 41.5 25.025 41.5 18.615 0 35.746-6.349 49.336-16.996 0 0-30.78-2.513-60.463-30.858"/><path fill="#A066CC" d="M.049 165.169C.049 209.258 35.855 245 80.025 245c0 0-19.313-13.411-25.025-41.5a171 171 0 0 1-1.502-8.591C14.734 186.624.001 161.988.001 161.988a80 80 0 0 0 .048 3.181"/><path fill="#531F7A" d="M80.025 197.589c11.382 0 21.353-1.337 30.031-3.493-11.566-6.075-24.162-15.021-35.363-28.151-5.615-6.581-10.879-14.213-15.487-23.062C56.048 136.821 53.354 132.818 51 127 38.626 96.416 36.529 69.102 36.529 69.102c-10.117 28.047-10.78 51.781-10.78 51.781 1.35 6.364 2.515 11.77 5.084 18.701 0 0 .218.528.643 1.494 2.099 4.771 9.233 20.228 19.953 35.53 3.732 5.326 7.898 10.633 12.438 15.464a132 132 0 0 0 5.031 5.074q.782.064 1.574.118c3.068.213 6.25.325 9.553.325"/></svg>
</span>
</h1>
</body>
</html>
28 changes: 28 additions & 0 deletions site/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "durable-site",
"private": true,
"type": "module",
"scripts": {
"dev": "astro dev",
"build": "astro build",
"preview": "astro preview",
"check": "astro check",
"deploy": "astro build && wrangler deploy",
"preview:cf": "astro build && wrangler dev"
},
"dependencies": {
"@astrojs/markdown-remark": "^7.3.4",
"@astrojs/markdown-satteri": "0.4.5",
"@astrojs/starlight": "0.42.6",
"@fontsource-variable/sora": "5.3.0",
"@fontsource/geist-mono": "5.3.0",
"@lucide/astro": "1.61.0",
"astro": "7.4.2"
},
"devDependencies": {
"@astrojs/check": "^0.9.12",
"@types/node": "^24.19.2",
"typescript": "^6.0.3",
"wrangler": "^4.151.3"
}
}
Loading
Loading