|
|
@@ -0,0 +1,201 @@
|
|
|
+/**
|
|
|
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
|
+ * SPDX-License-Identifier: MIT
|
|
|
+ */
|
|
|
+
|
|
|
+import { isArray, isObject, uniq } from 'lodash';
|
|
|
+import { IJsonSchema, JsonSchemaUtils } from '@flowgram.ai/json-schema';
|
|
|
+import { Scope } from '@flowgram.ai/editor';
|
|
|
+
|
|
|
+import {
|
|
|
+ IFlowConstantValue,
|
|
|
+ IFlowRefValue,
|
|
|
+ IFlowExpressionValue,
|
|
|
+ IFlowTemplateValue,
|
|
|
+ IFlowValue,
|
|
|
+ IFlowConstantRefValue,
|
|
|
+ FlowValueType,
|
|
|
+} from '@/typings';
|
|
|
+
|
|
|
+import { constantSchema, refSchema, expressionSchema, templateSchema } from './schema';
|
|
|
+
|
|
|
+export namespace FlowValueUtils {
|
|
|
+ /**
|
|
|
+ * Check if the value is a constant type
|
|
|
+ */
|
|
|
+ export function isConstant(value: any): value is IFlowConstantValue {
|
|
|
+ return constantSchema.safeParse(value).success;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Check if the value is a reference type
|
|
|
+ */
|
|
|
+ export function isRef(value: any): value is IFlowRefValue {
|
|
|
+ return refSchema.safeParse(value).success;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Check if the value is an expression type
|
|
|
+ */
|
|
|
+ export function isExpression(value: any): value is IFlowExpressionValue {
|
|
|
+ return expressionSchema.safeParse(value).success;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Check if the value is a template type
|
|
|
+ */
|
|
|
+ export function isTemplate(value: any): value is IFlowTemplateValue {
|
|
|
+ return templateSchema.safeParse(value).success;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Check if the value is either a constant or reference type
|
|
|
+ */
|
|
|
+ export function isConstantOrRef(value: any): value is IFlowConstantRefValue {
|
|
|
+ return isConstant(value) || isRef(value);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Check if the value is a valid flow value type
|
|
|
+ */
|
|
|
+ export function isFlowValue(value: any): value is IFlowValue {
|
|
|
+ return isConstant(value) || isRef(value) || isExpression(value) || isTemplate(value);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Traverse all flow values in the given value
|
|
|
+ * @param value The value to traverse
|
|
|
+ * @param options The options to traverse
|
|
|
+ * @returns A generator of flow values
|
|
|
+ */
|
|
|
+ export function* traverse(
|
|
|
+ value: any,
|
|
|
+ options: {
|
|
|
+ includeTypes?: FlowValueType[];
|
|
|
+ path?: string;
|
|
|
+ }
|
|
|
+ ): Generator<{ value: IFlowValue; path: string }> {
|
|
|
+ const { includeTypes = ['ref', 'template'], path = '' } = options;
|
|
|
+
|
|
|
+ if (isObject(value)) {
|
|
|
+ if (isRef(value) && includeTypes.includes('ref')) {
|
|
|
+ yield { value, path };
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isTemplate(value) && includeTypes.includes('template')) {
|
|
|
+ yield { value, path };
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isExpression(value) && includeTypes.includes('expression')) {
|
|
|
+ yield { value, path };
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isConstant(value) && includeTypes.includes('constant')) {
|
|
|
+ yield { value, path };
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (const [_key, _value] of Object.entries(value)) {
|
|
|
+ yield* traverse(_value, { ...options, path: `${path}.${_key}` });
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isArray(value)) {
|
|
|
+ for (const [_idx, _value] of value.entries()) {
|
|
|
+ yield* traverse(_value, { ...options, path: `${path}[${_idx}]` });
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get all key paths in the template value
|
|
|
+ * @param value The template value
|
|
|
+ * @returns A list of key paths
|
|
|
+ */
|
|
|
+ export function getTemplateKeyPaths(value: IFlowTemplateValue) {
|
|
|
+ // find all keyPath wrapped in {{}}
|
|
|
+ const keyPathReg = /{{(.*?)}}/g;
|
|
|
+ return uniq(value.content?.match(keyPathReg) || []).map((_keyPath) =>
|
|
|
+ _keyPath.slice(2, -2).split('.')
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Infer the schema of the constant value
|
|
|
+ * @param value
|
|
|
+ * @returns
|
|
|
+ */
|
|
|
+ export function inferConstantJsonSchema(value: IFlowConstantValue): IJsonSchema | undefined {
|
|
|
+ if (value?.schema) {
|
|
|
+ return value.schema;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (typeof value.content === 'string') {
|
|
|
+ return {
|
|
|
+ type: 'string',
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ if (typeof value.content === 'number') {
|
|
|
+ return {
|
|
|
+ type: 'number',
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ if (typeof value.content === 'boolean') {
|
|
|
+ return {
|
|
|
+ type: 'boolean',
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isObject(value.content)) {
|
|
|
+ return {
|
|
|
+ type: 'object',
|
|
|
+ };
|
|
|
+ }
|
|
|
+ return undefined;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Infer the schema of the flow value
|
|
|
+ * @param values The flow value or object contains flow value
|
|
|
+ * @param scope
|
|
|
+ * @returns
|
|
|
+ */
|
|
|
+ export function inferJsonSchema(values: any, scope: Scope): IJsonSchema | undefined {
|
|
|
+ if (isObject(values)) {
|
|
|
+ if (isConstant(values)) {
|
|
|
+ return inferConstantJsonSchema(values);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isRef(values)) {
|
|
|
+ const variable = scope.available.getByKeyPath(values?.content);
|
|
|
+ const schema = variable?.type ? JsonSchemaUtils.astToSchema(variable?.type) : undefined;
|
|
|
+
|
|
|
+ return schema;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isTemplate(values)) {
|
|
|
+ return { type: 'string' };
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ type: 'object',
|
|
|
+ properties: Object.keys(values).reduce((acc, key) => {
|
|
|
+ const schema = inferJsonSchema((values as any)[key], scope);
|
|
|
+ if (schema) {
|
|
|
+ acc[key] = schema;
|
|
|
+ }
|
|
|
+ return acc;
|
|
|
+ }, {} as Record<string, IJsonSchema>),
|
|
|
+ };
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|