@motiadev/core 0.0.20 → 0.0.22
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/index.d.ts +2 -3
- package/dist/index.js +2 -3
- package/dist/src/call-step-file.d.ts +3 -2
- package/dist/src/call-step-file.js +10 -5
- package/dist/src/cron-handler.d.ts +12 -2
- package/dist/src/cron-handler.js +23 -18
- package/dist/src/event-manager.js +9 -4
- package/dist/src/flows-endpoint.d.ts +4 -3
- package/dist/src/flows-endpoint.js +5 -4
- package/dist/src/get-step-config.d.ts +2 -0
- package/dist/src/get-step-config.js +22 -0
- package/dist/src/guards.d.ts +1 -1
- package/dist/src/guards.js +0 -1
- package/dist/src/locked-data.d.ts +26 -0
- package/dist/src/locked-data.js +144 -0
- package/dist/src/logger.d.ts +3 -3
- package/dist/src/logger.js +5 -1
- package/dist/src/node/get-module-export.js +5 -2
- package/dist/src/printer.d.ts +17 -0
- package/dist/src/printer.js +74 -0
- package/dist/src/server.d.ts +8 -10
- package/dist/src/server.js +31 -13
- package/dist/src/state/adapters/default-state-adapter.js +2 -2
- package/dist/src/step-handlers.d.ts +8 -2
- package/dist/src/step-handlers.js +34 -20
- package/dist/src/step-validator.d.ts +14 -0
- package/dist/src/step-validator.js +99 -0
- package/dist/src/types.d.ts +22 -28
- package/dist/src/utils.d.ts +2 -0
- package/dist/src/utils.js +15 -0
- package/package.json +4 -1
package/dist/index.d.ts
CHANGED
|
@@ -4,8 +4,7 @@ export * from './src/step-handlers';
|
|
|
4
4
|
export * from './src/event-manager';
|
|
5
5
|
export * from './src/logger';
|
|
6
6
|
export * from './src/state/create-state-adapter';
|
|
7
|
-
export * from './src/python/get-python-config';
|
|
8
|
-
export * from './src/ruby/get-ruby-config';
|
|
9
|
-
export * from './src/node/get-config';
|
|
10
7
|
export * from './src/cron-handler';
|
|
11
8
|
export * from './src/guards';
|
|
9
|
+
export * from './src/locked-data';
|
|
10
|
+
export * from './src/get-step-config';
|
package/dist/index.js
CHANGED
|
@@ -20,8 +20,7 @@ __exportStar(require("./src/step-handlers"), exports);
|
|
|
20
20
|
__exportStar(require("./src/event-manager"), exports);
|
|
21
21
|
__exportStar(require("./src/logger"), exports);
|
|
22
22
|
__exportStar(require("./src/state/create-state-adapter"), exports);
|
|
23
|
-
__exportStar(require("./src/python/get-python-config"), exports);
|
|
24
|
-
__exportStar(require("./src/ruby/get-ruby-config"), exports);
|
|
25
|
-
__exportStar(require("./src/node/get-config"), exports);
|
|
26
23
|
__exportStar(require("./src/cron-handler"), exports);
|
|
27
24
|
__exportStar(require("./src/guards"), exports);
|
|
25
|
+
__exportStar(require("./src/locked-data"), exports);
|
|
26
|
+
__exportStar(require("./src/get-step-config"), exports);
|
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { LockedData } from './locked-data';
|
|
2
|
+
import { Event, EventConfig, EventManager, InternalStateManager, Step } from './types';
|
|
3
|
+
export declare const callStepFile: <TData>(step: Step<EventConfig>, lockedData: LockedData, event: Event<TData>, eventManager: EventManager, state: InternalStateManager) => Promise<void>;
|
|
@@ -7,6 +7,7 @@ exports.callStepFile = void 0;
|
|
|
7
7
|
const step_handler_rpc_processor_1 = require("./step-handler-rpc-processor");
|
|
8
8
|
const child_process_1 = require("child_process");
|
|
9
9
|
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const utils_1 = require("./utils");
|
|
10
11
|
const nodeRunner = path_1.default.join(__dirname, 'node', 'node-runner.js');
|
|
11
12
|
const pythonRunner = path_1.default.join(__dirname, 'python', 'python-runner.py');
|
|
12
13
|
const rubyRunner = path_1.default.join(__dirname, 'ruby', 'ruby_runner.rb');
|
|
@@ -25,11 +26,11 @@ const getLanguageBasedRunner = (stepFilePath = '') => {
|
|
|
25
26
|
}
|
|
26
27
|
throw Error(`Unsupported file extension ${stepFilePath}`);
|
|
27
28
|
};
|
|
28
|
-
const callStepFile = (
|
|
29
|
+
const callStepFile = (step, lockedData, event, eventManager, state) => {
|
|
29
30
|
return new Promise((resolve, reject) => {
|
|
30
31
|
const jsonData = JSON.stringify({ ...event });
|
|
31
|
-
const { runner, command } = getLanguageBasedRunner(
|
|
32
|
-
const child = (0, child_process_1.spawn)(command, [runner,
|
|
32
|
+
const { runner, command } = getLanguageBasedRunner(step.filePath);
|
|
33
|
+
const child = (0, child_process_1.spawn)(command, [runner, step.filePath, jsonData], {
|
|
33
34
|
stdio: [undefined, undefined, undefined, 'ipc'],
|
|
34
35
|
});
|
|
35
36
|
const rpcProcessor = new step_handler_rpc_processor_1.RpcProcessor(child);
|
|
@@ -39,13 +40,17 @@ const callStepFile = (stepPath, step, event, eventManager, state) => {
|
|
|
39
40
|
rpcProcessor.handler('state.set', (input) => state.set(input.traceId, input.key, input.value));
|
|
40
41
|
rpcProcessor.handler('state.delete', (input) => state.delete(input.traceId, input.key));
|
|
41
42
|
rpcProcessor.handler('state.clear', (input) => state.clear(input.traceId));
|
|
42
|
-
rpcProcessor.handler('emit', (input) => {
|
|
43
|
+
rpcProcessor.handler('emit', async (input) => {
|
|
44
|
+
if (!(0, utils_1.isAllowedToEmit)(step, input.type)) {
|
|
45
|
+
lockedData.printer.printInvalidEmit(step, input.type);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
43
48
|
return eventManager.emit({
|
|
44
49
|
...input,
|
|
45
50
|
traceId: event.traceId,
|
|
46
51
|
flows: event.flows,
|
|
47
52
|
logger: event.logger,
|
|
48
|
-
},
|
|
53
|
+
}, step.filePath);
|
|
49
54
|
});
|
|
50
55
|
rpcProcessor.init();
|
|
51
56
|
child.stdout?.on('data', (data) => {
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
-
import { EventManager, Step } from './types';
|
|
2
1
|
import { Server } from 'socket.io';
|
|
3
|
-
|
|
2
|
+
import { LockedData } from './locked-data';
|
|
3
|
+
import { EventManager, Step, CronConfig } from './types';
|
|
4
|
+
export type CronManager = {
|
|
5
|
+
createCronJob: (step: Step<CronConfig>) => void;
|
|
6
|
+
removeCronJob: (step: Step<CronConfig>) => void;
|
|
7
|
+
close: () => void;
|
|
8
|
+
};
|
|
9
|
+
export declare const setupCronHandlers: (lockedData: LockedData, eventManager: EventManager, socketServer: Server) => {
|
|
10
|
+
createCronJob: (step: Step<CronConfig>) => void;
|
|
11
|
+
removeCronJob: (step: Step<CronConfig>) => void;
|
|
12
|
+
close: () => void;
|
|
13
|
+
};
|
package/dist/src/cron-handler.js
CHANGED
|
@@ -34,13 +34,12 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.setupCronHandlers = void 0;
|
|
37
|
-
const logger_1 = require("./logger");
|
|
38
37
|
const cron = __importStar(require("node-cron"));
|
|
39
|
-
const
|
|
38
|
+
const logger_1 = require("./logger");
|
|
40
39
|
const get_module_export_1 = require("./node/get-module-export");
|
|
41
|
-
const
|
|
42
|
-
const
|
|
43
|
-
|
|
40
|
+
const setupCronHandlers = (lockedData, eventManager, socketServer) => {
|
|
41
|
+
const cronJobs = new Map();
|
|
42
|
+
const createCronJob = (step) => {
|
|
44
43
|
const { config, filePath } = step;
|
|
45
44
|
const { cron: cronExpression } = config;
|
|
46
45
|
if (!cron.validate(cronExpression)) {
|
|
@@ -58,24 +57,21 @@ const setupCronHandlers = (steps, eventManager, socketServer) => {
|
|
|
58
57
|
const task = cron.schedule(cronExpression, async () => {
|
|
59
58
|
const traceId = Math.random().toString(36).substring(7);
|
|
60
59
|
const logger = new logger_1.Logger(traceId, config.flows, step.config.name, socketServer);
|
|
60
|
+
const flows = config.flows;
|
|
61
61
|
try {
|
|
62
62
|
const handler = await (0, get_module_export_1.getModuleExport)(filePath, 'handler');
|
|
63
63
|
const emit = async (event) => {
|
|
64
|
-
await eventManager.emit({
|
|
65
|
-
...event,
|
|
66
|
-
traceId,
|
|
67
|
-
flows: config.flows,
|
|
68
|
-
logger,
|
|
69
|
-
}, filePath);
|
|
64
|
+
await eventManager.emit({ ...event, traceId, flows, logger }, filePath);
|
|
70
65
|
};
|
|
71
66
|
if (handler) {
|
|
72
67
|
await handler({ emit, logger, traceId });
|
|
73
68
|
}
|
|
74
69
|
else {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
70
|
+
const data = { timestamp: Date.now() };
|
|
71
|
+
await Promise.all(config.emits.map((item) => {
|
|
72
|
+
const type = typeof item === 'string' ? item : item.type;
|
|
73
|
+
emit({ type, data });
|
|
74
|
+
}));
|
|
79
75
|
}
|
|
80
76
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
81
77
|
}
|
|
@@ -86,11 +82,20 @@ const setupCronHandlers = (steps, eventManager, socketServer) => {
|
|
|
86
82
|
});
|
|
87
83
|
}
|
|
88
84
|
});
|
|
89
|
-
cronJobs.set(step.
|
|
90
|
-
}
|
|
91
|
-
|
|
85
|
+
cronJobs.set(step.filePath, task);
|
|
86
|
+
};
|
|
87
|
+
const removeCronJob = (step) => {
|
|
88
|
+
const task = cronJobs.get(step.filePath);
|
|
89
|
+
if (task) {
|
|
90
|
+
task.stop();
|
|
91
|
+
cronJobs.delete(step.filePath);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
const close = () => {
|
|
92
95
|
cronJobs.forEach((task) => task.stop());
|
|
93
96
|
cronJobs.clear();
|
|
94
97
|
};
|
|
98
|
+
lockedData.cronSteps().forEach(createCronJob);
|
|
99
|
+
return { createCronJob, removeCronJob, close };
|
|
95
100
|
};
|
|
96
101
|
exports.setupCronHandlers = setupCronHandlers;
|
|
@@ -8,15 +8,20 @@ const createEventManager = () => {
|
|
|
8
8
|
const eventHandlers = handlers[event.type] ?? [];
|
|
9
9
|
const { logger, ...rest } = event;
|
|
10
10
|
logger.debug('[Flow Emit] Event emitted', { handlers: eventHandlers.length, data: rest, file });
|
|
11
|
-
eventHandlers.map((
|
|
11
|
+
eventHandlers.map((eventHandler) => eventHandler.handler(event));
|
|
12
12
|
};
|
|
13
|
-
const subscribe = (
|
|
13
|
+
const subscribe = (config) => {
|
|
14
|
+
const { event, handlerName, handler, filePath } = config;
|
|
14
15
|
if (!handlers[event]) {
|
|
15
16
|
handlers[event] = [];
|
|
16
17
|
}
|
|
17
18
|
logger_1.globalLogger.debug('[Flow Sub] Subscribing to event', { event, handlerName });
|
|
18
|
-
handlers[event].push(handler);
|
|
19
|
+
handlers[event].push({ filePath, handler: handler });
|
|
19
20
|
};
|
|
20
|
-
|
|
21
|
+
const unsubscribe = (config) => {
|
|
22
|
+
const { filePath, event } = config;
|
|
23
|
+
handlers[event] = handlers[event].filter((handler) => handler.filePath !== filePath);
|
|
24
|
+
};
|
|
25
|
+
return { emit, subscribe, unsubscribe };
|
|
21
26
|
};
|
|
22
27
|
exports.createEventManager = createEventManager;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Express } from 'express';
|
|
2
|
-
import { Emit
|
|
2
|
+
import { Emit } from './types';
|
|
3
|
+
import { LockedData } from './locked-data';
|
|
3
4
|
type FlowListResponse = {
|
|
4
5
|
id: string;
|
|
5
6
|
name: string;
|
|
@@ -34,6 +35,6 @@ type FlowResponse = FlowListResponse & {
|
|
|
34
35
|
steps: FlowStepResponse[];
|
|
35
36
|
edges: FlowEdge[];
|
|
36
37
|
};
|
|
37
|
-
export declare const flowsEndpoint: (
|
|
38
|
-
export declare const generateFlowsList: (
|
|
38
|
+
export declare const flowsEndpoint: (lockedData: LockedData, app: Express) => void;
|
|
39
|
+
export declare const generateFlowsList: (lockedData: LockedData) => FlowResponse[];
|
|
39
40
|
export {};
|
|
@@ -9,12 +9,13 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
9
9
|
const zod_to_json_schema_1 = __importDefault(require("zod-to-json-schema"));
|
|
10
10
|
const guards_1 = require("./guards");
|
|
11
11
|
const get_step_language_1 = require("./get-step-language");
|
|
12
|
-
const flowsEndpoint = (
|
|
13
|
-
const list = (0, exports.generateFlowsList)(flows);
|
|
12
|
+
const flowsEndpoint = (lockedData, app) => {
|
|
14
13
|
app.get('/flows', async (_, res) => {
|
|
14
|
+
const list = (0, exports.generateFlowsList)(lockedData);
|
|
15
15
|
res.status(200).send(list.map(({ id, name }) => ({ id, name })));
|
|
16
16
|
});
|
|
17
17
|
app.get('/flows/:id', async (req, res) => {
|
|
18
|
+
const list = (0, exports.generateFlowsList)(lockedData);
|
|
18
19
|
const { id } = req.params;
|
|
19
20
|
const flow = list.find((flow) => flow.id === id);
|
|
20
21
|
if (!flow) {
|
|
@@ -26,8 +27,8 @@ const flowsEndpoint = (flows, app) => {
|
|
|
26
27
|
});
|
|
27
28
|
};
|
|
28
29
|
exports.flowsEndpoint = flowsEndpoint;
|
|
29
|
-
const generateFlowsList = (
|
|
30
|
-
return Object.entries(flows).map(([flowId, flow]) => generateFlow(flowId, flow.steps));
|
|
30
|
+
const generateFlowsList = (lockedData) => {
|
|
31
|
+
return Object.entries(lockedData.flows).map(([flowId, flow]) => generateFlow(flowId, flow.steps));
|
|
31
32
|
};
|
|
32
33
|
exports.generateFlowsList = generateFlowsList;
|
|
33
34
|
// Helper functions
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getStepConfig = void 0;
|
|
4
|
+
const get_config_1 = require("./node/get-config");
|
|
5
|
+
const get_python_config_1 = require("./python/get-python-config");
|
|
6
|
+
const get_ruby_config_1 = require("./ruby/get-ruby-config");
|
|
7
|
+
const getStepConfig = async (filePath) => {
|
|
8
|
+
const isRb = filePath.endsWith('.rb');
|
|
9
|
+
const isPython = filePath.endsWith('.py');
|
|
10
|
+
const isNode = filePath.endsWith('.js') || filePath.endsWith('.ts');
|
|
11
|
+
if (isRb) {
|
|
12
|
+
return (0, get_ruby_config_1.getRubyConfig)(filePath);
|
|
13
|
+
}
|
|
14
|
+
else if (isPython) {
|
|
15
|
+
return (0, get_python_config_1.getPythonConfig)(filePath);
|
|
16
|
+
}
|
|
17
|
+
else if (isNode) {
|
|
18
|
+
return (0, get_config_1.getNodeFileConfig)(filePath);
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
};
|
|
22
|
+
exports.getStepConfig = getStepConfig;
|
package/dist/src/guards.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ApiRouteConfig, EventConfig, NoopConfig, Step, CronConfig } from './types';
|
|
2
2
|
export declare const isApiStep: (step: Step) => step is Step<ApiRouteConfig>;
|
|
3
|
-
export declare const isEventStep: (step: Step) => step is Step<EventConfig
|
|
3
|
+
export declare const isEventStep: (step: Step) => step is Step<EventConfig>;
|
|
4
4
|
export declare const isNoopStep: (step: Step) => step is Step<NoopConfig>;
|
|
5
5
|
export declare const isCronStep: (step: Step) => step is Step<CronConfig>;
|
package/dist/src/guards.js
CHANGED
|
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.isCronStep = exports.isNoopStep = exports.isEventStep = exports.isApiStep = void 0;
|
|
4
4
|
const isApiStep = (step) => step.config.type === 'api';
|
|
5
5
|
exports.isApiStep = isApiStep;
|
|
6
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
7
6
|
const isEventStep = (step) => step.config.type === 'event';
|
|
8
7
|
exports.isEventStep = isEventStep;
|
|
9
8
|
const isNoopStep = (step) => step.config.type === 'noop';
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ZodObject } from 'zod';
|
|
2
|
+
import { ApiRouteConfig, CronConfig, EventConfig, Flow, Step } from './types';
|
|
3
|
+
import { Printer } from './printer';
|
|
4
|
+
type FlowEvent = 'flow-created' | 'flow-removed' | 'flow-updated';
|
|
5
|
+
export declare class LockedData {
|
|
6
|
+
readonly baseDir: string;
|
|
7
|
+
flows: Record<string, Flow>;
|
|
8
|
+
activeSteps: Step[];
|
|
9
|
+
devSteps: Step[];
|
|
10
|
+
readonly printer: Printer;
|
|
11
|
+
private stepsMap;
|
|
12
|
+
private handlers;
|
|
13
|
+
constructor(baseDir: string);
|
|
14
|
+
on(event: FlowEvent, handler: (flowName: string) => void): void;
|
|
15
|
+
eventSteps(): Step<EventConfig<ZodObject<any>>>[];
|
|
16
|
+
apiSteps(): Step<ApiRouteConfig>[];
|
|
17
|
+
cronSteps(): Step<CronConfig>[];
|
|
18
|
+
updateStep(oldStep: Step, newStep: Step): boolean;
|
|
19
|
+
createStep(step: Step): boolean;
|
|
20
|
+
deleteStep(step: Step): void;
|
|
21
|
+
private createFlow;
|
|
22
|
+
private removeFlow;
|
|
23
|
+
private onFlowUpdated;
|
|
24
|
+
private isValidStep;
|
|
25
|
+
}
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LockedData = void 0;
|
|
4
|
+
const guards_1 = require("./guards");
|
|
5
|
+
const step_validator_1 = require("./step-validator");
|
|
6
|
+
const printer_1 = require("./printer");
|
|
7
|
+
class LockedData {
|
|
8
|
+
constructor(baseDir) {
|
|
9
|
+
this.baseDir = baseDir;
|
|
10
|
+
this.flows = {};
|
|
11
|
+
this.activeSteps = [];
|
|
12
|
+
this.devSteps = [];
|
|
13
|
+
this.stepsMap = {};
|
|
14
|
+
this.printer = new printer_1.Printer(baseDir);
|
|
15
|
+
this.handlers = {
|
|
16
|
+
'flow-created': [],
|
|
17
|
+
'flow-removed': [],
|
|
18
|
+
'flow-updated': [],
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
on(event, handler) {
|
|
22
|
+
this.handlers[event].push(handler);
|
|
23
|
+
}
|
|
24
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
25
|
+
eventSteps() {
|
|
26
|
+
return this.activeSteps.filter(guards_1.isEventStep);
|
|
27
|
+
}
|
|
28
|
+
apiSteps() {
|
|
29
|
+
return this.activeSteps.filter(guards_1.isApiStep);
|
|
30
|
+
}
|
|
31
|
+
cronSteps() {
|
|
32
|
+
return this.activeSteps.filter(guards_1.isCronStep);
|
|
33
|
+
}
|
|
34
|
+
updateStep(oldStep, newStep) {
|
|
35
|
+
if (!this.isValidStep(newStep)) {
|
|
36
|
+
this.deleteStep(oldStep);
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
if (oldStep.config.type !== newStep.config.type) {
|
|
40
|
+
this.activeSteps = this.activeSteps.filter((s) => s.filePath !== oldStep.filePath);
|
|
41
|
+
this.devSteps = this.devSteps.filter((s) => s.filePath !== oldStep.filePath);
|
|
42
|
+
if (newStep.config.virtualEmits) {
|
|
43
|
+
this.devSteps.push(newStep);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
this.activeSteps.push(newStep);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const savedStep = this.stepsMap[newStep.filePath];
|
|
50
|
+
const addedFlows = newStep.config.flows?.filter((flowName) => !oldStep.config.flows?.includes(flowName)) ?? [];
|
|
51
|
+
const removedFlows = oldStep.config.flows?.filter((flowName) => !newStep.config.flows?.includes(flowName)) ?? [];
|
|
52
|
+
const untouchedFlows = oldStep.config.flows?.filter((flowName) => newStep.config.flows?.includes(flowName)) ?? [];
|
|
53
|
+
untouchedFlows.forEach((flowName) => this.onFlowUpdated(flowName));
|
|
54
|
+
for (const flowName of addedFlows) {
|
|
55
|
+
if (!this.flows[flowName]) {
|
|
56
|
+
const flow = this.createFlow(flowName);
|
|
57
|
+
flow.steps.push(savedStep);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
this.flows[flowName].steps.push(savedStep);
|
|
61
|
+
this.onFlowUpdated(flowName);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
for (const flowName of removedFlows) {
|
|
65
|
+
const flowSteps = this.flows[flowName].steps;
|
|
66
|
+
this.flows[flowName].steps = flowSteps.filter(({ filePath }) => filePath !== newStep.filePath);
|
|
67
|
+
if (this.flows[flowName].steps.length === 0) {
|
|
68
|
+
this.removeFlow(flowName);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
this.onFlowUpdated(flowName);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
savedStep.config = newStep.config;
|
|
75
|
+
this.printer.printStepUpdated(newStep);
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
createStep(step) {
|
|
79
|
+
if (!this.isValidStep(step)) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
this.stepsMap[step.filePath] = step;
|
|
83
|
+
if (step.config.virtualEmits) {
|
|
84
|
+
this.devSteps.push(step);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
this.activeSteps.push(step);
|
|
88
|
+
}
|
|
89
|
+
for (const flowName of step.config.flows ?? []) {
|
|
90
|
+
if (!this.flows[flowName]) {
|
|
91
|
+
const flow = this.createFlow(flowName);
|
|
92
|
+
flow.steps.push(step);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
this.flows[flowName].steps.push(step);
|
|
96
|
+
this.onFlowUpdated(flowName);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
this.printer.printStepCreated(step);
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
deleteStep(step) {
|
|
103
|
+
// Remove step from active and dev steps
|
|
104
|
+
this.activeSteps = this.activeSteps.filter(({ filePath }) => filePath !== step.filePath);
|
|
105
|
+
this.devSteps = this.devSteps.filter(({ filePath }) => filePath !== step.filePath);
|
|
106
|
+
delete this.stepsMap[step.filePath];
|
|
107
|
+
for (const flowName of step.config.flows ?? []) {
|
|
108
|
+
const stepFlows = this.flows[flowName]?.steps;
|
|
109
|
+
if (stepFlows) {
|
|
110
|
+
this.flows[flowName].steps = stepFlows.filter(({ filePath }) => filePath !== step.filePath);
|
|
111
|
+
}
|
|
112
|
+
if (this.flows[flowName].steps.length === 0) {
|
|
113
|
+
this.removeFlow(flowName);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
this.onFlowUpdated(flowName);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
this.printer.printStepRemoved(step);
|
|
120
|
+
}
|
|
121
|
+
createFlow(flowName) {
|
|
122
|
+
const flow = { name: flowName, description: '', steps: [] };
|
|
123
|
+
this.flows[flowName] = flow;
|
|
124
|
+
this.handlers['flow-created'].forEach((handler) => handler(flowName));
|
|
125
|
+
this.printer.printFlowCreated(flowName);
|
|
126
|
+
return flow;
|
|
127
|
+
}
|
|
128
|
+
removeFlow(flowName) {
|
|
129
|
+
delete this.flows[flowName];
|
|
130
|
+
this.handlers['flow-removed'].forEach((handler) => handler(flowName));
|
|
131
|
+
this.printer.printFlowRemoved(flowName);
|
|
132
|
+
}
|
|
133
|
+
onFlowUpdated(flowName) {
|
|
134
|
+
this.handlers['flow-updated'].forEach((handler) => handler(flowName));
|
|
135
|
+
}
|
|
136
|
+
isValidStep(step) {
|
|
137
|
+
const validationResult = (0, step_validator_1.validateStep)(step);
|
|
138
|
+
if (!validationResult.success) {
|
|
139
|
+
this.printer.printValidationError(step.filePath, validationResult);
|
|
140
|
+
}
|
|
141
|
+
return validationResult.success;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
exports.LockedData = LockedData;
|
package/dist/src/logger.d.ts
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import { Server } from 'socket.io';
|
|
2
|
-
declare class BaseLogger {
|
|
2
|
+
export declare class BaseLogger {
|
|
3
3
|
private logger;
|
|
4
4
|
constructor(meta?: Record<string, unknown>);
|
|
5
5
|
info(message: string, args?: unknown): void;
|
|
6
6
|
error(message: string, args?: unknown): void;
|
|
7
7
|
debug(message: string, args?: unknown): void;
|
|
8
8
|
warn(message: string, args?: unknown): void;
|
|
9
|
+
log(args: unknown): void;
|
|
9
10
|
}
|
|
10
11
|
export declare class Logger extends BaseLogger {
|
|
11
12
|
private readonly traceId;
|
|
12
13
|
private readonly flows;
|
|
13
14
|
private readonly file;
|
|
14
15
|
private emitLog;
|
|
15
|
-
constructor(traceId: string, flows: string[], file: string, socketServer?: Server);
|
|
16
|
+
constructor(traceId: string, flows: string[] | undefined, file: string, socketServer?: Server);
|
|
16
17
|
log(message: any): void;
|
|
17
18
|
info: (message: string, args?: unknown) => void;
|
|
18
19
|
error: (message: string, args?: unknown) => void;
|
|
@@ -20,4 +21,3 @@ export declare class Logger extends BaseLogger {
|
|
|
20
21
|
warn: (message: string, args?: unknown) => void;
|
|
21
22
|
}
|
|
22
23
|
export declare const globalLogger: BaseLogger;
|
|
23
|
-
export {};
|
package/dist/src/logger.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.globalLogger = exports.Logger = void 0;
|
|
6
|
+
exports.globalLogger = exports.Logger = exports.BaseLogger = void 0;
|
|
7
7
|
const pino_1 = __importDefault(require("pino"));
|
|
8
8
|
const isDebugEnabled = () => process.env.LOG_LEVEL === 'debug';
|
|
9
9
|
class BaseLogger {
|
|
@@ -27,7 +27,11 @@ class BaseLogger {
|
|
|
27
27
|
warn(message, args) {
|
|
28
28
|
this.logger.warn(args, message);
|
|
29
29
|
}
|
|
30
|
+
log(args) {
|
|
31
|
+
this.logger.info(args);
|
|
32
|
+
}
|
|
30
33
|
}
|
|
34
|
+
exports.BaseLogger = BaseLogger;
|
|
31
35
|
class Logger extends BaseLogger {
|
|
32
36
|
constructor(traceId, flows, file, socketServer) {
|
|
33
37
|
super({ traceId, flows, file });
|
|
@@ -53,13 +53,16 @@ require('ts-node').register({
|
|
|
53
53
|
const getModuleExport = async (filePath, exportName) => {
|
|
54
54
|
try {
|
|
55
55
|
const resolvedFilePath = require.resolve(filePath);
|
|
56
|
+
if (resolvedFilePath in require.cache) {
|
|
57
|
+
delete require.cache[resolvedFilePath];
|
|
58
|
+
}
|
|
56
59
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
57
60
|
const module = require(resolvedFilePath);
|
|
58
61
|
return module[exportName];
|
|
59
62
|
}
|
|
60
63
|
catch (error) {
|
|
61
|
-
console.error(`Failed to extract ${exportName} from
|
|
62
|
-
|
|
64
|
+
console.error(`Failed to extract ${exportName} from`, error);
|
|
65
|
+
return undefined;
|
|
63
66
|
}
|
|
64
67
|
};
|
|
65
68
|
exports.getModuleExport = getModuleExport;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ValidationError } from './step-validator';
|
|
2
|
+
import { Step } from './types';
|
|
3
|
+
export declare class Printer {
|
|
4
|
+
private readonly baseDir;
|
|
5
|
+
constructor(baseDir: string);
|
|
6
|
+
printInvalidEmit(step: Step, emit: string): void;
|
|
7
|
+
printStepCreated(step: Step): void;
|
|
8
|
+
printStepUpdated(step: Step): void;
|
|
9
|
+
printStepRemoved(step: Step): void;
|
|
10
|
+
printFlowCreated(flowName: string): void;
|
|
11
|
+
printFlowUpdated(flowName: string): void;
|
|
12
|
+
printFlowRemoved(flowName: string): void;
|
|
13
|
+
printValidationError(stepPath: string, validationError: ValidationError): void;
|
|
14
|
+
private getRelativePath;
|
|
15
|
+
private getStepType;
|
|
16
|
+
private getStepPath;
|
|
17
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.Printer = void 0;
|
|
7
|
+
const colors_1 = __importDefault(require("colors"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const guards_1 = require("./guards");
|
|
10
|
+
const stepTag = colors_1.default.bold(colors_1.default.magenta('Step'));
|
|
11
|
+
const flowTag = colors_1.default.bold(colors_1.default.blue('Flow'));
|
|
12
|
+
const created = colors_1.default.green('➜ [CREATED]');
|
|
13
|
+
const updated = colors_1.default.yellow('➜ [UPDATED]');
|
|
14
|
+
const removed = colors_1.default.red('➜ [REMOVED]');
|
|
15
|
+
const invalidEmit = colors_1.default.red('➜ [INVALID EMIT]');
|
|
16
|
+
const error = colors_1.default.red('[ERROR]');
|
|
17
|
+
class Printer {
|
|
18
|
+
constructor(baseDir) {
|
|
19
|
+
this.baseDir = baseDir;
|
|
20
|
+
}
|
|
21
|
+
printInvalidEmit(step, emit) {
|
|
22
|
+
console.log(`${invalidEmit} ${stepTag} ${this.getStepType(step)} ${this.getStepPath(step)} tried to emit an event not defined in the step config: ${colors_1.default.yellow(emit)}`);
|
|
23
|
+
}
|
|
24
|
+
printStepCreated(step) {
|
|
25
|
+
console.log(`${created} ${stepTag} ${this.getStepType(step)} ${this.getStepPath(step)} created`);
|
|
26
|
+
}
|
|
27
|
+
printStepUpdated(step) {
|
|
28
|
+
console.log(`${updated} ${stepTag} ${this.getStepType(step)} ${this.getStepPath(step)} updated`);
|
|
29
|
+
}
|
|
30
|
+
printStepRemoved(step) {
|
|
31
|
+
console.log(`${removed} ${stepTag} ${this.getStepType(step)} ${this.getStepPath(step)} removed`);
|
|
32
|
+
}
|
|
33
|
+
printFlowCreated(flowName) {
|
|
34
|
+
console.log(`${created} ${flowTag} ${colors_1.default.bold(colors_1.default.cyan(flowName))} created`);
|
|
35
|
+
}
|
|
36
|
+
printFlowUpdated(flowName) {
|
|
37
|
+
console.log(`${updated} ${flowTag} ${colors_1.default.bold(colors_1.default.cyan(flowName))} updated`);
|
|
38
|
+
}
|
|
39
|
+
printFlowRemoved(flowName) {
|
|
40
|
+
console.log(`${removed} ${flowTag} ${colors_1.default.bold(colors_1.default.cyan(flowName))} removed`);
|
|
41
|
+
}
|
|
42
|
+
printValidationError(stepPath, validationError) {
|
|
43
|
+
const relativePath = this.getRelativePath(stepPath);
|
|
44
|
+
console.log(`${error} ${colors_1.default.bold(colors_1.default.cyan(relativePath))}`);
|
|
45
|
+
validationError.errors?.forEach((error) => {
|
|
46
|
+
if (error.path) {
|
|
47
|
+
console.log(`${colors_1.default.red('│')} ${colors_1.default.yellow(`✖ ${error.path}`)}: ${error.message}`);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
console.log(`${colors_1.default.red('│')} ${colors_1.default.yellow('✖')} ${error.message}`);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
console.log(`${colors_1.default.red('└─')} ${colors_1.default.red(validationError.error)} `);
|
|
54
|
+
}
|
|
55
|
+
getRelativePath(filePath) {
|
|
56
|
+
return path_1.default.relative(this.baseDir, filePath);
|
|
57
|
+
}
|
|
58
|
+
getStepType(step) {
|
|
59
|
+
if ((0, guards_1.isApiStep)(step))
|
|
60
|
+
return colors_1.default.gray('(API)');
|
|
61
|
+
if ((0, guards_1.isEventStep)(step))
|
|
62
|
+
return colors_1.default.gray('(Event)');
|
|
63
|
+
if ((0, guards_1.isCronStep)(step))
|
|
64
|
+
return colors_1.default.gray('(Cron)');
|
|
65
|
+
if ((0, guards_1.isNoopStep)(step))
|
|
66
|
+
return colors_1.default.gray('(Noop)');
|
|
67
|
+
return colors_1.default.gray('(Unknown)');
|
|
68
|
+
}
|
|
69
|
+
getStepPath(step) {
|
|
70
|
+
const stepPath = this.getRelativePath(step.filePath);
|
|
71
|
+
return colors_1.default.bold(colors_1.default.cyan(stepPath));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
exports.Printer = Printer;
|
package/dist/src/server.d.ts
CHANGED
|
@@ -1,19 +1,17 @@
|
|
|
1
|
+
import { CronManager } from './cron-handler';
|
|
1
2
|
import { Express } from 'express';
|
|
2
3
|
import http from 'http';
|
|
3
4
|
import { Server as SocketIOServer } from 'socket.io';
|
|
4
5
|
import { StateAdapter } from './state/state-adapter';
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
flows: LockedData['flows'];
|
|
9
|
-
eventManager: EventManager;
|
|
10
|
-
state: StateAdapter;
|
|
11
|
-
};
|
|
12
|
-
type ServerOutput = {
|
|
6
|
+
import { ApiRouteConfig, EventManager, Step } from './types';
|
|
7
|
+
import { LockedData } from './locked-data';
|
|
8
|
+
export type MotiaServer = {
|
|
13
9
|
app: Express;
|
|
14
10
|
server: http.Server;
|
|
15
11
|
socketServer: SocketIOServer;
|
|
16
12
|
close: () => Promise<void>;
|
|
13
|
+
removeRoute: (step: Step<ApiRouteConfig>) => void;
|
|
14
|
+
addRoute: (step: Step<ApiRouteConfig>) => void;
|
|
15
|
+
cronManager: CronManager;
|
|
17
16
|
};
|
|
18
|
-
export declare const createServer: (
|
|
19
|
-
export {};
|
|
17
|
+
export declare const createServer: (lockedData: LockedData, eventManager: EventManager, state: StateAdapter) => Promise<MotiaServer>;
|
package/dist/src/server.js
CHANGED
|
@@ -15,13 +15,13 @@ const guards_1 = require("./guards");
|
|
|
15
15
|
const logger_1 = require("./logger");
|
|
16
16
|
const get_module_export_1 = require("./node/get-module-export");
|
|
17
17
|
const steps_1 = require("./steps");
|
|
18
|
-
const
|
|
19
|
-
|
|
18
|
+
const utils_1 = require("./utils");
|
|
19
|
+
const createServer = async (lockedData, eventManager, state) => {
|
|
20
20
|
const app = (0, express_1.default)();
|
|
21
21
|
const server = http_1.default.createServer(app);
|
|
22
22
|
const io = new socket_io_1.Server(server);
|
|
23
|
-
const allSteps = [...steps_1.systemSteps, ...
|
|
24
|
-
const
|
|
23
|
+
const allSteps = [...steps_1.systemSteps, ...lockedData.activeSteps];
|
|
24
|
+
const cronManager = (0, cron_handler_1.setupCronHandlers)(lockedData, eventManager, io);
|
|
25
25
|
const asyncHandler = (step, flows) => {
|
|
26
26
|
return async (req, res) => {
|
|
27
27
|
const traceId = (0, crypto_1.randomUUID)();
|
|
@@ -35,6 +35,10 @@ const createServer = async (options) => {
|
|
|
35
35
|
queryParams: req.query,
|
|
36
36
|
};
|
|
37
37
|
const emit = async ({ data, type }) => {
|
|
38
|
+
if (!(0, utils_1.isAllowedToEmit)(step, type)) {
|
|
39
|
+
lockedData.printer.printInvalidEmit(step, type);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
38
42
|
await eventManager.emit({ data, type, traceId, flows, logger }, step.filePath);
|
|
39
43
|
};
|
|
40
44
|
try {
|
|
@@ -54,30 +58,44 @@ const createServer = async (options) => {
|
|
|
54
58
|
};
|
|
55
59
|
app.use(body_parser_1.default.json());
|
|
56
60
|
app.use(body_parser_1.default.urlencoded({ extended: true }));
|
|
57
|
-
const
|
|
58
|
-
|
|
61
|
+
const router = express_1.default.Router();
|
|
62
|
+
const addRoute = (step) => {
|
|
59
63
|
const { method, flows, path } = step.config;
|
|
60
64
|
logger_1.globalLogger.debug('[API] Registering route', step.config);
|
|
61
65
|
if (method === 'POST') {
|
|
62
|
-
|
|
66
|
+
router.post(path, asyncHandler(step, flows));
|
|
63
67
|
}
|
|
64
68
|
else if (method === 'GET') {
|
|
65
|
-
|
|
69
|
+
router.get(path, asyncHandler(step, flows));
|
|
66
70
|
}
|
|
67
71
|
else {
|
|
68
72
|
throw new Error(`Unsupported method: ${method}`);
|
|
69
73
|
}
|
|
70
|
-
}
|
|
71
|
-
(
|
|
74
|
+
};
|
|
75
|
+
const removeRoute = (step) => {
|
|
76
|
+
const { path, method } = step.config;
|
|
77
|
+
const routerStack = router.stack;
|
|
78
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
79
|
+
const filteredStack = routerStack.filter((layer) => {
|
|
80
|
+
if (layer.route) {
|
|
81
|
+
const match = layer.route.path === path && layer.route.methods[method.toLowerCase()];
|
|
82
|
+
return !match;
|
|
83
|
+
}
|
|
84
|
+
return true;
|
|
85
|
+
});
|
|
86
|
+
router.stack = filteredStack;
|
|
87
|
+
};
|
|
88
|
+
allSteps.filter(guards_1.isApiStep).forEach(addRoute);
|
|
89
|
+
app.use(router);
|
|
90
|
+
(0, flows_endpoint_1.flowsEndpoint)(lockedData, app);
|
|
72
91
|
server.on('error', (error) => {
|
|
73
92
|
console.error('Server error:', error);
|
|
74
|
-
cleanupCronJobs();
|
|
75
93
|
});
|
|
76
94
|
const close = async () => {
|
|
77
|
-
|
|
95
|
+
cronManager.close();
|
|
78
96
|
await io.close();
|
|
79
97
|
server.close();
|
|
80
98
|
};
|
|
81
|
-
return { app, server, socketServer: io, close };
|
|
99
|
+
return { app, server, socketServer: io, close, removeRoute, addRoute, cronManager };
|
|
82
100
|
};
|
|
83
101
|
exports.createServer = createServer;
|
|
@@ -41,10 +41,10 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
41
41
|
const path = __importStar(require("path"));
|
|
42
42
|
class FileStateAdapter {
|
|
43
43
|
constructor(config) {
|
|
44
|
-
this.filePath = path.join(config.filePath, 'motia
|
|
44
|
+
this.filePath = path.join(config.filePath, 'motia.state.json');
|
|
45
45
|
}
|
|
46
46
|
async init() {
|
|
47
|
-
const dir = this.filePath.replace('motia
|
|
47
|
+
const dir = this.filePath.replace('motia.state.json', '');
|
|
48
48
|
try {
|
|
49
49
|
fs_1.default.realpathSync(dir);
|
|
50
50
|
}
|
|
@@ -1,2 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { EventConfig, EventManager, Step } from './types';
|
|
2
|
+
import { LockedData } from './locked-data';
|
|
3
|
+
import { StateAdapter } from './state/state-adapter';
|
|
4
|
+
export type MotiaEventManager = {
|
|
5
|
+
createHandler: (step: Step<EventConfig>) => void;
|
|
6
|
+
removeHandler: (step: Step<EventConfig>) => void;
|
|
7
|
+
};
|
|
8
|
+
export declare const createStepHandlers: (lockedData: LockedData, eventManager: EventManager, state: StateAdapter) => MotiaEventManager;
|
|
@@ -2,32 +2,46 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createStepHandlers = void 0;
|
|
4
4
|
const logger_1 = require("./logger");
|
|
5
|
-
const guards_1 = require("./guards");
|
|
6
5
|
const call_step_file_1 = require("./call-step-file");
|
|
7
|
-
const createStepHandlers = (
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
const createStepHandlers = (lockedData, eventManager, state) => {
|
|
7
|
+
const eventSteps = lockedData.eventSteps();
|
|
8
|
+
logger_1.globalLogger.debug(`[step handler] creating step handlers for ${eventSteps.length} steps`);
|
|
9
|
+
const createHandler = (step) => {
|
|
10
10
|
const { config, filePath } = step;
|
|
11
11
|
const { subscribes } = config;
|
|
12
12
|
logger_1.globalLogger.debug('[step handler] establishing step subscriptions', { filePath, step: step.config.name });
|
|
13
13
|
subscribes.forEach((subscribe) => {
|
|
14
|
-
eventManager.subscribe(
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
14
|
+
eventManager.subscribe({
|
|
15
|
+
filePath,
|
|
16
|
+
event: subscribe,
|
|
17
|
+
handlerName: step.config.name,
|
|
18
|
+
handler: async (event) => {
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
20
|
+
const { logger, ...rest } = event;
|
|
21
|
+
logger_1.globalLogger.debug('[step handler] received event', { event: rest, step: step.config.name });
|
|
22
|
+
try {
|
|
23
|
+
await (0, call_step_file_1.callStepFile)(step, lockedData, event, eventManager, state);
|
|
24
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
logger_1.globalLogger.error(`[step handler] error calling step`, {
|
|
28
|
+
error: error.message,
|
|
29
|
+
filePath,
|
|
30
|
+
step: step.config.name,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
},
|
|
29
34
|
});
|
|
30
35
|
});
|
|
31
|
-
}
|
|
36
|
+
};
|
|
37
|
+
const removeHandler = (step) => {
|
|
38
|
+
const { config, filePath } = step;
|
|
39
|
+
const { subscribes } = config;
|
|
40
|
+
subscribes.forEach((subscribe) => {
|
|
41
|
+
eventManager.unsubscribe({ filePath, event: subscribe });
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
eventSteps.forEach(createHandler);
|
|
45
|
+
return { removeHandler, createHandler };
|
|
32
46
|
};
|
|
33
47
|
exports.createStepHandlers = createStepHandlers;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Step } from './types';
|
|
2
|
+
export type ValidationSuccess = {
|
|
3
|
+
success: true;
|
|
4
|
+
};
|
|
5
|
+
export type ValidationError = {
|
|
6
|
+
success: false;
|
|
7
|
+
error: string;
|
|
8
|
+
errors?: Array<{
|
|
9
|
+
path: string;
|
|
10
|
+
message: string;
|
|
11
|
+
}>;
|
|
12
|
+
};
|
|
13
|
+
export type ValidationResult = ValidationSuccess | ValidationError;
|
|
14
|
+
export declare const validateStep: (step: Step) => ValidationResult;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateStep = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const emits = zod_1.z.array(zod_1.z.union([
|
|
6
|
+
zod_1.z.string(),
|
|
7
|
+
zod_1.z
|
|
8
|
+
.object({
|
|
9
|
+
type: zod_1.z.string(),
|
|
10
|
+
label: zod_1.z.string().optional(),
|
|
11
|
+
conditional: zod_1.z.boolean().optional(),
|
|
12
|
+
})
|
|
13
|
+
.strict(),
|
|
14
|
+
]));
|
|
15
|
+
const noopSchema = zod_1.z
|
|
16
|
+
.object({
|
|
17
|
+
type: zod_1.z.literal('noop'),
|
|
18
|
+
name: zod_1.z.string(),
|
|
19
|
+
description: zod_1.z.string().optional(),
|
|
20
|
+
virtualEmits: emits,
|
|
21
|
+
virtualSubscribes: zod_1.z.array(zod_1.z.string()),
|
|
22
|
+
flows: zod_1.z.array(zod_1.z.string()).optional(),
|
|
23
|
+
})
|
|
24
|
+
.strict();
|
|
25
|
+
const eventSchema = zod_1.z
|
|
26
|
+
.object({
|
|
27
|
+
type: zod_1.z.literal('event'),
|
|
28
|
+
name: zod_1.z.string(),
|
|
29
|
+
description: zod_1.z.string().optional(),
|
|
30
|
+
subscribes: zod_1.z.array(zod_1.z.string()),
|
|
31
|
+
emits: emits,
|
|
32
|
+
virtualEmits: emits.optional(),
|
|
33
|
+
input: zod_1.z.any(),
|
|
34
|
+
flows: zod_1.z.array(zod_1.z.string()).optional(),
|
|
35
|
+
})
|
|
36
|
+
.strict();
|
|
37
|
+
const apiSchema = zod_1.z
|
|
38
|
+
.object({
|
|
39
|
+
type: zod_1.z.literal('api'),
|
|
40
|
+
name: zod_1.z.string(),
|
|
41
|
+
description: zod_1.z.string().optional(),
|
|
42
|
+
path: zod_1.z.string(),
|
|
43
|
+
method: zod_1.z.string(),
|
|
44
|
+
emits: emits,
|
|
45
|
+
virtualEmits: emits.optional(),
|
|
46
|
+
virtualSubscribes: zod_1.z.array(zod_1.z.string()).optional(),
|
|
47
|
+
flows: zod_1.z.array(zod_1.z.string()).optional(),
|
|
48
|
+
bodySchema: zod_1.z.instanceof(zod_1.z.ZodObject).optional(),
|
|
49
|
+
})
|
|
50
|
+
.strict();
|
|
51
|
+
const cronSchema = zod_1.z
|
|
52
|
+
.object({
|
|
53
|
+
type: zod_1.z.literal('cron'),
|
|
54
|
+
name: zod_1.z.string(),
|
|
55
|
+
description: zod_1.z.string().optional(),
|
|
56
|
+
cron: zod_1.z.string(),
|
|
57
|
+
virtualEmits: emits.optional(),
|
|
58
|
+
emits: emits,
|
|
59
|
+
flows: zod_1.z.array(zod_1.z.string()).optional(),
|
|
60
|
+
})
|
|
61
|
+
.strict();
|
|
62
|
+
const validateStep = (step) => {
|
|
63
|
+
try {
|
|
64
|
+
if (step.config.type === 'noop') {
|
|
65
|
+
noopSchema.parse(step.config);
|
|
66
|
+
}
|
|
67
|
+
else if (step.config.type === 'event') {
|
|
68
|
+
eventSchema.parse(step.config);
|
|
69
|
+
}
|
|
70
|
+
else if (step.config.type === 'api') {
|
|
71
|
+
apiSchema.parse(step.config);
|
|
72
|
+
}
|
|
73
|
+
else if (step.config.type === 'cron') {
|
|
74
|
+
cronSchema.parse(step.config);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
return {
|
|
78
|
+
success: false,
|
|
79
|
+
error: 'Invalid step type',
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
return { success: true };
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
if (error instanceof zod_1.z.ZodError) {
|
|
86
|
+
return {
|
|
87
|
+
success: false,
|
|
88
|
+
error: 'Invalid step configuration',
|
|
89
|
+
errors: error.errors.map((err) => ({ path: err.path.join('.'), message: err.message })),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
// Handle unexpected errors
|
|
93
|
+
return {
|
|
94
|
+
success: false,
|
|
95
|
+
error: 'Unexpected validation error occurred',
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
exports.validateStep = validateStep;
|
package/dist/src/types.d.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { z, ZodObject } from 'zod';
|
|
2
|
-
import {
|
|
3
|
-
import { Server as SocketIOServer } from 'socket.io';
|
|
4
|
-
import { Logger } from './logger';
|
|
2
|
+
import { BaseLogger, Logger } from './logger';
|
|
5
3
|
export type InternalStateManager = {
|
|
6
4
|
get<T>(traceId: string, key: string): Promise<T | null>;
|
|
7
5
|
set<T>(traceId: string, key: string, value: T): Promise<void>;
|
|
@@ -25,7 +23,7 @@ export type Emit = string | {
|
|
|
25
23
|
label?: string;
|
|
26
24
|
conditional?: boolean;
|
|
27
25
|
};
|
|
28
|
-
export type EventConfig<TInput extends ZodObject<any
|
|
26
|
+
export type EventConfig<TInput extends ZodObject<any> = any> = {
|
|
29
27
|
type: 'event';
|
|
30
28
|
name: string;
|
|
31
29
|
description?: string;
|
|
@@ -33,7 +31,7 @@ export type EventConfig<TInput extends ZodObject<any>> = {
|
|
|
33
31
|
emits: Emit[];
|
|
34
32
|
virtualEmits?: Emit[];
|
|
35
33
|
input: TInput;
|
|
36
|
-
flows
|
|
34
|
+
flows?: string[];
|
|
37
35
|
};
|
|
38
36
|
export type NoopConfig = {
|
|
39
37
|
type: 'noop';
|
|
@@ -41,7 +39,7 @@ export type NoopConfig = {
|
|
|
41
39
|
description?: string;
|
|
42
40
|
virtualEmits: Emit[];
|
|
43
41
|
virtualSubscribes: string[];
|
|
44
|
-
flows
|
|
42
|
+
flows?: string[];
|
|
45
43
|
};
|
|
46
44
|
export type ApiRouteMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'OPTIONS' | 'HEAD';
|
|
47
45
|
export type ApiRouteConfig = {
|
|
@@ -50,10 +48,10 @@ export type ApiRouteConfig = {
|
|
|
50
48
|
description?: string;
|
|
51
49
|
path: string;
|
|
52
50
|
method: ApiRouteMethod;
|
|
53
|
-
emits:
|
|
51
|
+
emits: Emit[];
|
|
54
52
|
virtualEmits?: Emit[];
|
|
55
53
|
virtualSubscribes?: string[];
|
|
56
|
-
flows
|
|
54
|
+
flows?: string[];
|
|
57
55
|
bodySchema?: ZodObject<any>;
|
|
58
56
|
};
|
|
59
57
|
export type ApiRequest = {
|
|
@@ -74,23 +72,32 @@ export type CronConfig = {
|
|
|
74
72
|
description?: string;
|
|
75
73
|
cron: string;
|
|
76
74
|
virtualEmits?: Emit[];
|
|
77
|
-
emits:
|
|
78
|
-
flows
|
|
75
|
+
emits: Emit[];
|
|
76
|
+
flows?: string[];
|
|
79
77
|
};
|
|
80
78
|
export type StepHandler<T> = T extends EventConfig<any> ? EventHandler<T['input']> : T extends ApiRouteConfig ? ApiRouteHandler : T extends CronConfig ? never : never;
|
|
81
|
-
export type MotiaServer = Server;
|
|
82
|
-
export type MotiaSocketServer = SocketIOServer;
|
|
83
79
|
export type Event<TData = unknown> = {
|
|
84
80
|
type: string;
|
|
85
81
|
data: TData;
|
|
86
82
|
traceId: string;
|
|
87
|
-
flows
|
|
88
|
-
logger:
|
|
83
|
+
flows?: string[];
|
|
84
|
+
logger: BaseLogger;
|
|
89
85
|
};
|
|
90
86
|
export type Handler<TData = unknown> = (event: Event<TData>) => Promise<void>;
|
|
87
|
+
export type SubscribeConfig<TData> = {
|
|
88
|
+
event: string;
|
|
89
|
+
handlerName: string;
|
|
90
|
+
filePath: string;
|
|
91
|
+
handler: Handler<TData>;
|
|
92
|
+
};
|
|
93
|
+
export type UnsubscribeConfig = {
|
|
94
|
+
filePath: string;
|
|
95
|
+
event: string;
|
|
96
|
+
};
|
|
91
97
|
export type EventManager = {
|
|
92
98
|
emit: <TData>(event: Event<TData>, file?: string) => Promise<void>;
|
|
93
|
-
subscribe: <TData>(
|
|
99
|
+
subscribe: <TData>(config: SubscribeConfig<TData>) => void;
|
|
100
|
+
unsubscribe: (config: UnsubscribeConfig) => void;
|
|
94
101
|
};
|
|
95
102
|
export type StepConfig = EventConfig<ZodObject<any>> | NoopConfig | ApiRouteConfig | CronConfig;
|
|
96
103
|
export type Step<TConfig extends StepConfig = StepConfig> = {
|
|
@@ -103,16 +110,3 @@ export type Flow = {
|
|
|
103
110
|
description?: string;
|
|
104
111
|
steps: Step[];
|
|
105
112
|
};
|
|
106
|
-
export type LockedData = {
|
|
107
|
-
baseDir: string;
|
|
108
|
-
steps: {
|
|
109
|
-
active: Step[];
|
|
110
|
-
dev: Step[];
|
|
111
|
-
};
|
|
112
|
-
flows: Record<string, Flow>;
|
|
113
|
-
state: {
|
|
114
|
-
adapter: string;
|
|
115
|
-
host: string;
|
|
116
|
-
port: number;
|
|
117
|
-
};
|
|
118
|
-
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isAllowedToEmit = void 0;
|
|
4
|
+
const guards_1 = require("./guards");
|
|
5
|
+
const toType = (emit) => (typeof emit === 'string' ? emit : emit.type);
|
|
6
|
+
const isAllowedToEmit = (step, emit) => {
|
|
7
|
+
if ((0, guards_1.isApiStep)(step)) {
|
|
8
|
+
return step.config.emits.map(toType).includes(emit);
|
|
9
|
+
}
|
|
10
|
+
if ((0, guards_1.isEventStep)(step)) {
|
|
11
|
+
return step.config.emits.map(toType).includes(emit);
|
|
12
|
+
}
|
|
13
|
+
return false;
|
|
14
|
+
};
|
|
15
|
+
exports.isAllowedToEmit = isAllowedToEmit;
|
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@motiadev/core",
|
|
3
3
|
"main": "dist/index.js",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.22",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"body-parser": "^1.20.3",
|
|
7
|
+
"colors": "^1.4.0",
|
|
7
8
|
"express": "^4.21.2",
|
|
8
9
|
"ioredis": "^5.4.2",
|
|
9
10
|
"node-cron": "^3.0.3",
|
|
@@ -19,7 +20,9 @@
|
|
|
19
20
|
"@types/express": "^5.0.0",
|
|
20
21
|
"@types/jest": "^29.5.14",
|
|
21
22
|
"@types/node-cron": "^3.0.11",
|
|
23
|
+
"@types/supertest": "^6.0.2",
|
|
22
24
|
"jest": "^29.7.0",
|
|
25
|
+
"supertest": "^7.0.0",
|
|
23
26
|
"ts-jest": "^29.2.5",
|
|
24
27
|
"typescript": "^5.7.2"
|
|
25
28
|
},
|