workflowmodule

package module
v0.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 14, 2026 License: Apache-2.0 Imports: 10 Imported by: 0

README

workflow-module

A weedbox / Uber Fx wrapper around github.com/weedbox/workflow. It wires the workflow engine and the GORM-backed store to the injected database.DatabaseConnector, auto-migrates the workflow tables on startup, and exposes a WorkflowManager for other modules to inject.

Install

go get github.com/weedbox/workflow-module

Requires Go 1.26+ and a database.DatabaseConnector provider (e.g. sqlite_connector or postgres_connector from github.com/weedbox/common-modules).

Register the module

import (
    sqlite "github.com/weedbox/common-modules/sqlite_connector"
    workflowmodule "github.com/weedbox/workflow-module"
)

func loadModules() ([]fx.Option, error) {
    return []fx.Option{
        // ... configs, logger ...
        sqlite.Module("database"),
        workflowmodule.Module("workflow"),
        // ... daemon ...
    }, nil
}

Inject the manager

WorkflowManager is registered as a named Fx dependency. Use the name tag matching the scope you passed to Module().

import workflowmodule "github.com/weedbox/workflow-module"

type Params struct {
    weedbox.Params
    Workflow *workflowmodule.WorkflowManager `name:"workflow"`
}

type LeaveModule struct {
    weedbox.Module[*Params]
}

func (m *LeaveModule) OnStart(ctx context.Context) error {
    tpl := workflowmodule.WorkflowTemplate{
        ID:         "leave-request",
        ReturnMode: workflowmodule.ReturnModeDirect,
        Stages: []workflowmodule.WorkflowStage{
            {StageIndex: 1, ReviewType: workflowmodule.ReviewTypeSingle, ApproverIDs: []string{"manager"}},
            {StageIndex: 2, ReviewType: workflowmodule.ReviewTypeSingle, ApproverIDs: []string{"director"}},
        },
    }
    return m.Params().Workflow.SaveTemplate(ctx, tpl)
}

The manager exposes everything workflow.Service does (CreateDraft, Submit, Approve, Return, Withdraw, RevokeApprove, Resubmit, RejectClose, GetApplication, ListLogs, SaveTemplate, GetTemplate) plus accessors:

Accessor Returns Use for
Engine() *workflow.WorkflowEngine CanView / CanReview / CanEditForm / CanResubmit / CanWithdraw / CanRevokeApprove permission queries
Store() *gormstore.Store Low-level GORM reads, shared transactions
Service() *workflow.Service Direct service access when you need it

Re-exported domain types

The module re-exports the underlying library's types and constants under its own package, so depending code does not need a separate import of github.com/weedbox/workflow:

workflowmodule.Application
workflowmodule.WorkflowTemplate
workflowmodule.WorkflowStage
workflowmodule.ReviewLog

workflowmodule.StatusDraft / StatusInReview / StatusReturned / StatusApproved / StatusRejectedClosed
workflowmodule.ReturnModeStrict / ReturnModeDirect
workflowmodule.ReviewTypeSingle / ReviewTypeAll
workflowmodule.ActionSubmit / ActionApprove / ActionReturn / ActionRejectClose / ActionWithdraw / ActionRevokeApprove

workflowmodule.ErrInvalidStatus / ErrNoPermission / ErrCommentRequired / ...

Dynamic approvers (ApproverResolver)

The default resolver uses the static ApproverIDs baked into each WorkflowStage. Replace it via SetResolver to compute reviewers at runtime (role lookup, org chart, amount-based routing, …):

m.Params().Workflow.SetResolver(workflowmodule.ApproverResolverFunc(
    func(app workflowmodule.Application, stage workflowmodule.WorkflowStage) ([]string, error) {
        return orgChart.Lookup(app.OwnerID, stage.StageIndex), nil
    },
))

With a custom resolver, WorkflowStage.ApproverIDs may be left empty. The result must be stable for a given (app, stage) within a single approval round — see the underlying library's docs for the full contract.

