@unlaxer/tramli-plugins 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/api/plugin-registry.d.ts +19 -0
- package/dist/cjs/api/plugin-registry.js +55 -0
- package/dist/cjs/api/types.d.ts +51 -0
- package/dist/cjs/api/types.js +23 -0
- package/dist/cjs/audit/audit-store-plugin.d.ts +7 -0
- package/dist/cjs/audit/audit-store-plugin.js +14 -0
- package/dist/cjs/audit/auditing-flow-store.d.ts +24 -0
- package/dist/cjs/audit/auditing-flow-store.js +33 -0
- package/dist/cjs/diagram/diagram-generation-plugin.d.ts +9 -0
- package/dist/cjs/diagram/diagram-generation-plugin.js +19 -0
- package/dist/cjs/diagram/diagram-plugin.d.ts +5 -0
- package/dist/cjs/diagram/diagram-plugin.js +16 -0
- package/dist/cjs/diagram/types.d.ts +5 -0
- package/dist/cjs/diagram/types.js +2 -0
- package/dist/cjs/docs/documentation-plugin.d.ts +7 -0
- package/dist/cjs/docs/documentation-plugin.js +33 -0
- package/dist/cjs/docs/flow-documentation-plugin.d.ts +8 -0
- package/dist/cjs/docs/flow-documentation-plugin.js +19 -0
- package/dist/cjs/eventstore/compensation-service.d.ts +11 -0
- package/dist/cjs/eventstore/compensation-service.js +22 -0
- package/dist/cjs/eventstore/event-log-store-decorator.d.ts +24 -0
- package/dist/cjs/eventstore/event-log-store-decorator.js +50 -0
- package/dist/cjs/eventstore/event-log-store-plugin.d.ts +7 -0
- package/dist/cjs/eventstore/event-log-store-plugin.js +14 -0
- package/dist/cjs/eventstore/replay-service.d.ts +14 -0
- package/dist/cjs/eventstore/replay-service.js +33 -0
- package/dist/cjs/eventstore/types.d.ts +19 -0
- package/dist/cjs/eventstore/types.js +2 -0
- package/dist/cjs/hierarchy/entry-exit-compiler.d.ts +8 -0
- package/dist/cjs/hierarchy/entry-exit-compiler.js +39 -0
- package/dist/cjs/hierarchy/hierarchy-code-generator.d.ts +10 -0
- package/dist/cjs/hierarchy/hierarchy-code-generator.js +48 -0
- package/dist/cjs/hierarchy/hierarchy-generation-plugin.d.ts +8 -0
- package/dist/cjs/hierarchy/hierarchy-generation-plugin.js +22 -0
- package/dist/cjs/hierarchy/types.d.ts +27 -0
- package/dist/cjs/hierarchy/types.js +21 -0
- package/dist/cjs/idempotency/idempotency-runtime-plugin.d.ts +11 -0
- package/dist/cjs/idempotency/idempotency-runtime-plugin.js +22 -0
- package/dist/cjs/idempotency/idempotent-rich-resume-executor.d.ts +9 -0
- package/dist/cjs/idempotency/idempotent-rich-resume-executor.js +22 -0
- package/dist/cjs/idempotency/in-memory-idempotency-registry.d.ts +5 -0
- package/dist/cjs/idempotency/in-memory-idempotency-registry.js +14 -0
- package/dist/cjs/idempotency/types.d.ts +13 -0
- package/dist/cjs/idempotency/types.js +2 -0
- package/dist/cjs/index.d.ts +36 -0
- package/dist/cjs/index.js +72 -0
- package/dist/cjs/lint/default-flow-policies.d.ts +2 -0
- package/dist/cjs/lint/default-flow-policies.js +41 -0
- package/dist/cjs/lint/policy-lint-plugin.d.ts +11 -0
- package/dist/cjs/lint/policy-lint-plugin.js +27 -0
- package/dist/cjs/lint/types.d.ts +5 -0
- package/dist/cjs/lint/types.js +2 -0
- package/dist/cjs/observability/observability-plugin.d.ts +25 -0
- package/dist/cjs/observability/observability-plugin.js +36 -0
- package/dist/cjs/resume/rich-resume.d.ts +21 -0
- package/dist/cjs/resume/rich-resume.js +47 -0
- package/dist/cjs/subflow/guaranteed-subflow-validator.d.ts +8 -0
- package/dist/cjs/subflow/guaranteed-subflow-validator.js +31 -0
- package/dist/cjs/testing/scenario-generation-plugin.d.ts +9 -0
- package/dist/cjs/testing/scenario-generation-plugin.js +19 -0
- package/dist/cjs/testing/scenario-test-plugin.d.ts +8 -0
- package/dist/cjs/testing/scenario-test-plugin.js +28 -0
- package/dist/cjs/testing/types.d.ts +7 -0
- package/dist/cjs/testing/types.js +2 -0
- package/dist/esm/api/plugin-registry.d.ts +19 -0
- package/dist/esm/api/plugin-registry.js +51 -0
- package/dist/esm/api/types.d.ts +51 -0
- package/dist/esm/api/types.js +19 -0
- package/dist/esm/audit/audit-store-plugin.d.ts +7 -0
- package/dist/esm/audit/audit-store-plugin.js +10 -0
- package/dist/esm/audit/auditing-flow-store.d.ts +24 -0
- package/dist/esm/audit/auditing-flow-store.js +29 -0
- package/dist/esm/diagram/diagram-generation-plugin.d.ts +9 -0
- package/dist/esm/diagram/diagram-generation-plugin.js +15 -0
- package/dist/esm/diagram/diagram-plugin.d.ts +5 -0
- package/dist/esm/diagram/diagram-plugin.js +12 -0
- package/dist/esm/diagram/types.d.ts +5 -0
- package/dist/esm/diagram/types.js +1 -0
- package/dist/esm/docs/documentation-plugin.d.ts +7 -0
- package/dist/esm/docs/documentation-plugin.js +29 -0
- package/dist/esm/docs/flow-documentation-plugin.d.ts +8 -0
- package/dist/esm/docs/flow-documentation-plugin.js +15 -0
- package/dist/esm/eventstore/compensation-service.d.ts +11 -0
- package/dist/esm/eventstore/compensation-service.js +18 -0
- package/dist/esm/eventstore/event-log-store-decorator.d.ts +24 -0
- package/dist/esm/eventstore/event-log-store-decorator.js +46 -0
- package/dist/esm/eventstore/event-log-store-plugin.d.ts +7 -0
- package/dist/esm/eventstore/event-log-store-plugin.js +10 -0
- package/dist/esm/eventstore/replay-service.d.ts +14 -0
- package/dist/esm/eventstore/replay-service.js +28 -0
- package/dist/esm/eventstore/types.d.ts +19 -0
- package/dist/esm/eventstore/types.js +1 -0
- package/dist/esm/hierarchy/entry-exit-compiler.d.ts +8 -0
- package/dist/esm/hierarchy/entry-exit-compiler.js +35 -0
- package/dist/esm/hierarchy/hierarchy-code-generator.d.ts +10 -0
- package/dist/esm/hierarchy/hierarchy-code-generator.js +44 -0
- package/dist/esm/hierarchy/hierarchy-generation-plugin.d.ts +8 -0
- package/dist/esm/hierarchy/hierarchy-generation-plugin.js +18 -0
- package/dist/esm/hierarchy/types.d.ts +27 -0
- package/dist/esm/hierarchy/types.js +16 -0
- package/dist/esm/idempotency/idempotency-runtime-plugin.d.ts +11 -0
- package/dist/esm/idempotency/idempotency-runtime-plugin.js +18 -0
- package/dist/esm/idempotency/idempotent-rich-resume-executor.d.ts +9 -0
- package/dist/esm/idempotency/idempotent-rich-resume-executor.js +18 -0
- package/dist/esm/idempotency/in-memory-idempotency-registry.d.ts +5 -0
- package/dist/esm/idempotency/in-memory-idempotency-registry.js +10 -0
- package/dist/esm/idempotency/types.d.ts +13 -0
- package/dist/esm/idempotency/types.js +1 -0
- package/dist/esm/index.d.ts +36 -0
- package/dist/esm/index.js +38 -0
- package/dist/esm/lint/default-flow-policies.d.ts +2 -0
- package/dist/esm/lint/default-flow-policies.js +38 -0
- package/dist/esm/lint/policy-lint-plugin.d.ts +11 -0
- package/dist/esm/lint/policy-lint-plugin.js +23 -0
- package/dist/esm/lint/types.d.ts +5 -0
- package/dist/esm/lint/types.js +1 -0
- package/dist/esm/observability/observability-plugin.d.ts +25 -0
- package/dist/esm/observability/observability-plugin.js +31 -0
- package/dist/esm/resume/rich-resume.d.ts +21 -0
- package/dist/esm/resume/rich-resume.js +42 -0
- package/dist/esm/subflow/guaranteed-subflow-validator.d.ts +8 -0
- package/dist/esm/subflow/guaranteed-subflow-validator.js +27 -0
- package/dist/esm/testing/scenario-generation-plugin.d.ts +9 -0
- package/dist/esm/testing/scenario-generation-plugin.js +15 -0
- package/dist/esm/testing/scenario-test-plugin.d.ts +8 -0
- package/dist/esm/testing/scenario-test-plugin.js +24 -0
- package/dist/esm/testing/types.d.ts +7 -0
- package/dist/esm/testing/types.js +1 -0
- package/package.json +39 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ScenarioGenerationPlugin = void 0;
|
|
4
|
+
const scenario_test_plugin_js_1 = require("./scenario-test-plugin.js");
|
|
5
|
+
class ScenarioGenerationPlugin {
|
|
6
|
+
delegate = new scenario_test_plugin_js_1.ScenarioTestPlugin();
|
|
7
|
+
descriptor() {
|
|
8
|
+
return {
|
|
9
|
+
id: 'scenario-tests',
|
|
10
|
+
displayName: 'Scenario Test Generator',
|
|
11
|
+
description: 'Produces scenario-oriented test plans from a flow definition.',
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
kind() { return 'GENERATION'; }
|
|
15
|
+
generate(input) {
|
|
16
|
+
return this.delegate.generate(input);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
exports.ScenarioGenerationPlugin = ScenarioGenerationPlugin;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { FlowDefinition } from '@unlaxer/tramli';
|
|
2
|
+
import type { FlowTestPlan } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Generates BDD-style test scenarios from a flow definition.
|
|
5
|
+
*/
|
|
6
|
+
export declare class ScenarioTestPlugin {
|
|
7
|
+
generate<S extends string>(definition: FlowDefinition<S>): FlowTestPlan;
|
|
8
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ScenarioTestPlugin = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Generates BDD-style test scenarios from a flow definition.
|
|
6
|
+
*/
|
|
7
|
+
class ScenarioTestPlugin {
|
|
8
|
+
generate(definition) {
|
|
9
|
+
const scenarios = [];
|
|
10
|
+
for (const t of definition.transitions) {
|
|
11
|
+
const steps = [];
|
|
12
|
+
steps.push(`given flow in ${t.from}`);
|
|
13
|
+
if (t.type === 'external' && t.guard) {
|
|
14
|
+
steps.push(`when external data satisfies guard ${t.guard.name}`);
|
|
15
|
+
}
|
|
16
|
+
if (t.type === 'auto' && t.processor) {
|
|
17
|
+
steps.push(`when auto processor ${t.processor.name} runs`);
|
|
18
|
+
}
|
|
19
|
+
if (t.type === 'branch' && t.branch) {
|
|
20
|
+
steps.push(`when branch ${t.branch.name} selects a route`);
|
|
21
|
+
}
|
|
22
|
+
steps.push(`then flow reaches ${t.to}`);
|
|
23
|
+
scenarios.push({ name: `${t.from}_to_${t.to}`, steps });
|
|
24
|
+
}
|
|
25
|
+
return { scenarios };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
exports.ScenarioTestPlugin = ScenarioTestPlugin;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { FlowDefinition, FlowEngine } from '@unlaxer/tramli';
|
|
2
|
+
import type { FlowPlugin } from './types.js';
|
|
3
|
+
import { PluginReport } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Central registry for tramli plugins.
|
|
6
|
+
* Manages plugin lifecycle: analyze → wrap store → install engine → bind adapters.
|
|
7
|
+
*/
|
|
8
|
+
export declare class PluginRegistry<S extends string> {
|
|
9
|
+
private plugins;
|
|
10
|
+
register(plugin: FlowPlugin): this;
|
|
11
|
+
/** Run all analysis plugins against a FlowDefinition. */
|
|
12
|
+
analyzeAll(definition: FlowDefinition<S>): PluginReport;
|
|
13
|
+
/** Apply all store plugins (wrapping in registration order). */
|
|
14
|
+
applyStorePlugins(store: any): any;
|
|
15
|
+
/** Install all engine plugins. */
|
|
16
|
+
installEnginePlugins(engine: FlowEngine): void;
|
|
17
|
+
/** Bind all runtime adapter plugins. Returns map of plugin id → bound adapter. */
|
|
18
|
+
bindRuntimeAdapters(engine: FlowEngine): Map<string, unknown>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { PluginReport } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Central registry for tramli plugins.
|
|
4
|
+
* Manages plugin lifecycle: analyze → wrap store → install engine → bind adapters.
|
|
5
|
+
*/
|
|
6
|
+
export class PluginRegistry {
|
|
7
|
+
plugins = [];
|
|
8
|
+
register(plugin) {
|
|
9
|
+
this.plugins.push(plugin);
|
|
10
|
+
return this;
|
|
11
|
+
}
|
|
12
|
+
/** Run all analysis plugins against a FlowDefinition. */
|
|
13
|
+
analyzeAll(definition) {
|
|
14
|
+
const report = new PluginReport();
|
|
15
|
+
for (const p of this.plugins) {
|
|
16
|
+
if (p.kind() === 'ANALYSIS') {
|
|
17
|
+
p.analyze(definition, report);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return report;
|
|
21
|
+
}
|
|
22
|
+
/** Apply all store plugins (wrapping in registration order). */
|
|
23
|
+
applyStorePlugins(store) {
|
|
24
|
+
let wrapped = store;
|
|
25
|
+
for (const p of this.plugins) {
|
|
26
|
+
if (p.kind() === 'STORE') {
|
|
27
|
+
wrapped = p.wrapStore(wrapped);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return wrapped;
|
|
31
|
+
}
|
|
32
|
+
/** Install all engine plugins. */
|
|
33
|
+
installEnginePlugins(engine) {
|
|
34
|
+
for (const p of this.plugins) {
|
|
35
|
+
if (p.kind() === 'ENGINE') {
|
|
36
|
+
p.install(engine);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** Bind all runtime adapter plugins. Returns map of plugin id → bound adapter. */
|
|
41
|
+
bindRuntimeAdapters(engine) {
|
|
42
|
+
const adapters = new Map();
|
|
43
|
+
for (const p of this.plugins) {
|
|
44
|
+
if (p.kind() === 'RUNTIME_ADAPTER') {
|
|
45
|
+
const adapter = p.bind(engine);
|
|
46
|
+
adapters.set(p.descriptor().id, adapter);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return adapters;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { FlowDefinition, FlowEngine } from '@unlaxer/tramli';
|
|
2
|
+
/** Plugin kind classification. */
|
|
3
|
+
export type PluginKind = 'ANALYSIS' | 'STORE' | 'ENGINE' | 'RUNTIME_ADAPTER' | 'GENERATION' | 'DOCUMENTATION';
|
|
4
|
+
/** Plugin descriptor. */
|
|
5
|
+
export interface PluginDescriptor {
|
|
6
|
+
id: string;
|
|
7
|
+
displayName: string;
|
|
8
|
+
description: string;
|
|
9
|
+
}
|
|
10
|
+
/** Base plugin interface. */
|
|
11
|
+
export interface FlowPlugin {
|
|
12
|
+
descriptor(): PluginDescriptor;
|
|
13
|
+
kind(): PluginKind;
|
|
14
|
+
id?(): string;
|
|
15
|
+
}
|
|
16
|
+
/** Analysis plugin — static analysis of FlowDefinition. */
|
|
17
|
+
export interface AnalysisPlugin<S extends string> extends FlowPlugin {
|
|
18
|
+
analyze(definition: FlowDefinition<S>, report: PluginReport): void;
|
|
19
|
+
}
|
|
20
|
+
/** Store plugin — wraps FlowStore with additional behavior. */
|
|
21
|
+
export interface StorePlugin extends FlowPlugin {
|
|
22
|
+
wrapStore(store: any): any;
|
|
23
|
+
}
|
|
24
|
+
/** Engine plugin — installs hooks on FlowEngine. */
|
|
25
|
+
export interface EnginePlugin extends FlowPlugin {
|
|
26
|
+
install(engine: FlowEngine): void;
|
|
27
|
+
}
|
|
28
|
+
/** Runtime adapter plugin — binds FlowEngine to return richer API. */
|
|
29
|
+
export interface RuntimeAdapterPlugin<R> extends FlowPlugin {
|
|
30
|
+
bind(engine: FlowEngine): R;
|
|
31
|
+
}
|
|
32
|
+
/** Generation plugin — generates output from input. */
|
|
33
|
+
export interface GenerationPlugin<I, O> extends FlowPlugin {
|
|
34
|
+
generate(input: I): O;
|
|
35
|
+
}
|
|
36
|
+
/** Documentation plugin — generates string documentation. */
|
|
37
|
+
export interface DocumentationPlugin<I> extends GenerationPlugin<I, string> {
|
|
38
|
+
}
|
|
39
|
+
/** Plugin report — collects analysis findings. */
|
|
40
|
+
export declare class PluginReport {
|
|
41
|
+
private entries;
|
|
42
|
+
add(pluginId: string, severity: string, message: string): void;
|
|
43
|
+
warn(pluginId: string, message: string): void;
|
|
44
|
+
error(pluginId: string, message: string): void;
|
|
45
|
+
asText(): string;
|
|
46
|
+
findings(): {
|
|
47
|
+
pluginId: string;
|
|
48
|
+
severity: string;
|
|
49
|
+
message: string;
|
|
50
|
+
}[];
|
|
51
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** Plugin report — collects analysis findings. */
|
|
2
|
+
export class PluginReport {
|
|
3
|
+
entries = [];
|
|
4
|
+
add(pluginId, severity, message) {
|
|
5
|
+
this.entries.push({ pluginId, severity, message });
|
|
6
|
+
}
|
|
7
|
+
warn(pluginId, message) {
|
|
8
|
+
this.add(pluginId, 'WARN', message);
|
|
9
|
+
}
|
|
10
|
+
error(pluginId, message) {
|
|
11
|
+
this.add(pluginId, 'ERROR', message);
|
|
12
|
+
}
|
|
13
|
+
asText() {
|
|
14
|
+
if (this.entries.length === 0)
|
|
15
|
+
return 'No findings.';
|
|
16
|
+
return this.entries.map(e => `[${e.severity}] ${e.pluginId}: ${e.message}`).join('\n');
|
|
17
|
+
}
|
|
18
|
+
findings() { return [...this.entries]; }
|
|
19
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { StorePlugin, PluginDescriptor } from '../api/types.js';
|
|
2
|
+
import { AuditingFlowStore } from './auditing-flow-store.js';
|
|
3
|
+
export declare class AuditStorePlugin implements StorePlugin {
|
|
4
|
+
descriptor(): PluginDescriptor;
|
|
5
|
+
kind(): "STORE";
|
|
6
|
+
wrapStore(store: any): AuditingFlowStore;
|
|
7
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { AuditingFlowStore } from './auditing-flow-store.js';
|
|
2
|
+
export class AuditStorePlugin {
|
|
3
|
+
descriptor() {
|
|
4
|
+
return { id: 'audit', displayName: 'Audit Store', description: 'Captures transition + produced-data diffs' };
|
|
5
|
+
}
|
|
6
|
+
kind() { return 'STORE'; }
|
|
7
|
+
wrapStore(store) {
|
|
8
|
+
return new AuditingFlowStore(store);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { FlowContext } from '@unlaxer/tramli';
|
|
2
|
+
export interface AuditedTransitionRecord {
|
|
3
|
+
flowId: string;
|
|
4
|
+
from: string | null;
|
|
5
|
+
to: string;
|
|
6
|
+
trigger: string;
|
|
7
|
+
timestamp: Date;
|
|
8
|
+
producedDataSnapshot: Map<string, unknown>;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* FlowStore decorator that captures produced-data snapshots on each transition.
|
|
12
|
+
*/
|
|
13
|
+
export declare class AuditingFlowStore {
|
|
14
|
+
private readonly delegate;
|
|
15
|
+
private auditLog;
|
|
16
|
+
constructor(delegate: any);
|
|
17
|
+
create(flow: any): void;
|
|
18
|
+
loadForUpdate<S extends string>(flowId: string, definition?: any): any;
|
|
19
|
+
save(flow: any): void;
|
|
20
|
+
recordTransition(flowId: string, from: any, to: string, trigger: string, ctx: FlowContext): void;
|
|
21
|
+
get auditedTransitions(): readonly AuditedTransitionRecord[];
|
|
22
|
+
get transitionLog(): any;
|
|
23
|
+
clear(): void;
|
|
24
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FlowStore decorator that captures produced-data snapshots on each transition.
|
|
3
|
+
*/
|
|
4
|
+
export class AuditingFlowStore {
|
|
5
|
+
delegate;
|
|
6
|
+
auditLog = [];
|
|
7
|
+
constructor(delegate) {
|
|
8
|
+
this.delegate = delegate;
|
|
9
|
+
}
|
|
10
|
+
create(flow) { this.delegate.create(flow); }
|
|
11
|
+
loadForUpdate(flowId, definition) {
|
|
12
|
+
return this.delegate.loadForUpdate(flowId, definition);
|
|
13
|
+
}
|
|
14
|
+
save(flow) { this.delegate.save(flow); }
|
|
15
|
+
recordTransition(flowId, from, to, trigger, ctx) {
|
|
16
|
+
this.delegate.recordTransition(flowId, from, to, trigger, ctx);
|
|
17
|
+
const snapshot = new Map();
|
|
18
|
+
for (const [k, v] of ctx.snapshot()) {
|
|
19
|
+
snapshot.set(k, v);
|
|
20
|
+
}
|
|
21
|
+
this.auditLog.push({
|
|
22
|
+
flowId, from: from?.toString() ?? null, to, trigger,
|
|
23
|
+
timestamp: new Date(), producedDataSnapshot: snapshot,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
get auditedTransitions() { return this.auditLog; }
|
|
27
|
+
get transitionLog() { return this.delegate.transitionLog; }
|
|
28
|
+
clear() { this.delegate.clear?.(); this.auditLog = []; }
|
|
29
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { FlowDefinition } from '@unlaxer/tramli';
|
|
2
|
+
import type { GenerationPlugin, PluginDescriptor } from '../api/types.js';
|
|
3
|
+
import type { DiagramBundle } from './types.js';
|
|
4
|
+
export declare class DiagramGenerationPlugin<S extends string> implements GenerationPlugin<FlowDefinition<S>, DiagramBundle> {
|
|
5
|
+
private readonly delegate;
|
|
6
|
+
descriptor(): PluginDescriptor;
|
|
7
|
+
kind(): "GENERATION";
|
|
8
|
+
generate(input: FlowDefinition<S>): DiagramBundle;
|
|
9
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { DiagramPlugin } from './diagram-plugin.js';
|
|
2
|
+
export class DiagramGenerationPlugin {
|
|
3
|
+
delegate = new DiagramPlugin();
|
|
4
|
+
descriptor() {
|
|
5
|
+
return {
|
|
6
|
+
id: 'diagram',
|
|
7
|
+
displayName: 'Diagram Generator',
|
|
8
|
+
description: 'Generates Mermaid and data-flow bundles from a flow definition.',
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
kind() { return 'GENERATION'; }
|
|
12
|
+
generate(input) {
|
|
13
|
+
return this.delegate.generate(input);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { MermaidGenerator } from '@unlaxer/tramli';
|
|
2
|
+
export class DiagramPlugin {
|
|
3
|
+
generate(definition) {
|
|
4
|
+
const mermaid = MermaidGenerator.generate(definition);
|
|
5
|
+
const json = definition.dataFlowGraph?.toJson() ?? '{}';
|
|
6
|
+
const md = `# ${definition.name}\n\n` +
|
|
7
|
+
`- initial: \`${definition.initialState}\`\n` +
|
|
8
|
+
`- states: \`${definition.allStates().length}\`\n` +
|
|
9
|
+
`- transitions: \`${definition.transitions.length}\`\n`;
|
|
10
|
+
return { mermaid, dataFlowJson: json, markdownSummary: md };
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates a markdown flow catalog from a flow definition.
|
|
3
|
+
*/
|
|
4
|
+
export class DocumentationPlugin {
|
|
5
|
+
toMarkdown(definition) {
|
|
6
|
+
const lines = [];
|
|
7
|
+
lines.push(`# Flow Catalog: ${definition.name}`);
|
|
8
|
+
lines.push('');
|
|
9
|
+
lines.push('## States');
|
|
10
|
+
lines.push('');
|
|
11
|
+
for (const state of definition.allStates()) {
|
|
12
|
+
const config = definition.stateConfig[state];
|
|
13
|
+
let suffix = '';
|
|
14
|
+
if (config?.initial)
|
|
15
|
+
suffix += ' (initial)';
|
|
16
|
+
if (config?.terminal)
|
|
17
|
+
suffix += ' (terminal)';
|
|
18
|
+
lines.push(`- \`${state}\`${suffix}`);
|
|
19
|
+
}
|
|
20
|
+
lines.push('');
|
|
21
|
+
lines.push('## Transitions');
|
|
22
|
+
lines.push('');
|
|
23
|
+
for (const t of definition.transitions) {
|
|
24
|
+
const via = t.processor?.name ?? t.guard?.name ?? t.branch?.name ?? t.type;
|
|
25
|
+
lines.push(`- \`${t.from} -> ${t.to}\` via \`${via}\``);
|
|
26
|
+
}
|
|
27
|
+
return lines.join('\n');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { FlowDefinition } from '@unlaxer/tramli';
|
|
2
|
+
import type { DocumentationPlugin as DocPluginSPI, PluginDescriptor } from '../api/types.js';
|
|
3
|
+
export declare class FlowDocumentationPlugin<S extends string> implements DocPluginSPI<FlowDefinition<S>> {
|
|
4
|
+
private readonly delegate;
|
|
5
|
+
descriptor(): PluginDescriptor;
|
|
6
|
+
kind(): "DOCUMENTATION";
|
|
7
|
+
generate(input: FlowDefinition<S>): string;
|
|
8
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { DocumentationPlugin } from './documentation-plugin.js';
|
|
2
|
+
export class FlowDocumentationPlugin {
|
|
3
|
+
delegate = new DocumentationPlugin();
|
|
4
|
+
descriptor() {
|
|
5
|
+
return {
|
|
6
|
+
id: 'docs',
|
|
7
|
+
displayName: 'Documentation Generator',
|
|
8
|
+
description: 'Renders a markdown flow catalog from a flow definition.',
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
kind() { return 'DOCUMENTATION'; }
|
|
12
|
+
generate(input) {
|
|
13
|
+
return this.delegate.toMarkdown(input);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { VersionedTransitionEvent, CompensationResolver } from './types.js';
|
|
2
|
+
import type { EventLogStoreDecorator } from './event-log-store-decorator.js';
|
|
3
|
+
/**
|
|
4
|
+
* Compensation service — records compensation events for failed transitions.
|
|
5
|
+
*/
|
|
6
|
+
export declare class CompensationService {
|
|
7
|
+
private readonly resolver;
|
|
8
|
+
private readonly eventLog;
|
|
9
|
+
constructor(resolver: CompensationResolver, eventLog: EventLogStoreDecorator);
|
|
10
|
+
compensate(event: VersionedTransitionEvent, cause: Error): boolean;
|
|
11
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compensation service — records compensation events for failed transitions.
|
|
3
|
+
*/
|
|
4
|
+
export class CompensationService {
|
|
5
|
+
resolver;
|
|
6
|
+
eventLog;
|
|
7
|
+
constructor(resolver, eventLog) {
|
|
8
|
+
this.resolver = resolver;
|
|
9
|
+
this.eventLog = eventLog;
|
|
10
|
+
}
|
|
11
|
+
compensate(event, cause) {
|
|
12
|
+
const plan = this.resolver(event, cause);
|
|
13
|
+
if (!plan)
|
|
14
|
+
return false;
|
|
15
|
+
this.eventLog.appendCompensation(event.flowId, plan.action, JSON.stringify(plan.metadata));
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { FlowContext } from '@unlaxer/tramli';
|
|
2
|
+
import type { VersionedTransitionEvent } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* FlowStore decorator that maintains an append-only event log.
|
|
5
|
+
* Tenure-lite: not full event sourcing, intentionally lighter.
|
|
6
|
+
*/
|
|
7
|
+
export declare class EventLogStoreDecorator {
|
|
8
|
+
private readonly delegate;
|
|
9
|
+
private eventLog;
|
|
10
|
+
private versionCounters;
|
|
11
|
+
constructor(delegate: any);
|
|
12
|
+
create(flow: any): void;
|
|
13
|
+
loadForUpdate<S extends string>(flowId: string, definition?: any): any;
|
|
14
|
+
save(flow: any): void;
|
|
15
|
+
recordTransition(flowId: string, from: any, to: string, trigger: string, ctx: FlowContext): void;
|
|
16
|
+
/** All events across all flows. */
|
|
17
|
+
events(): readonly VersionedTransitionEvent[];
|
|
18
|
+
/** Events for a specific flow. */
|
|
19
|
+
eventsForFlow(flowId: string): VersionedTransitionEvent[];
|
|
20
|
+
/** Append a compensation event. */
|
|
21
|
+
appendCompensation(flowId: string, trigger: string, metadata: string): void;
|
|
22
|
+
get transitionLog(): any;
|
|
23
|
+
clear(): void;
|
|
24
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FlowStore decorator that maintains an append-only event log.
|
|
3
|
+
* Tenure-lite: not full event sourcing, intentionally lighter.
|
|
4
|
+
*/
|
|
5
|
+
export class EventLogStoreDecorator {
|
|
6
|
+
delegate;
|
|
7
|
+
eventLog = [];
|
|
8
|
+
versionCounters = new Map();
|
|
9
|
+
constructor(delegate) {
|
|
10
|
+
this.delegate = delegate;
|
|
11
|
+
}
|
|
12
|
+
create(flow) { this.delegate.create(flow); }
|
|
13
|
+
loadForUpdate(flowId, definition) {
|
|
14
|
+
return this.delegate.loadForUpdate(flowId, definition);
|
|
15
|
+
}
|
|
16
|
+
save(flow) { this.delegate.save(flow); }
|
|
17
|
+
recordTransition(flowId, from, to, trigger, ctx) {
|
|
18
|
+
this.delegate.recordTransition(flowId, from, to, trigger, ctx);
|
|
19
|
+
const version = (this.versionCounters.get(flowId) ?? 0) + 1;
|
|
20
|
+
this.versionCounters.set(flowId, version);
|
|
21
|
+
this.eventLog.push({
|
|
22
|
+
flowId, version, type: 'TRANSITION',
|
|
23
|
+
from: from?.toString() ?? null, to, trigger,
|
|
24
|
+
timestamp: new Date(),
|
|
25
|
+
stateSnapshot: JSON.stringify(Object.fromEntries(ctx.snapshot())),
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/** All events across all flows. */
|
|
29
|
+
events() { return this.eventLog; }
|
|
30
|
+
/** Events for a specific flow. */
|
|
31
|
+
eventsForFlow(flowId) {
|
|
32
|
+
return this.eventLog.filter(e => e.flowId === flowId);
|
|
33
|
+
}
|
|
34
|
+
/** Append a compensation event. */
|
|
35
|
+
appendCompensation(flowId, trigger, metadata) {
|
|
36
|
+
const version = (this.versionCounters.get(flowId) ?? 0) + 1;
|
|
37
|
+
this.versionCounters.set(flowId, version);
|
|
38
|
+
this.eventLog.push({
|
|
39
|
+
flowId, version, type: 'COMPENSATION',
|
|
40
|
+
from: null, to: 'COMPENSATED', trigger,
|
|
41
|
+
timestamp: new Date(), stateSnapshot: metadata,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
get transitionLog() { return this.delegate.transitionLog; }
|
|
45
|
+
clear() { this.delegate.clear?.(); this.eventLog = []; this.versionCounters.clear(); }
|
|
46
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { StorePlugin, PluginDescriptor } from '../api/types.js';
|
|
2
|
+
import { EventLogStoreDecorator } from './event-log-store-decorator.js';
|
|
3
|
+
export declare class EventLogStorePlugin implements StorePlugin {
|
|
4
|
+
descriptor(): PluginDescriptor;
|
|
5
|
+
kind(): "STORE";
|
|
6
|
+
wrapStore(store: any): EventLogStoreDecorator;
|
|
7
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { EventLogStoreDecorator } from './event-log-store-decorator.js';
|
|
2
|
+
export class EventLogStorePlugin {
|
|
3
|
+
descriptor() {
|
|
4
|
+
return { id: 'eventstore', displayName: 'Event Log Store', description: 'Tenure-lite: append-only transition log with replay' };
|
|
5
|
+
}
|
|
6
|
+
kind() { return 'STORE'; }
|
|
7
|
+
wrapStore(store) {
|
|
8
|
+
return new EventLogStoreDecorator(store);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { VersionedTransitionEvent, ProjectionReducer } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Replay service — stateAtVersion using full-snapshot assumption.
|
|
4
|
+
* If moving to diff-only persistence, this must become a fold/reducer.
|
|
5
|
+
*/
|
|
6
|
+
export declare class ReplayService {
|
|
7
|
+
stateAtVersion(events: readonly VersionedTransitionEvent[], flowId: string, targetVersion: number): string | null;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Projection replay service — custom reducers for materialized views.
|
|
11
|
+
*/
|
|
12
|
+
export declare class ProjectionReplayService {
|
|
13
|
+
stateAtVersion<T>(events: readonly VersionedTransitionEvent[], flowId: string, targetVersion: number, reducer: ProjectionReducer<T>): T;
|
|
14
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Replay service — stateAtVersion using full-snapshot assumption.
|
|
3
|
+
* If moving to diff-only persistence, this must become a fold/reducer.
|
|
4
|
+
*/
|
|
5
|
+
export class ReplayService {
|
|
6
|
+
stateAtVersion(events, flowId, targetVersion) {
|
|
7
|
+
const flowEvents = events
|
|
8
|
+
.filter(e => e.flowId === flowId && e.type === 'TRANSITION' && e.version <= targetVersion)
|
|
9
|
+
.sort((a, b) => a.version - b.version);
|
|
10
|
+
if (flowEvents.length === 0)
|
|
11
|
+
return null;
|
|
12
|
+
return flowEvents[flowEvents.length - 1].to;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Projection replay service — custom reducers for materialized views.
|
|
17
|
+
*/
|
|
18
|
+
export class ProjectionReplayService {
|
|
19
|
+
stateAtVersion(events, flowId, targetVersion, reducer) {
|
|
20
|
+
let state = reducer.initialState();
|
|
21
|
+
for (const event of events) {
|
|
22
|
+
if (event.flowId === flowId && event.version <= targetVersion) {
|
|
23
|
+
state = reducer.apply(state, event);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return state;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface VersionedTransitionEvent {
|
|
2
|
+
flowId: string;
|
|
3
|
+
version: number;
|
|
4
|
+
type: 'TRANSITION' | 'COMPENSATION';
|
|
5
|
+
from: string | null;
|
|
6
|
+
to: string;
|
|
7
|
+
trigger: string;
|
|
8
|
+
timestamp: Date;
|
|
9
|
+
stateSnapshot: string;
|
|
10
|
+
}
|
|
11
|
+
export interface CompensationPlan {
|
|
12
|
+
action: string;
|
|
13
|
+
metadata: Record<string, string>;
|
|
14
|
+
}
|
|
15
|
+
export type CompensationResolver = (event: VersionedTransitionEvent, cause: Error) => CompensationPlan | null;
|
|
16
|
+
export interface ProjectionReducer<T> {
|
|
17
|
+
initialState(): T;
|
|
18
|
+
apply(current: T, event: VersionedTransitionEvent): T;
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { HierarchicalFlowSpec, HierarchicalTransitionSpec } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Synthesizes entry/exit transitions from hierarchical state specs.
|
|
4
|
+
*/
|
|
5
|
+
export declare class EntryExitCompiler {
|
|
6
|
+
synthesize(spec: HierarchicalFlowSpec): HierarchicalTransitionSpec[];
|
|
7
|
+
private walk;
|
|
8
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Synthesizes entry/exit transitions from hierarchical state specs.
|
|
3
|
+
*/
|
|
4
|
+
export class EntryExitCompiler {
|
|
5
|
+
synthesize(spec) {
|
|
6
|
+
const generated = [];
|
|
7
|
+
for (const state of spec.rootStates) {
|
|
8
|
+
this.walk(state, generated, null);
|
|
9
|
+
}
|
|
10
|
+
return generated;
|
|
11
|
+
}
|
|
12
|
+
walk(state, out, parentName) {
|
|
13
|
+
if (state.entryProduces.length > 0) {
|
|
14
|
+
out.push({
|
|
15
|
+
from: parentName ?? `${state.name}__ENTRY_START`,
|
|
16
|
+
to: state.name,
|
|
17
|
+
trigger: `__entry__${state.name}`,
|
|
18
|
+
requires: [],
|
|
19
|
+
produces: [...state.entryProduces],
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
if (state.exitProduces.length > 0) {
|
|
23
|
+
out.push({
|
|
24
|
+
from: state.name,
|
|
25
|
+
to: `${state.name}__EXIT_END`,
|
|
26
|
+
trigger: `__exit__${state.name}`,
|
|
27
|
+
requires: [],
|
|
28
|
+
produces: [...state.exitProduces],
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
for (const child of state.children) {
|
|
32
|
+
this.walk(child, out, state.name);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|