UNPKG

sequential-workflow-editor-model

Version:

This package contains the model for [Sequential Workflow Editor](https://github.com/nocode-js/sequential-workflow-editor).

1,267 lines (1,226 loc) 59.5 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.sequentialWorkflowEditorModel = {})); })(this, (function (exports) { 'use strict'; class DefaultValueContext { static create(activator, propertyContext) { return new DefaultValueContext(activator, propertyContext); } constructor(activator, propertyContext) { this.activator = activator; this.propertyContext = propertyContext; this.getPropertyValue = this.propertyContext.getPropertyValue; this.formatPropertyValue = this.propertyContext.formatPropertyValue; this.activateStep = this.activator.activateStep; } } const SEPARATOR = '/'; class Path { static create(path) { if (typeof path === 'string') { path = path.split(SEPARATOR); } return new Path(path); } static root() { return new Path([]); } constructor(parts) { this.parts = parts; } read(object) { // eslint-disable-next-line @typescript-eslint/no-explicit-any let result = object; for (const part of this.parts) { result = result[part]; if (result === undefined) { throw new Error(`Cannot read path: ${this.parts.join(SEPARATOR)}`); } } return result; } write(object, value) { // eslint-disable-next-line @typescript-eslint/no-explicit-any let result = object; for (let i = 0; i < this.parts.length - 1; i++) { result = result[this.parts[i]]; if (result === undefined) { throw new Error(`Cannot write path: ${this.toString()}`); } } result[this.last()] = value; } equals(other) { if (typeof other === 'string') { other = Path.create(other); } return this.parts.length === other.parts.length && this.startsWith(other); } add(part) { return new Path([...this.parts, part]); } last() { if (this.parts.length === 0) { throw new Error('Root path has no last part'); } return this.parts[this.parts.length - 1]; } startsWith(other) { if (typeof other === 'string') { other = Path.create(other); } if (this.parts.length < other.parts.length) { return false; } for (let i = 0; i < other.parts.length; i++) { if (this.parts[i] !== other.parts[i]) { return false; } } return true; } toString() { return this.parts.join(SEPARATOR); } } class SimpleEvent { constructor() { this.listeners = []; this.forward = (value) => { if (this.listeners.length > 0) { this.listeners.forEach(listener => listener(value)); } }; } subscribe(listener) { this.listeners.push(listener); } unsubscribe(listener) { const index = this.listeners.indexOf(listener); if (index >= 0) { this.listeners.splice(index, 1); } else { throw new Error('Unknown listener'); } } count() { return this.listeners.length; } } function readPropertyValue(name, model, object) { const nameStr = String(name); const path = Path.create(['properties', nameStr]); if (!model.dependencies.some(dep => dep.equals(path))) { throw new Error(`Property ${nameStr} is not registered as dependency`); } return path.read(object); } class PropertyContext { static create(object, propertyModel, definitionModel) { return new PropertyContext(object, propertyModel, definitionModel); } constructor(object, propertyModel, definitionModel) { this.object = object; this.propertyModel = propertyModel; this.definitionModel = definitionModel; /** * @returns the type of the step, or `null` if the object is root. */ this.tryGetStepType = () => { const type = this.object.type; return type ? type : null; }; /** * Get the value of a property by name. * @param name The name of the property. * @returns The value of the property. */ this.getPropertyValue = (name) => { return readPropertyValue(name, this.propertyModel, this.object); }; /** * @returns The supported value types for variables. */ this.getValueTypes = () => { return this.definitionModel.valueTypes; }; /** * Format a property value using a formatter function. * @param name The name of the property. * @param formatter The formatter function. * @param undefinedValue The value to return if the property value is `null` or `undefined`. */ this.formatPropertyValue = (name, formatter, undefinedValue) => { const value = this.getPropertyValue(name); if (value === undefined || value === null) { return undefinedValue || '?'; } return formatter(value); }; } } class ModelActivator { static create(definitionModel, uidGenerator) { return new ModelActivator(definitionModel, uidGenerator); } constructor(definitionModel, uidGenerator) { this.definitionModel = definitionModel; this.uidGenerator = uidGenerator; this.activateDefinition = () => { const definition = { properties: {} }; this.activatePropertiesInOrder(definition, this.definitionModel.root.properties); const propertyContext = PropertyContext.create(definition, this.definitionModel.root.sequence, this.definitionModel); const defaultValueContext = DefaultValueContext.create(this, propertyContext); const sequence = this.definitionModel.root.sequence.value.getDefaultValue(defaultValueContext); return Object.assign(Object.assign({}, definition), { sequence }); }; this.activateStep = (stepType) => { const model = this.definitionModel.steps[stepType]; if (!model) { throw new Error(`Unknown step type: ${stepType}`); } const step = { id: this.uidGenerator(), type: stepType, componentType: model.componentType, properties: {} }; this.activatePropertiesInOrder(step, model.properties); const propertyContext = PropertyContext.create(step, model.name, this.definitionModel); const defaultValueContext = DefaultValueContext.create(this, propertyContext); const name = model.name.value.getDefaultValue(defaultValueContext); return Object.assign(Object.assign({}, step), { name }); }; } activatePropertiesInOrder(object, models) { let i; for (i = 0; i < models.length; i++) { const model = models[i]; if (model.dependencies.length === 0) { this.activateProperty(object, model); } } for (i = 0; i < models.length; i++) { const model = models[i]; if (model.dependencies.length > 0) { this.activateProperty(object, model); } } } activateProperty(object, model) { const propertyContext = PropertyContext.create(object, model, this.definitionModel); const defaultValueContext = DefaultValueContext.create(this, propertyContext); const defaultValue = model.value.getDefaultValue(defaultValueContext); model.value.path.write(object, defaultValue); } } const anyVariablesValueModelId = 'anyVariables'; const createAnyVariablesValueModel = (configuration) => ({ create: (path) => { var _a; return ({ id: anyVariablesValueModelId, label: (_a = configuration.label) !== null && _a !== void 0 ? _a : 'Variables', editorId: configuration.editorId, path, configuration, getDefaultValue() { return { variables: [] }; }, getVariableDefinitions: () => null, validate(context) { const errors = {}; const value = context.getValue(); value.variables.forEach((variable, index) => { if (!context.hasVariable(variable.name, variable.type)) { errors[index] = context.i18n('anyVariables.variableIsLost', 'Variable :name is lost', { name: variable.name }); return; } if (configuration.valueTypes && !configuration.valueTypes.includes(variable.type)) { errors[index] = context.i18n('anyVariables.invalidVariableType', 'Variable :name has invalid type', { name: variable.name }); return; } }); return Object.keys(errors).length > 0 ? errors : null; } }); } }); function createValidationSingleError(error) { return { $: error }; } function booleanValueModelValidator(context) { const value = context.getValue(); if (typeof value !== 'boolean') { return createValidationSingleError(context.i18n('boolean.invalidType', 'The value must be a boolean')); } return null; } const booleanValueModelId = 'boolean'; const createBooleanValueModel = (configuration) => ({ create: (path) => { var _a; return ({ id: booleanValueModelId, editorId: configuration.editorId, label: (_a = configuration.label) !== null && _a !== void 0 ? _a : 'Boolean', path, configuration, getDefaultValue() { if (configuration.defaultValue !== undefined) { return configuration.defaultValue; } return false; }, getVariableDefinitions: () => null, validate: booleanValueModelValidator }); } }); function branchesValueModelValidator(context) { const configuration = context.model.configuration; const branches = context.getValue(); if (typeof branches !== 'object') { return createValidationSingleError(context.i18n('branches.mustBeObject', 'The value must be object')); } const branchNames = Object.keys(branches); if (branchNames.length === 0) { return createValidationSingleError(context.i18n('branches.empty', 'No branches defined')); } if (!configuration.dynamic) { const configurationBranchNames = Object.keys(configuration.branches); if (branchNames.length !== configurationBranchNames.length) { return createValidationSingleError(context.i18n('branches.invalidLength', 'Invalid number of branches')); } const missingBranchName = configurationBranchNames.find(branchName => !branchNames.includes(branchName)); if (missingBranchName) { return createValidationSingleError(context.i18n('branches.missingBranch', 'Missing branch: :name', { name: missingBranchName })); } } return null; } const branchesValueModelId = 'branches'; const createBranchesValueModel = (configuration) => ({ create: (path) => ({ id: branchesValueModelId, editorId: configuration.editorId, label: 'Branches', path, configuration, getDefaultValue(context) { const branches = Object.keys(configuration.branches).reduce((result, branchName) => { result[branchName] = configuration.branches[branchName].map(type => context.activateStep(type)); return result; }, {}); return branches; }, getVariableDefinitions: () => null, validate: branchesValueModelValidator }) }); function choiceValueModelValidator(context) { const value = context.getValue(); const configuration = context.model.configuration; if (!configuration.choices.includes(value)) { return createValidationSingleError(context.i18n('choice.notSupportedValue', 'Value is not supported')); } return null; } const choiceValueModelId = 'choice'; function createChoiceValueModel(configuration) { if (configuration.choices.length < 1) { throw new Error('At least one choice must be provided.'); } return { create: (path) => { var _a; return ({ id: choiceValueModelId, label: (_a = configuration.label) !== null && _a !== void 0 ? _a : 'Choice', editorId: configuration.editorId, path, configuration, getDefaultValue() { if (configuration.defaultValue) { if (!configuration.choices.includes(configuration.defaultValue)) { throw new Error(`Default value "${configuration.defaultValue}" does not match any of the choices`); } return configuration.defaultValue; } return configuration.choices[0]; }, getVariableDefinitions: () => null, validate: choiceValueModelValidator }); } }; } const dynamicValueModelId = 'dynamic'; function createDynamicValueModel(configuration) { if (configuration.models.length < 1) { throw new Error('Dynamic value model must have at least one choice'); } return { create(path) { const valuePath = path.add('value'); const subModels = configuration.models.map(modelFactory => modelFactory.create(valuePath)); return { id: dynamicValueModelId, label: 'Dynamic value', path, configuration, subModels, getDefaultValue(context) { const model = subModels[0]; return { modelId: model.id, value: model.getDefaultValue(context) }; }, getVariableDefinitions: () => null, validate(context) { const value = context.getValue(); const model = subModels.find(m => m.id === value.modelId); if (!model) { const availableModels = subModels.map(m => m.id).join(', '); throw new Error(`Cannot find sub model id: ${value.modelId} (available: ${availableModels})`); } const childContext = context.createChildContext(model); return model.validate(childContext); } }; } }; } class GeneratedStringContext { static create(context) { return new GeneratedStringContext(context); } constructor(context) { this.context = context; this.getPropertyValue = this.context.getPropertyValue; this.formatPropertyValue = this.context.formatPropertyValue; } } const generatedStringValueModelId = 'generatedString'; function createGeneratedStringValueModel(configuration) { return { create: (path) => { var _a; return ({ id: generatedStringValueModelId, label: (_a = configuration.label) !== null && _a !== void 0 ? _a : 'Generated string', editorId: configuration.editorId, path, configuration, getDefaultValue(context) { const subContext = GeneratedStringContext.create(context); return configuration.generator(subContext); }, getVariableDefinitions: () => null, validate(context) { const subContext = GeneratedStringContext.create(context); const value = configuration.generator(subContext); if (context.getValue() !== value) { return createValidationSingleError(context.i18n('generatedString.differentValue', 'Generator returns different value than the current value')); } return null; } }); } }; } const nullableAnyVariableValueModelId = 'nullableAnyVariable'; const createNullableAnyVariableValueModel = (configuration) => ({ create: (path) => { var _a; return ({ id: nullableAnyVariableValueModelId, label: (_a = configuration.label) !== null && _a !== void 0 ? _a : 'Variable', editorId: configuration.editorId, path, configuration, getDefaultValue() { return null; }, getVariableDefinitions: () => null, validate(context) { const value = context.getValue(); if (configuration.isRequired && !value) { return createValidationSingleError(context.i18n('nullableAnyVariable.variableIsRequired', 'The variable is required')); } if (value) { if (!context.hasVariable(value.name, value.type)) { return createValidationSingleError(context.i18n('nullableAnyVariable.variableIsLost', 'The variable :name is lost', { name: value.name })); } if (configuration.valueTypes && !configuration.valueTypes.includes(value.type)) { return createValidationSingleError(context.i18n('nullableAnyVariable.invalidVariableType', 'The variable :name has invalid type', { name: value.name })); } } return null; } }); } }); const nullableVariableValueModelId = 'nullableVariable'; const createNullableVariableValueModel = (configuration) => ({ create: (path) => { var _a; return ({ id: nullableVariableValueModelId, label: (_a = configuration.label) !== null && _a !== void 0 ? _a : 'Variable', editorId: configuration.editorId, path, configuration, getDefaultValue() { return null; }, getVariableDefinitions: () => null, validate(context) { const value = context.getValue(); if (configuration.isRequired && !value) { return createValidationSingleError(context.i18n('nullableVariable.variableIsRequired', 'The variable is required')); } if (value && value.name) { if (!context.hasVariable(value.name, configuration.valueType)) { return createValidationSingleError(context.i18n('nullableVariable.variableIsLost', 'The variable :name is not found', { name: value.name })); } } return null; } }); } }); const MAX_LENGTH = 32; function variableNameValidator(i18n, name) { if (!name) { return i18n('variableName.required', 'Variable name is required'); } if (name.length > MAX_LENGTH) { return i18n('variableName.maxLength', 'Variable name must be :n characters or less', { n: String(MAX_LENGTH) }); } if (!/^[A-Za-z][a-zA-Z_0-9-]*$/.test(name)) { return i18n('variableName.invalidCharacters', 'Variable name contains invalid characters'); } return null; } const nullableVariableDefinitionValueModelId = 'nullableVariableDefinition'; const createNullableVariableDefinitionValueModel = (configuration) => ({ create: (path) => ({ id: nullableVariableDefinitionValueModelId, label: 'Variable definition', path, configuration, getDefaultValue() { return configuration.defaultValue || null; }, getVariableDefinitions(context) { const value = context.getValue(); if (value) { return [value]; } return null; }, validate(context) { const value = context.getValue(); if (configuration.isRequired && !value) { return createValidationSingleError(context.i18n('nullableVariableDefinition.variableIsRequired', 'The variable is required')); } if (value) { const nameError = variableNameValidator(context.i18n, value.name); if (nameError) { return createValidationSingleError(nameError); } if (value.type !== configuration.valueType) { return createValidationSingleError(context.i18n('nullableVariableDefinition.expectedType', 'Variable type must be :type', { type: configuration.valueType })); } if (context.isVariableDuplicated(value.name)) { return createValidationSingleError(context.i18n('nullableVariableDefinition.variableIsDuplicated', 'Variable name is already used')); } } return null; } }) }); function numberValueModelValidator(context) { const value = context.getValue(); const configuration = context.model.configuration; if (isNaN(value) || typeof value !== 'number') { return createValidationSingleError(context.i18n('number.valueMustBeNumber', 'The value must be a number')); } if (configuration.min !== undefined && value < configuration.min) { return createValidationSingleError(context.i18n('number.valueTooLow', 'The value must be at least :min', { min: String(configuration.min) })); } if (configuration.max !== undefined && value > configuration.max) { return createValidationSingleError(context.i18n('number.valueTooHigh', 'The value must be at most :max', { max: String(configuration.max) })); } return null; } const numberValueModelId = 'number'; const createNumberValueModel = (configuration) => ({ create: (path) => { var _a; return ({ id: numberValueModelId, label: (_a = configuration.label) !== null && _a !== void 0 ? _a : 'Number', editorId: configuration.editorId, path, configuration, getDefaultValue() { if (configuration.defaultValue !== undefined) { return configuration.defaultValue; } if (configuration.min !== undefined) { return configuration.min; } return 0; }, getVariableDefinitions: () => null, validate: numberValueModelValidator }); } }); const sequenceValueModelId = 'sequence'; const createSequenceValueModel = (configuration) => ({ create: (path) => ({ id: sequenceValueModelId, label: 'Sequence', path, configuration, getDefaultValue(context) { return configuration.sequence.map(type => context.activateStep(type)); }, getVariableDefinitions: () => null, validate: () => null }) }); function stringValueModelValidator(context) { const value = context.getValue(); const configuration = context.model.configuration; if (typeof value !== 'string') { return createValidationSingleError(context.i18n('string.valueMustBeString', 'The value must be a string')); } if (configuration.minLength !== undefined && value.length < configuration.minLength) { return createValidationSingleError(context.i18n('string.valueTooShort', 'The value must be at least :min characters long', { min: String(configuration.minLength) })); } if (configuration.pattern && !configuration.pattern.test(value)) { return createValidationSingleError(context.i18n('string.valueDoesNotMatchPattern', 'The value does not match the required pattern')); } return null; } const stringValueModelId = 'string'; const createStringValueModel = (configuration) => ({ create: (path) => { var _a; return ({ id: stringValueModelId, label: (_a = configuration.label) !== null && _a !== void 0 ? _a : 'String', editorId: configuration.editorId, path, configuration, getDefaultValue() { return configuration.defaultValue || ''; }, getVariableDefinitions: () => null, validate: stringValueModelValidator }); } }); function stringDictionaryValueModelValidator(context) { const errors = {}; const value = context.getValue(); const configuration = context.model.configuration; const count = value.items.length; if (configuration.uniqueKeys) { for (let index = 0; index < count; index++) { const key = value.items[index].key; const duplicate = value.items.findIndex((item, i) => i !== index && item.key === key); if (duplicate >= 0) { errors[index] = context.i18n('stringDictionary.duplicatedKey', 'Key name is duplicated'); } } } for (let index = 0; index < count; index++) { const item = value.items[index]; if (!item.key) { errors[index] = context.i18n('stringDictionary.keyIsRequired', 'Key is required'); } if (configuration.valueMinLength !== undefined && item.value.length < configuration.valueMinLength) { errors[index] = context.i18n('stringDictionary.valueTooShort', 'Value must be at least :min characters long', { min: String(configuration.valueMinLength) }); } } return Object.keys(errors).length > 0 ? errors : null; } const stringDictionaryValueModelId = 'stringDictionary'; const createStringDictionaryValueModel = (configuration) => ({ create: (path) => { var _a; return ({ id: stringDictionaryValueModelId, label: (_a = configuration.label) !== null && _a !== void 0 ? _a : 'Dictionary', editorId: configuration.editorId, path, configuration, getDefaultValue() { return { items: [] }; }, getVariableDefinitions: () => null, validate: stringDictionaryValueModelValidator }); } }); const variableDefinitionsValueModelId = 'variableDefinitions'; const createVariableDefinitionsValueModel = (configuration) => ({ create: (path) => ({ id: variableDefinitionsValueModelId, label: 'Variable definitions', path, configuration, getDefaultValue() { var _a; return ((_a = configuration.defaultValue) !== null && _a !== void 0 ? _a : { variables: [] }); }, getVariableDefinitions(context) { return context.getValue().variables.filter(variable => !!variable.name); }, validate: (context) => { const errors = {}; const value = context.getValue(); value.variables.forEach((variable, index) => { const nameError = variableNameValidator(context.i18n, variable.name); if (nameError) { errors[index] = nameError; return; } const isDuplicated = value.variables.some((v, i) => i !== index && v.name === variable.name); if (isDuplicated) { errors[index] = context.i18n('variableDefinitions.variableNameIsDuplicated', 'Variable name is duplicated'); return; } if (context.isVariableDuplicated(variable.name)) { errors[index] = context.i18n('variableDefinitions.variableNameIsDuplicated', 'Variable name is already used'); return; } if (configuration.valueTypes && !configuration.valueTypes.includes(variable.type)) { errors[index] = context.i18n('variableDefinitions.valueTypeIsNotAllowed', 'Value type is not allowed'); } }); return Object.keys(errors).length > 0 ? errors : null; } }) }); function buildLabel(value) { return value.charAt(0).toUpperCase() + value.slice(1); } class PropertyModelBuilder { constructor(path, circularDependencyDetector) { this.path = path; this.circularDependencyDetector = circularDependencyDetector; this._dependencies = []; } /** * @returns `true` if the model of the value is set, otherwise `false`. */ hasValue() { return !!this._value; } /** * Sets the model of the value. * @param valueModelFactory The factory function that creates the model of the value. * @example `builder.value(stringValueModel({ defaultValue: 'Some value' }));` */ value(valueModelFactory) { if (this._value) { throw new Error(`Model is already set for ${this.path.toString()}`); } this._value = valueModelFactory; return this; } /** * Sets the label of the property. * @param label The label of the property. * @example `builder.label('Foo');` */ label(label) { this._label = label; return this; } /** * Sets the hint of the property. The hint is displayed in the property editor. * @param hint The hint of the property. * @example `builder.hint('This is a hint');` */ hint(hint) { this._hint = hint; return this; } /** * Sets which properties the property depends on. Values of dependent properties are available in the custom validator. * @param propertyName Name of the property in the step. */ dependentProperty(propertyName) { const propName = String(propertyName); const path = Path.create(['properties', propName]); if (this._dependencies.some(dep => dep.equals(path))) { throw new Error(`Dependency ${propName} is already set`); } this._dependencies.push(path); this.circularDependencyDetector.check(this.path, path); return this; } /** * Sets the custom validator of the property. * @param validator The custom validator of the property. * @example `builder.customValidator({ validate(context) { return 'error'; } });` */ validator(validator) { if (this._validator) { throw new Error('Custom validator is already set'); } this._validator = validator; return this; } build() { if (!this._value) { throw new Error(`Model is not set for ${this.path.toString()}`); } return { path: this.path, label: this._label || this.getDefaultLabel(), hint: this._hint, dependencies: this._dependencies, validator: this._validator, value: this._value.create(this.path) }; } getDefaultLabel() { return buildLabel(this.path.last()); } } class CircularDependencyDetector { constructor() { this.dependencies = []; } check(source, target) { if (this.dependencies.some(dep => dep.source.equals(target))) { throw new Error(`It is not allowed to depend on dependency with dependency: ${source.toString()} <-> ${target.toString()}`); } this.dependencies.push({ source, target }); } } const namePath = Path.create(['name']); class StepModelBuilder { constructor(type, componentType) { this.type = type; this.componentType = componentType; this.circularDependencyDetector = new CircularDependencyDetector(); this._toolbox = true; this.nameBuilder = new PropertyModelBuilder(namePath, this.circularDependencyDetector); this.propertyBuilder = []; if (!type) { throw new Error('Step type is empty'); } if (!componentType) { throw new Error('Component type is empty'); } } /** * Sets the label of the step. This field is used in the toolbox and the editor to display the step. */ label(label) { this._label = label; return this; } /** * Sets the description of the step. * @param description The description of the step. * @example `builder.description('This step does something useful.');` */ description(description) { this._description = description; return this; } /** * Sets the category of the step. This field is used in the toolbox to group steps. * @param category The category of the step. * @example `builder.category('Utilities');` */ category(category) { this._category = category; return this; } /** * Sets whether the step should be displayed in the toolbox. Default is `true`. * @param toolbox Whether the step should be displayed in the toolbox. * @example `builder.toolbox(false);` */ toolbox(toolbox) { this._toolbox = toolbox; return this; } /** * Sets the validator of the step. * @param validator The validator. */ validator(validator) { this._validator = validator; return this; } /** * @returns The builder for the `name` property. * @example `builder.name().value(createStringValueModel({ defaultValue: 'Some name' })).label('Name');` */ name() { return this.nameBuilder; } /** * @param propertyName Name of the property in the step. * @returns The builder for the property. * @example `builder.property('foo').value(createStringValueModel({ defaultValue: 'Some value' })).label('Foo');` */ property(propertyName) { const path = Path.create(['properties', String(propertyName)]); const builder = new PropertyModelBuilder(path, this.circularDependencyDetector); this.propertyBuilder.push(builder); return builder; } build() { var _a; if (!this.nameBuilder.hasValue()) { this.nameBuilder.value(createStringValueModel({ defaultValue: buildLabel(this.type), minLength: 1 })); } return { type: this.type, componentType: this.componentType, label: (_a = this._label) !== null && _a !== void 0 ? _a : buildLabel(this.type), category: this._category, description: this._description, toolbox: this._toolbox, validator: this._validator, name: this.nameBuilder.build(), properties: this.propertyBuilder.map(builder => builder.build()) }; } } function createStepModel(type, componentType, build) { const builder = new StepModelBuilder(type, componentType); build(builder); return builder.build(); } const branchesPath = Path.create('branches'); class BranchedStepModelBuilder extends StepModelBuilder { constructor() { super(...arguments); this.branchesBuilder = new PropertyModelBuilder(branchesPath, this.circularDependencyDetector); } /** * @returns the builder for the branches property. * @example `builder.branches().value(createBranchesValueModel(...));` */ branches() { return this.branchesBuilder; } build() { if (!this.branchesBuilder.hasValue()) { throw new Error(`Branches configuration is not set for ${this.type}`); } const model = super.build(); model.properties.push(this.branchesBuilder.build()); return model; } } function createBranchedStepModel(type, componentType, build) { const builder = new BranchedStepModelBuilder(type, componentType); build(builder); return builder.build(); } const sequencePath$1 = Path.create(['sequence']); class RootModelBuilder { constructor() { this.circularDependencyDetector = new CircularDependencyDetector(); this.propertyBuilders = []; this.sequenceBuilder = new PropertyModelBuilder(sequencePath$1, this.circularDependencyDetector); } /** * @param propertyName Name of the property. * @returns The builder for the property. * @example `builder.property('foo').value(createStringValueModel({ defaultValue: 'Some value' })).label('Foo');` */ property(propertyName) { const path = Path.create(['properties', String(propertyName)]); const builder = new PropertyModelBuilder(path, this.circularDependencyDetector); this.propertyBuilders.push(builder); return builder; } /** * @returns the builder for the sequence property. * @example `builder.sequence().value(createSequenceValueModel(...));` */ sequence() { return this.sequenceBuilder; } build() { if (!this.sequenceBuilder.hasValue()) { this.sequenceBuilder.value(createSequenceValueModel({ sequence: [] })); } return { sequence: this.sequenceBuilder.build(), properties: this.propertyBuilders.map(builder => builder.build()) }; } } function createRootModel(build) { const builder = new RootModelBuilder(); build(builder); return builder.build(); } class DefinitionModelBuilder { constructor() { this.stepModels = {}; // Nothing... } root(modelOrCallback) { if (this.rootModel) { throw new Error('Root model is already defined'); } if (typeof modelOrCallback === 'function') { this.rootModel = createRootModel(modelOrCallback); } else { this.rootModel = modelOrCallback; } return this; } steps(models) { for (const model of models) { this.step(model); } return this; } step(model) { if (this.stepModels[model.type]) { throw new Error(`Step model with type ${model.type} is already defined`); } this.stepModels[model.type] = model; return this; } valueTypes(valueTypes) { if (this._valueTypes) { throw new Error('Value types are already set'); } this._valueTypes = valueTypes; return this; } build() { var _a; if (!this.rootModel) { throw new Error('Root model is not defined'); } return { root: this.rootModel, steps: this.stepModels, valueTypes: (_a = this._valueTypes) !== null && _a !== void 0 ? _a : [] }; } } function createDefinitionModel(build) { const builder = new DefinitionModelBuilder(); build(builder); return builder.build(); } const sequencePath = Path.create('sequence'); class SequentialStepModelBuilder extends StepModelBuilder { constructor() { super(...arguments); this.sequenceBuilder = new PropertyModelBuilder(sequencePath, this.circularDependencyDetector); } /** * @returns the builder for the sequence property. * @example `builder.sequence().value(createSequenceValueModel(...));` */ sequence() { return this.sequenceBuilder; } build() { if (!this.sequenceBuilder.hasValue()) { this.sequenceBuilder.value(createSequenceValueModel({ sequence: [] })); } const model = super.build(); model.properties.push(this.sequenceBuilder.build()); return model; } } function createSequentialStepModel(type, componentType, build) { const builder = new SequentialStepModelBuilder(type, componentType); build(builder); return builder.build(); } class ScopedPropertyContext { static create(propertyContext, parentsProvider, i18n) { return new ScopedPropertyContext(propertyContext, i18n, parentsProvider); } constructor(propertyContext, i18n, parentsProvider) { this.propertyContext = propertyContext; this.i18n = i18n; this.parentsProvider = parentsProvider; this.tryGetStepType = this.propertyContext.tryGetStepType; this.getPropertyValue = this.propertyContext.getPropertyValue; this.formatPropertyValue = this.propertyContext.formatPropertyValue; this.getValueTypes = this.propertyContext.getValueTypes; this.hasVariable = (variableName, valueType) => { return this.getVariables().some(v => v.name === variableName && (valueType === null || v.type === valueType)); }; this.findFirstUndefinedVariable = (variableNames) => { const variables = new Set(this.getVariables().map(v => v.name)); return variableNames.find(name => !variables.has(name)); }; this.isVariableDuplicated = (variableName) => { return this.getVariables().filter(v => v.name === variableName).length > 1; }; this.tryGetVariableType = (variableName) => { const variable = this.getVariables().find(v => v.name === variableName); return variable ? variable.type : null; }; this.getVariables = () => { return this.parentsProvider.getVariables(); }; } } class ValueContext { static createFromDefinitionContext(valueModel, propertyModel, definitionContext, i18n) { const propertyContext = PropertyContext.create(definitionContext.object, propertyModel, definitionContext.definitionModel); const scopedPropertyContext = ScopedPropertyContext.create(propertyContext, definitionContext.parentsProvider, i18n); return new ValueContext(valueModel, scopedPropertyContext); } constructor(model, scopedPropertyContext) { this.model = model; this.scopedPropertyContext = scopedPropertyContext; this.onValueChanged = new SimpleEvent(); this.tryGetStepType = this.scopedPropertyContext.tryGetStepType; this.getPropertyValue = this.scopedPropertyContext.getPropertyValue; this.formatPropertyValue = this.scopedPropertyContext.formatPropertyValue; this.getValueTypes = this.scopedPropertyContext.getValueTypes; this.hasVariable = this.scopedPropertyContext.hasVariable; this.findFirstUndefinedVariable = this.scopedPropertyContext.findFirstUndefinedVariable; this.isVariableDuplicated = this.scopedPropertyContext.isVariableDuplicated; this.tryGetVariableType = this.scopedPropertyContext.tryGetVariableType; this.getVariables = this.scopedPropertyContext.getVariables; this.i18n = this.scopedPropertyContext.i18n; this.getValue = () => { return this.model.path.read(this.scopedPropertyContext.propertyContext.object); }; this.setValue = (value) => { this.model.path.write(this.scopedPropertyContext.propertyContext.object, value); this.onValueChanged.forward(this.model.path); }; this.validate = () => { return this.model.validate(this); }; } createChildContext(childValueModel) { const context = new ValueContext(childValueModel, this.scopedPropertyContext); context.onValueChanged.subscribe(this.onValueChanged.forward); return context; } } class ParentsProvider { static createForStep(step, definition, definitionModel, definitionWalker, i18n) { return new ParentsProvider(step, definition, definitionModel, definitionWalker, i18n); } static createForRoot(definition, definitionModel, definitionWalker, i18n) { return new ParentsProvider(null, definition, definitionModel, definitionWalker, i18n); } constructor(step, definition, definitionModel, definitionWalker, i18n) { this.step = step; this.definition = definition; this.definitionModel = definitionModel; this.definitionWalker = definitionWalker; this.i18n = i18n; this.getParentStepTypes = () => { if (this.step) { const parents = this.definitionWalker.getParents(this.definition, this.step); return parents.slice(0, parents.length - 1).filter(p => typeof p === 'object').map(p => p.type); } return []; }; } getVariables() { const result = []; const rootContext = DefinitionContext.createForRoot(this.definition, this.definitionModel, this.definitionWalker, this.i18n); this.appendVariables(result, null, this.definitionModel.root.properties, rootContext); if (this.step) { const parents = this.definitionWalker.getParents(this.definition, this.step); const count = parents.length; for (let index = 0; index < count; index++) { const parent = parents[index]; if (typeof parent === 'string') { continue; } const parentModel = this.definitionModel.steps[parent.type]; if (!parentModel) { throw new Error(`Unknown step type: ${parent.type}`); } const parentContext = DefinitionContext.createForStep(parent, this.definition, this.definitionModel, this.definitionWalker, this.i18n); this.appendVariables(result, parent.id, parentModel.properties, parentContext); } } return result; } appendVariables(result, stepId, propertyModels, definitionContext) { for (const propertyModel of propertyModels) { const valueContext = ValueContext.createFromDefinitionContext(propertyModel.value, propertyModel, definitionContext, this.i18n); const definitions = propertyModel.value.getVariableDefinitions(valueContext); if (!definitions) { continue; } for (const definition of definitions) { result.push({ name: definition.name, type: definition.type, stepId, valueModelPath: propertyModel.value.path }); } } } } class DefinitionContext { static createForStep(step, definition, definitionModel, definitionWalker, i18n) { const parentsProvider = ParentsProvider.createForS