Selecting a database connector

By default the module consumes the unnamed default database.DatabaseConnector from the Fx graph. With common-modules ≥ v0.0.44 every sqlite_connector / postgres_connector module also registers itself as a named provider keyed by its scope, and the first connector loaded in the process is exposed as the unnamed default. So:

  • Single connector → default injection just works:
    sqlite_connector.Module("db"),
    workflowmodule.Module("workflow"),  // consumes "db" as the default
    
  • Multiple connectors → point the module at one by name:
    sqlite_connector.Module("primary_db"),    // first-loaded → also the default
    postgres_connector.Module("analytics_db"),
    workflowmodule.Module("workflow", workflowmodule.WithDatabaseName("analytics_db")),
    

Multi-app tests in a single process must call fxmodule.ResetClaim[database.DatabaseConnector]() between apps so the "default connector" claim is released for the next app. See common-modules' README for the test caveat. The tests in this module supply the connector directly (without going through sqlite_connector.Module), so they do not trigger the claim mechanism.

Configuration

Key Type Default Description
{scope}.auto_migrate bool true Run gormstore.AutoMigrate during OnStart. Disable when you manage migrations externally.
[workflow]
auto_migrate = true

Database schema

Tables are created by gormstore.AutoMigrate:

  • wf_templates
  • wf_applications
  • wf_review_logs

Table names are fixed by the underlying library and not configurable.

Authorization boundary

The library does not authenticate callers. Submit, Resubmit, and SaveTemplate trust the caller to have already verified the requester. The operatorID arg on Approve / Return / RejectClose is checked against the stage's resolved approver list — that is workflow routing, not identity verification. Wire authentication in the layer above this module.

Tests

go test ./...

Documentation

Overview

Package workflowmodule wraps github.com/weedbox/workflow as a weedbox/Fx module. It wires the engine and the GORM-backed store to the injected database.DatabaseConnector, auto-migrates the workflow tables on startup, and exposes a WorkflowManager that callers inject into their own modules.

Index

Constants

View Source
const (
	StatusDraft          = workflow.StatusDraft
	StatusInReview       = workflow.StatusInReview
	StatusReturned       = workflow.StatusReturned
	StatusApproved       = workflow.StatusApproved
	StatusRejectedClosed = workflow.StatusRejectedClosed
)

Application status values.

View Source
const (
	ReturnModeStrict = workflow.ReturnModeStrict
	ReturnModeDirect = workflow.ReturnModeDirect
)

Return modes.

View Source
const (
	ReviewTypeSingle = workflow.ReviewTypeSingle
	ReviewTypeAll    = workflow.ReviewTypeAll
)

Review types.

View Source
const (
	ActionSubmit        = workflow.ActionSubmit
	ActionApprove       = workflow.ActionApprove
	ActionReturn        = workflow.ActionReturn
	ActionRejectClose   = workflow.ActionRejectClose
	ActionWithdraw      = workflow.ActionWithdraw
	ActionRevokeApprove = workflow.ActionRevokeApprove
)

Review log actions.

View Source
const ModuleName = "WorkflowModule"

Variables

View Source
var (
	ErrInvalidStatus    = workflow.ErrInvalidStatus
	ErrNoPermission     = workflow.ErrNoPermission
	ErrCommentRequired  = workflow.ErrCommentRequired
	ErrTemplateEmpty    = workflow.ErrTemplateEmpty
	ErrTemplateMismatch = workflow.ErrTemplateMismatch
	ErrAlreadyApproved  = workflow.ErrAlreadyApproved
	ErrInvalidTemplate  = workflow.ErrInvalidTemplate
	ErrStaleReturnStage = workflow.ErrStaleReturnStage
	ErrNotFound         = workflow.ErrNotFound
	ErrRevokeNotAllowed = workflow.ErrRevokeNotAllowed
	ErrNothingToRevoke  = workflow.ErrNothingToRevoke
)

