logo   async
machine.dev

/docs/schema.md

This document contains a brief overview of asyncmachine-go schema formats. JSON schemas can be found in /docs/jsonschema.

Schema-v0

  • type safety NO
  • reflection NO
  • bootstrapped NO
  • inheritance NO
  • generated NO
  • bootstrapped NO

Schemas for asyncmachine-go are optional and for simple things, we don’t need a typed schema (nor type safety), and we can construct the machine like so:

mach := newMach("DryWaterWet", am.Schema{
    "Wet": {
        Require: am.S{"Water"},
    },
    "Dry": {
        Remove: am.S{"Water"},
    },
    "Water": {
        Add:    am.S{"Wet"},
        Remove: am.S{"Dry"},
    },
})

All the state-related calls happen via raw strings. It’s good for prototyping and nothing else.

Schema-v1

  • type safety YES
  • reflection NO
  • bootstrapped NO
  • inheritance NO
  • generated NO
  • bootstrapped NO

The v1 is the simplest schema file format:

package states

import am "github.com/pancsta/asyncmachine-go/pkg/machine"

// S is a type alias for a list of state names.
type S = am.S

// States map defines relations and properties of states.
var States = am.Schema{
    CreatingExpense: {Remove: GroupExpense},
    ExpenseCreated:  {Remove: GroupExpense},
    WaitingForApproval: {
        Auto:   true,
        Remove: GroupApproval,
    },
    ApprovalGranted: {Remove: GroupApproval},
    PaymentInProgress: {
        Auto:   true,
        Remove: GroupPayment,
    },
    PaymentCompleted: {Remove: GroupPayment},
}

// Groups of mutually exclusive states.

var (
    GroupExpense  = S{CreatingExpense, ExpenseCreated}
    GroupApproval = S{WaitingForApproval, ApprovalGranted}
    GroupPayment  = S{PaymentInProgress, PaymentCompleted}
)

// #region boilerplate defs

// Names of all the states (pkg enum).

const (
    CreatingExpense    = "CreatingExpense"
    ExpenseCreated     = "ExpenseCreated"
    WaitingForApproval = "WaitingForApproval"
    ApprovalGranted    = "ApprovalGranted"
    PaymentInProgress  = "PaymentInProgress"
    PaymentCompleted   = "PaymentCompleted"
)

// Names is an ordered list of all the state names.
var Names = S{CreatingExpense, ExpenseCreated, WaitingForApproval, ApprovalGranted, PaymentInProgress, PaymentCompleted}

// #endregion

Usage:

import ss "github.com/pancsta/asyncmachine-go/examples/temporal_expense/states"

// ...

h := &MachineHandlers{}
mach, err := am.NewCommon(ctx, "expense", ss.Schema, ss.Names, h, nil, nil)

Schema-v2

  • type safety YES
  • reflection YES
  • bootstrapped YES
  • inheritance YES
  • generated NO
  • bootstrapped YES

The v2 is the actual schema file format and should be used everywhere. The problem is the use of reflection that causes issues with TinyGo. Bootstrappable by am-gen schema.

import (
    am "github.com/pancsta/asyncmachine-go/pkg/machine"
    "github.com/pancsta/asyncmachine-go/pkg/states"
    . "github.com/pancsta/asyncmachine-go/pkg/states/global"
)

// ...

// ServerStatesDef contains all the states of the Client state machine.
type ServerStatesDef struct {
    *am.StatesBase

    // basics

    // Ready - Client is fully connected to the server.
    Ready string

    // rpc

    // Starting listening
    RpcStarting string
    // setting up RPC accepting
    RpcAccepting string
    // RPC is accepting or has accepted connections
    RpcReady string

    // RPC client connected (technically)
    ClientConnected string
    // RPC client fully usable
    HandshakeDone string

    // How many times the client requested a full sync.
    MetricSync string
    // TCP tunneled over websocket
    WebSocketTunnel string

    // inherit from SharedStatesDef
    *SharedStatesDef
}

