bazis 0.127.0 → 0.128.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/cli/generateModule.ts +5 -5
- package/cli/generateProcess.ts +56 -0
- package/cli/main.ts +35 -1
- package/cli/moduleRegistration.ts +23 -10
- package/cli/parseCli.ts +45 -3
- package/cli/templates/process.ts +28 -0
- package/cli/workflow.ts +66 -0
- package/core/infra/connectors/codex/CodexAppServerClient.ts +1 -1
- package/core/workflow/contracts/WorkflowClock.ts +21 -0
- package/core/workflow/definitions_module/WorkflowDefinitions.module.ts +14 -6
- package/core/workflow/definitions_module/code/CodeProcessCatalog.ts +19 -0
- package/core/workflow/definitions_module/code/CodeProcessPublisher.ts +44 -0
- package/core/workflow/definitions_module/code/codeProcesses.ts +85 -0
- package/core/workflow/definitions_module/code/defineProcess.ts +40 -0
- package/core/workflow/definitions_module/code/inspectProcesses.ts +74 -0
- package/core/workflow/definitions_module/services/IWorkflowDefinitions.service.ts +11 -2
- package/core/workflow/definitions_module/services/WorkflowDefinitions.service.ts +106 -45
- package/core/workflow/index.ts +14 -0
- package/core/workflow/runtime_module/WorkflowRuntime.module.ts +2 -0
- package/core/workflow/runtime_module/background/WorkflowRetention.ts +4 -2
- package/core/workflow/runtime_module/background/WorkflowWorker.ts +11 -88
- package/core/workflow/runtime_module/internal/Schedules.ts +4 -2
- package/core/workflow/runtime_module/internal/ToolStep.ts +3 -3
- package/core/workflow/runtime_module/internal/WorkflowEngine.ts +18 -11
- package/core/workflow/runtime_module/internal/WorkflowJobs.ts +100 -0
- package/core/workflow/tasks_module/WorkflowTasks.module.ts +4 -2
- package/core/workflow/tasks_module/services/WorkflowTasks.service.ts +4 -2
- package/core/workflow/testing/index.ts +162 -0
- package/library/terminal/messages.ts +28 -0
- package/library/terminal/screen.ts +1 -0
- package/library/workflow/index.ts +1 -0
- package/library/workflow/mermaid.ts +87 -0
- package/package.json +2 -1
|
@@ -32,6 +32,8 @@ const NOUNS_EN = {
|
|
|
32
32
|
change: ["change", "changes", "changes"],
|
|
33
33
|
part: ["part", "parts", "parts"],
|
|
34
34
|
context: ["database context", "database contexts", "database contexts"],
|
|
35
|
+
process: ["process", "processes", "processes"],
|
|
36
|
+
node: ["node", "nodes", "nodes"],
|
|
35
37
|
} as const satisfies Record<string, Forms>;
|
|
36
38
|
|
|
37
39
|
export type Noun = keyof typeof NOUNS_EN;
|
|
@@ -60,6 +62,8 @@ const NOUNS_RU: Record<Noun, Forms> = {
|
|
|
60
62
|
change: ["изменение", "изменения", "изменений"],
|
|
61
63
|
part: ["часть", "части", "частей"],
|
|
62
64
|
context: ["контекст базы данных", "контекста базы данных", "контекстов базы данных"],
|
|
65
|
+
process: ["процесс", "процесса", "процессов"],
|
|
66
|
+
node: ["узел", "узла", "узлов"],
|
|
63
67
|
};
|
|
64
68
|
|
|
65
69
|
/** Genitive after «из N»: «из 1 подключения», «из 3 подключений», «из 21 подключения». */
|
|
@@ -239,6 +243,18 @@ const en = {
|
|
|
239
243
|
needsCache: "this module caches: pass runApp(AppModule, { cache: memory() })",
|
|
240
244
|
keptOnFailure: "scaffold files were kept — fix the error and run bazis codegen",
|
|
241
245
|
},
|
|
246
|
+
workflow: {
|
|
247
|
+
app: "app",
|
|
248
|
+
section: "PROCESSES IN CODE",
|
|
249
|
+
none: "no module declares processes — add one with bazis g process <Name> --module <Name>",
|
|
250
|
+
passed: (count: number) => `${count} ${formEn(count, NOUNS_EN.process)} checked`,
|
|
251
|
+
failed: (count: number) => `process check failed · ${count} ${formEn(count, NOUNS_EN.error)}`,
|
|
252
|
+
fix: "fix the documents or the Tools: the application refuses to start with these errors",
|
|
253
|
+
processKind: "process",
|
|
254
|
+
processCreated: (name: string) => `process ${name} is generated`,
|
|
255
|
+
processPlanned: (name: string) => `process ${name} is planned`,
|
|
256
|
+
processRegistered: (host: string) => `listed in processes of ${host}`,
|
|
257
|
+
},
|
|
242
258
|
errors: {
|
|
243
259
|
unknown: "Use bazis --help to list commands and options.",
|
|
244
260
|
bunTooOld: (required: string, actual: string) => `bazis needs Bun ${required.startsWith(">=") ? `${required.slice(2).trim()} or newer` : required}; this is Bun ${actual}.`,
|
|
@@ -404,6 +420,18 @@ const ru: Messages = {
|
|
|
404
420
|
needsCache: "модуль кэширует: передайте runApp(AppModule, { cache: memory() })",
|
|
405
421
|
keptOnFailure: "файлы модуля оставлены — исправьте ошибку и запустите bazis codegen",
|
|
406
422
|
},
|
|
423
|
+
workflow: {
|
|
424
|
+
app: "приложение",
|
|
425
|
+
section: "ПРОЦЕССЫ В КОДЕ",
|
|
426
|
+
none: "ни один модуль не объявляет процессы — добавьте процесс: bazis g process <Name> --module <Name>",
|
|
427
|
+
passed: (count) => `процессы в порядке · ${count} ${formRu(count, NOUNS_RU.process)}`,
|
|
428
|
+
failed: (count) => `проверка процессов не прошла · ${count} ${formRu(count, NOUNS_RU.error)}`,
|
|
429
|
+
fix: "исправьте документы или Tools: с этими ошибками приложение не запустится",
|
|
430
|
+
processKind: "процесс",
|
|
431
|
+
processCreated: (name) => `процесс ${name} создан`,
|
|
432
|
+
processPlanned: (name) => `процесс ${name} запланирован`,
|
|
433
|
+
processRegistered: (host) => `добавлен в processes модуля ${host}`,
|
|
434
|
+
},
|
|
407
435
|
errors: {
|
|
408
436
|
unknown: "Список команд и опций: bazis --help.",
|
|
409
437
|
bunTooOld: (required, actual) => `bazis нужен Bun ${required.startsWith(">=") ? `${required.slice(2).trim()} или новее` : required}, а запущен Bun ${actual}.`,
|
|
@@ -58,6 +58,7 @@ export const MODE = Object.freeze({
|
|
|
58
58
|
new: ["#ff2ee6", "#7b61ff"] as readonly string[],
|
|
59
59
|
gen: ["#c77dff", "#ff2ee6"] as readonly string[],
|
|
60
60
|
codegen: ["#00f0ff", "#c77dff"] as readonly string[],
|
|
61
|
+
workflow: ["#3dff7a", "#ffd60a"] as readonly string[],
|
|
61
62
|
});
|
|
62
63
|
|
|
63
64
|
/**
|
|
@@ -22,4 +22,5 @@ export { DATA_PATH, isPath, readPath, resolveInput, type InstanceData } from "./
|
|
|
22
22
|
export { evaluateCondition } from "./conditions";
|
|
23
23
|
export { durationMs } from "./duration";
|
|
24
24
|
export { parseCron, type CronSchedule } from "./cron";
|
|
25
|
+
export { processToMermaid } from "./mermaid";
|
|
25
26
|
export { checkFormSpec, formFieldCodes, validateForm, type FormField, type FormFieldType, type FormIssue, type FormSpec } from "./formFields";
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { Condition, NodeKind, ProcessDocument } from "./ProcessDocument";
|
|
2
|
+
|
|
3
|
+
/** Words Mermaid reads as syntax: such node ids are renamed. */
|
|
4
|
+
const RESERVED = new Set(["end", "graph", "flowchart", "subgraph", "style", "class", "classdef", "click", "default", "linkstyle", "direction"]);
|
|
5
|
+
|
|
6
|
+
const SHAPES: Record<NodeKind, readonly [open: string, close: string]> = {
|
|
7
|
+
manualStart: ["([", "])"],
|
|
8
|
+
scheduleStart: ["([", "])"],
|
|
9
|
+
eventStart: ["([", "])"],
|
|
10
|
+
end: ["((", "))"],
|
|
11
|
+
userTask: ["[", "]"],
|
|
12
|
+
serviceTask: ["[[", "]]"],
|
|
13
|
+
timerCatch: ["{{", "}}"],
|
|
14
|
+
eventWait: ["{{", "}}"],
|
|
15
|
+
exclusiveGateway: ["{", "}"],
|
|
16
|
+
parallelGateway: ["{", "}"],
|
|
17
|
+
boundaryError: ["((", "))"],
|
|
18
|
+
boundaryTimer: ["((", "))"],
|
|
19
|
+
boundaryEvent: ["((", "))"],
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A process as a Mermaid `flowchart` (WORKFLOW-001 §14): starts are stadiums,
|
|
24
|
+
* Tool steps subroutines, tasks rectangles, waits hexagons, gateways rhombi,
|
|
25
|
+
* ends and boundary events circles. A boundary event hangs on its step by a
|
|
26
|
+
* dotted link. Labels show the node kind's detail: the Tool, the cron, the
|
|
27
|
+
* duration, the event key; flows show their title or condition.
|
|
28
|
+
*/
|
|
29
|
+
export function processToMermaid(document: ProcessDocument, direction: "LR" | "TB" = "LR"): string {
|
|
30
|
+
const ids = new Map<string, string>();
|
|
31
|
+
const used = new Set<string>();
|
|
32
|
+
const idOf = (nodeId: string): string => {
|
|
33
|
+
const known = ids.get(nodeId);
|
|
34
|
+
if (known !== undefined) return known;
|
|
35
|
+
let id = /^[A-Za-z][A-Za-z0-9_]*$/.test(nodeId) && !RESERVED.has(nodeId.toLowerCase()) ? nodeId : `n_${nodeId.replace(/[^A-Za-z0-9_]/g, "_")}`;
|
|
36
|
+
while (used.has(id)) id = `${id}_`;
|
|
37
|
+
used.add(id);
|
|
38
|
+
ids.set(nodeId, id);
|
|
39
|
+
return id;
|
|
40
|
+
};
|
|
41
|
+
const lines = [`flowchart ${direction}`];
|
|
42
|
+
for (const [nodeId, node] of Object.entries(document.nodes)) {
|
|
43
|
+
const [open, close] = SHAPES[node.kind] ?? ["[", "]"];
|
|
44
|
+
const detail = detailOf(node.kind, node.config ?? {});
|
|
45
|
+
lines.push(` ${idOf(nodeId)}${open}"${label(node.title || nodeId)}${detail === undefined ? "" : `<br/><small>${label(detail)}</small>`}"${close}`);
|
|
46
|
+
}
|
|
47
|
+
for (const [nodeId, node] of Object.entries(document.nodes)) {
|
|
48
|
+
if (node.attachedTo !== undefined && document.nodes[node.attachedTo] !== undefined) lines.push(` ${idOf(node.attachedTo)} -.- ${idOf(nodeId)}`);
|
|
49
|
+
}
|
|
50
|
+
for (const flow of Object.values(document.flows)) {
|
|
51
|
+
if (document.nodes[flow.source] === undefined || document.nodes[flow.target] === undefined) continue;
|
|
52
|
+
const text = flow.title ?? (flow.condition !== undefined ? conditionText(flow.condition) : flow.isDefault === true ? "default" : undefined);
|
|
53
|
+
lines.push(` ${idOf(flow.source)} -->${text === undefined ? "" : `|"${label(text)}"|`} ${idOf(flow.target)}`);
|
|
54
|
+
}
|
|
55
|
+
return `${lines.join("\n")}\n`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function detailOf(kind: NodeKind, config: Readonly<Record<string, unknown>>): string | undefined {
|
|
59
|
+
const text = (value: unknown) => (typeof value === "string" && value !== "" ? value : undefined);
|
|
60
|
+
switch (kind) {
|
|
61
|
+
case "serviceTask": return text(config.tool);
|
|
62
|
+
case "scheduleStart": return text(config.cron);
|
|
63
|
+
case "eventStart":
|
|
64
|
+
case "eventWait":
|
|
65
|
+
case "boundaryEvent": return text(config.event);
|
|
66
|
+
case "timerCatch":
|
|
67
|
+
case "boundaryTimer": return text(config.duration) ?? (text(config.at) === undefined ? undefined : `at ${config.at}`);
|
|
68
|
+
case "boundaryError": return text(config.errorCode);
|
|
69
|
+
case "userTask": {
|
|
70
|
+
const assignee = config.assignee as { role?: unknown; user?: unknown } | undefined;
|
|
71
|
+
return assignee === undefined ? undefined : text(assignee.role) !== undefined ? `role ${assignee.role}` : text(assignee.user) === undefined ? undefined : `user ${assignee.user}`;
|
|
72
|
+
}
|
|
73
|
+
default: return undefined;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function conditionText(condition: Condition): string {
|
|
78
|
+
if ("all" in condition) return condition.all.map(conditionText).join(" and ");
|
|
79
|
+
if ("any" in condition) return condition.any.map(conditionText).join(" or ");
|
|
80
|
+
if ("not" in condition) return `not (${conditionText(condition.not)})`;
|
|
81
|
+
return condition.op === "empty" || condition.op === "notEmpty" ? `${condition.path} ${condition.op}` : `${condition.path} ${condition.op} ${JSON.stringify(condition.value)}`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Text inside a quoted Mermaid label: quotes and markup are entities. */
|
|
85
|
+
function label(value: string): string {
|
|
86
|
+
return value.replace(/&/g, "&").replace(/"/g, "#quot;").replace(/</g, "<").replace(/>/g, ">");
|
|
87
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bazis",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.128.0",
|
|
4
4
|
"description": "Modular backend framework for Bun in TypeScript: DI wired by codegen, HTTP, a PostgreSQL ORM, JWT, WebSocket, agents and a CLI.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Alexander Alenin",
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"./core/testing": "./core/testing/index.ts",
|
|
45
45
|
"./core/websocket": "./core/websocket/index.ts",
|
|
46
46
|
"./core/workflow": "./core/workflow/index.ts",
|
|
47
|
+
"./core/workflow/testing": "./core/workflow/testing/index.ts",
|
|
47
48
|
"./library/boundary": "./library/boundary/index.ts",
|
|
48
49
|
"./library/http-client": "./library/http-client/index.ts",
|
|
49
50
|
"./library/jsonapi": "./library/jsonapi/index.ts",
|