Engine and store errors.

View Source
var ErrNotReady = errors.New("workflowmodule: manager has no store yet (called before OnStart)")

ErrNotReady is returned by manager methods called before the module's OnStart hook has attached a Store. Inject the manager via Fx and use it from your own module's OnStart or later — never from InitDefaultConfigs or other pre-start code paths.

Functions

func Module

func Module(scope string, opts ...Option) fx.Option

Module returns the Fx option that registers the workflow module under scope. The manager is exported as a named dependency (`name:"<scope>"`) so other Method-2 modules can inject it.

Pass WithDatabaseName to pick a specific named database.DatabaseConnector when the host app registers more than one.

Types

type Application

type Application = workflow.Application

Re-exported domain types so callers can depend on this module alone, without also importing github.com/weedbox/workflow directly.

type ApproverResolver

type ApproverResolver = workflow.ApproverResolver

Re-exported domain types so callers can depend on this module alone, without also importing github.com/weedbox/workflow directly.

type ApproverResolverFunc

type ApproverResolverFunc = workflow.ApproverResolverFunc

Re-exported domain types so callers can depend on this module alone, without also importing github.com/weedbox/workflow directly.

type Option

type Option func(*options)

Option configures the workflow module at registration time.

func WithDatabaseName

func WithDatabaseName(name string) Option

WithDatabaseName selects which named database.DatabaseConnector this module should consume. The name must match the one used on the provider, e.g.

fx.Provide(fx.Annotated{Name: "workflow_db", Target: func() database.DatabaseConnector { ... }})
workflowmodule.Module("workflow", workflowmodule.WithDatabaseName("workflow_db"))

