@dixxanta08/stategate 0.1.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/README.md +0 -0
- package/dist/errors.d.ts +9 -0
- package/dist/errors.js +27 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +141 -0
- package/dist/machine.d.ts +0 -0
- package/dist/machine.js +1 -0
- package/dist/types.d.ts +78 -0
- package/dist/types.js +2 -0
- package/dist/utils.d.ts +0 -0
- package/dist/utils.js +1 -0
- package/package.json +36 -0
package/README.md
ADDED
|
Binary file
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { TransitionParams } from "./types";
|
|
2
|
+
export declare class AbortTransition extends Error {
|
|
3
|
+
details: TransitionParams;
|
|
4
|
+
constructor(params?: TransitionParams);
|
|
5
|
+
}
|
|
6
|
+
export declare class InvalidTransition extends Error {
|
|
7
|
+
details: TransitionParams;
|
|
8
|
+
constructor(params?: TransitionParams);
|
|
9
|
+
}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.InvalidTransition = exports.AbortTransition = void 0;
|
|
4
|
+
class AbortTransition extends Error {
|
|
5
|
+
constructor(params = {}) {
|
|
6
|
+
var _a;
|
|
7
|
+
super(params.message || "Aborting transition");
|
|
8
|
+
this.name = "AbortTransition";
|
|
9
|
+
this.details = {
|
|
10
|
+
timestamp: (_a = params.timestamp) !== null && _a !== void 0 ? _a : new Date(),
|
|
11
|
+
...params,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
exports.AbortTransition = AbortTransition;
|
|
16
|
+
class InvalidTransition extends Error {
|
|
17
|
+
constructor(params = {}) {
|
|
18
|
+
var _a;
|
|
19
|
+
super(params.message || "Invalid transition");
|
|
20
|
+
this.name = "InvalidTransition";
|
|
21
|
+
this.details = {
|
|
22
|
+
timestamp: (_a = params.timestamp) !== null && _a !== void 0 ? _a : new Date(),
|
|
23
|
+
...params,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
exports.InvalidTransition = InvalidTransition;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { EventHandler, PreTransitionParams, PostTransitionParams, MachineConfig } from "./types";
|
|
2
|
+
export declare class StateMachine {
|
|
3
|
+
private handlers;
|
|
4
|
+
private readonly config;
|
|
5
|
+
constructor(config: MachineConfig);
|
|
6
|
+
preTransition(params: PreTransitionParams): Promise<void>;
|
|
7
|
+
transition(entity: any, nextState: string, context?: {
|
|
8
|
+
actor: string;
|
|
9
|
+
meta?: any;
|
|
10
|
+
}): Promise<void>;
|
|
11
|
+
postTransition(params: PostTransitionParams): Promise<void>;
|
|
12
|
+
on(event: string, callback: EventHandler): void;
|
|
13
|
+
emit(event: string): void;
|
|
14
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.StateMachine = void 0;
|
|
4
|
+
const errors_1 = require("./errors");
|
|
5
|
+
class StateMachine {
|
|
6
|
+
constructor(config) {
|
|
7
|
+
var _a;
|
|
8
|
+
this.handlers = {};
|
|
9
|
+
const transitionKeys = Object.keys(config.transitions);
|
|
10
|
+
transitionKeys.forEach((tK) => {
|
|
11
|
+
const nextStates = Object.keys(config.transitions[tK]);
|
|
12
|
+
nextStates.forEach((nS) => {
|
|
13
|
+
if (config.transitions[tK][nS].isAbortable &&
|
|
14
|
+
!config.transitions[tK][nS].onAbort) {
|
|
15
|
+
config.transitions[tK][nS].onAbort = () => {
|
|
16
|
+
console.log(`Aborting transition from ${tK} to ${nS}`);
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
this.config = {
|
|
22
|
+
...config,
|
|
23
|
+
stateKey: (_a = config.stateKey) !== null && _a !== void 0 ? _a : "status",
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
async preTransition(params) {
|
|
27
|
+
var _a;
|
|
28
|
+
const { currentState, nextState, nextTransition, allowedNextStates, entity, context, } = params;
|
|
29
|
+
if (!(nextState in allowedNextStates)) {
|
|
30
|
+
throw new errors_1.InvalidTransition({
|
|
31
|
+
message: "Invalid Transaction",
|
|
32
|
+
from: currentState,
|
|
33
|
+
to: nextState,
|
|
34
|
+
meta: context,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
nextTransition.onBefore && (await nextTransition.onBefore(entity));
|
|
38
|
+
const beforeTransitionHooks = (_a = this.config.globalHooks) === null || _a === void 0 ? void 0 : _a.beforeTransition;
|
|
39
|
+
const payload = {
|
|
40
|
+
from: currentState,
|
|
41
|
+
to: nextState,
|
|
42
|
+
actor: context === null || context === void 0 ? void 0 : context.actor,
|
|
43
|
+
meta: context === null || context === void 0 ? void 0 : context.meta,
|
|
44
|
+
timestamp: new Date(),
|
|
45
|
+
};
|
|
46
|
+
const hooks = beforeTransitionHooks !== null && beforeTransitionHooks !== void 0 ? beforeTransitionHooks : [];
|
|
47
|
+
await Promise.all(hooks.map((fn) => fn(payload)));
|
|
48
|
+
}
|
|
49
|
+
async transition(entity, nextState, context) {
|
|
50
|
+
var _a, _b, _c, _d, _e, _f;
|
|
51
|
+
const stateKey = this.config.stateKey;
|
|
52
|
+
const currentState = entity[stateKey];
|
|
53
|
+
const allowedNextStates = this.config.transitions[currentState];
|
|
54
|
+
const nextTransition = allowedNextStates[nextState];
|
|
55
|
+
const entitySnapshot = structuredClone(entity);
|
|
56
|
+
try {
|
|
57
|
+
await this.preTransition({
|
|
58
|
+
currentState,
|
|
59
|
+
allowedNextStates,
|
|
60
|
+
nextState,
|
|
61
|
+
nextTransition,
|
|
62
|
+
entity,
|
|
63
|
+
context,
|
|
64
|
+
});
|
|
65
|
+
entity[stateKey] = nextState;
|
|
66
|
+
await this.postTransition({
|
|
67
|
+
currentState,
|
|
68
|
+
nextState,
|
|
69
|
+
entity,
|
|
70
|
+
nextTransition,
|
|
71
|
+
stateKey,
|
|
72
|
+
context,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
catch (e) {
|
|
76
|
+
const error = e;
|
|
77
|
+
if (e instanceof errors_1.InvalidTransition) {
|
|
78
|
+
const invalidTransitionErrorPayload = {
|
|
79
|
+
from: currentState,
|
|
80
|
+
to: nextState,
|
|
81
|
+
message: (error === null || error === void 0 ? void 0 : error.message) ? error.message : "Invalid transition.",
|
|
82
|
+
timestamp: new Date(),
|
|
83
|
+
meta: context,
|
|
84
|
+
};
|
|
85
|
+
(_b = (_a = this.config.globalHooks) === null || _a === void 0 ? void 0 : _a.onInvalidTransition) === null || _b === void 0 ? void 0 : _b.call(_a, invalidTransitionErrorPayload);
|
|
86
|
+
}
|
|
87
|
+
if ((nextTransition === null || nextTransition === void 0 ? void 0 : nextTransition.isAbortable) && e instanceof errors_1.AbortTransition) {
|
|
88
|
+
entity = structuredClone(entitySnapshot);
|
|
89
|
+
const abortTransitionErrorPayload = {
|
|
90
|
+
from: currentState,
|
|
91
|
+
to: nextState,
|
|
92
|
+
message: (error === null || error === void 0 ? void 0 : error.message) ? error.message : "Aborting transition.",
|
|
93
|
+
timestamp: new Date(),
|
|
94
|
+
meta: context,
|
|
95
|
+
};
|
|
96
|
+
(_c = nextTransition.onAbort) === null || _c === void 0 ? void 0 : _c.call(nextTransition, abortTransitionErrorPayload);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
console.log("Abort Transition triggered but skipped over transition {isAbortable:false} ", error);
|
|
100
|
+
}
|
|
101
|
+
if ((_d = this.config.globalHooks) === null || _d === void 0 ? void 0 : _d.onError) {
|
|
102
|
+
const errorPaylaod = {
|
|
103
|
+
from: currentState,
|
|
104
|
+
to: nextState,
|
|
105
|
+
message: (error === null || error === void 0 ? void 0 : error.message) ? error.message : "Aborting transition.",
|
|
106
|
+
timestamp: new Date(),
|
|
107
|
+
meta: context,
|
|
108
|
+
};
|
|
109
|
+
(_f = (_e = this.config.globalHooks) === null || _e === void 0 ? void 0 : _e.onError) === null || _f === void 0 ? void 0 : _f.call(_e, error, errorPaylaod);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
async postTransition(params) {
|
|
114
|
+
var _a;
|
|
115
|
+
const { currentState, nextState, nextTransition, stateKey, entity, context, } = params;
|
|
116
|
+
if (entity[stateKey] !== nextState) {
|
|
117
|
+
throw new Error("Transition failed or had incongurencies");
|
|
118
|
+
}
|
|
119
|
+
nextTransition.onAfter && (await nextTransition.onAfter(entity));
|
|
120
|
+
const afterTransitionHooks = (_a = this.config.globalHooks) === null || _a === void 0 ? void 0 : _a.afterTransition;
|
|
121
|
+
const payload = {
|
|
122
|
+
from: currentState,
|
|
123
|
+
to: nextState,
|
|
124
|
+
actor: context === null || context === void 0 ? void 0 : context.actor,
|
|
125
|
+
meta: context === null || context === void 0 ? void 0 : context.meta,
|
|
126
|
+
timestamp: new Date(),
|
|
127
|
+
};
|
|
128
|
+
const hooks = afterTransitionHooks !== null && afterTransitionHooks !== void 0 ? afterTransitionHooks : [];
|
|
129
|
+
await Promise.all(hooks.map((fn) => fn(payload)));
|
|
130
|
+
}
|
|
131
|
+
on(event, callback) {
|
|
132
|
+
if (!this.handlers[event]) {
|
|
133
|
+
this.handlers[event] = [];
|
|
134
|
+
}
|
|
135
|
+
this.handlers[event].push(callback);
|
|
136
|
+
}
|
|
137
|
+
emit(event) {
|
|
138
|
+
this.handlers[event].forEach((fn) => fn());
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
exports.StateMachine = StateMachine;
|
|
File without changes
|
package/dist/machine.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
export type TransitionParams = {
|
|
2
|
+
from?: string;
|
|
3
|
+
to?: string;
|
|
4
|
+
message?: string;
|
|
5
|
+
timestamp?: Date;
|
|
6
|
+
meta?: any;
|
|
7
|
+
};
|
|
8
|
+
export type TransitionPayload = {
|
|
9
|
+
from: string;
|
|
10
|
+
to: string;
|
|
11
|
+
actor?: string;
|
|
12
|
+
meta?: any;
|
|
13
|
+
timestamp: Date;
|
|
14
|
+
};
|
|
15
|
+
export type EventHandler = (entity?: any) => Promise<void>;
|
|
16
|
+
export type AbortHandler = (errorPaylaod?: AbortTransitionErrorPayload) => void;
|
|
17
|
+
export type TransitionHandler = (payload: TransitionPayload) => Promise<void>;
|
|
18
|
+
export type InvalidTransitionHandler = (payload: TransitionPayload) => void;
|
|
19
|
+
export type ErrorTransitionHandler = (error: Error, payload: TransitionPayload) => void;
|
|
20
|
+
export type TransitionConfig = {
|
|
21
|
+
isAbortable?: boolean;
|
|
22
|
+
onBefore?: EventHandler;
|
|
23
|
+
onAbort?: AbortHandler;
|
|
24
|
+
onAfter?: EventHandler;
|
|
25
|
+
details?: {
|
|
26
|
+
label: string;
|
|
27
|
+
description: string;
|
|
28
|
+
allowedActors: string;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
export type PreTransitionParams = {
|
|
32
|
+
currentState: string;
|
|
33
|
+
allowedNextStates: Record<string, TransitionConfig>;
|
|
34
|
+
nextState: string;
|
|
35
|
+
nextTransition: TransitionConfig;
|
|
36
|
+
entity: any;
|
|
37
|
+
context?: {
|
|
38
|
+
actor?: string;
|
|
39
|
+
meta?: any;
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
export type PostTransitionParams = {
|
|
43
|
+
currentState: string;
|
|
44
|
+
nextState: string;
|
|
45
|
+
nextTransition: TransitionConfig;
|
|
46
|
+
entity: any;
|
|
47
|
+
stateKey: string;
|
|
48
|
+
context?: {
|
|
49
|
+
actor?: string;
|
|
50
|
+
meta?: any;
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
export type MachineConfig = {
|
|
54
|
+
initialState: string;
|
|
55
|
+
stateKey?: string;
|
|
56
|
+
transitions: Record<string, Record<string, TransitionConfig>>;
|
|
57
|
+
globalHooks?: {
|
|
58
|
+
beforeTransition?: TransitionHandler[];
|
|
59
|
+
afterTransition?: TransitionHandler[];
|
|
60
|
+
onInvalidTransition?: InvalidTransitionHandler;
|
|
61
|
+
onError?: ErrorTransitionHandler;
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
export type NormalizedMachineconfig = Omit<MachineConfig, "stateKey"> & {
|
|
65
|
+
stateKey: string;
|
|
66
|
+
};
|
|
67
|
+
export type BaseErrorPayload<TMeta = any> = {
|
|
68
|
+
message: string;
|
|
69
|
+
timestamp: Date;
|
|
70
|
+
meta?: TMeta;
|
|
71
|
+
};
|
|
72
|
+
export type AbortTransitionErrorPayload<TMeta = any> = BaseErrorPayload<TMeta> & {
|
|
73
|
+
from: string;
|
|
74
|
+
to: string;
|
|
75
|
+
};
|
|
76
|
+
export type GlobalErrorPayload<TMeta = any> = BaseErrorPayload<TMeta> & {
|
|
77
|
+
error: Error;
|
|
78
|
+
};
|
package/dist/types.js
ADDED
package/dist/utils.d.ts
ADDED
|
File without changes
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dixxanta08/stategate",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Deterministic state machine for backend workflows",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/dixxanta08/stategate.git"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://github.com/dixxanta08/stategate",
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/dixxanta08/stategate/issues"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"state-machine",
|
|
20
|
+
"orders",
|
|
21
|
+
"backend",
|
|
22
|
+
"ecommerce",
|
|
23
|
+
"fsm"
|
|
24
|
+
],
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsc",
|
|
28
|
+
"test": "jest",
|
|
29
|
+
"prepublishOnly": "npm run build"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/jest": "^29.5.12",
|
|
33
|
+
"jest": "^30.2.0",
|
|
34
|
+
"ts-jest": "^29.1.2"
|
|
35
|
+
}
|
|
36
|
+
}
|