// ServerGroupsDef contains all the state groups of the Client state machine.
type ServerGroupsDef struct {
    *SharedGroupsDef

    // Rpc is a group for RPC ready states.
    Rpc S
}

// ServerSchema represents all relations and properties of ClientStates.
var ServerSchema = SchemaMerge(
    // inherit from SharedStruct
    SharedSchema,
    am.Schema{

        ssS.ErrNetwork: {
            Require: S{am.StateException},
            Remove:  S{ssS.ClientConnected},
        },

        // inject Server states into HandshakeDone
        ssS.HandshakeDone: StateAdd(
            SharedSchema[ssS.HandshakeDone],
            am.State{
                Require: S{ssS.ClientConnected},
                // TODO why?
                Remove: S{Exception},
            }),

        // Server

        ssS.Start: {Add: S{ssS.RpcStarting}},
        ssS.Ready: {
            Auto:    true,
            Require: S{ssS.HandshakeDone, ssS.RpcReady},
        },

        ssS.RpcStarting: {
            Require: S{ssS.Start},
            Remove:  sgS.Rpc,
        },
        ssS.RpcAccepting: {
            Require: S{ssS.Start},
            Remove:  sgS.Rpc,
        },
        ssS.RpcReady: {
            Require: S{ssS.Start},
            Remove:  sgS.Rpc,
        },
        ssS.ClientConnected: {
            Require: S{ssS.RpcReady},
        },

        ssS.MetricSync:      {Multi: true},
        ssS.WebSocketTunnel: {},
    })

// EXPORTS AND GROUPS

var (
    ssS = am.NewStates(ServerStatesDef{})
    sgS = am.NewStateGroups(ServerGroupsDef{
        Rpc: S{ssS.RpcStarting, ssS.RpcAccepting, ssS.RpcReady},
    }, SharedGroups)

    // ServerStates contains all the states for the Client machine.
    ServerStates = ssS
    // ServerGroups contains all the state groups for the Client machine.
    ServerGroups = sgS
)

See /tools/cmd/am-gen for schema bootstrapping commands.

YAML Schema

YAML schema files are useful for bootstrapping the project boilerplate via am-gen starter-kit.

Start:
BaseDBReady:
    remove:
        - BaseDBStarting
BaseDBSaving:
    multi: true
BaseDBStarting:
    remove:
        - BaseDBReady
CharacterReady:
    remove:
        - RestoreCharacter
        - GenCharacter
CheckStories:
    multi: true
    require:
        - Start
CheckingMenuRefs:
    multi: true
    require:
        - Start
RestoreCharacter:
GenCharacter:

YAML Machine

YAML machine files can be used to generate a schema via am-gen schema-from-file.

id: my-serialized-mach
state_names:
    - Start
    - BaseDBReady
    - BaseDBSaving
time:
    - 1
    - 2
    - 0
queue_tick: 25
machine_tick: 1

MachAddress URLs

Machine addresses are implemented in am-dbg and am-vis for convenient copy-pasta.

Format: mach://<mach-id>[/<tx-id>[/<step>]][?<query-params>]

  • Host (MachId): State machine identifier (e.g. mach://my-mach).
  • Path (TxId, Step):
    • /<tx-id>: Specific transition ID (e.g. mach://my-mach/tx-101).
    • /<tx-id>/<step>: Step index within the transition (e.g. mach://my-mach/tx-101/2).
  • Query Parameters:
    • t=<uint64>: Machine logical time (MachTime).
    • q=<uint64>: Queue tick (QueueTick).
    • ht=<RFC3339>: Wall-clock timestamp (HumanTime).
    • state=<string>: Selected/highlighted state name.
    • group=<string>: Selected/highlighted state group name.

Example:

mach://auth-service/tx-45/2?state=Ready&t=1450