Leave unset to inject the unnamed connector (the default and only mode supported by common-modules' built-in sqlite/postgres connectors out of the box).

type Params

type Params struct {
	weedbox.Params

	Database database.DatabaseConnector
}

Params declares the module's Fx dependencies. The database is injected as the common-modules interface so any connector (sqlite, postgres, ...) works.

type ReviewLog

type ReviewLog = workflow.ReviewLog

Re-exported domain types so callers can depend on this module alone, without also importing github.com/weedbox/workflow directly.

type WorkflowManager

type WorkflowManager struct {
	// contains filtered or unexported fields
}

WorkflowManager is the user-facing handle for the workflow module. It owns the engine, the GORM-backed store, and the service that ties them together.

Concurrency: all exported methods are safe for concurrent use after OnStart. SetResolver may be called before or after OnStart; subsequent operations see the new resolver immediately because the engine pointer never changes.

func (*WorkflowManager) Approve

func (m *WorkflowManager) Approve(ctx context.Context, applicationID, operatorID string) (workflow.Application, workflow.ReviewLog, error)

Approve records a stage approval, advancing the workflow when conditions are met.

func (*WorkflowManager) CreateDraft

func (m *WorkflowManager) CreateDraft(ctx context.Context, app workflow.Application) error

CreateDraft persists a new application in Draft status.

func (*WorkflowManager) Engine

Engine returns the underlying workflow engine. Use this for permission queries (CanView, CanReview, CanEditForm, CanResubmit).

func (*WorkflowManager) GetApplication

func (m *WorkflowManager) GetApplication(ctx context.Context, applicationID string) (workflow.Application, error)

GetApplication loads an application snapshot.

func (*WorkflowManager) GetTemplate

func (m *WorkflowManager) GetTemplate(ctx context.Context, templateID string) (workflow.WorkflowTemplate, error)

GetTemplate fetches a template definition by ID.

func (*WorkflowManager) ListLogs

func (m *WorkflowManager) ListLogs(ctx context.Context, applicationID string) ([]workflow.ReviewLog, error)

ListLogs returns the chronological review log for an application.

func (*WorkflowManager) RejectClose

func (m *WorkflowManager) RejectClose(ctx context.Context, applicationID, operatorID, comment string) (workflow.Application, workflow.ReviewLog, error)

RejectClose terminally closes the application with a required comment.

func (*WorkflowManager) Resubmit

func (m *WorkflowManager) Resubmit(ctx context.Context, applicationID string) (workflow.Application, workflow.ReviewLog, error)

Resubmit puts a Returned application back into the review queue.

func (*WorkflowManager) Return

func (m *WorkflowManager) Return(ctx context.Context, applicationID, operatorID, comment string) (workflow.Application, workflow.ReviewLog, error)

Return sends the application back to the owner with a required comment.

func (*WorkflowManager) RevokeApprove added in v0.0.2

func (m *WorkflowManager) RevokeApprove(ctx context.Context, applicationID, operatorID, comment string) (workflow.Application, workflow.ReviewLog, error)

RevokeApprove cancels operatorID's own pending approval at the current stage. Only valid for an ALL stage whose quorum is still outstanding.

func (*WorkflowManager) SaveTemplate

func (m *WorkflowManager) SaveTemplate(ctx context.Context, tpl workflow.WorkflowTemplate) error

SaveTemplate persists a template and reflows every in-flight application bound to it. See workflow.Service.SaveTemplate for reflow semantics.

func (*WorkflowManager) Service

func (m *WorkflowManager) Service() *workflow.Service

Service returns the workflow.Service that ties engine and store together. Prefer the forwarding methods below for everyday use.

func (*WorkflowManager) SetResolver

func (m *WorkflowManager) SetResolver(r workflow.ApproverResolver)

SetResolver installs a custom approver resolver on the engine. Calling this after the module has accepted live traffic is allowed but caller-coordinated: changing the resolver mid-round can affect ALL-stage approval counting.

func (*WorkflowManager) Store

func (m *WorkflowManager) Store() *gormstore.Store

Store returns the GORM-backed store. Use this for low-level reads (template listing, log queries) or when you need to share the transaction with your own code via Store.Transact.

func (*WorkflowManager) Submit

func (m *WorkflowManager) Submit(ctx context.Context, applicationID string) (workflow.Application, workflow.ReviewLog, error)

Submit moves a Draft application into In_Review at stage 1.

func (*WorkflowManager) Withdraw added in v0.0.2

func (m *WorkflowManager) Withdraw(ctx context.Context, applicationID, comment string) (workflow.Application, workflow.ReviewLog, error)

Withdraw pulls a still-in-review application back to Draft on the owner's behalf. Like Submit/Resubmit it trusts the caller to have verified the requester is the owner; gate with Engine().CanWithdraw upstream if needed.

type WorkflowModule

type WorkflowModule struct {
	weedbox.Module[*Params]
	// contains filtered or unexported fields
}

WorkflowModule is the weedbox module wrapper. The actual user-facing API lives on the embedded WorkflowManager.

func (*WorkflowModule) InitDefaultConfigs

func (m *WorkflowModule) InitDefaultConfigs()

InitDefaultConfigs registers Viper defaults for this module's scope.

func (*WorkflowModule) Manager

func (m *WorkflowModule) Manager() *WorkflowManager

Manager returns the WorkflowManager owned by this module.

func (*WorkflowModule) OnStart

func (m *WorkflowModule) OnStart(ctx context.Context) error

OnStart builds the GORM store, runs AutoMigrate (when configured), and wires the engine/store into the manager.

func (*WorkflowModule) OnStop

func (m *WorkflowModule) OnStop(ctx context.Context) error

OnStop is a no-op; the underlying *gorm.DB is owned by the connector module.

type WorkflowStage

type WorkflowStage = workflow.WorkflowStage

Re-exported domain types so callers can depend on this module alone, without also importing github.com/weedbox/workflow directly.

type WorkflowTemplate

type WorkflowTemplate = workflow.WorkflowTemplate

Re-exported domain types so callers can depend on this module alone, without also importing github.com/weedbox/workflow directly.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL