bazis 0.131.0 → 0.133.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/templates/process.ts +1 -1
- package/core/infra/connectors/codex/CodexAppServerClient.ts +1 -1
- package/core/workflow/{runtime_module/config/Runtime.config.ts → contracts/Workflow.config.ts} +15 -6
- package/core/workflow/contracts/WorkflowExtensions.ts +40 -0
- package/core/workflow/contracts/WorkflowInputValidator.ts +6 -5
- package/core/workflow/definitions_module/WorkflowDefinitions.module.ts +16 -3
- package/core/workflow/definitions_module/access/Definitions.permissions.ts +4 -3
- package/core/workflow/definitions_module/access/Folder.references.ts +37 -0
- package/core/workflow/definitions_module/access/Process.references.ts +1 -1
- package/core/workflow/definitions_module/access/ProcessFolder.lookup.ts +20 -0
- package/core/workflow/definitions_module/code/CatalogImport.ts +95 -0
- package/core/workflow/definitions_module/code/CodeProcessPublisher.ts +27 -16
- package/core/workflow/definitions_module/code/codeProcesses.ts +7 -3
- package/core/workflow/definitions_module/code/defineProcess.ts +10 -4
- package/core/workflow/definitions_module/http/Processes.controller.ts +66 -23
- package/core/workflow/definitions_module/model/Definitions.dbContext.ts +2 -2
- package/core/workflow/definitions_module/model/WfProcess.model.ts +19 -5
- package/core/workflow/definitions_module/model/WfVersion.model.ts +1 -1
- package/core/workflow/definitions_module/services/IWorkflowDefinitions.service.ts +35 -10
- package/core/workflow/definitions_module/services/WorkflowDefinitions.service.ts +90 -28
- package/core/workflow/generated/deps.ts +15 -3
- package/core/workflow/index.ts +4 -2
- package/core/workflow/internal/WorkflowLanguages.ts +35 -0
- package/core/workflow/internal/canonicalJson.ts +8 -0
- package/core/workflow/internal/http.ts +20 -1
- package/core/workflow/runtime_module/WorkflowRuntime.module.ts +4 -2
- package/core/workflow/runtime_module/access/Runtime.permissions.ts +5 -3
- package/core/workflow/runtime_module/background/WorkflowRetention.ts +3 -3
- package/core/workflow/runtime_module/background/WorkflowWorker.ts +2 -2
- package/core/workflow/runtime_module/events/Runtime.events.ts +6 -6
- package/core/workflow/runtime_module/http/Instances.controller.ts +30 -13
- package/core/workflow/runtime_module/internal/ChildCalls.ts +186 -0
- package/core/workflow/runtime_module/internal/GraphCache.ts +7 -1
- package/core/workflow/runtime_module/internal/RunLimits.ts +124 -0
- package/core/workflow/runtime_module/internal/Schedules.ts +2 -2
- package/core/workflow/runtime_module/internal/TimerStep.ts +11 -1
- package/core/workflow/runtime_module/internal/ToolStep.ts +43 -12
- package/core/workflow/runtime_module/internal/WorkflowEngine.ts +559 -123
- package/core/workflow/runtime_module/internal/WorkflowJobs.ts +39 -10
- package/core/workflow/runtime_module/model/Runtime.dbContext.ts +2 -2
- package/core/workflow/runtime_module/model/WfHistory.model.ts +2 -2
- package/core/workflow/runtime_module/model/WfInstance.model.ts +34 -6
- package/core/workflow/runtime_module/model/WfJob.model.ts +29 -4
- package/core/workflow/runtime_module/model/WfSubscription.model.ts +1 -1
- package/core/workflow/runtime_module/model/WfToken.model.ts +15 -3
- package/core/workflow/runtime_module/services/IWorkflowRuntime.service.ts +43 -4
- package/core/workflow/runtime_module/services/WorkflowRuntime.service.ts +56 -13
- package/core/workflow/tasks_module/events/TaskEvents.ts +4 -8
- package/core/workflow/tasks_module/http/Tasks.controller.ts +14 -13
- package/core/workflow/tasks_module/model/Tasks.dbContext.ts +2 -2
- package/core/workflow/tasks_module/model/WfTask.model.ts +8 -4
- package/core/workflow/tasks_module/services/IWorkflowTasks.service.ts +3 -3
- package/core/workflow/tasks_module/services/WorkflowTasks.service.ts +4 -4
- package/library/openapi/codegen.ts +3 -0
- package/library/validation/types/ValidatorOptions.ts +2 -0
- package/library/workflow/ProcessDocument.ts +73 -23
- package/library/workflow/expressionSyntax.ts +243 -0
- package/library/workflow/expressions.ts +347 -0
- package/library/workflow/formFields.ts +118 -33
- package/library/workflow/index.ts +48 -9
- package/library/workflow/localized.ts +35 -0
- package/library/workflow/masking.ts +72 -0
- package/library/workflow/mermaid.ts +39 -13
- package/library/workflow/nodeKinds.ts +147 -17
- package/library/workflow/palette.ts +110 -0
- package/library/workflow/paths.ts +37 -6
- package/library/workflow/structure.ts +311 -0
- package/library/workflow/upgradeDocument.ts +84 -0
- package/library/workflow/validateProcess.ts +246 -52
- package/package.json +1 -1
- package/library/workflow/conditions.ts +0 -44
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { contextOnlyController } from "../../../http/Routing/contextOnlyControllers";
|
|
2
|
-
import { can } from "../../../access";
|
|
2
|
+
import { can, type AccessObject } from "../../../access";
|
|
3
|
+
import type { LocalizedText } from "../../../../library/workflow";
|
|
3
4
|
import { Authorize, Controller, Created, Get, Patch, Post, Put, type HttpContext } from "../../../http";
|
|
4
5
|
import { buildListDocument } from "../../../../library/jsonapi";
|
|
5
|
-
import { body, callerOf, filterOf, listQuery, optionalInteger, optionalString, pagingOfList, param, requiredString } from "../../internal/http";
|
|
6
|
+
import { body, callerOf, defaultLanguage, filterOf, listQuery, optionalInteger, optionalString, optionalText, pagingOfList, param, requiredString, withTitleText } from "../../internal/http";
|
|
6
7
|
import { WorkflowError } from "../../contracts/WorkflowError";
|
|
7
|
-
import { IWorkflowDefinitions } from "../services/IWorkflowDefinitions.service";
|
|
8
|
+
import { IWorkflowDefinitions, type ProcessSummary } from "../services/IWorkflowDefinitions.service";
|
|
8
9
|
|
|
9
10
|
/** The process catalog over HTTP (WORKFLOW-001 §3); rights are `workflow.processes.*` (WORKFLOW-002 §6.2). */
|
|
10
11
|
@Controller("workflow")
|
|
@@ -14,12 +15,19 @@ export class ProcessesController {
|
|
|
14
15
|
@Get("processes")
|
|
15
16
|
@Authorize(can("workflow.processes.read"))
|
|
16
17
|
async list(ctx: HttpContext) {
|
|
17
|
-
const { access } = await callerOf(ctx);
|
|
18
|
-
const list = listQuery(ctx, { filter: { archived: ["eq"], search: ["eq"] }, sort: ["key", "title", "createdAt", "updatedAt"] });
|
|
18
|
+
const { access, language } = await callerOf(ctx);
|
|
19
|
+
const list = listQuery(ctx, { filter: { archived: ["eq"], search: ["eq"], folder: ["eq"], owner: ["eq"] }, sort: ["key", "title", "folder", "createdAt", "updatedAt"] });
|
|
19
20
|
const archived = filterOf(list, "archived");
|
|
20
21
|
if (archived !== undefined && archived !== "true" && archived !== "false") throw new WorkflowError("invalid_input", "archived must be true or false");
|
|
21
|
-
const page = await this.definitions.list({
|
|
22
|
-
|
|
22
|
+
const page = await this.definitions.list({
|
|
23
|
+
...pagingOfList(list),
|
|
24
|
+
access: await access.filter("workflow.processes.read"),
|
|
25
|
+
archived: archived === undefined ? undefined : archived === "true",
|
|
26
|
+
search: filterOf(list, "search"),
|
|
27
|
+
folder: filterOf(list, "folder"),
|
|
28
|
+
owner: filterOf(list, "owner"),
|
|
29
|
+
});
|
|
30
|
+
return buildListDocument(this.localized(page.items, language, ctx), list, page.total, { basePath: ctx.path });
|
|
23
31
|
}
|
|
24
32
|
|
|
25
33
|
@Post("processes")
|
|
@@ -27,22 +35,25 @@ export class ProcessesController {
|
|
|
27
35
|
async create(ctx: HttpContext) {
|
|
28
36
|
const request = await body(ctx);
|
|
29
37
|
const key = requiredString(request.key, "key", 64);
|
|
30
|
-
const
|
|
38
|
+
const folder = optionalString(request.folder, "folder", 500) ?? null;
|
|
39
|
+
const { actor, request: from, language } = await callerOf(ctx, "workflow.processes.manage", { process: key, folder });
|
|
31
40
|
const process = await this.definitions.create({
|
|
32
41
|
key,
|
|
33
|
-
title:
|
|
34
|
-
description:
|
|
42
|
+
title: optionalText(request.title) as LocalizedText,
|
|
43
|
+
description: optionalText(request.description) ?? null,
|
|
44
|
+
folder,
|
|
45
|
+
owner: optionalString(request.owner, "owner") ?? null,
|
|
35
46
|
retentionDays: optionalInteger(request.retentionDays, "retentionDays") ?? null,
|
|
36
47
|
}, actor, { request: from });
|
|
37
|
-
return Created(`${ctx.path}/${process.key}`, process);
|
|
48
|
+
return Created(`${ctx.path}/${process.key}`, withTitleText(process, language, defaultLanguage(ctx)));
|
|
38
49
|
}
|
|
39
50
|
|
|
40
51
|
@Get("processes/:key")
|
|
41
52
|
@Authorize(can("workflow.processes.read"))
|
|
42
53
|
async get(ctx: HttpContext) {
|
|
43
54
|
const key = param(ctx, "key");
|
|
44
|
-
await callerOf(ctx, "workflow.processes.read",
|
|
45
|
-
return await this.definitions.get(key);
|
|
55
|
+
const { language } = await callerOf(ctx, "workflow.processes.read", await this.objectOf(key));
|
|
56
|
+
return withTitleText(await this.definitions.get(key), language, defaultLanguage(ctx));
|
|
46
57
|
}
|
|
47
58
|
|
|
48
59
|
@Patch("processes/:key")
|
|
@@ -50,14 +61,20 @@ export class ProcessesController {
|
|
|
50
61
|
async update(ctx: HttpContext) {
|
|
51
62
|
const key = param(ctx, "key");
|
|
52
63
|
const request = await body(ctx);
|
|
53
|
-
const { actor, request: from } = await callerOf(ctx, "workflow.processes.manage",
|
|
64
|
+
const { actor, request: from, language } = await callerOf(ctx, "workflow.processes.manage", await this.objectOf(key));
|
|
54
65
|
if (request.archived !== undefined && typeof request.archived !== "boolean") throw new WorkflowError("invalid_input", "archived must be a boolean");
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
66
|
+
const folder = optionalString(request.folder, "folder", 500) ?? null;
|
|
67
|
+
// Moving a process needs the right in the folder it goes to as well.
|
|
68
|
+
if (request.folder !== undefined) await callerOf(ctx, "workflow.processes.manage", { process: key, folder });
|
|
69
|
+
const process = await this.definitions.update(key, {
|
|
70
|
+
...(request.title === undefined ? {} : { title: optionalText(request.title) as LocalizedText }),
|
|
71
|
+
...(request.description === undefined ? {} : { description: optionalText(request.description) ?? null }),
|
|
72
|
+
...(request.folder === undefined ? {} : { folder }),
|
|
73
|
+
...(request.owner === undefined ? {} : { owner: optionalString(request.owner, "owner") ?? null }),
|
|
58
74
|
...(request.retentionDays === undefined ? {} : { retentionDays: optionalInteger(request.retentionDays, "retentionDays") ?? null }),
|
|
59
75
|
...(request.archived === undefined ? {} : { archived: request.archived as boolean }),
|
|
60
76
|
}, actor, { request: from });
|
|
77
|
+
return withTitleText(process, language, defaultLanguage(ctx));
|
|
61
78
|
}
|
|
62
79
|
|
|
63
80
|
@Put("processes/:key/draft")
|
|
@@ -65,19 +82,19 @@ export class ProcessesController {
|
|
|
65
82
|
async saveDraft(ctx: HttpContext) {
|
|
66
83
|
const key = param(ctx, "key");
|
|
67
84
|
const request = await body(ctx);
|
|
68
|
-
const { actor, request: from } = await callerOf(ctx, "workflow.processes.manage",
|
|
85
|
+
const { actor, request: from, language } = await callerOf(ctx, "workflow.processes.manage", await this.objectOf(key));
|
|
69
86
|
const expected = optionalString(request.expected, "expected");
|
|
70
87
|
const expectedAt = expected === undefined ? undefined : new Date(expected);
|
|
71
88
|
if (expectedAt !== undefined && Number.isNaN(expectedAt.getTime())) throw new WorkflowError("invalid_input", "expected must be the draftUpdatedAt that was read");
|
|
72
|
-
return await this.definitions.saveDraft(key, request.document, actor, { request: from, ...(expectedAt === undefined ? {} : { expected: expectedAt }) });
|
|
89
|
+
return await this.definitions.saveDraft(key, request.document, actor, { request: from, language, ...(expectedAt === undefined ? {} : { expected: expectedAt }) });
|
|
73
90
|
}
|
|
74
91
|
|
|
75
92
|
@Post("processes/:key/publish")
|
|
76
93
|
@Authorize(can("workflow.processes.publish"))
|
|
77
94
|
async publish(ctx: HttpContext) {
|
|
78
95
|
const key = param(ctx, "key");
|
|
79
|
-
const { actor, request } = await callerOf(ctx, "workflow.processes.publish",
|
|
80
|
-
const version = await this.definitions.publish(key, actor, { request });
|
|
96
|
+
const { actor, request, language } = await callerOf(ctx, "workflow.processes.publish", await this.objectOf(key));
|
|
97
|
+
const version = await this.definitions.publish(key, actor, { request, language });
|
|
81
98
|
return Created(`${ctx.path.replace(/\/publish$/, "")}/versions/${version.version}`, version);
|
|
82
99
|
}
|
|
83
100
|
|
|
@@ -85,7 +102,7 @@ export class ProcessesController {
|
|
|
85
102
|
@Authorize(can("workflow.processes.read"))
|
|
86
103
|
async versions(ctx: HttpContext) {
|
|
87
104
|
const key = param(ctx, "key");
|
|
88
|
-
await callerOf(ctx, "workflow.processes.read",
|
|
105
|
+
await callerOf(ctx, "workflow.processes.read", await this.objectOf(key));
|
|
89
106
|
return { data: await this.definitions.versions(key) };
|
|
90
107
|
}
|
|
91
108
|
|
|
@@ -93,16 +110,42 @@ export class ProcessesController {
|
|
|
93
110
|
@Authorize(can("workflow.processes.read"))
|
|
94
111
|
async version(ctx: HttpContext) {
|
|
95
112
|
const key = param(ctx, "key");
|
|
96
|
-
await callerOf(ctx, "workflow.processes.read",
|
|
113
|
+
await callerOf(ctx, "workflow.processes.read", await this.objectOf(key));
|
|
97
114
|
const version = optionalInteger(param(ctx, "version"), "version");
|
|
98
115
|
if (version === undefined) throw new WorkflowError("invalid_input", "version must be an integer");
|
|
99
116
|
return await this.definitions.version(key, version);
|
|
100
117
|
}
|
|
101
118
|
|
|
119
|
+
/** Checks a document without storing it: every finding, as a save and a publication would give them. */
|
|
120
|
+
@Post("validate")
|
|
121
|
+
@Authorize(can("workflow.processes.read"))
|
|
122
|
+
async validate(ctx: HttpContext) {
|
|
123
|
+
const request = await body(ctx);
|
|
124
|
+
const { actor, language } = await callerOf(ctx);
|
|
125
|
+
const key = optionalString(request.key, "key", 64);
|
|
126
|
+
return { issues: await this.definitions.validate(request.document, { actor, language, ...(key === undefined ? {} : { processes: [key] }) }) };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** What a designer builds its forms from (WORKFLOW-002 §4.2). */
|
|
130
|
+
@Get("palette")
|
|
131
|
+
@Authorize(can("workflow.processes.read"))
|
|
132
|
+
async palette() {
|
|
133
|
+
return this.definitions.palette();
|
|
134
|
+
}
|
|
135
|
+
|
|
102
136
|
@Get("tools")
|
|
103
137
|
@Authorize(can("workflow.processes.read"))
|
|
104
138
|
async tools() {
|
|
105
139
|
return { data: this.definitions.tools() };
|
|
106
140
|
}
|
|
141
|
+
|
|
142
|
+
/** A process as the object of a permission. */
|
|
143
|
+
private async objectOf(key: string): Promise<AccessObject> {
|
|
144
|
+
return { process: key, folder: await this.definitions.folderOf(key) };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private localized(items: readonly ProcessSummary[], language: string, ctx: HttpContext) {
|
|
148
|
+
return items.map((item) => withTitleText(item, language, defaultLanguage(ctx)));
|
|
149
|
+
}
|
|
107
150
|
}
|
|
108
151
|
contextOnlyController(ProcessesController);
|
|
@@ -10,7 +10,7 @@ export class DefinitionsDbContext extends DbContext {
|
|
|
10
10
|
/** The tables of the Definitions part: bazis creates and checks them in the application database. */
|
|
11
11
|
export const definitionsStore = defineOrmOwnedStoreV1({
|
|
12
12
|
contract: "bazis.orm-owned-store/v1",
|
|
13
|
-
storeKey: "bazis.workflow.definitions",
|
|
13
|
+
storeKey: "bazis.workflow.definitions.v2",
|
|
14
14
|
formatVersion: 1,
|
|
15
|
-
ownedScope: { schema: "public", tablePrefix: "
|
|
15
|
+
ownedScope: { schema: "public", tablePrefix: "bazis_wf2_def_" },
|
|
16
16
|
});
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { Column, Entity, Index, UUID } from "../../../orm";
|
|
2
|
+
import type { LocalizedText } from "../../../../library/workflow";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
|
-
* A process in the catalog: the key it is started by, its names,
|
|
5
|
-
* designer edits and the retention of finished instances. Changes; the
|
|
5
|
+
* A process in the catalog: the key it is started by, its card (names, folder,
|
|
6
|
+
* owner), the draft a designer edits and the retention of finished instances. Changes; the
|
|
6
7
|
* published versions ({@link WfVersion}) do not.
|
|
7
8
|
*/
|
|
8
|
-
@Entity({ table: "
|
|
9
|
+
@Entity({ table: "bazis_wf2_def_processes" })
|
|
9
10
|
export class WfProcess {
|
|
10
11
|
@UUID({ version: "v7" })
|
|
11
12
|
id = "";
|
|
@@ -14,11 +15,24 @@ export class WfProcess {
|
|
|
14
15
|
@Column({ type: "text" })
|
|
15
16
|
key = "";
|
|
16
17
|
|
|
18
|
+
@Column({ type: "json" })
|
|
19
|
+
title: LocalizedText = "";
|
|
20
|
+
|
|
21
|
+
/** Every text of `title`, the default language first: what a list searches and sorts by. */
|
|
17
22
|
@Column({ type: "text" })
|
|
18
|
-
|
|
23
|
+
titleText = "";
|
|
24
|
+
|
|
25
|
+
@Column({ type: "json", nullable: true })
|
|
26
|
+
description: LocalizedText | null = null;
|
|
27
|
+
|
|
28
|
+
/** A path, `Sales/Orders`: there is no folder entity. */
|
|
29
|
+
@Index()
|
|
30
|
+
@Column({ type: "text", nullable: true })
|
|
31
|
+
folder: string | null = null;
|
|
19
32
|
|
|
33
|
+
/** Whose process it is: an opaque id of the application. */
|
|
20
34
|
@Column({ type: "text", nullable: true })
|
|
21
|
-
|
|
35
|
+
owner: string | null = null;
|
|
22
36
|
|
|
23
37
|
/** The draft document; kept with validation errors too. */
|
|
24
38
|
@Column({ type: "json", nullable: true })
|
|
@@ -4,7 +4,7 @@ import { WfProcess } from "./WfProcess.model";
|
|
|
4
4
|
/** A published version of a process: an immutable snapshot of its document. */
|
|
5
5
|
@ForeignKey(() => WfProcess, { properties: ["processId"], onDelete: "cascade" })
|
|
6
6
|
@Index(["processId", "version"], { unique: true })
|
|
7
|
-
@Entity({ table: "
|
|
7
|
+
@Entity({ table: "bazis_wf2_def_versions" })
|
|
8
8
|
export class WfVersion {
|
|
9
9
|
@UUID({ version: "v7" })
|
|
10
10
|
id = "";
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import type { AccessFilter } from "../../../access";
|
|
2
2
|
import type { AgentToolContract } from "../../../agent";
|
|
3
|
-
import type { ProcessDocument, ProcessIssue } from "../../../../library/workflow";
|
|
3
|
+
import type { LocalizedText, ProcessDocument, ProcessIssue, ProcessPalette } from "../../../../library/workflow";
|
|
4
4
|
import type { WorkflowActor, WorkflowAuditOptions, WorkflowPage, WorkflowPaging } from "../../contracts/types";
|
|
5
5
|
import type { OwnedCodeProcess } from "../code/codeProcesses";
|
|
6
6
|
|
|
7
7
|
export interface ProcessSummary {
|
|
8
8
|
readonly key: string;
|
|
9
|
-
readonly title:
|
|
10
|
-
readonly description:
|
|
9
|
+
readonly title: LocalizedText;
|
|
10
|
+
readonly description: LocalizedText | null;
|
|
11
|
+
/** A path, `Sales/Orders`. */
|
|
12
|
+
readonly folder: string | null;
|
|
13
|
+
/** Whose process it is: an opaque id. */
|
|
14
|
+
readonly owner: string | null;
|
|
11
15
|
readonly latestVersion: number | null;
|
|
12
16
|
readonly retentionDays: number | null;
|
|
13
17
|
readonly archived: boolean;
|
|
@@ -41,14 +45,18 @@ export interface VersionDetails extends VersionSummary {
|
|
|
41
45
|
|
|
42
46
|
export interface CreateProcess {
|
|
43
47
|
readonly key: string;
|
|
44
|
-
readonly title:
|
|
45
|
-
readonly description?:
|
|
48
|
+
readonly title: LocalizedText;
|
|
49
|
+
readonly description?: LocalizedText | null;
|
|
50
|
+
readonly folder?: string | null;
|
|
51
|
+
readonly owner?: string | null;
|
|
46
52
|
readonly retentionDays?: number | null;
|
|
47
53
|
}
|
|
48
54
|
|
|
49
55
|
export interface UpdateProcess {
|
|
50
|
-
readonly title?:
|
|
51
|
-
readonly description?:
|
|
56
|
+
readonly title?: LocalizedText;
|
|
57
|
+
readonly description?: LocalizedText | null;
|
|
58
|
+
readonly folder?: string | null;
|
|
59
|
+
readonly owner?: string | null;
|
|
52
60
|
readonly retentionDays?: number | null;
|
|
53
61
|
readonly archived?: boolean;
|
|
54
62
|
}
|
|
@@ -57,11 +65,22 @@ export interface ProcessListQuery extends WorkflowPaging {
|
|
|
57
65
|
/** Which processes the caller may see, from `IAccess`; without it the calling code has decided. */
|
|
58
66
|
readonly access?: AccessFilter;
|
|
59
67
|
readonly archived?: boolean;
|
|
60
|
-
/** Part of the key or title, case-insensitive. */
|
|
68
|
+
/** Part of the key or a title in any language, case-insensitive. */
|
|
61
69
|
readonly search?: string;
|
|
70
|
+
/** This folder and those below it. */
|
|
71
|
+
readonly folder?: string;
|
|
72
|
+
readonly owner?: string;
|
|
62
73
|
}
|
|
63
74
|
|
|
64
|
-
|
|
75
|
+
/** Who asks for a validation: it goes to the application's `WorkflowExtensions`. */
|
|
76
|
+
export interface ValidateOptions {
|
|
77
|
+
readonly language?: string;
|
|
78
|
+
readonly actor?: WorkflowActor;
|
|
79
|
+
/** Keys that count as published besides the catalog: the process itself, processes of the code about to be published. */
|
|
80
|
+
readonly processes?: readonly string[];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface SaveDraftOptions extends WorkflowAuditOptions, Pick<ValidateOptions, "language"> {
|
|
65
84
|
/** The `draftUpdatedAt` the caller read: another save since then is `conflict`. Without it the last save wins. */
|
|
66
85
|
readonly expected?: Date;
|
|
67
86
|
}
|
|
@@ -81,6 +100,12 @@ export interface SavedDraft {
|
|
|
81
100
|
export abstract class IWorkflowDefinitions {
|
|
82
101
|
abstract list(query?: ProcessListQuery): Promise<WorkflowPage<ProcessSummary>>;
|
|
83
102
|
abstract get(key: string): Promise<ProcessDetails>;
|
|
103
|
+
/** The folder of a process, for a check of rights; `null` also for a process that does not exist. */
|
|
104
|
+
abstract folderOf(key: string): Promise<string | null>;
|
|
105
|
+
/** Every finding of a document, with the application's `extensions` checks; nothing is stored. */
|
|
106
|
+
abstract validate(document: unknown, options?: ValidateOptions): Promise<readonly ProcessIssue[]>;
|
|
107
|
+
/** What a designer builds its forms from: node kinds, functions, paths and Tools. */
|
|
108
|
+
abstract palette(): ProcessPalette;
|
|
84
109
|
abstract create(process: CreateProcess, actor: WorkflowActor, options?: WorkflowAuditOptions): Promise<ProcessDetails>;
|
|
85
110
|
/** A process declared in code takes only `archived`; other fields give `conflict`. */
|
|
86
111
|
abstract update(key: string, changes: UpdateProcess, actor: WorkflowActor, options?: WorkflowAuditOptions): Promise<ProcessDetails>;
|
|
@@ -90,7 +115,7 @@ export abstract class IWorkflowDefinitions {
|
|
|
90
115
|
* Validates the draft and stores it as the next version; the same document
|
|
91
116
|
* again returns the latest version. `conflict` for a process declared in code.
|
|
92
117
|
*/
|
|
93
|
-
abstract publish(key: string, actor: WorkflowActor, options?: WorkflowAuditOptions): Promise<VersionDetails>;
|
|
118
|
+
abstract publish(key: string, actor: WorkflowActor, options?: WorkflowAuditOptions & Pick<ValidateOptions, "language">): Promise<VersionDetails>;
|
|
94
119
|
abstract versions(key: string): Promise<readonly VersionSummary[]>;
|
|
95
120
|
abstract version(key: string, version: number): Promise<VersionDetails>;
|
|
96
121
|
/** The latest published version of a process that is not archived. */
|
|
@@ -2,11 +2,15 @@ import { grantsToWhere } from "../../../access";
|
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { paginate, UniqueViolationError } from "../../../orm";
|
|
4
4
|
import type { AgentToolContract } from "../../../agent";
|
|
5
|
-
import { emptyProcess, hasErrors, validateProcess, type ProcessDocument, type ProcessIssue } from "../../../../library/workflow";
|
|
5
|
+
import { allTexts, checkLocalizedText, emptyProcess, hasErrors, processPalette, validateProcess, type LocalizedText, type ProcessDocument, type ProcessIssue, type ProcessPalette } from "../../../../library/workflow";
|
|
6
|
+
import type { ServiceProvider } from "../../../di";
|
|
6
7
|
import { isUuid, pageQueryOf, type WorkflowActor, type WorkflowAuditOptions, type WorkflowPage } from "../../contracts/types";
|
|
7
8
|
import type { WorkflowClock } from "../../contracts/WorkflowClock";
|
|
8
9
|
import { WorkflowError } from "../../contracts/WorkflowError";
|
|
10
|
+
import { WorkflowExtensions, type ExtensionTarget, type WorkflowPortContext } from "../../contracts/WorkflowExtensions";
|
|
11
|
+
import { canonicalJson } from "../../internal/canonicalJson";
|
|
9
12
|
import type { WorkflowEvents } from "../../internal/WorkflowEvents";
|
|
13
|
+
import type { WorkflowLanguages } from "../../internal/WorkflowLanguages";
|
|
10
14
|
import type { WorkflowTools } from "../../internal/WorkflowTools";
|
|
11
15
|
import type { CodeProcessCatalog } from "../code/CodeProcessCatalog";
|
|
12
16
|
import type { OwnedCodeProcess } from "../code/codeProcesses";
|
|
@@ -23,11 +27,14 @@ import {
|
|
|
23
27
|
type SavedDraft,
|
|
24
28
|
type SaveDraftOptions,
|
|
25
29
|
type UpdateProcess,
|
|
30
|
+
type ValidateOptions,
|
|
26
31
|
type VersionDetails,
|
|
27
32
|
type VersionSummary,
|
|
28
33
|
} from "./IWorkflowDefinitions.service";
|
|
29
34
|
|
|
30
35
|
const KEY = /^[a-z][a-z0-9_.-]{0,63}$/;
|
|
36
|
+
/** A path of segments without empty ones: `Sales/Orders`. */
|
|
37
|
+
const FOLDER = /^[^/\s](?:[^/]*[^/\s])?(?:\/[^/\s](?:[^/]*[^/\s])?)*$/;
|
|
31
38
|
/** Who publishes the processes declared in code. */
|
|
32
39
|
const CODE_ACTOR: WorkflowActor = { kind: "system", id: "code" };
|
|
33
40
|
|
|
@@ -38,6 +45,9 @@ export class WorkflowDefinitions extends IWorkflowDefinitions {
|
|
|
38
45
|
private readonly code: CodeProcessCatalog,
|
|
39
46
|
private readonly clock: WorkflowClock,
|
|
40
47
|
private readonly events: WorkflowEvents,
|
|
48
|
+
private readonly languages: WorkflowLanguages,
|
|
49
|
+
/** The scope: the port is asked here, so that the application's own registration replaces the default. */
|
|
50
|
+
private readonly services: ServiceProvider,
|
|
41
51
|
) {
|
|
42
52
|
super();
|
|
43
53
|
}
|
|
@@ -51,9 +61,16 @@ export class WorkflowDefinitions extends IWorkflowDefinitions {
|
|
|
51
61
|
if (query.archived === false) source = source.where((p) => p.archivedAt.isNull());
|
|
52
62
|
if (query.search !== undefined && query.search.trim() !== "") {
|
|
53
63
|
const text = query.search.trim();
|
|
54
|
-
source = source.where((p) => p.key.contains(text, { ignoreCase: true }).or(p.
|
|
64
|
+
source = source.where((p) => p.key.contains(text, { ignoreCase: true }).or(p.titleText.contains(text, { ignoreCase: true })));
|
|
55
65
|
}
|
|
56
|
-
|
|
66
|
+
if (query.folder !== undefined) {
|
|
67
|
+
const folder = query.folder;
|
|
68
|
+
source = source.where((p) => p.folder.eq(folder).or(p.folder.startsWith(`${folder}/`)));
|
|
69
|
+
}
|
|
70
|
+
if (query.owner !== undefined) source = source.where((p) => p.owner.eq(query.owner!));
|
|
71
|
+
// The list sorts by a title as `titleText`: `title` itself is JSON.
|
|
72
|
+
const sort = query.sort?.map((field) => (field.field === "title" ? { ...field, field: "titleText" } : field));
|
|
73
|
+
const { items, total } = await paginate(source, pageQueryOf({ ...query, ...(sort === undefined ? {} : { sort }) }, { field: "key", dir: "asc" }));
|
|
57
74
|
return { items: items.map((row) => this.summaryOf(row)), total };
|
|
58
75
|
}
|
|
59
76
|
|
|
@@ -61,12 +78,46 @@ export class WorkflowDefinitions extends IWorkflowDefinitions {
|
|
|
61
78
|
return this.detailsOf(await this.process(key, false));
|
|
62
79
|
}
|
|
63
80
|
|
|
81
|
+
async folderOf(key: string): Promise<string | null> {
|
|
82
|
+
const row = await this.db.processes.asNoTracking().firstOrDefault((p) => p.key.eq(key));
|
|
83
|
+
return row?.folder ?? null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async validate(document: unknown, options: ValidateOptions = {}): Promise<readonly ProcessIssue[]> {
|
|
87
|
+
const port = this.services.resolve(WorkflowExtensions);
|
|
88
|
+
const namespaces = new Set(port.namespaces());
|
|
89
|
+
const published = await this.db.processes.asNoTracking().where((p) => p.archivedAt.isNull().and(p.latestVersion.isNotNull())).select((p) => ({ key: p.key })).toList();
|
|
90
|
+
const processes = new Set([...published.map((row) => row.key), ...options.processes ?? []]);
|
|
91
|
+
const issues = validateProcess(document, { tools: (name) => this.workflowTools.has(name), processes: (key) => processes.has(key), extensions: (namespace) => namespaces.has(namespace) });
|
|
92
|
+
if (namespaces.size === 0 || issues.some((issue) => issue.code === "too_large" || issue.code === "schema")) return issues;
|
|
93
|
+
const context: WorkflowPortContext = { partition: null, language: options.language ?? this.languages.default, actor: options.actor ?? null };
|
|
94
|
+
const found: ProcessIssue[] = [...issues];
|
|
95
|
+
const { extensions, variables, nodes } = document as ProcessDocument;
|
|
96
|
+
const checkAll = async (data: Readonly<Record<string, unknown>> | undefined, target: ExtensionTarget): Promise<void> => {
|
|
97
|
+
if (typeof data !== "object" || data === null) return;
|
|
98
|
+
for (const [namespace, value] of Object.entries(data)) {
|
|
99
|
+
if (!namespaces.has(namespace)) continue;
|
|
100
|
+
for (const message of await port.check(namespace, value, target, context)) found.push({ severity: "error", code: "extension_invalid", message: `${namespace}: ${message}`, ...target });
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
await checkAll(extensions, {});
|
|
104
|
+
if (Array.isArray(variables)) for (const variable of variables) await checkAll(variable?.extensions, { variable: variable?.name });
|
|
105
|
+
for (const [node, raw] of Object.entries(nodes)) await checkAll(raw?.extensions, { node });
|
|
106
|
+
return found;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
palette(): ProcessPalette {
|
|
110
|
+
return processPalette(this.tools());
|
|
111
|
+
}
|
|
112
|
+
|
|
64
113
|
async create(process: CreateProcess, actor: WorkflowActor, options: WorkflowAuditOptions = {}): Promise<ProcessDetails> {
|
|
65
114
|
if (typeof process.key !== "string" || !KEY.test(process.key)) throw new WorkflowError("invalid_input", "key must match ^[a-z][a-z0-9_.-]{0,63}$");
|
|
66
115
|
const row = Object.assign(new WfProcess(), {
|
|
67
116
|
key: process.key,
|
|
68
|
-
|
|
117
|
+
...this.titled(process.title),
|
|
69
118
|
description: description(process.description),
|
|
119
|
+
folder: folder(process.folder),
|
|
120
|
+
owner: owner(process.owner),
|
|
70
121
|
retentionDays: retention(process.retentionDays),
|
|
71
122
|
draft: emptyProcess(),
|
|
72
123
|
draftUpdatedAt: this.clock.now(),
|
|
@@ -88,19 +139,22 @@ export class WorkflowDefinitions extends IWorkflowDefinitions {
|
|
|
88
139
|
const row = await this.process(key, true);
|
|
89
140
|
const { archived, ...fields } = changes;
|
|
90
141
|
if (Object.values(fields).some((value) => value !== undefined)) this.assertNotCode(key);
|
|
91
|
-
const
|
|
92
|
-
|
|
142
|
+
const card = (): string => canonicalJson({ title: row.title, description: row.description, folder: row.folder, owner: row.owner, retentionDays: row.retentionDays });
|
|
143
|
+
const before = { card: card(), archived: row.archivedAt !== null };
|
|
144
|
+
if (changes.title !== undefined) Object.assign(row, this.titled(changes.title));
|
|
93
145
|
if (changes.description !== undefined) row.description = description(changes.description);
|
|
146
|
+
if (changes.folder !== undefined) row.folder = folder(changes.folder);
|
|
147
|
+
if (changes.owner !== undefined) row.owner = owner(changes.owner);
|
|
94
148
|
if (changes.retentionDays !== undefined) row.retentionDays = retention(changes.retentionDays);
|
|
95
149
|
if (archived !== undefined) row.archivedAt = archived ? row.archivedAt ?? this.clock.now() : null;
|
|
96
150
|
await this.db.saveChanges();
|
|
97
|
-
if (
|
|
151
|
+
if (card() !== before.card) await this.changed("updated", key, actor, options);
|
|
98
152
|
if (archived !== undefined && archived !== before.archived) await this.changed(archived ? "archived" : "restored", key, actor, options);
|
|
99
153
|
return this.detailsOf(row);
|
|
100
154
|
}
|
|
101
155
|
|
|
102
156
|
async saveDraft(key: string, document: unknown, actor: WorkflowActor, options: SaveDraftOptions = {}): Promise<SavedDraft> {
|
|
103
|
-
const issues = this.validate(document);
|
|
157
|
+
const issues = await this.validate(document, { actor, ...(options.language === undefined ? {} : { language: options.language }) });
|
|
104
158
|
if (issues.some((issue) => issue.code === "too_large")) throw new WorkflowError("invalid_document", "The document is too large", issues);
|
|
105
159
|
return await this.db.transactionScope(async (transaction) => {
|
|
106
160
|
const row = await this.db.processes.where((p) => p.key.eq(key)).forUpdate().firstOrDefault();
|
|
@@ -120,13 +174,13 @@ export class WorkflowDefinitions extends IWorkflowDefinitions {
|
|
|
120
174
|
});
|
|
121
175
|
}
|
|
122
176
|
|
|
123
|
-
async publish(key: string, actor: WorkflowActor, options: WorkflowAuditOptions = {}): Promise<VersionDetails> {
|
|
177
|
+
async publish(key: string, actor: WorkflowActor, options: WorkflowAuditOptions & Pick<ValidateOptions, "language"> = {}): Promise<VersionDetails> {
|
|
124
178
|
return await this.db.transactionScope(async (transaction) => {
|
|
125
179
|
const row = await this.db.processes.where((p) => p.key.eq(key)).forUpdate().firstOrDefault();
|
|
126
180
|
if (!row) throw notFound(key);
|
|
127
181
|
this.assertNotCode(key);
|
|
128
182
|
if (row.archivedAt !== null) throw new WorkflowError("archived", `Process ${key} is archived`);
|
|
129
|
-
const issues = this.validate(row.draft);
|
|
183
|
+
const issues = await this.validate(row.draft, { actor, processes: [key], ...(options.language === undefined ? {} : { language: options.language }) });
|
|
130
184
|
if (hasErrors(issues)) throw new WorkflowError("invalid_document", `Process ${key} has errors and cannot be published`, issues);
|
|
131
185
|
const { version, created } = await this.publishDraft(row, actor);
|
|
132
186
|
if (created) transaction.afterCommit(() => this.changed("published", key, actor, options, version.version));
|
|
@@ -151,9 +205,13 @@ export class WorkflowDefinitions extends IWorkflowDefinitions {
|
|
|
151
205
|
this.db.processes.add(row);
|
|
152
206
|
transaction.afterCommit(() => this.changed("created", process.key, CODE_ACTOR));
|
|
153
207
|
}
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
if (row.
|
|
208
|
+
const named = this.titled(process.title);
|
|
209
|
+
const about = description(process.description);
|
|
210
|
+
if (canonicalJson(row.title) !== canonicalJson(named.title)) Object.assign(row, named);
|
|
211
|
+
if (canonicalJson(row.description) !== canonicalJson(about)) row.description = about;
|
|
212
|
+
const fields = { folder: folder(process.folder), owner: owner(process.owner), retentionDays: retention(process.retentionDays) };
|
|
213
|
+
if (row.folder !== fields.folder) row.folder = fields.folder;
|
|
214
|
+
if (row.owner !== fields.owner) row.owner = fields.owner;
|
|
157
215
|
if (row.retentionDays !== fields.retentionDays) row.retentionDays = fields.retentionDays;
|
|
158
216
|
if (row.draft === null || canonicalJson(row.draft) !== canonicalJson(process.document)) {
|
|
159
217
|
row.draft = process.document;
|
|
@@ -260,6 +318,8 @@ export class WorkflowDefinitions extends IWorkflowDefinitions {
|
|
|
260
318
|
key: row.key,
|
|
261
319
|
title: row.title,
|
|
262
320
|
description: row.description,
|
|
321
|
+
folder: row.folder,
|
|
322
|
+
owner: row.owner,
|
|
263
323
|
latestVersion: row.latestVersion,
|
|
264
324
|
retentionDays: row.retentionDays,
|
|
265
325
|
archived: row.archivedAt !== null,
|
|
@@ -278,8 +338,11 @@ export class WorkflowDefinitions extends IWorkflowDefinitions {
|
|
|
278
338
|
};
|
|
279
339
|
}
|
|
280
340
|
|
|
281
|
-
|
|
282
|
-
|
|
341
|
+
/** A title with the column a list searches and sorts by. */
|
|
342
|
+
private titled(value: unknown): { readonly title: LocalizedText; readonly titleText: string } {
|
|
343
|
+
const problem = checkLocalizedText(value, 200);
|
|
344
|
+
if (problem) throw new WorkflowError("invalid_input", `title ${problem}`);
|
|
345
|
+
return { title: value as LocalizedText, titleText: allTexts(value as LocalizedText, this.languages.default) };
|
|
283
346
|
}
|
|
284
347
|
|
|
285
348
|
private async process(key: string, tracked: boolean): Promise<WfProcess> {
|
|
@@ -294,14 +357,22 @@ function notFound(key: string): WorkflowError {
|
|
|
294
357
|
return new WorkflowError("not_found", `Process ${key} does not exist`);
|
|
295
358
|
}
|
|
296
359
|
|
|
297
|
-
function
|
|
298
|
-
if (
|
|
360
|
+
function description(value: unknown): LocalizedText | null {
|
|
361
|
+
if (value === undefined || value === null) return null;
|
|
362
|
+
const problem = checkLocalizedText(value, 2000, 0);
|
|
363
|
+
if (problem) throw new WorkflowError("invalid_input", `description ${problem}`);
|
|
364
|
+
return value as LocalizedText;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function folder(value: unknown): string | null {
|
|
368
|
+
if (value === undefined || value === null) return null;
|
|
369
|
+
if (typeof value !== "string" || value.length > 500 || !FOLDER.test(value)) throw new WorkflowError("invalid_input", "folder must be a path like Sales/Orders");
|
|
299
370
|
return value;
|
|
300
371
|
}
|
|
301
372
|
|
|
302
|
-
function
|
|
373
|
+
function owner(value: unknown): string | null {
|
|
303
374
|
if (value === undefined || value === null) return null;
|
|
304
|
-
if (typeof value !== "string" || value.length >
|
|
375
|
+
if (typeof value !== "string" || value === "" || value.length > 200) throw new WorkflowError("invalid_input", "owner must be up to 200 characters");
|
|
305
376
|
return value;
|
|
306
377
|
}
|
|
307
378
|
|
|
@@ -321,12 +392,3 @@ function versionOf(key: string, row: WfVersion): VersionDetails {
|
|
|
321
392
|
document: row.document as ProcessDocument,
|
|
322
393
|
};
|
|
323
394
|
}
|
|
324
|
-
|
|
325
|
-
/** JSON with sorted keys: the same document gives the same hash whatever the key order. */
|
|
326
|
-
function canonicalJson(value: unknown): string {
|
|
327
|
-
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
|
328
|
-
if (typeof value === "object" && value !== null) {
|
|
329
|
-
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson((value as Record<string, unknown>)[key])}`).join(",")}}`;
|
|
330
|
-
}
|
|
331
|
-
return JSON.stringify(value) ?? "null";
|
|
332
|
-
}
|
|
@@ -5,7 +5,10 @@ import { DbContextOptions } from "../../../library/orm/DbContextOptions";
|
|
|
5
5
|
import { ServiceProvider } from "../../di/ServiceProvider";
|
|
6
6
|
import { EventBus } from "../../kernel/events/EventBus";
|
|
7
7
|
import { WorkflowClock } from "../contracts/WorkflowClock";
|
|
8
|
+
import { FolderReferences } from "../definitions_module/access/Folder.references";
|
|
8
9
|
import { ProcessReferences } from "../definitions_module/access/Process.references";
|
|
10
|
+
import { ProcessFolderLookup } from "../definitions_module/access/ProcessFolder.lookup";
|
|
11
|
+
import { CatalogImport } from "../definitions_module/code/CatalogImport";
|
|
9
12
|
import { CodeProcessCatalog } from "../definitions_module/code/CodeProcessCatalog";
|
|
10
13
|
import { CodeProcessPublisher } from "../definitions_module/code/CodeProcessPublisher";
|
|
11
14
|
import { ProcessesController } from "../definitions_module/http/Processes.controller";
|
|
@@ -13,12 +16,15 @@ import { DefinitionsDbContext } from "../definitions_module/model/Definitions.db
|
|
|
13
16
|
import { IWorkflowDefinitions } from "../definitions_module/services/IWorkflowDefinitions.service";
|
|
14
17
|
import { WorkflowDefinitions } from "../definitions_module/services/WorkflowDefinitions.service";
|
|
15
18
|
import { WorkflowEvents } from "../internal/WorkflowEvents";
|
|
19
|
+
import { WorkflowLanguages } from "../internal/WorkflowLanguages";
|
|
16
20
|
import { WorkflowTools } from "../internal/WorkflowTools";
|
|
17
21
|
import { WorkflowRetention } from "../runtime_module/background/WorkflowRetention";
|
|
18
22
|
import { WorkflowWorker } from "../runtime_module/background/WorkflowWorker";
|
|
19
23
|
import { EventsController } from "../runtime_module/http/Events.controller";
|
|
20
24
|
import { InstancesController } from "../runtime_module/http/Instances.controller";
|
|
25
|
+
import { ChildCalls } from "../runtime_module/internal/ChildCalls";
|
|
21
26
|
import { GraphCache } from "../runtime_module/internal/GraphCache";
|
|
27
|
+
import { RunLimits } from "../runtime_module/internal/RunLimits";
|
|
22
28
|
import { Schedules } from "../runtime_module/internal/Schedules";
|
|
23
29
|
import { TimerStep } from "../runtime_module/internal/TimerStep";
|
|
24
30
|
import { ToolStep } from "../runtime_module/internal/ToolStep";
|
|
@@ -33,13 +39,18 @@ import { IWorkflowTasks } from "../tasks_module/services/IWorkflowTasks.service"
|
|
|
33
39
|
import { WorkflowTasks } from "../tasks_module/services/WorkflowTasks.service";
|
|
34
40
|
|
|
35
41
|
registerFrameworkClassDeps([
|
|
42
|
+
[CatalogImport, [DefinitionsDbContext, WorkflowLanguages]],
|
|
43
|
+
[ChildCalls, [RuntimeDbContext, WorkflowEngine, IWorkflowDefinitions, ServiceProvider, WorkflowLanguages]],
|
|
36
44
|
[CodeProcessCatalog, [ServiceProvider]],
|
|
37
45
|
[CodeProcessPublisher, [ServiceProvider]],
|
|
38
46
|
[DefinitionsDbContext, [DbContextOptions]],
|
|
39
47
|
[EventsController, [IWorkflowRuntime]],
|
|
40
|
-
[
|
|
48
|
+
[FolderReferences, [DefinitionsDbContext]],
|
|
49
|
+
[InstancesController, [IWorkflowRuntime, IWorkflowDefinitions]],
|
|
41
50
|
[ProcessesController, [IWorkflowDefinitions]],
|
|
51
|
+
[ProcessFolderLookup, [DefinitionsDbContext]],
|
|
42
52
|
[ProcessReferences, [DefinitionsDbContext]],
|
|
53
|
+
[RunLimits, [RuntimeDbContext, WorkflowEngine, IWorkflowDefinitions]],
|
|
43
54
|
[RuntimeDbContext, [DbContextOptions]],
|
|
44
55
|
[Schedules, [RuntimeDbContext, WorkflowEngine, IWorkflowDefinitions]],
|
|
45
56
|
[TaskEvents, [TasksDbContext]],
|
|
@@ -47,11 +58,12 @@ registerFrameworkClassDeps([
|
|
|
47
58
|
[TasksDbContext, [DbContextOptions]],
|
|
48
59
|
[TimerStep, [RuntimeDbContext, WorkflowEngine]],
|
|
49
60
|
[ToolStep, [RuntimeDbContext, WorkflowEngine, WorkflowTools]],
|
|
50
|
-
[WorkflowDefinitions, [DefinitionsDbContext, WorkflowTools, CodeProcessCatalog, WorkflowClock, WorkflowEvents]],
|
|
61
|
+
[WorkflowDefinitions, [DefinitionsDbContext, WorkflowTools, CodeProcessCatalog, WorkflowClock, WorkflowEvents, WorkflowLanguages, ServiceProvider]],
|
|
51
62
|
[WorkflowEngine, [RuntimeDbContext, IWorkflowDefinitions, GraphCache, WorkflowEvents, WorkflowClock]],
|
|
52
63
|
[WorkflowEvents, [EventBus, ServiceProvider, "optional:Logger"]],
|
|
64
|
+
[WorkflowLanguages, ["Config:workflow"]],
|
|
53
65
|
[WorkflowRetention, [ServiceProvider, "Config:workflow"]],
|
|
54
|
-
[WorkflowRuntime, [RuntimeDbContext, WorkflowEngine, IWorkflowDefinitions, ServiceProvider]],
|
|
66
|
+
[WorkflowRuntime, [RuntimeDbContext, WorkflowEngine, IWorkflowDefinitions, ServiceProvider, WorkflowLanguages]],
|
|
55
67
|
[WorkflowTasks, [TasksDbContext, IWorkflowRuntime, WorkflowClock, WorkflowEvents]],
|
|
56
68
|
[WorkflowTools, [ServiceProvider]],
|
|
57
69
|
[WorkflowWorker, [ServiceProvider, "Config:workflow"]],
|