@pylonts/flow
v1.1.3
Published
Flow framework — blackboard slots, named-step runner, ts-morph flow scanning, controller-type generation
Readme
@pylonts/flow
Flow framework — blackboard Flow, named-step runner, ts-morph flow scanning, controller-type generation.
New here? Start with GUIDE.md — end-to-end: generate controller_types, write slots, write flow files, run.
Concepts
- Blackboard (
Flow): each run creates one freshFlow(a plain shared record) and destroys it afterwards. The slot contract is project-defined and hand-maintained: slots are just the record's fields, e.g. a step writesflow.bd = {...}and a later step readsflow.bd.phone. The library only requires a record of shared data (Flow<TSlots>=TSlots). - Flow files:
flow/flows/<module>.flow.ts(driverapi) orflow/flows/<module>.<driver>.flow.ts(e.g.browser). Each file's default export object lists its flow methods: method name = flow key. - Orchestration: a plain name array. The same names run unchanged under
any driver — implementations are pure methods
(flow, deps)injected by the runner.
Usage
import { scanFlows, runSteps } from '@pylonts/flow';
import type { Flow } from './flow/slots.ts'; // project slot contract
// a step: pure method reading/writing the shared Flow via injected deps
const adminAddBd = async (flow: Flow, deps: ApiDeps) => {
const bd = await deps.getModule('admin-app').getController(BdController).add({ ... });
flow.bd = { id: bd.id, name: bd.name, phone: bd.phone };
};
const index = scanFlows('flow/flows');
const flow = await runSteps({
index,
driver: 'browser', // swap to 'api' for the api driver
deps: page, // playwright Page / ApiDeps
names: ['adminAddBd', 'bdLogin', 'registerMerchant'],
});
console.log(flow.bd); // the shared blackboard, typed via FlowThe scanner enforces the cross-driver contract: the same module must expose the same flow names under every driver it implements (throws otherwise).
Browser entry
Launching playwright, running the named steps against a fresh page, and closing the browser is provided out of the box (playwright is a peer dependency):
import { runBrowserFlow } from '@pylonts/flow';
const flow = await runBrowserFlow({
flowsDir: 'flow/flows',
names: ['adminAddBd', 'bdLogin', 'registerMerchant'],
});
console.log(flow.bd); // shared blackboard after the runController-type generation
Scans project controllers (@Body/@Response decorators) and emits shared
abstract type facades for flow api drivers:
import { generateControllerTypes } from '@pylonts/flow';
generateControllerTypes({
modulesRoot: 'api-admin/src/modules',
outputDir: 'flow/controller_types',
});Or via the CLI (tsx is required as a peer dependency):
pylon-flow gen-types
pylon-flow gen-types --modules-root api-admin/src/modules --output-dir flow/controller_types--modules-root defaults to ./api-admin/src/modules, --output-dir to
./flow/controller_types.
Running flows (CLI)
Run a named flow without project-side entry files (tsx, playwright and
@pylonts/runtime are peer dependencies):
# api driver: local runtime, direct DB (run from the backend root so tsconfig
# path aliases resolve; --cwd defaults to process.cwd())
pylon-flow run --driver api --flows ./flow/flows --names adminAddBd,bdLogin,registerMerchant --cwd . --bootstrap ./flow/api-env.ts
# browser driver: real UI via playwright
pylon-flow run --driver browser --flows ./flow/flows --names adminAddBdThe api driver's --bootstrap module must export bootstrap or
bootstrapApi (project singletons like the token Redis driver).
API
scanFlows(flowsDir)— ts-morph scan →flows[module][driver] = { file, names }runSteps({ index, driver, deps, names })— execute named steps against a fresh blackboardrunBrowserFlow({ flowsDir, names, headless? })— browser driver entry (playwright peer dep)generateControllerTypes(options)— regenerate controller type facadesscanControllerSkeletons(modulesRoot)— low-level skeleton scanFlow<TSlots>— blackboard type (the shared record itself;Flow<TSlots>=TSlots)
The api driver's local runtime lives in @pylonts/runtime:
runLocalFlow({ flowsDir, names, cwd?, bootstrap? }) — scans controllers
into a DirectRouter and runs the named steps in-process (direct DB, no HTTP).
