Skip to content

Repository files navigation

dandori

dandori now lives in ritsu, as one of its languages, and its documentation in English and Japanese is at https://i2y.github.io/ritsu/dandori/. This repository keeps dandori's history and its release 0.1.0; from 0.23.0 dandori comes with ritsu, and runs as ritsu dandori … or as dandori:

$ brew install i2y/tap/ritsu
$ cargo install --git https://github.com/i2y/ritsu --locked ritsu

dandori は ritsu の言語の一つになりました。文書は https://i2y.github.io/ritsu/dandori/ja/ にあり、0.23.0 からは ritsu と一緒に入ります(ritsu dandori … か dandori で呼びます)。

A small typed language for workflows that call business rules. A workflow books a hotel stay, reserves the lines of an order, answers a customer's inquiry: it calls APIs and rules, waits, retries, and drives things like a Stripe PaymentIntent from state to state.

  • Checked before it runs. Types, every arm of every match, every state a payment or an order can be left in when the workflow ends, retries that could repeat a change on the other side, the service of a .proto the workflow implements, and how long a run's history can grow on the platform it is built for.
  • Built for five platforms. Temporal (TypeScript, Python or Go), AWS Step Functions (ASL with JSONata), AWS Lambda durable functions, Argo Workflows and pydantic-graph. What each of them runs is played against one reference interpreter, on every scenario the tests generate.
  • Decisions come from outside the workflow. A .flow branches only on what a rule or a task answered: an API, an agent (OpenAI's models, Claude, or any Open Responses endpoint) whose answer comes back in a declared type, TypeSafe's Jev, which answers typed questions with how sure it is, your own code, a person's approval. A decision that must have no gaps can be a table in rulec, which proves it complete and free of overlaps, and a rule's state machine can be the type of the thing a workflow drives.

Documentation: https://i2y.github.io/dandori/, in English and Japanese. The pages are also readable here, in website/docs and website/docs-ja.

Try it in the browser: https://i2y.github.io/dandori/playground/. The checker, the builds and dandori doc, compiled to wasm32 and run in the page, on the examples or on a flow you edit, with the rules each calls; nothing is sent anywhere.

The name comes from 段取り (dandori), arranging the steps of a job beforehand.

What the checker says

In a hotel booking that authorizes a card and captures at check-out (tests/fixtures/hotel_naive.flow, a first draft of the example):

error[E020]: tests/fixtures/hotel_naive.flow:95:1: the workflow can end here with the case `pi` in requires_payment_method, processing, which is not final (succeeded, canceled are)
    95 |   succeed outcome = stayed
  the run that gets there:
      81  quote = hold(…)
      84  match quote.handling: auto
      84  create_intent: pi starts in requires_confirmation
      85  confirm_intent: pi requires_confirmation → requires_capture
      90  match pi.status: requires_capture
      90  wait until booking.check_out
      93  capture_intent: pi requires_capture → processing
          `settle` happens on the other side: pi processing → requires_payment_method
      95  succeed

The transitions come from payment_intent.rule, a transcription of Stripe's documents into a rulec state machine. The workflow says which events happen on their own (external authenticate, settle, expire), and the checker follows them too: waiting until check-out, the authorization can expire, and then the capture is refused. Every diagnostic comes with a run that gets there; Diagnostics lists all 30 codes.

A workflow

From the hotel booking, as written for Temporal (examples/hotel/temporal):

case pi : PaymentIntent follows payment_intent.payment
  held capture_method = manual
  held confirmation_method = automatic
  external authenticate, settle, expire
  refused when refused = true

flow
  let quote = hold(room: booking.room, nights: booking.nights)
  match quote.handling
    review => succeed outcome = awaiting_review
    auto => pi <- create_intent(amount: quote.amount, currency: "jpy", payment_method: booking.card, capture_method: manual)
  pi <- confirm_intent(intent: pi.id)
    on card_declined => pi <- get_intent(intent: pi.id)
  …

A .flow has no comparison or arithmetic. It branches only by matching an enum, a bool, or a value that may be absent, which a rule or a task answered. A task says how it is called, the errors it comes back with, how it is retried, whether it takes an idempotency key, and what it does to a case (starts, sends, observes). What a task takes and answers is written as records and enums, or taken from the description of the API it calls: a message of a .proto is a type as it is (warehouse.ReserveResponse), and the task is held to the same description. The workflow's own entry can be a service of a .proto, from which clients in other languages are made (workflow fulfillment v1 implements shop.FulfillmentService); the checker holds the workflow to it. Every loop has a bound, so a run's history has one too. Write a workflow reads the whole example.

Install

$ git clone https://github.com/i2y/dandori
$ cd dandori
$ cargo install --path .

dandori builds with a recent stable Rust, and its one dependency is serde_json.

rulec is needed only for a workflow that uses rules (use rule): dandori reads them through rulec, found through DANDORI_RULEC, else on the PATH, and rulec gen writes the code of each rule, and a Connect service for it that a workflow can call instead. Install it with brew install i2y/tap/rulec, or take a binary from its releases; dandori is tested with rulec 0.22.0. A rule whose enum comes from a .proto is called at its service only with rulec 0.22.0 or later, the first whose rulec api says what the service calls the enum's values. A workflow without rules is checked and built without rulec, but its branches can only match what its tasks answer, and it has no cases, since a case follows a rule's state machine.

For AI agents

skills/dandori is an Agent Skill for using dandori: the loop from a first draft to a build, the language on one page, what to ask a person, and the fix for each diagnostic, with the reference pages it needs. Copy it into ~/.claude/skills/, or into a project's .claude/skills/; skills/README.md says more.

Commands

dandori check <file.flow>...
dandori build <file.flow> --target temporal|temporal-python|temporal-go|asl|durable|argo|pydantic-graph [--out <dir>]
dandori scenarios <file.flow> [--out <dir>]
dandori run <file.flow> --scenario <file.json> [--target reference|asl|temporal|temporal-python|temporal-go|durable|argo|pydantic-graph]
dandori doc <file.flow> [--format html] [--out <dir>]

--lang ja prints the messages in Japanese. build refuses what its platform cannot do (E050), and a workflow whose one run can outgrow the platform (E040). doc draws the workflow for the person who reviews it: Mermaid charts that GitHub draws in a pull request, with tables of every call and every way the workflow can end, or one HTML page on which each scenario lights up the way its run goes (the hotel booking, drawn). The rules it calls come with it, as rulec doc renders them for whoever approves them.

Target What build writes
temporal the workflow, its activities, a worker and a client, in TypeScript
temporal-python the same in Python, named alike, so a worker in one language can serve another
temporal-go the same in Go, as one package, named alike too
asl the state machine, in ASL with JSONata, and a Lambda handler for every rule it calls by Lambda
durable a Lambda durable function in TypeScript
argo a WorkflowTemplate, and the caller image that makes its calls
pydantic-graph a graph that runs in your own Python process

Build for a platform has what each of them writes, and What a task calls what a task becomes on each.

Examples

examples/ has five, each written for Temporal, for AWS and for pydantic-graph: a hotel booking held to Stripe's OpenAPI document; an order in a warehouse's system, whose AWS version calls a rule at the rule's own Connect service; the fulfillment of an order, with a child flow, which implements a service of a .proto and takes the types of the warehouse's answers from the warehouse's .proto; an inquiry sorted by Jev and read and answered by agents; and an application scored by Jev and, when a rule says so, approved by a person. Every version has a Japanese twin beside it (hotel.ja.flow), with Japanese names everywhere but where an API description fixes them. Examples says how the versions differ.

How it is checked

The reference interpreter defines what a .flow means. The tests generate the scenarios of every example and run each of them nine ways: in the reference interpreter, the ASL under JSONata 2.0.6 and on LocalStack's Step Functions, the Temporal workflow in TypeScript, in Python and in Go on the Temporal CLI's dev server, the durable function in the SDK's local test runner, the WorkflowTemplate on Argo Workflows in a kind cluster, and the graph with pydantic-graph. Each must make the same calls, with the same arguments and idempotency keys, and end the same way. The page on the site that runs dandori in the browser must answer every example as the command does. How it is checked tells the rest, and how to run the tests.

Most of the flows and fixtures under tests/ have Japanese names, on purpose: they see that names outside ASCII come through all five platforms as identifiers, keys and URL paths.

Status

Early. Not yet: Parallel with different branches, OpenAPI documents in YAML, types made from an OpenAPI document or a Smithy model (a .proto makes them), protobuf's binary encoding and Connect's streams, the clients of a service a workflow implements written, for the languages dandori does not build for, by a plugin of protoc, cases the workflow holds itself, a rule that walks a list of elements, a rule's preconditions checked at the task that produced the value, runs on AWS and on a production Temporal cluster or Temporal Cloud (the tests run on the Temporal CLI's dev server), the caller image run against real Lambda, HTTP and AWS endpoints from Argo, and agents run against OpenAI and Anthropic themselves. The design, the decisions and what is left are in DESIGN.md, in Japanese; its principles are on Design.

License

Licensed under either of Apache License, Version 2.0 or MIT license, at your option. The cut-down copies of Stripe's OpenAPI document and of the Smithy models of Amazon SNS and SQS under examples/ keep their own licenses (THIRD_PARTY_NOTICES.md).

About

A small typed language for workflows that call business rules. Checked before it runs: every match arm, every state a payment or an order can be left in, retries that could repeat a change, history size. Built for Temporal, AWS Step Functions, Lambda durable functions, Argo Workflows and pydantic-graph, each held to one reference interpreter.

Topics

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages