Core workflow engine and type definitions used by Qualsoft workflow-enabled applications. This package provides the workflow engine, state store interface, and helper utilities for driving step-based workflows.
npm install @qualsoft/workflow-corebash
npm install @qualsoft/workflow-core
`
Vue d'ensemble de l'interface
$3
`ts
import {
WorkflowEngine,
InMemoryActionRegistry,
resolveSteps,
findAction,
findStep,
type WorkflowStateStore,
type StepWorkflowDefinition,
type WorkflowRecord,
type WorkflowContext,
type WorkflowRunInput,
type WorkflowRunResult
} from '@qualsoft/workflow-core';
`
$3
Implémentez WorkflowStateStore pour connecter le moteur à votre couche de stockage :
`ts
const stateStore: WorkflowStateStore = {
async getWorkflowDefinition(formId, workflowId) {
// Load definition
},
async getRecord(formId, recordId) {
// Load record
},
async updateRecord(formId, recordId, payload) {
// Persist updated record fields
}
};
`
$3
`ts
const engine = new WorkflowEngine({
stateStore,
actionRegistry: new InMemoryActionRegistry()
});
const result = await engine.run({
formId: 'forms-123',
workflowId: 'workflow-abc',
recordId: 'record-789',
actionId: 'approve-action',
context: {
user: { email: 'approver@example.com' },
timestamp: new Date().toISOString()
}
});
console.log(result.updatedRecord.statut);
`
$3
Les steps de workflow peuvent définir des actionRunners exécutés à l'entrée d'une étape ou après un délai. Chaque runner précise un type (ex: assignUser, assignRole, updateRecordFields, notifyUser, slaTimer) et un déclencheur :
`ts
steps: [
{
id: 'review',
label: 'Revue',
actor: 'role',
role: 'quality',
fields: [],
actions: [],
actionRunners: [
{
id: 'review-notify',
label: 'Notifier le responsable',
type: 'notifyUser',
trigger: {
type: 'afterDelay',
delay: { value: 2, unit: 'days', source: 'stepEntry' }
},
payload: {
subject: 'Relance revue',
message: 'Une revue est en attente.'
}
}
]
}
]
`
Le moteur exécute les runners immédiats via le registre d'actions et planifie les runners différés via l'action scheduleTask (si enregistrée).
Registre d'actions unitaires
Le moteur s'appuie sur un registre d'actions qui associe un type d'action à une fonction unique. Chaque action est enregistrée séparément (ex: sendEmail) et tout type non enregistré est ignoré (avec un avertissement).
$3
`ts
const registry = new InMemoryActionRegistry();
registry.register({
type: 'sendEmail',
async run(payload, context) {
console.log('payload', payload, 'context', context);
}
});
`
Structures de données
$3
La méthode WorkflowEngine.run attend :
`ts
type WorkflowRunInput = {
formId: string;
workflowId: string;
recordId: string;
actionId: string;
context?: {
formId: string;
recordId: string;
workflowId: string;
user?: {
id?: string;
email?: string;
displayName?: string;
roles?: string[];
};
timestamp?: string;
actions?: Array<{ type: string; payload: unknown }>;
[key: string]: unknown;
};
};
`
$3
Le résultat contient l'enregistrement mis à jour et la définition de workflow résolue :
`ts
type WorkflowRunResult = {
updatedRecord: WorkflowRecord;
definition: StepWorkflowDefinition;
};
`
$3
Une définition moderne s'appuie sur steps. Elle peut aussi exposer states/transitions (format legacy) et sera convertie automatiquement via resolveSteps.
`ts
type StepWorkflowDefinition = {
id: string;
name?: string;
initialState?: string;
steps?: WorkflowStep[];
states?: Array<{ id: string; label: string; isFinal?: boolean }>;
transitions?: Array<{ from: string; to: string; actor: WorkflowActor; role?: string }>;
};
`
$3
`ts
type WorkflowRecord = {
id?: string;
statut?: string;
customFields?: Record;
workflowHistory?: WorkflowStepHistoryEntry[];
[key: string]: unknown;
};
`
Exemple complet
`ts
import {
WorkflowEngine,
InMemoryActionRegistry,
type WorkflowStateStore,
type StepWorkflowDefinition,
type WorkflowRecord
} from '@qualsoft/workflow-core';
const definition: StepWorkflowDefinition = {
id: 'workflow-qa',
name: 'Validation QA',
initialState: 'draft',
steps: [
{
id: 'draft',
label: 'Brouillon',
actor: 'creator',
fields: [],
actions: [
{
id: 'submit',
label: 'Soumettre',
defaultNextStepId: 'review',
conditionalRoutes: []
}
]
},
{
id: 'review',
label: 'Revue',
actor: 'role',
role: 'quality',
fields: [],
actions: [
{
id: 'approve',
label: 'Approuver',
defaultNextStepId: 'done',
conditionalRoutes: []
}
]
},
{
id: 'done',
label: 'Terminé',
actor: 'none',
isFinal: true,
fields: [],
actions: []
}
]
};
const record: WorkflowRecord = {
id: 'record-42',
statut: 'draft',
workflowHistory: []
};
const stateStore: WorkflowStateStore = {
async getWorkflowDefinition() {
return definition;
},
async getRecord() {
return record;
},
async updateRecord(_formId, _recordId, payload) {
Object.assign(record, payload);
}
};
const actionRegistry = new InMemoryActionRegistry();
const engine = new WorkflowEngine({
stateStore,
actionRegistry
});
const result = await engine.run({
formId: 'form-1',
workflowId: 'workflow-qa',
recordId: 'record-42',
actionId: 'submit',
context: {
formId: 'form-1',
workflowId: 'workflow-qa',
recordId: 'record-42',
user: { id: 'user-1', email: 'creator@example.com' }
}
});
console.log(result.updatedRecord.statut); // => "review"
`
Migration strategy:
states/transitions → steps
Workflow definitions can still expose legacy states/transitions. Use resolveSteps to centralize conversion to steps and keep the rest of the application working with the modern structure:
`ts
const steps = resolveSteps(definition);
`
resolveSteps converts transitions into step actions and logs a clear error when transitions reference missing states (or when transitions are provided without any states). Use these logs to identify definitions that need to be updated. The recommended migration path is:
1. Load definitions and run them through resolveSteps.
2. Fix any logged inconsistencies in legacy states/transitions.
3. Persist definitions back with steps populated so future reads can skip conversion.
Build
`bash
npm run build
`
The compiled JavaScript and type declarations are output to lib/.
Publish
`bash
npm version patch && npm publish --access public
`
> The prepare` script runs the build automatically during publish.