@nocobase/plugin-workflow 0.10.1-alpha.1 → 0.11.1-alpha.1
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/client.d.ts +2 -3
- package/client.js +1 -30
- package/lib/client/AddButton.js +13 -11
- package/lib/client/Branch.js +10 -8
- package/lib/client/CanvasContent.js +12 -10
- package/lib/client/ExecutionCanvas.js +37 -33
- package/lib/client/ExecutionPage.js +4 -9
- package/lib/client/WorkflowCanvas.js +18 -15
- package/lib/client/WorkflowPage.js +4 -9
- package/lib/client/WorkflowProvider.js +1 -40
- package/lib/client/components/CollectionBlockInitializer.js +3 -3
- package/lib/client/components/CollectionFieldset.d.ts +1 -1
- package/lib/client/components/CollectionFieldset.js +15 -16
- package/lib/client/components/Duration.js +5 -5
- package/lib/client/components/DynamicExpression.d.ts +3 -3
- package/lib/client/components/FieldsSelect.d.ts +1 -1
- package/lib/client/components/FieldsSelect.js +10 -7
- package/lib/client/components/NodeDescription.js +45 -31
- package/lib/client/components/RadioWithTooltip.js +13 -20
- package/lib/client/components/ValueBlock.js +14 -21
- package/lib/client/components/renderEngineReference.js +1 -8
- package/lib/client/index.d.ts +12 -4
- package/lib/client/index.js +78 -15
- package/lib/client/locale/zh-CN.d.ts +5 -1
- package/lib/client/locale/zh-CN.js +6 -2
- package/lib/client/nodes/aggregate.d.ts +8 -3
- package/lib/client/nodes/aggregate.js +5 -4
- package/lib/client/nodes/calculation.d.ts +6 -4
- package/lib/client/nodes/calculation.js +22 -28
- package/lib/client/nodes/condition.d.ts +2 -10
- package/lib/client/nodes/condition.js +19 -37
- package/lib/client/nodes/create.d.ts +5 -6
- package/lib/client/nodes/create.js +1 -3
- package/lib/client/nodes/destroy.d.ts +1 -1
- package/lib/client/nodes/index.d.ts +2 -3
- package/lib/client/nodes/index.js +95 -102
- package/lib/client/nodes/loop.d.ts +1 -1
- package/lib/client/nodes/loop.js +46 -54
- package/lib/client/nodes/manual/FormBlockInitializer.js +6 -5
- package/lib/client/nodes/manual/ModeConfig.js +23 -30
- package/lib/client/nodes/manual/SchemaConfig.d.ts +4 -5
- package/lib/client/nodes/manual/SchemaConfig.js +180 -25
- package/lib/client/nodes/manual/WorkflowTodo.js +95 -110
- package/lib/client/nodes/manual/WorkflowTodoBlockInitializer.d.ts +2 -5
- package/lib/client/nodes/manual/WorkflowTodoBlockInitializer.js +6 -5
- package/lib/client/nodes/manual/forms/create.js +8 -1
- package/lib/client/nodes/manual/forms/custom.js +22 -22
- package/lib/client/nodes/manual/forms/update.js +8 -1
- package/lib/client/nodes/manual/index.d.ts +6 -1
- package/lib/client/nodes/manual/index.js +5 -4
- package/lib/client/nodes/parallel.js +23 -20
- package/lib/client/nodes/query.d.ts +3 -5
- package/lib/client/nodes/query.js +1 -3
- package/lib/client/nodes/request.d.ts +2 -2
- package/lib/client/nodes/request.js +7 -7
- package/lib/client/nodes/sql.d.ts +26 -0
- package/lib/client/{triggers/schedule/DateFieldsSelect.js → nodes/sql.js} +37 -46
- package/lib/client/nodes/update.d.ts +2 -2
- package/lib/client/nodes/update.js +1 -1
- package/lib/client/schemas/collection.d.ts +3 -4
- package/lib/client/schemas/collection.js +11 -17
- package/lib/client/style.d.ts +18 -13
- package/lib/client/style.js +315 -292
- package/lib/client/triggers/collection.d.ts +13 -13
- package/lib/client/triggers/collection.js +5 -1
- package/lib/client/triggers/index.d.ts +3 -4
- package/lib/client/triggers/index.js +51 -53
- package/lib/client/triggers/schedule/EndsByField.js +11 -11
- package/lib/client/triggers/schedule/OnField.js +45 -33
- package/lib/client/triggers/schedule/RepeatField.js +4 -4
- package/lib/client/triggers/schedule/ScheduleConfig.js +24 -31
- package/lib/client/triggers/schedule/index.d.ts +1 -1
- package/lib/client/triggers/schedule/index.js +32 -20
- package/lib/client/variable.d.ts +31 -13
- package/lib/client/variable.js +44 -29
- package/lib/server/Plugin.d.ts +3 -6
- package/lib/server/Plugin.js +15 -12
- package/lib/server/Processor.d.ts +3 -5
- package/lib/server/Processor.js +2 -2
- package/lib/server/actions/nodes.js +7 -7
- package/lib/server/fields/expression-field.d.ts +1 -2
- package/lib/server/fields/expression-field.js +1 -8
- package/lib/server/functions/index.d.ts +2 -3
- package/lib/server/index.d.ts +1 -0
- package/lib/server/index.js +12 -0
- package/lib/server/instructions/aggregate.d.ts +1 -1
- package/lib/server/instructions/aggregate.js +5 -5
- package/lib/server/instructions/condition.d.ts +2 -1
- package/lib/server/instructions/create.d.ts +2 -2
- package/lib/server/instructions/create.js +13 -13
- package/lib/server/instructions/delay.d.ts +3 -3
- package/lib/server/instructions/delay.js +66 -64
- package/lib/server/instructions/destroy.d.ts +1 -1
- package/lib/server/instructions/index.d.ts +5 -5
- package/lib/server/instructions/index.js +1 -1
- package/lib/server/instructions/loop.d.ts +1 -2
- package/lib/server/instructions/manual/actions.js +19 -7
- package/lib/server/instructions/manual/forms/create.js +7 -1
- package/lib/server/instructions/manual/forms/index.d.ts +1 -1
- package/lib/server/instructions/manual/forms/update.js +7 -1
- package/lib/server/instructions/manual/index.d.ts +1 -1
- package/lib/server/instructions/parallel.d.ts +1 -2
- package/lib/server/instructions/query.d.ts +1 -1
- package/lib/server/instructions/query.js +8 -1
- package/lib/server/instructions/request.d.ts +3 -3
- package/lib/server/instructions/request.js +5 -2
- package/lib/server/instructions/sql.d.ts +12 -0
- package/lib/server/instructions/sql.js +34 -0
- package/lib/server/instructions/update.d.ts +1 -1
- package/lib/server/migrations/20230221071831-calculation-expression.js +1 -1
- package/lib/server/migrations/20230221121203-condition-calculation.js +1 -1
- package/lib/server/migrations/20230221162902-jsonb-to-json.js +7 -7
- package/lib/server/migrations/20230411034722-manual-multi-form.js +1 -8
- package/lib/server/migrations/20230710115902-manual-action-values.d.ts +4 -0
- package/lib/server/migrations/20230710115902-manual-action-values.js +97 -0
- package/lib/server/triggers/collection.d.ts +1 -1
- package/lib/server/triggers/collection.js +15 -13
- package/lib/server/triggers/index.d.ts +1 -1
- package/lib/server/triggers/schedule.d.ts +1 -1
- package/lib/server/triggers/schedule.js +18 -18
- package/lib/server/{models → types}/Execution.d.ts +2 -3
- package/lib/server/{models → types}/FlowNode.d.ts +1 -2
- package/lib/server/{models → types}/Job.d.ts +1 -2
- package/lib/server/{models → types}/Workflow.d.ts +1 -2
- package/lib/server/types/index.d.ts +4 -0
- package/lib/server/types/index.js +5 -0
- package/lib/server/utils.d.ts +2 -0
- package/lib/server/utils.js +21 -0
- package/package.json +39 -18
- package/server.d.ts +2 -3
- package/server.js +1 -30
- package/src/client/AddButton.tsx +111 -0
- package/src/client/Branch.tsx +37 -0
- package/src/client/CanvasContent.tsx +25 -0
- package/src/client/ExecutionCanvas.tsx +166 -0
- package/src/client/ExecutionLink.tsx +16 -0
- package/src/client/ExecutionPage.tsx +45 -0
- package/src/client/ExecutionResourceProvider.tsx +21 -0
- package/src/client/FlowContext.ts +7 -0
- package/src/client/WorkflowCanvas.tsx +221 -0
- package/src/client/WorkflowLink.tsx +16 -0
- package/src/client/WorkflowPage.tsx +52 -0
- package/src/client/WorkflowProvider.tsx +84 -0
- package/src/client/components/CollectionBlockInitializer.tsx +71 -0
- package/src/client/components/CollectionFieldset.tsx +160 -0
- package/src/client/components/Duration.tsx +45 -0
- package/src/client/components/DynamicExpression.tsx +53 -0
- package/src/client/components/FieldsSelect.tsx +32 -0
- package/src/client/components/FilterDynamicComponent.tsx +15 -0
- package/src/client/components/NodeDescription.tsx +51 -0
- package/src/client/components/NullRender.tsx +3 -0
- package/src/client/components/OpenDrawer.tsx +24 -0
- package/src/client/components/RadioWithTooltip.tsx +38 -0
- package/src/client/components/ValueBlock.tsx +67 -0
- package/src/client/components/renderEngineReference.tsx +30 -0
- package/src/client/constants.tsx +91 -0
- package/src/client/index.tsx +51 -0
- package/src/client/interfaces/expression.tsx +25 -0
- package/src/client/locale/en-US.ts +136 -0
- package/src/client/locale/es-ES.ts +129 -0
- package/src/client/locale/index.ts +18 -0
- package/src/client/locale/ja-JP.ts +90 -0
- package/src/client/locale/pt-BR.ts +136 -0
- package/src/client/locale/ru-RU.ts +90 -0
- package/src/client/locale/tr-TR.ts +90 -0
- package/src/client/locale/zh-CN.ts +248 -0
- package/src/client/nodes/aggregate.tsx +327 -0
- package/src/client/nodes/calculation.tsx +216 -0
- package/src/client/nodes/condition.tsx +463 -0
- package/src/client/nodes/create.tsx +85 -0
- package/src/client/nodes/delay.tsx +37 -0
- package/src/client/nodes/destroy.tsx +34 -0
- package/src/client/nodes/index.tsx +485 -0
- package/src/client/nodes/loop.tsx +144 -0
- package/src/client/nodes/manual/AssigneesSelect.tsx +33 -0
- package/src/client/nodes/manual/DetailsBlockProvider.tsx +80 -0
- package/src/client/nodes/manual/FormBlockInitializer.tsx +69 -0
- package/src/client/nodes/manual/FormBlockProvider.tsx +75 -0
- package/src/client/nodes/manual/ModeConfig.tsx +84 -0
- package/src/client/nodes/manual/SchemaConfig.tsx +509 -0
- package/src/client/nodes/manual/WorkflowTodo.tsx +607 -0
- package/src/client/nodes/manual/WorkflowTodoBlockInitializer.tsx +28 -0
- package/src/client/nodes/manual/forms/create.tsx +92 -0
- package/src/client/nodes/manual/forms/custom.tsx +392 -0
- package/src/client/nodes/manual/forms/update.tsx +134 -0
- package/src/client/nodes/manual/index.tsx +162 -0
- package/src/client/nodes/manual/utils.ts +28 -0
- package/src/client/nodes/parallel.tsx +138 -0
- package/src/client/nodes/query.tsx +88 -0
- package/src/client/nodes/request.tsx +185 -0
- package/src/client/nodes/sql.tsx +37 -0
- package/src/client/nodes/update.tsx +99 -0
- package/src/client/schemas/collection.ts +75 -0
- package/src/client/schemas/executions.tsx +169 -0
- package/src/client/schemas/workflows.ts +364 -0
- package/src/client/style.tsx +347 -0
- package/src/client/triggers/collection.tsx +190 -0
- package/src/client/triggers/index.tsx +311 -0
- package/src/client/triggers/schedule/EndsByField.tsx +40 -0
- package/src/client/triggers/schedule/OnField.tsx +64 -0
- package/src/client/triggers/schedule/RepeatField.tsx +116 -0
- package/src/client/triggers/schedule/ScheduleConfig.tsx +227 -0
- package/src/client/triggers/schedule/constants.ts +4 -0
- package/src/client/triggers/schedule/index.tsx +78 -0
- package/src/client/triggers/schedule/locale/Cron.zh-CN.ts +79 -0
- package/src/client/utils.ts +36 -0
- package/src/client/variable.tsx +318 -0
- package/src/index.ts +1 -0
- package/src/server/Plugin.ts +355 -0
- package/src/server/Processor.ts +354 -0
- package/src/server/__tests__/Plugin.test.ts +398 -0
- package/src/server/__tests__/Processor.test.ts +474 -0
- package/src/server/__tests__/actions/workflows.test.ts +419 -0
- package/src/server/__tests__/collections/categories.ts +27 -0
- package/src/server/__tests__/collections/comments.ts +24 -0
- package/src/server/__tests__/collections/posts.ts +42 -0
- package/src/server/__tests__/collections/replies.ts +9 -0
- package/src/server/__tests__/collections/tags.ts +15 -0
- package/src/server/__tests__/index.ts +89 -0
- package/src/server/__tests__/instructions/aggregate.test.ts +294 -0
- package/src/server/__tests__/instructions/calculation.test.ts +265 -0
- package/src/server/__tests__/instructions/condition.test.ts +335 -0
- package/src/server/__tests__/instructions/create.test.ts +129 -0
- package/src/server/__tests__/instructions/delay.test.ts +182 -0
- package/src/server/__tests__/instructions/destroy.test.ts +58 -0
- package/src/server/__tests__/instructions/loop.test.ts +331 -0
- package/src/server/__tests__/instructions/manual.test.ts +1173 -0
- package/src/server/__tests__/instructions/parallel.test.ts +445 -0
- package/src/server/__tests__/instructions/query.test.ts +359 -0
- package/src/server/__tests__/instructions/request.test.ts +247 -0
- package/src/server/__tests__/instructions/sql.test.ts +162 -0
- package/src/server/__tests__/instructions/update.test.ts +189 -0
- package/src/server/__tests__/triggers/collection.test.ts +333 -0
- package/src/server/__tests__/triggers/schedule.test.ts +369 -0
- package/src/server/actions/index.ts +25 -0
- package/src/server/actions/nodes.ts +214 -0
- package/src/server/actions/workflows.ts +178 -0
- package/src/server/collections/executions.ts +35 -0
- package/src/server/collections/flow_nodes.ts +54 -0
- package/src/server/collections/jobs.ts +31 -0
- package/src/server/collections/workflows.ts +88 -0
- package/src/server/constants.ts +26 -0
- package/src/server/fields/expression-field.ts +11 -0
- package/src/server/fields/index.ts +7 -0
- package/src/server/functions/index.ts +16 -0
- package/src/server/index.ts +6 -0
- package/src/server/instructions/aggregate.ts +42 -0
- package/src/server/instructions/calculation.ts +41 -0
- package/src/server/instructions/condition.ts +172 -0
- package/src/server/instructions/create.ts +39 -0
- package/src/server/instructions/delay.ts +105 -0
- package/src/server/instructions/destroy.ts +23 -0
- package/src/server/instructions/index.ts +64 -0
- package/src/server/instructions/loop.ts +99 -0
- package/src/server/instructions/manual/actions.ts +91 -0
- package/src/server/instructions/manual/collecions/jobs.ts +17 -0
- package/src/server/instructions/manual/collecions/users.ts +15 -0
- package/src/server/instructions/manual/collecions/users_jobs.ts +50 -0
- package/src/server/instructions/manual/forms/create.ts +23 -0
- package/src/server/instructions/manual/forms/index.ts +12 -0
- package/src/server/instructions/manual/forms/update.ts +23 -0
- package/src/server/instructions/manual/index.ts +184 -0
- package/src/server/instructions/parallel.ts +121 -0
- package/src/server/instructions/query.ts +42 -0
- package/src/server/instructions/request.ts +88 -0
- package/src/server/instructions/sql.ts +25 -0
- package/src/server/instructions/update.ts +24 -0
- package/src/server/migrations/20221129153547-calculation-variables.ts +64 -0
- package/src/server/migrations/20230221032941-change-request-body-type.ts +76 -0
- package/src/server/migrations/20230221071831-calculation-expression.ts +102 -0
- package/src/server/migrations/20230221121203-condition-calculation.ts +82 -0
- package/src/server/migrations/20230221162902-jsonb-to-json.ts +51 -0
- package/src/server/migrations/20230411034722-manual-multi-form.ts +282 -0
- package/src/server/migrations/20230612021134-manual-collection-block.ts +138 -0
- package/src/server/migrations/20230710115902-manual-action-values.ts +78 -0
- package/src/server/triggers/collection.ts +146 -0
- package/src/server/triggers/index.ts +22 -0
- package/src/server/triggers/schedule.ts +567 -0
- package/src/server/types/Execution.ts +26 -0
- package/src/server/types/FlowNode.ts +21 -0
- package/src/server/types/Job.ts +18 -0
- package/src/server/types/Workflow.ts +36 -0
- package/src/server/types/index.ts +4 -0
- package/src/server/utils.ts +17 -0
- package/lib/client/triggers/schedule/DateFieldsSelect.d.ts +0 -2
- /package/lib/server/{models → types}/Execution.js +0 -0
- /package/lib/server/{models → types}/FlowNode.js +0 -0
- /package/lib/server/{models → types}/Job.js +0 -0
- /package/lib/server/{models → types}/Workflow.js +0 -0
|
@@ -0,0 +1,567 @@
|
|
|
1
|
+
import { fn, literal, Op, where } from '@nocobase/database';
|
|
2
|
+
import parser from 'cron-parser';
|
|
3
|
+
import Plugin, { Trigger } from '..';
|
|
4
|
+
import type { WorkflowModel } from '../types';
|
|
5
|
+
|
|
6
|
+
export type ScheduleOnField =
|
|
7
|
+
| string
|
|
8
|
+
| {
|
|
9
|
+
field: string;
|
|
10
|
+
// in seconds
|
|
11
|
+
offset?: number;
|
|
12
|
+
unit?: 1000 | 60000 | 3600000 | 86400000;
|
|
13
|
+
};
|
|
14
|
+
export interface ScheduleTriggerConfig {
|
|
15
|
+
// trigger mode
|
|
16
|
+
mode: number;
|
|
17
|
+
// how to repeat
|
|
18
|
+
repeat?: string | number | null;
|
|
19
|
+
// limit of repeat times
|
|
20
|
+
limit?: number;
|
|
21
|
+
|
|
22
|
+
startsOn?: ScheduleOnField;
|
|
23
|
+
endsOn?: ScheduleOnField;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const SCHEDULE_MODE = {
|
|
27
|
+
CONSTANT: 0,
|
|
28
|
+
COLLECTION_FIELD: 1,
|
|
29
|
+
} as const;
|
|
30
|
+
|
|
31
|
+
interface ScheduleMode {
|
|
32
|
+
on?(this: ScheduleTrigger, workflow: WorkflowModel): void;
|
|
33
|
+
off?(this: ScheduleTrigger, workflow: WorkflowModel): void;
|
|
34
|
+
shouldCache(this: ScheduleTrigger, workflow: WorkflowModel, now: Date): Promise<boolean> | boolean;
|
|
35
|
+
trigger(this: ScheduleTrigger, workflow: WorkflowModel, now: Date): any;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const ScheduleModes = new Map<number, ScheduleMode>();
|
|
39
|
+
|
|
40
|
+
function parseDateWithoutMs(date: string) {
|
|
41
|
+
return Math.floor(Date.parse(date) / 1000) * 1000;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
ScheduleModes.set(SCHEDULE_MODE.CONSTANT, {
|
|
45
|
+
shouldCache(workflow, now) {
|
|
46
|
+
const { startsOn, endsOn, repeat } = workflow.config;
|
|
47
|
+
const timestamp = now.getTime();
|
|
48
|
+
|
|
49
|
+
// NOTE: align to second start
|
|
50
|
+
const startTime = parseDateWithoutMs(startsOn);
|
|
51
|
+
if (!startTime || startTime > timestamp + this.cacheCycle) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (repeat) {
|
|
56
|
+
if (typeof repeat === 'number') {
|
|
57
|
+
const next = timestamp - ((timestamp - startTime) % repeat) + repeat;
|
|
58
|
+
if (next <= timestamp || next > timestamp + this.cacheCycle) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (endsOn) {
|
|
64
|
+
const endTime = parseDateWithoutMs(endsOn);
|
|
65
|
+
if (!endTime || endTime <= timestamp) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
} else {
|
|
70
|
+
if (startTime <= timestamp) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return true;
|
|
76
|
+
},
|
|
77
|
+
trigger(workflow, now) {
|
|
78
|
+
const { startsOn, endsOn, repeat } = workflow.config;
|
|
79
|
+
const timestamp = now.getTime();
|
|
80
|
+
// NOTE: align to second start
|
|
81
|
+
const startTime = parseDateWithoutMs(startsOn);
|
|
82
|
+
if (!startTime || startTime > timestamp) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (repeat) {
|
|
87
|
+
if (typeof repeat === 'number') {
|
|
88
|
+
if (Math.round(timestamp - startTime) % repeat) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (endsOn) {
|
|
94
|
+
const endTime = parseDateWithoutMs(endsOn);
|
|
95
|
+
if (!endTime || endTime < timestamp) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
if (startTime !== timestamp) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return this.plugin.trigger(workflow, { date: now });
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
function getOnTimestampWithOffset(on, now: Date) {
|
|
110
|
+
switch (typeof on) {
|
|
111
|
+
case 'string':
|
|
112
|
+
return parseDateWithoutMs(on);
|
|
113
|
+
case 'object': {
|
|
114
|
+
const { field, offset = 0, unit = 1000 } = on;
|
|
115
|
+
if (!field) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
const timestamp = now.getTime();
|
|
119
|
+
// onDate + offset > now
|
|
120
|
+
// onDate > now - offset
|
|
121
|
+
return timestamp - offset * unit;
|
|
122
|
+
}
|
|
123
|
+
default:
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function getDataOptionTime(data, on, dir = 1) {
|
|
129
|
+
if (!on) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
switch (typeof on) {
|
|
133
|
+
case 'string': {
|
|
134
|
+
const time = parseDateWithoutMs(on);
|
|
135
|
+
return time ? time : null;
|
|
136
|
+
}
|
|
137
|
+
case 'object': {
|
|
138
|
+
const { field, offset = 0, unit = 1000 } = on;
|
|
139
|
+
return data.get(field) ? data.get(field).getTime() - offset * unit * dir : null;
|
|
140
|
+
}
|
|
141
|
+
default:
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function getHookId(workflow, type: string) {
|
|
147
|
+
return `${type}#${workflow.id}`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const DialectTimestampFnMap: { [key: string]: (col: string) => string } = {
|
|
151
|
+
postgres(col) {
|
|
152
|
+
return `CAST(FLOOR(extract(epoch from "${col}")) AS INTEGER)`;
|
|
153
|
+
},
|
|
154
|
+
mysql(col) {
|
|
155
|
+
return `CAST(FLOOR(UNIX_TIMESTAMP(\`${col}\`)) AS SIGNED INTEGER)`;
|
|
156
|
+
},
|
|
157
|
+
sqlite(col) {
|
|
158
|
+
return `CAST(FLOOR(unixepoch(${col})) AS INTEGER)`;
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
DialectTimestampFnMap.mariadb = DialectTimestampFnMap.mysql;
|
|
162
|
+
|
|
163
|
+
ScheduleModes.set(SCHEDULE_MODE.COLLECTION_FIELD, {
|
|
164
|
+
on(workflow) {
|
|
165
|
+
const { collection, startsOn, endsOn, repeat } = workflow.config;
|
|
166
|
+
const event = `${collection}.afterSave`;
|
|
167
|
+
const name = getHookId(workflow, event);
|
|
168
|
+
if (this.events.has(name)) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
// NOTE: toggle cache depends on new date
|
|
172
|
+
const listener = async (data, options) => {
|
|
173
|
+
const now = new Date();
|
|
174
|
+
now.setMilliseconds(0);
|
|
175
|
+
const timestamp = now.getTime();
|
|
176
|
+
const startTime = getDataOptionTime(data, startsOn);
|
|
177
|
+
const endTime = getDataOptionTime(data, endsOn, -1);
|
|
178
|
+
if (!startTime) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (startTime && startTime > timestamp + this.cacheCycle) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (endTime && endTime <= timestamp) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (!matchNext.call(this, workflow, now)) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (
|
|
191
|
+
typeof repeat === 'number' &&
|
|
192
|
+
repeat > this.cacheCycle &&
|
|
193
|
+
(timestamp - startTime) % repeat > this.cacheCycle
|
|
194
|
+
) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
this.setCache(workflow);
|
|
199
|
+
};
|
|
200
|
+
this.events.set(name, listener);
|
|
201
|
+
this.plugin.app.db.on(event, listener);
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
off(workflow) {
|
|
205
|
+
const { collection } = workflow.config;
|
|
206
|
+
const event = `${collection}.afterSave`;
|
|
207
|
+
const name = getHookId(workflow, event);
|
|
208
|
+
if (this.events.has(name)) {
|
|
209
|
+
const listener = this.events.get(name);
|
|
210
|
+
this.events.delete(name);
|
|
211
|
+
this.plugin.app.db.off(event, listener);
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
|
|
215
|
+
async shouldCache(workflow, now) {
|
|
216
|
+
const { db } = this.plugin.app;
|
|
217
|
+
const { startsOn, endsOn, repeat, collection } = workflow.config;
|
|
218
|
+
const timestamp = now.getTime();
|
|
219
|
+
|
|
220
|
+
const startTimestamp = getOnTimestampWithOffset(startsOn, now);
|
|
221
|
+
if (!startTimestamp) {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const conditions: any[] = [
|
|
226
|
+
{
|
|
227
|
+
[startsOn.field]: {
|
|
228
|
+
[Op.lt]: new Date(startTimestamp + this.cacheCycle),
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
];
|
|
232
|
+
|
|
233
|
+
// when repeat is number, means repeat after startsOn
|
|
234
|
+
// (now - startsOn) % repeat <= cacheCycle
|
|
235
|
+
if (repeat) {
|
|
236
|
+
const tsFn = DialectTimestampFnMap[db.options.dialect!];
|
|
237
|
+
if (typeof repeat === 'number' && repeat > this.cacheCycle && tsFn) {
|
|
238
|
+
conditions.push(
|
|
239
|
+
where(
|
|
240
|
+
fn('MOD', literal(`${Math.round(timestamp / 1000)} - ${tsFn(startsOn.field)}`), Math.round(repeat / 1000)),
|
|
241
|
+
{ [Op.lt]: Math.round(this.cacheCycle / 1000) },
|
|
242
|
+
),
|
|
243
|
+
);
|
|
244
|
+
// conditions.push(literal(`mod(${timestamp} - ${tsFn(startsOn.field)} * 1000, ${repeat}) < ${this.cacheCycle}`));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (endsOn) {
|
|
248
|
+
const endTimestamp = getOnTimestampWithOffset(endsOn, now);
|
|
249
|
+
if (!endTimestamp) {
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
if (typeof endsOn === 'string') {
|
|
253
|
+
if (endTimestamp <= timestamp) {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
} else {
|
|
257
|
+
conditions.push({
|
|
258
|
+
[endsOn.field]: {
|
|
259
|
+
[Op.gte]: new Date(endTimestamp + this.interval),
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
} else {
|
|
265
|
+
conditions.push({
|
|
266
|
+
[startsOn.field]: {
|
|
267
|
+
[Op.gte]: new Date(startTimestamp),
|
|
268
|
+
},
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const { model } = db.getCollection(collection);
|
|
273
|
+
const count = await model.count({
|
|
274
|
+
where: { [Op.and]: conditions },
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
return Boolean(count);
|
|
278
|
+
},
|
|
279
|
+
|
|
280
|
+
async trigger(workflow, now: Date) {
|
|
281
|
+
const { startsOn, repeat, endsOn, collection, appends } = workflow.config;
|
|
282
|
+
const timestamp = now.getTime();
|
|
283
|
+
|
|
284
|
+
const startTimestamp = getOnTimestampWithOffset(startsOn, now);
|
|
285
|
+
if (!startTimestamp) {
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const conditions: any[] = [
|
|
290
|
+
{
|
|
291
|
+
[startsOn.field]: {
|
|
292
|
+
[Op.lt]: new Date(startTimestamp + this.interval),
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
];
|
|
296
|
+
|
|
297
|
+
if (repeat) {
|
|
298
|
+
// startsOn not after now
|
|
299
|
+
conditions.push({
|
|
300
|
+
[startsOn.field]: {
|
|
301
|
+
[Op.lt]: new Date(startTimestamp),
|
|
302
|
+
},
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
const tsFn = DialectTimestampFnMap[this.plugin.app.db.options.dialect!];
|
|
306
|
+
if (typeof repeat === 'number' && tsFn) {
|
|
307
|
+
conditions.push(
|
|
308
|
+
where(
|
|
309
|
+
fn('MOD', literal(`${Math.round(timestamp / 1000)} - ${tsFn(startsOn.field)}`), Math.round(repeat / 1000)),
|
|
310
|
+
{ [Op.eq]: 0 },
|
|
311
|
+
),
|
|
312
|
+
);
|
|
313
|
+
// conditions.push(literal(`MOD(CAST(${timestamp} AS BIGINT) - CAST((FLOOR(${tsFn(startsOn.field)}) AS BIGINT) * 1000), ${repeat}) = 0`));
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (endsOn) {
|
|
317
|
+
const endTimestamp = getOnTimestampWithOffset(endsOn, now);
|
|
318
|
+
if (!endTimestamp) {
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (typeof endsOn === 'string') {
|
|
323
|
+
if (endTimestamp <= timestamp) {
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
326
|
+
} else {
|
|
327
|
+
conditions.push({
|
|
328
|
+
[endsOn.field]: {
|
|
329
|
+
[Op.gte]: new Date(endTimestamp + this.interval),
|
|
330
|
+
},
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
} else {
|
|
335
|
+
// startsOn exactly equal to now in 1s
|
|
336
|
+
conditions.push({
|
|
337
|
+
[startsOn.field]: {
|
|
338
|
+
[Op.gte]: new Date(startTimestamp),
|
|
339
|
+
},
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const repo = this.plugin.app.db.getRepository(collection);
|
|
344
|
+
const instances = await repo.find({
|
|
345
|
+
filter: {
|
|
346
|
+
$and: conditions,
|
|
347
|
+
},
|
|
348
|
+
appends,
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
instances.forEach((item) => {
|
|
352
|
+
this.plugin.trigger(workflow, {
|
|
353
|
+
date: now,
|
|
354
|
+
data: item.toJSON(),
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
},
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
function matchNext(this: ScheduleTrigger, workflow, now: Date, range: number = this.cacheCycle): boolean {
|
|
361
|
+
const { repeat } = workflow.config;
|
|
362
|
+
// no repeat means no need to rerun
|
|
363
|
+
// but if in current cycle, should be put in cache
|
|
364
|
+
// no repeat but in current cycle means startsOn has been configured
|
|
365
|
+
// so we need to more info to determine if necessary config items
|
|
366
|
+
if (typeof repeat !== 'string') {
|
|
367
|
+
return true;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const currentDate = new Date(now);
|
|
371
|
+
currentDate.setMilliseconds(-1);
|
|
372
|
+
const timestamp = now.getTime();
|
|
373
|
+
const interval = parser.parseExpression(repeat, { currentDate });
|
|
374
|
+
const next = interval.next();
|
|
375
|
+
|
|
376
|
+
// NOTE: cache all workflows will be matched in current cycle
|
|
377
|
+
if (next.getTime() - timestamp <= range) {
|
|
378
|
+
return true;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return false;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
export default class ScheduleTrigger extends Trigger {
|
|
385
|
+
static CacheRules = [
|
|
386
|
+
({ config, allExecuted }) => (config.limit ? allExecuted < config.limit : true) && config.startsOn,
|
|
387
|
+
matchNext,
|
|
388
|
+
function (workflow, now) {
|
|
389
|
+
const { mode } = workflow.config;
|
|
390
|
+
const modeHandlers = ScheduleModes.get(mode);
|
|
391
|
+
if (!modeHandlers) {
|
|
392
|
+
return false;
|
|
393
|
+
}
|
|
394
|
+
return modeHandlers.shouldCache.call(this, workflow, now);
|
|
395
|
+
},
|
|
396
|
+
];
|
|
397
|
+
|
|
398
|
+
static TriggerRules = [
|
|
399
|
+
({ config, allExecuted }) => (config.limit ? allExecuted < config.limit : true) && config.startsOn,
|
|
400
|
+
function (workflow, now) {
|
|
401
|
+
return matchNext.call(this, workflow, now, 0);
|
|
402
|
+
},
|
|
403
|
+
];
|
|
404
|
+
|
|
405
|
+
events = new Map();
|
|
406
|
+
|
|
407
|
+
private timer: NodeJS.Timeout | null = null;
|
|
408
|
+
|
|
409
|
+
private cache: Map<number | string, any> = new Map();
|
|
410
|
+
|
|
411
|
+
// running interval, default to 1s
|
|
412
|
+
interval = 1_000;
|
|
413
|
+
// caching workflows in range, default to 1min
|
|
414
|
+
cacheCycle = 60_000;
|
|
415
|
+
|
|
416
|
+
constructor(plugin: Plugin) {
|
|
417
|
+
super(plugin);
|
|
418
|
+
|
|
419
|
+
plugin.app.on('beforeStop', () => {
|
|
420
|
+
if (this.timer) {
|
|
421
|
+
clearInterval(this.timer);
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
init() {
|
|
427
|
+
if (this.timer) {
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const now = new Date();
|
|
432
|
+
|
|
433
|
+
// NOTE: assign to this.timer to avoid duplicated initialization
|
|
434
|
+
this.timer = setTimeout(
|
|
435
|
+
this.run,
|
|
436
|
+
// NOTE:
|
|
437
|
+
// try to align to system time on each second starts,
|
|
438
|
+
// after at least 1 second initialized for anything to get ready.
|
|
439
|
+
// so jobs in 2 seconds will be missed at first start.
|
|
440
|
+
1_000 - now.getMilliseconds(),
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
run = () => {
|
|
445
|
+
const now = new Date();
|
|
446
|
+
// 1001 to avoid 999
|
|
447
|
+
const nextInterval = 1_001 - now.getMilliseconds();
|
|
448
|
+
now.setMilliseconds(0);
|
|
449
|
+
|
|
450
|
+
// NOTE: trigger `onTick` for high interval jobs which are cached in last 1 min
|
|
451
|
+
this.onTick(now);
|
|
452
|
+
|
|
453
|
+
// NOTE: reload when second match cache cycle
|
|
454
|
+
if (!(now.getTime() % this.cacheCycle)) {
|
|
455
|
+
this.reload();
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
this.timer = setTimeout(this.run, nextInterval);
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
async onTick(now) {
|
|
462
|
+
// NOTE: trigger workflows in sequence when sqlite due to only one transaction
|
|
463
|
+
const isSqlite = this.plugin.app.db.options.dialect === 'sqlite';
|
|
464
|
+
|
|
465
|
+
return Array.from(this.cache.values()).reduce(
|
|
466
|
+
(prev, workflow) => {
|
|
467
|
+
if (!this.shouldTrigger(workflow, now)) {
|
|
468
|
+
return prev;
|
|
469
|
+
}
|
|
470
|
+
if (isSqlite) {
|
|
471
|
+
return prev.then(() => this.trigger(workflow, now));
|
|
472
|
+
}
|
|
473
|
+
this.trigger(workflow, now);
|
|
474
|
+
return null;
|
|
475
|
+
},
|
|
476
|
+
isSqlite ? Promise.resolve() : null,
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
async reload() {
|
|
481
|
+
const WorkflowModel = this.plugin.app.db.getCollection('workflows').model;
|
|
482
|
+
const workflows = await WorkflowModel.findAll({
|
|
483
|
+
where: { enabled: true, type: 'schedule' },
|
|
484
|
+
include: [
|
|
485
|
+
{
|
|
486
|
+
association: 'executions',
|
|
487
|
+
attributes: ['id', 'createdAt'],
|
|
488
|
+
separate: true,
|
|
489
|
+
limit: 1,
|
|
490
|
+
order: [['createdAt', 'DESC']],
|
|
491
|
+
},
|
|
492
|
+
],
|
|
493
|
+
group: ['id'],
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
// NOTE: clear cached jobs in last cycle
|
|
497
|
+
this.cache = new Map();
|
|
498
|
+
|
|
499
|
+
this.inspect(workflows);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
inspect(workflows) {
|
|
503
|
+
const now = new Date();
|
|
504
|
+
now.setMilliseconds(0);
|
|
505
|
+
|
|
506
|
+
workflows.forEach(async (workflow) => {
|
|
507
|
+
const should = await this.shouldCache(workflow, now);
|
|
508
|
+
|
|
509
|
+
if (should) {
|
|
510
|
+
this.plugin.app.logger.info('caching scheduled workflow will run in next minute:', workflow.id);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
this.setCache(workflow, !should);
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
setCache(workflow, out = false) {
|
|
518
|
+
out ? this.cache.delete(workflow.id) : this.cache.set(workflow.id, workflow);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
async shouldCache(workflow, now) {
|
|
522
|
+
for await (const rule of (<typeof ScheduleTrigger>this.constructor).CacheRules) {
|
|
523
|
+
if (!(await rule.call(this, workflow, now))) {
|
|
524
|
+
return false;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
return true;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
shouldTrigger(workflow, now): boolean {
|
|
531
|
+
for (const rule of (<typeof ScheduleTrigger>this.constructor).TriggerRules) {
|
|
532
|
+
if (!rule.call(this, workflow, now)) {
|
|
533
|
+
return false;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
return true;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
async trigger(workflow, date: Date) {
|
|
540
|
+
const { mode } = workflow.config;
|
|
541
|
+
const modeHandlers = ScheduleModes.get(mode);
|
|
542
|
+
if (!modeHandlers) {
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
return modeHandlers.trigger.call(this, workflow, date);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
on(workflow) {
|
|
549
|
+
// NOTE: lazy initialization
|
|
550
|
+
this.init();
|
|
551
|
+
|
|
552
|
+
const { mode } = workflow.config;
|
|
553
|
+
const modeHandlers = ScheduleModes.get(mode);
|
|
554
|
+
if (modeHandlers && modeHandlers.on) {
|
|
555
|
+
modeHandlers.on.call(this, workflow);
|
|
556
|
+
}
|
|
557
|
+
this.inspect([workflow]);
|
|
558
|
+
}
|
|
559
|
+
off(workflow) {
|
|
560
|
+
const { mode } = workflow.config;
|
|
561
|
+
const modeHandlers = ScheduleModes.get(mode);
|
|
562
|
+
if (modeHandlers && modeHandlers.off) {
|
|
563
|
+
modeHandlers.off.call(this, workflow);
|
|
564
|
+
}
|
|
565
|
+
this.cache.delete(workflow.id);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { BelongsToGetAssociationMixin, Database, HasManyGetAssociationsMixin, Model } from '@nocobase/database';
|
|
2
|
+
import JobModel from './Job';
|
|
3
|
+
import WorkflowModel from './Workflow';
|
|
4
|
+
|
|
5
|
+
export default class ExecutionModel extends Model {
|
|
6
|
+
declare static readonly database: Database;
|
|
7
|
+
|
|
8
|
+
declare id: number;
|
|
9
|
+
declare title: string;
|
|
10
|
+
declare context: any;
|
|
11
|
+
declare status: number;
|
|
12
|
+
// NOTE: this duplicated column is for transaction in preparing cycle from workflow
|
|
13
|
+
declare useTransaction: boolean;
|
|
14
|
+
declare transaction: string;
|
|
15
|
+
|
|
16
|
+
declare createdAt: Date;
|
|
17
|
+
declare updatedAt: Date;
|
|
18
|
+
|
|
19
|
+
declare key: string;
|
|
20
|
+
|
|
21
|
+
declare workflow?: WorkflowModel;
|
|
22
|
+
declare getWorkflow: BelongsToGetAssociationMixin<WorkflowModel>;
|
|
23
|
+
|
|
24
|
+
declare jobs?: JobModel[];
|
|
25
|
+
declare getJobs: HasManyGetAssociationsMixin<JobModel>;
|
|
26
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { BelongsToGetAssociationMixin, Database, Model } from '@nocobase/database';
|
|
2
|
+
import WorkflowModel from './Workflow';
|
|
3
|
+
|
|
4
|
+
export default class FlowNodeModel extends Model {
|
|
5
|
+
declare static readonly database: Database;
|
|
6
|
+
|
|
7
|
+
declare id: number;
|
|
8
|
+
declare title: string;
|
|
9
|
+
declare branchIndex: null | number;
|
|
10
|
+
declare type: string;
|
|
11
|
+
declare config: any;
|
|
12
|
+
|
|
13
|
+
declare createdAt: Date;
|
|
14
|
+
declare updatedAt: Date;
|
|
15
|
+
|
|
16
|
+
declare upstream: FlowNodeModel;
|
|
17
|
+
declare downstream: FlowNodeModel;
|
|
18
|
+
|
|
19
|
+
declare workflow?: WorkflowModel;
|
|
20
|
+
declare getWorkflow: BelongsToGetAssociationMixin<WorkflowModel>;
|
|
21
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { BelongsToGetAssociationMixin, Model } from '@nocobase/database';
|
|
2
|
+
import FlowNodeModel from './FlowNode';
|
|
3
|
+
|
|
4
|
+
export default class JobModel extends Model {
|
|
5
|
+
declare id: number;
|
|
6
|
+
declare status: number;
|
|
7
|
+
declare result: any;
|
|
8
|
+
|
|
9
|
+
declare createdAt: Date;
|
|
10
|
+
declare updatedAt: Date;
|
|
11
|
+
|
|
12
|
+
declare upstreamId: number;
|
|
13
|
+
declare upstream: JobModel;
|
|
14
|
+
|
|
15
|
+
declare nodeId: number;
|
|
16
|
+
declare node?: FlowNodeModel;
|
|
17
|
+
declare getNode: BelongsToGetAssociationMixin<FlowNodeModel>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Database,
|
|
3
|
+
HasManyCountAssociationsMixin,
|
|
4
|
+
HasManyCreateAssociationMixin,
|
|
5
|
+
HasManyGetAssociationsMixin,
|
|
6
|
+
Model,
|
|
7
|
+
} from '@nocobase/database';
|
|
8
|
+
import ExecutionModel from './Execution';
|
|
9
|
+
import FlowNodeModel from './FlowNode';
|
|
10
|
+
|
|
11
|
+
export default class WorkflowModel extends Model {
|
|
12
|
+
declare static database: Database;
|
|
13
|
+
|
|
14
|
+
declare id: number;
|
|
15
|
+
declare key: string;
|
|
16
|
+
declare title: string;
|
|
17
|
+
declare enabled: boolean;
|
|
18
|
+
declare current: boolean;
|
|
19
|
+
declare description?: string;
|
|
20
|
+
declare type: string;
|
|
21
|
+
declare config: any;
|
|
22
|
+
declare useTransaction: boolean;
|
|
23
|
+
declare executed: number;
|
|
24
|
+
|
|
25
|
+
declare createdAt: Date;
|
|
26
|
+
declare updatedAt: Date;
|
|
27
|
+
|
|
28
|
+
declare nodes: FlowNodeModel[];
|
|
29
|
+
declare getNodes: HasManyGetAssociationsMixin<FlowNodeModel>;
|
|
30
|
+
declare createNode: HasManyCreateAssociationMixin<FlowNodeModel>;
|
|
31
|
+
|
|
32
|
+
declare executions: ExecutionModel[];
|
|
33
|
+
declare countExecutions: HasManyCountAssociationsMixin;
|
|
34
|
+
declare getExecutions: HasManyGetAssociationsMixin<ExecutionModel>;
|
|
35
|
+
declare createExecution: HasManyCreateAssociationMixin<ExecutionModel>;
|
|
36
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Model } from '@nocobase/database';
|
|
2
|
+
|
|
3
|
+
export function toJSON(data: Model | Model[]): object {
|
|
4
|
+
if (typeof data !== 'object' || !data) {
|
|
5
|
+
return data;
|
|
6
|
+
}
|
|
7
|
+
if (Array.isArray(data)) {
|
|
8
|
+
return data.map(toJSON);
|
|
9
|
+
}
|
|
10
|
+
const result = data.get();
|
|
11
|
+
Object.keys((<typeof Model>data.constructor).associations).forEach((key) => {
|
|
12
|
+
if (result[key] != null) {
|
|
13
|
+
result[key] = toJSON(result[key]);
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
return result;
|
|
17
|
+
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|