Skip to main content
Deno 2 is finally here 🎉️
Learn more

Denoflow

Table of Contents

About

Deno script based yaml workflow file.

WIP. Use with care.

Ideal result is having a apps schemas, with a web gui to generate yaml configs.

Now we can write yaml by ourself, and actually, it’s not that hard.

Getting Started

mkdir workflows

Fetch Example

Fetch from Hacker News API to webhook.

touch workflows/fetch.yml
on:
  use: fetch
  args:
    - https://test.owenyoung.com/slim.json
  then: return ctx.result.json()
  itemsPath: hits
  uniqueKey: objectID
steps: 
  - use: fetch
    args:
      - https://enyvb91j5zjv9.x.pipedream.net/
      - method: POST
        headers:
          'Content-Type': 'application/json'
        body: ${{JSON.stringify(ctx.item)}}

Open: https://requestbin.com/r/enyvb91j5zjv9/23eNPamD4DK4YK1rfEB1FAQOKIj , See live webhook request.

deno run --allow-read --allow-net --allow-write --allow-env --unstable https://denopkg.com/denoflow/denoflow@main/cli.ts run

It will scan the workflows directory and run all valid .yml files.

RSS Feed to Discord Webhook Message

touch workflows/rss.yml
on:
  use: fetch
  args:
    - https://actionsflow.github.io/test-page/hn-rss.xml
  then: |
    const rss = await import("https://deno.land/x/rss/mod.ts");
    const xml = await ctx.result.text();
    const feed = await rss.parseFeed(xml);
    return feed.entries;
steps:
  - use: fetch
    args:
      - <your discord webhook url>
      - method: POST
        headers:
          'Content-Type': 'application/json'
        body: ${{ JSON.stringify({content:ctx.item.title.value}) }}
deno run --allow-read --allow-net --allow-write --allow-env --unstable https://denopkg.com/denoflow/denoflow@main/cli.ts run

Life Cycle

[on.use] -> [on.then] -> [on.run] -> [step.use] -> [step.then] -> [step.run] 

Prerequisites

Install Deno first.

Installing

deno install -n denoflow --allow-read --allow-net --allow-write --allow-env  https://denopkg.com/denoflow/denoflow@main/cli.ts

Then, you can run it with denoflow run , or denoflow run <files>

Usage

Usage:
  $ denoflow run [...files]

Options:
  --force      Force run workflow files (default: false)
  --max-items  max items for workflow every runs 
  -h, --help   Display this message

YAML Syntax

You can use ${{variable}} in any fields to inject variables into your workflow, we inject ctx variable in template and script. For example:

Expressions

steps:
  - if: ${{ctx.items.lengh>10}}
    then: console.log(ctx.item);

All ctx see Context

State

You can simply use ctx.state to get or set state, for example:

let currentState = ctx.state || {};

let sent = ctx.state.sent || [];

if(sent.includes(ctx.item.id)){
  sent.push(ctx.item.id);
}

ctx.state = {
  sent
};

// deno flow will save the state for you , next you can read it.
return;

The state will be saved to data folder in json format. If needed, we’ll add sqlite or postgres etc as the key value database.

Syntax

All workflow syntax:

on:
  # Optional, environment variables to set before running the trigger
  env:
    test: test
  # Optional, if run the trigger
  if: true
  # Optional run a ts file, can use local file, cwd based workflow file
  from: https://deno.land/x/axiod@0.24/mod.ts
  # Optional, module name, default is the default exported module.
  # If from is not provided, will use global function.
  use: get
  # Function parameters, support all types, nested object
  args:
    - https://test.owenyoung.com/slim.json
  # Optional, When function succeeds, will run the then script
  then: return ctx.result;
  # items path, default is the root result. e.g. `data.items` `data.list` 
  itemsPath: hits
  # item id, default is `id`, e.g. `guid`, `item.id`
  uniqueKey: objectID
  # force, ignore uniqueKey, trigger all items. the Default is false
  force: true
  # Optional, maxItems , default is undefined, means no limit, if set, will run first n items with steps.
  maxItems: 1
steps:
  - from: ./to-json.ts
    if: ${{ctx.env.test === 'test2'}}
    env:
      test: test
    args:
      - ${{ctx.item}}
      - ${{ctx.state}}

Context

export interface PublicContext {
  env: Record<string, string | undefined>; // env vars
  cwd: string; // current working directory
  workflowPath: string; // workflowfile absolute path
  workflowRelativePath: string; // workflow file path relative to cwd
  workflowCwd: string; // workflow cwd, absolute path
  options?: WorkflowOptions; // workflow options, formated by getDefaultWorkflowOptions
  result?: unknown; // last step result
  error?: unknown; // last step error
  ok?: boolean; // last step state, true if no error
  items: unknown[]; // trigger items
  item?: unknown; // trigger item
  itemIndex?: number; // trigger item index
  steps: Record<string | number, unknown>; // steps results
  stepIndex?: number; // current step index
  stepOkResults: Record<string | number, boolean>; // step ok status map
  stepErrors: Record<string | number, unknown>; // step errors
  state: unknown; // workflow custom state
}