@nocobase/flow-engine 2.1.0-alpha.10 → 2.1.0-alpha.12
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/lib/FlowDefinition.d.ts +4 -0
- package/lib/FlowSchemaRegistry.d.ts +154 -0
- package/lib/FlowSchemaRegistry.js +1427 -0
- package/lib/components/subModel/AddSubModelButton.js +11 -0
- package/lib/flow-schema-registry/fieldBinding.d.ts +32 -0
- package/lib/flow-schema-registry/fieldBinding.js +165 -0
- package/lib/flow-schema-registry/modelPatches.d.ts +16 -0
- package/lib/flow-schema-registry/modelPatches.js +235 -0
- package/lib/flow-schema-registry/schemaInference.d.ts +17 -0
- package/lib/flow-schema-registry/schemaInference.js +207 -0
- package/lib/flow-schema-registry/utils.d.ts +25 -0
- package/lib/flow-schema-registry/utils.js +293 -0
- package/lib/flowEngine.js +4 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.js +3 -1
- package/lib/models/DisplayItemModel.d.ts +1 -1
- package/lib/models/EditableItemModel.d.ts +1 -1
- package/lib/models/FilterableItemModel.d.ts +1 -1
- package/lib/runjs-context/setup.js +1 -0
- package/lib/server.d.ts +10 -0
- package/lib/server.js +32 -0
- package/lib/types.d.ts +233 -0
- package/package.json +4 -4
- package/server.d.ts +1 -0
- package/server.js +1 -0
- package/src/FlowSchemaRegistry.ts +1799 -0
- package/src/__tests__/FlowSchemaRegistry.test.ts +1951 -0
- package/src/__tests__/flow-engine.test.ts +48 -0
- package/src/__tests__/flowEngine.modelLoaders.test.ts +5 -1
- package/src/__tests__/flowEngine.saveModel.test.ts +4 -0
- package/src/__tests__/runjsContext.test.ts +3 -0
- package/src/__tests__/runjsContextRuntime.test.ts +2 -0
- package/src/components/subModel/AddSubModelButton.tsx +15 -1
- package/src/components/subModel/__tests__/AddSubModelButton.test.tsx +1 -0
- package/src/flow-schema-registry/fieldBinding.ts +171 -0
- package/src/flow-schema-registry/modelPatches.ts +260 -0
- package/src/flow-schema-registry/schemaInference.ts +210 -0
- package/src/flow-schema-registry/utils.ts +268 -0
- package/src/flowEngine.ts +7 -1
- package/src/index.ts +1 -0
- package/src/models/DisplayItemModel.tsx +1 -1
- package/src/models/EditableItemModel.tsx +1 -1
- package/src/models/FilterableItemModel.tsx +1 -1
- package/src/runjs-context/setup.ts +1 -0
- package/src/server.ts +11 -0
- package/src/types.ts +273 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import _ from 'lodash';
|
|
11
|
+
import type { ISchema } from '@formily/json-schema';
|
|
12
|
+
import type { FlowDynamicHint, FlowJsonSchema, FlowSchemaCoverage } from '../types';
|
|
13
|
+
import { createFlowHint } from './utils';
|
|
14
|
+
|
|
15
|
+
export type StepSchemaResolution = {
|
|
16
|
+
schema?: FlowJsonSchema;
|
|
17
|
+
hints: FlowDynamicHint[];
|
|
18
|
+
coverage: FlowSchemaCoverage['status'];
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type UiSchemaLike =
|
|
22
|
+
| Record<string, ISchema>
|
|
23
|
+
| ((...args: any[]) => Record<string, ISchema> | Promise<Record<string, ISchema>>)
|
|
24
|
+
| undefined;
|
|
25
|
+
|
|
26
|
+
function inferSchemaFromUiSchemaValue(
|
|
27
|
+
name: string,
|
|
28
|
+
uiSchema: ISchema,
|
|
29
|
+
path: string,
|
|
30
|
+
hints: FlowDynamicHint[],
|
|
31
|
+
): FlowJsonSchema {
|
|
32
|
+
if (!uiSchema || typeof uiSchema !== 'object' || Array.isArray(uiSchema)) {
|
|
33
|
+
return { type: 'object', additionalProperties: true };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const xComponent = (uiSchema as any)['x-component'];
|
|
37
|
+
if (typeof xComponent === 'function') {
|
|
38
|
+
hints.push(
|
|
39
|
+
createFlowHint(
|
|
40
|
+
{
|
|
41
|
+
kind: 'custom-component',
|
|
42
|
+
path,
|
|
43
|
+
message: `${name} uses a custom component and needs manual schema review.`,
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
unresolvedReason: 'function-x-component',
|
|
47
|
+
recommendedFallback: { type: 'object', additionalProperties: true },
|
|
48
|
+
},
|
|
49
|
+
),
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const reactions = (uiSchema as any)['x-reactions'];
|
|
54
|
+
if (
|
|
55
|
+
reactions &&
|
|
56
|
+
((Array.isArray(reactions) && reactions.some((item) => typeof item === 'function')) ||
|
|
57
|
+
typeof reactions === 'function')
|
|
58
|
+
) {
|
|
59
|
+
hints.push(
|
|
60
|
+
createFlowHint(
|
|
61
|
+
{
|
|
62
|
+
kind: 'x-reactions',
|
|
63
|
+
path,
|
|
64
|
+
message: `${name} contains function-based x-reactions and only static schema is generated.`,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
unresolvedReason: 'function-x-reactions',
|
|
68
|
+
},
|
|
69
|
+
),
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const schema: FlowJsonSchema = {};
|
|
74
|
+
const type = (uiSchema as any).type;
|
|
75
|
+
if (type) {
|
|
76
|
+
schema.type = type;
|
|
77
|
+
}
|
|
78
|
+
if ((uiSchema as any).description) {
|
|
79
|
+
schema.description = (uiSchema as any).description;
|
|
80
|
+
}
|
|
81
|
+
if ((uiSchema as any).default !== undefined) {
|
|
82
|
+
schema.default = _.cloneDeep((uiSchema as any).default);
|
|
83
|
+
}
|
|
84
|
+
if ((uiSchema as any).enum) {
|
|
85
|
+
if (
|
|
86
|
+
Array.isArray((uiSchema as any).enum) &&
|
|
87
|
+
(uiSchema as any).enum.every((item) => _.isPlainObject(item) && 'value' in item)
|
|
88
|
+
) {
|
|
89
|
+
schema.enum = (uiSchema as any).enum.map((item) => item.value);
|
|
90
|
+
} else {
|
|
91
|
+
schema.enum = _.cloneDeep((uiSchema as any).enum);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if ((uiSchema as any).const !== undefined) {
|
|
95
|
+
schema.const = _.cloneDeep((uiSchema as any).const);
|
|
96
|
+
}
|
|
97
|
+
if ((uiSchema as any).required === true) {
|
|
98
|
+
schema.__required = true;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const validator = (uiSchema as any)['x-validator'];
|
|
102
|
+
if (_.isPlainObject(validator)) {
|
|
103
|
+
if (validator.minimum !== undefined) schema.minimum = validator.minimum;
|
|
104
|
+
if (validator.maximum !== undefined) schema.maximum = validator.maximum;
|
|
105
|
+
if (validator.minLength !== undefined) schema.minLength = validator.minLength;
|
|
106
|
+
if (validator.maxLength !== undefined) schema.maxLength = validator.maxLength;
|
|
107
|
+
if (validator.pattern !== undefined) schema.pattern = validator.pattern;
|
|
108
|
+
} else if (Array.isArray(validator)) {
|
|
109
|
+
for (const item of validator) {
|
|
110
|
+
if (_.isPlainObject(item)) {
|
|
111
|
+
Object.assign(schema, _.pick(item, ['minimum', 'maximum', 'minLength', 'maxLength', 'pattern']));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
} else if (typeof validator === 'string') {
|
|
115
|
+
if (validator === 'integer') schema.type = 'integer';
|
|
116
|
+
if (validator === 'email') schema.format = 'email';
|
|
117
|
+
if (validator === 'url') schema.format = 'uri';
|
|
118
|
+
if (validator === 'uid') schema.pattern = '^[A-Za-z0-9_-]+$';
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if ((uiSchema as any).properties && _.isPlainObject((uiSchema as any).properties)) {
|
|
122
|
+
schema.type = schema.type || 'object';
|
|
123
|
+
schema.properties = {};
|
|
124
|
+
const required: string[] = [];
|
|
125
|
+
for (const [propName, propValue] of Object.entries((uiSchema as any).properties)) {
|
|
126
|
+
const childSchema = inferSchemaFromUiSchemaValue(propName, propValue as ISchema, `${path}.${propName}`, hints);
|
|
127
|
+
if ((childSchema as any).__required) {
|
|
128
|
+
required.push(propName);
|
|
129
|
+
delete (childSchema as any).__required;
|
|
130
|
+
}
|
|
131
|
+
(schema.properties as any)[propName] = childSchema;
|
|
132
|
+
}
|
|
133
|
+
if (required.length) {
|
|
134
|
+
schema.required = required;
|
|
135
|
+
}
|
|
136
|
+
schema.additionalProperties = false;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if ((uiSchema as any).items) {
|
|
140
|
+
schema.type = schema.type || 'array';
|
|
141
|
+
if (_.isPlainObject((uiSchema as any).items)) {
|
|
142
|
+
schema.items = inferSchemaFromUiSchemaValue(`${name}.items`, (uiSchema as any).items, `${path}.items`, hints);
|
|
143
|
+
} else if (Array.isArray((uiSchema as any).items)) {
|
|
144
|
+
schema.items = (uiSchema as any).items.map((item, index) =>
|
|
145
|
+
inferSchemaFromUiSchemaValue(`${name}.items[${index}]`, item, `${path}.items[${index}]`, hints),
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (!schema.type) {
|
|
151
|
+
schema.type = 'object';
|
|
152
|
+
schema.additionalProperties = true;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return schema;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function inferParamsSchemaFromUiSchema(
|
|
159
|
+
name: string,
|
|
160
|
+
uiSchema: UiSchemaLike,
|
|
161
|
+
path: string,
|
|
162
|
+
): StepSchemaResolution {
|
|
163
|
+
const hints: FlowDynamicHint[] = [];
|
|
164
|
+
if (!uiSchema) {
|
|
165
|
+
return { schema: undefined, hints, coverage: 'unresolved' };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (typeof uiSchema === 'function') {
|
|
169
|
+
hints.push(
|
|
170
|
+
createFlowHint(
|
|
171
|
+
{
|
|
172
|
+
kind: 'dynamic-ui-schema',
|
|
173
|
+
path,
|
|
174
|
+
message: `${name} uses function-based uiSchema and requires manual schema patch.`,
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
unresolvedReason: 'function-ui-schema',
|
|
178
|
+
recommendedFallback: { type: 'object', additionalProperties: true },
|
|
179
|
+
},
|
|
180
|
+
),
|
|
181
|
+
);
|
|
182
|
+
return {
|
|
183
|
+
schema: { type: 'object', additionalProperties: true },
|
|
184
|
+
hints,
|
|
185
|
+
coverage: 'unresolved',
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const properties: Record<string, FlowJsonSchema> = {};
|
|
190
|
+
const required: string[] = [];
|
|
191
|
+
for (const [key, value] of Object.entries(uiSchema)) {
|
|
192
|
+
const childSchema = inferSchemaFromUiSchemaValue(key, value, `${path}.${key}`, hints);
|
|
193
|
+
if ((childSchema as any).__required) {
|
|
194
|
+
required.push(key);
|
|
195
|
+
delete (childSchema as any).__required;
|
|
196
|
+
}
|
|
197
|
+
properties[key] = childSchema;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
schema: {
|
|
202
|
+
type: 'object',
|
|
203
|
+
properties,
|
|
204
|
+
...(required.length ? { required } : {}),
|
|
205
|
+
additionalProperties: false,
|
|
206
|
+
},
|
|
207
|
+
hints,
|
|
208
|
+
coverage: hints.length ? 'mixed' : 'auto',
|
|
209
|
+
};
|
|
210
|
+
}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import _ from 'lodash';
|
|
11
|
+
import type { FlowDynamicHint, FlowJsonSchema, FlowSchemaDocs, FlowSubModelSlotSchema } from '../types';
|
|
12
|
+
|
|
13
|
+
export const JSON_SCHEMA_DRAFT_07 = 'http://json-schema.org/draft-07/schema#';
|
|
14
|
+
|
|
15
|
+
export function stableStringify(input: any): string {
|
|
16
|
+
if (Array.isArray(input)) {
|
|
17
|
+
return `[${input.map((item) => stableStringify(item)).join(',')}]`;
|
|
18
|
+
}
|
|
19
|
+
if (_.isPlainObject(input)) {
|
|
20
|
+
const entries = Object.entries(input)
|
|
21
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
22
|
+
.map(([key, value]) => `${JSON.stringify(key)}:${stableStringify(value)}`);
|
|
23
|
+
return `{${entries.join(',')}}`;
|
|
24
|
+
}
|
|
25
|
+
return JSON.stringify(input) ?? 'null';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function deepFreezePlainGraph<T>(input: T, seen = new WeakSet<object>()): T {
|
|
29
|
+
if (Array.isArray(input)) {
|
|
30
|
+
if (seen.has(input)) {
|
|
31
|
+
return input;
|
|
32
|
+
}
|
|
33
|
+
seen.add(input);
|
|
34
|
+
for (const item of input) {
|
|
35
|
+
deepFreezePlainGraph(item, seen);
|
|
36
|
+
}
|
|
37
|
+
return Object.freeze(input);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!_.isPlainObject(input)) {
|
|
41
|
+
return input;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const objectValue = input as Record<string, any>;
|
|
45
|
+
if (seen.has(objectValue)) {
|
|
46
|
+
return input;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
seen.add(objectValue);
|
|
50
|
+
for (const value of Object.values(objectValue)) {
|
|
51
|
+
deepFreezePlainGraph(value, seen);
|
|
52
|
+
}
|
|
53
|
+
return Object.freeze(objectValue) as T;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function hashString(input: string): string {
|
|
57
|
+
let hash = 0;
|
|
58
|
+
for (let index = 0; index < input.length; index++) {
|
|
59
|
+
hash = (hash * 31 + input.charCodeAt(index)) >>> 0;
|
|
60
|
+
}
|
|
61
|
+
return hash.toString(16).padStart(8, '0');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function deepMergeReplaceArrays<T>(base: T, patch: any): T {
|
|
65
|
+
if (typeof patch === 'undefined') {
|
|
66
|
+
return _.cloneDeep(base);
|
|
67
|
+
}
|
|
68
|
+
if (typeof base === 'undefined') {
|
|
69
|
+
return _.cloneDeep(patch);
|
|
70
|
+
}
|
|
71
|
+
return _.mergeWith({}, _.cloneDeep(base), _.cloneDeep(patch), (_objValue, srcValue) => {
|
|
72
|
+
if (Array.isArray(srcValue)) {
|
|
73
|
+
return _.cloneDeep(srcValue);
|
|
74
|
+
}
|
|
75
|
+
return undefined;
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function mergeSchemas(base?: FlowJsonSchema, patch?: FlowJsonSchema): FlowJsonSchema | undefined {
|
|
80
|
+
if (!base && !patch) return undefined;
|
|
81
|
+
if (!base) return _.cloneDeep(patch);
|
|
82
|
+
if (!patch) return _.cloneDeep(base);
|
|
83
|
+
return deepMergeReplaceArrays(base, patch);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function normalizeFlowHintMetadata(metadata?: FlowDynamicHint['x-flow']): FlowDynamicHint['x-flow'] | undefined {
|
|
87
|
+
if (!metadata || typeof metadata !== 'object' || Array.isArray(metadata)) {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const slotRules =
|
|
92
|
+
metadata.slotRules && typeof metadata.slotRules === 'object' && !Array.isArray(metadata.slotRules)
|
|
93
|
+
? _.pickBy(
|
|
94
|
+
{
|
|
95
|
+
slotKey: metadata.slotRules.slotKey,
|
|
96
|
+
type: metadata.slotRules.type,
|
|
97
|
+
allowedUses: Array.isArray(metadata.slotRules.allowedUses)
|
|
98
|
+
? metadata.slotRules.allowedUses.filter(Boolean)
|
|
99
|
+
: metadata.slotRules.allowedUses,
|
|
100
|
+
},
|
|
101
|
+
(value) => value !== undefined,
|
|
102
|
+
)
|
|
103
|
+
: undefined;
|
|
104
|
+
|
|
105
|
+
const normalized = _.pickBy(
|
|
106
|
+
{
|
|
107
|
+
slotRules: slotRules && Object.keys(slotRules).length > 0 ? slotRules : undefined,
|
|
108
|
+
contextRequirements: Array.isArray(metadata.contextRequirements)
|
|
109
|
+
? metadata.contextRequirements.filter(Boolean)
|
|
110
|
+
: metadata.contextRequirements,
|
|
111
|
+
unresolvedReason: metadata.unresolvedReason,
|
|
112
|
+
recommendedFallback: metadata.recommendedFallback,
|
|
113
|
+
},
|
|
114
|
+
(value) => value !== undefined,
|
|
115
|
+
) as FlowDynamicHint['x-flow'];
|
|
116
|
+
|
|
117
|
+
return Object.keys(normalized).length > 0 ? normalized : undefined;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function normalizeFlowHint(hint: FlowDynamicHint): FlowDynamicHint {
|
|
121
|
+
const normalizedHint = { ...hint };
|
|
122
|
+
const flowMetadata = normalizeFlowHintMetadata(hint['x-flow']);
|
|
123
|
+
if (flowMetadata) {
|
|
124
|
+
normalizedHint['x-flow'] = flowMetadata;
|
|
125
|
+
} else {
|
|
126
|
+
delete normalizedHint['x-flow'];
|
|
127
|
+
}
|
|
128
|
+
return normalizedHint;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function normalizeSchemaHints(hints?: FlowDynamicHint[]): FlowDynamicHint[] {
|
|
132
|
+
return Array.isArray(hints)
|
|
133
|
+
? _.uniqBy(
|
|
134
|
+
hints.map((item) => normalizeFlowHint(item)),
|
|
135
|
+
(item) => `${item.kind}:${item.path || ''}:${item.message}`,
|
|
136
|
+
)
|
|
137
|
+
: [];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function normalizeSchemaDocs(docs?: FlowSchemaDocs): FlowSchemaDocs {
|
|
141
|
+
return {
|
|
142
|
+
description: docs?.description,
|
|
143
|
+
examples: Array.isArray(docs?.examples) ? _.cloneDeep(docs.examples) : [],
|
|
144
|
+
minimalExample: docs?.minimalExample === undefined ? undefined : _.cloneDeep(docs.minimalExample),
|
|
145
|
+
commonPatterns: Array.isArray(docs?.commonPatterns) ? _.cloneDeep(docs.commonPatterns) : [],
|
|
146
|
+
antiPatterns: Array.isArray(docs?.antiPatterns) ? _.cloneDeep(docs.antiPatterns) : [],
|
|
147
|
+
dynamicHints: normalizeSchemaHints(docs?.dynamicHints),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function normalizeStringArray(values?: string[]): string[] {
|
|
152
|
+
if (!Array.isArray(values)) {
|
|
153
|
+
return [];
|
|
154
|
+
}
|
|
155
|
+
return _.uniq(values.map((item) => String(item || '').trim()).filter(Boolean));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function createFlowHint(hint: FlowDynamicHint, metadata?: FlowDynamicHint['x-flow']): FlowDynamicHint {
|
|
159
|
+
const result = { ...hint };
|
|
160
|
+
const flowMetadata = normalizeFlowHintMetadata({
|
|
161
|
+
...(hint['x-flow'] || {}),
|
|
162
|
+
...(metadata || {}),
|
|
163
|
+
});
|
|
164
|
+
if (flowMetadata) {
|
|
165
|
+
result['x-flow'] = flowMetadata;
|
|
166
|
+
} else {
|
|
167
|
+
delete result['x-flow'];
|
|
168
|
+
}
|
|
169
|
+
return result;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function collectAllowedUses(slot?: FlowSubModelSlotSchema): string[] {
|
|
173
|
+
if (!slot) {
|
|
174
|
+
return [];
|
|
175
|
+
}
|
|
176
|
+
if (Array.isArray(slot.uses)) {
|
|
177
|
+
return slot.uses.filter(Boolean);
|
|
178
|
+
}
|
|
179
|
+
return slot.use ? [slot.use] : [];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function buildSkeletonFromSchema(
|
|
183
|
+
schema?: FlowJsonSchema,
|
|
184
|
+
options: {
|
|
185
|
+
propertyName?: string;
|
|
186
|
+
depth?: number;
|
|
187
|
+
} = {},
|
|
188
|
+
): any {
|
|
189
|
+
if (!schema) return undefined;
|
|
190
|
+
if (schema.default !== undefined) return _.cloneDeep(schema.default);
|
|
191
|
+
if (schema.const !== undefined) return _.cloneDeep(schema.const);
|
|
192
|
+
if (Array.isArray(schema.enum) && schema.enum.length > 0) return _.cloneDeep(schema.enum[0]);
|
|
193
|
+
|
|
194
|
+
const depth = options.depth || 0;
|
|
195
|
+
if (depth > 4) {
|
|
196
|
+
return undefined;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const type = Array.isArray(schema.type) ? schema.type[0] : schema.type;
|
|
200
|
+
if (type === 'object' || (!type && _.isPlainObject(schema.properties))) {
|
|
201
|
+
const result: Record<string, any> = {};
|
|
202
|
+
const properties = (schema.properties || {}) as Record<string, FlowJsonSchema>;
|
|
203
|
+
const required = new Set<string>(schema.required || []);
|
|
204
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
205
|
+
const child = buildSkeletonFromSchema(value, {
|
|
206
|
+
propertyName: key,
|
|
207
|
+
depth: depth + 1,
|
|
208
|
+
});
|
|
209
|
+
const includeOptionalTopLevelShell =
|
|
210
|
+
depth === 0 &&
|
|
211
|
+
['stepParams', 'subModels', 'flowRegistry'].includes(key) &&
|
|
212
|
+
child !== undefined &&
|
|
213
|
+
((_.isPlainObject(child) && Object.keys(child).length > 0) || Array.isArray(child));
|
|
214
|
+
if (
|
|
215
|
+
!includeOptionalTopLevelShell &&
|
|
216
|
+
!required.has(key) &&
|
|
217
|
+
value.default === undefined &&
|
|
218
|
+
value.const === undefined
|
|
219
|
+
) {
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
if (child !== undefined) {
|
|
223
|
+
result[key] = child;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return result;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (type === 'array') {
|
|
230
|
+
return [];
|
|
231
|
+
}
|
|
232
|
+
if (type === 'boolean') {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
if (type === 'integer' || type === 'number') {
|
|
236
|
+
return 0;
|
|
237
|
+
}
|
|
238
|
+
if (type === 'string') {
|
|
239
|
+
const propertyName = options.propertyName || 'value';
|
|
240
|
+
if (propertyName === 'uid') {
|
|
241
|
+
return 'todo-uid';
|
|
242
|
+
}
|
|
243
|
+
return '';
|
|
244
|
+
}
|
|
245
|
+
if (schema.oneOf?.length) {
|
|
246
|
+
return buildSkeletonFromSchema(schema.oneOf[0], {
|
|
247
|
+
propertyName: options.propertyName,
|
|
248
|
+
depth: depth + 1,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
if (schema.anyOf?.length) {
|
|
252
|
+
return buildSkeletonFromSchema(schema.anyOf[0], {
|
|
253
|
+
propertyName: options.propertyName,
|
|
254
|
+
depth: depth + 1,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
return undefined;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export function toSchemaTitle(input: any, fallback: string): string {
|
|
261
|
+
if (typeof input === 'string') {
|
|
262
|
+
return input;
|
|
263
|
+
}
|
|
264
|
+
if (typeof input === 'number' || typeof input === 'boolean') {
|
|
265
|
+
return String(input);
|
|
266
|
+
}
|
|
267
|
+
return fallback;
|
|
268
|
+
}
|
package/src/flowEngine.ts
CHANGED
|
@@ -1336,7 +1336,13 @@ export class FlowEngine {
|
|
|
1336
1336
|
return hydrated;
|
|
1337
1337
|
}
|
|
1338
1338
|
|
|
1339
|
-
const
|
|
1339
|
+
const shouldUseEnsure = !extra?.skipSave && !uid && !!parentId && !!subKey && options?.subType === 'object';
|
|
1340
|
+
const data = shouldUseEnsure
|
|
1341
|
+
? (await this._modelRepository.ensure(options, {
|
|
1342
|
+
includeAsyncNode: !!(options?.includeAsyncNode || options?.async),
|
|
1343
|
+
})) ?? (await this._modelRepository.findOne(options))
|
|
1344
|
+
: await this._modelRepository.findOne(options);
|
|
1345
|
+
|
|
1340
1346
|
let model: T | null = null;
|
|
1341
1347
|
if (data?.uid) {
|
|
1342
1348
|
model = await this.createModelAsync<T>(data as any, extra);
|
package/src/index.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { DefaultStructure } from '
|
|
10
|
+
import { DefaultStructure } from '../types';
|
|
11
11
|
import { CollectionFieldModel } from './CollectionFieldModel';
|
|
12
12
|
|
|
13
13
|
export class DisplayItemModel<T extends DefaultStructure = DefaultStructure> extends CollectionFieldModel<T> {}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { DefaultStructure } from '
|
|
10
|
+
import { DefaultStructure } from '../types';
|
|
11
11
|
import { CollectionFieldModel } from './CollectionFieldModel';
|
|
12
12
|
|
|
13
13
|
export class EditableItemModel<T extends DefaultStructure = DefaultStructure> extends CollectionFieldModel<T> {}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { DefaultStructure } from '
|
|
10
|
+
import { DefaultStructure } from '../types';
|
|
11
11
|
import { CollectionFieldModel } from './CollectionFieldModel';
|
|
12
12
|
|
|
13
13
|
export class FilterableItemModel<T extends DefaultStructure = DefaultStructure> extends CollectionFieldModel<T> {}
|
|
@@ -47,6 +47,7 @@ export async function setupRunJSContexts() {
|
|
|
47
47
|
RunJSContextRegistry.register(version, 'JSFieldModel', JSFieldRunJSContext, { scenes: ['detail'] });
|
|
48
48
|
RunJSContextRegistry.register(version, 'JSEditableFieldModel', JSEditableFieldRunJSContext, { scenes: ['form'] });
|
|
49
49
|
RunJSContextRegistry.register(version, 'JSItemModel', JSItemRunJSContext, { scenes: ['form'] });
|
|
50
|
+
RunJSContextRegistry.register(version, 'JSItemActionModel', JSItemRunJSContext, { scenes: ['table'] });
|
|
50
51
|
RunJSContextRegistry.register(version, 'JSColumnModel', JSColumnRunJSContext, { scenes: ['table'] });
|
|
51
52
|
RunJSContextRegistry.register(version, 'FormJSFieldItemModel', FormJSFieldItemRunJSContext, { scenes: ['form'] });
|
|
52
53
|
RunJSContextRegistry.register(version, 'JSRecordActionModel', JSRecordActionRunJSContext, { scenes: ['table'] });
|
package/src/server.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export * from './types';
|
|
11
|
+
export * from './FlowSchemaRegistry';
|