@bolttech/form-engine-core

中文文档
1.2.3发布于 2026/10/02TypeScriptES Module近 30 天下载 1731

Development maintenance documentation

原始使用文档
文档目录17 节
  1. Arquitecture Overview
  2. FormGroup responsability
  3. FormCore responsability
  4. FormField responsability
  5. Changing private field class properties implementation
  6. Value change Rules
  7. Props change and visibility rules
  8. Error messages display
  9. subject dependencies
  10. field
  11. build fields with a schema
  12. fields
  13. templates
  14. build fields without schema
  15. form group event callback register
  16. form group onData
  17. form group onValid

Form Engine Core

Development maintenance documentation

  1. Arquitecture Overview
  1. Changing private field class properties implementation
  2. Value change Rules
  3. Props change and visibility rules
  4. Error messages display
  5. subject dependencies
  1. build fields with a schema
  1. build fields without schema
  2. form group event callback register

Arquitecture Overview

form-engine general arquitecture

Form-engine basic arquitecture is class based, composed by FormGroup, Form and Field classes and the communication is Observable Based in order to feed the adapters that will be the responsible to emit and recieve the reactive manipulations configured on the schema.

Each class is independent from bottom to top, meaning that you can instanciate a Field independently, but a Form relies on Fields to work, because a Form class manages Fields that interact with each others, same as FormGroup that relies on Forms in order to work.

FormGroup manages forms stored as a Map of key/value, same with Form that stores fields as a Map of key/value.

FormGroup responsability

  • manage the add and removal of forms by it's key
  • manage data inserted by user onto forms to provide callbacks to the adapter
  • manage validity of forms to provide callbacks to the adapter
  • work is in progress to check which functionalities it needs

FormCore responsability

  • manage the add and removal of fields by it's key
  • manage visibility of fields based on schema configuration
  • manage resetValues based on schema configuration
  • manage templating properties change emitted across fields
  • manage component configuration based on mapper list config
  • manage callback subscription on events emmited by fields (adapter external actions trigger)
  • manage form values submission and validity check

FormField responsability

  • manage storage and emission of properties such as validations, value, error messages
  • manage api schema configurations for requests
  • manage initialization of adapters external configuration binding generalization

Changing private field class properties implementation

On the process of implementation, it's crucial to understand that the reactivity of the form relies on RXJS, the way this properties change can cause side effects on other properties and all are managed by RXJS subjects, so for all this class properties, each time they change, they need to emit to the corresponding subject, so all this properties have a subject emmited on the end.

If you work in any maintenance involving this properties:

private _props: Record<string, unknown>;
private _value: unknown;
private _stateValue: Record<string, unknown>;
private _metadata: unknown;
private _visibility: boolean;
private _errors: TErrorMessages;
private _api: TApiResponse;
private _valid: boolean;
private _mounted: boolean;

they all have a get and set method constructors

The get constructor is responsible to deliver the value

The set constructor is responsible to set the value on this private properties along with emitting to the corresponding subject and the form template subject

private _stateValue: unknown;
private _metadata: unknown;
errorsString: string;
errorsList: string[];

This properties are computed based on other private properties,

_stateValue has the mask applied to the _value when set value method is called

_metadata has the metadata sent by the valueChangeEvent mapper function when set value is called

when set errors method is called, errorsString is the errors message list concatenated in a string, separated by commas , and errorsList is the list of the errors message

If you try to change this values directly without using it's constructors, it can cause serious problems on the form reactivity, even in a last resort scenario, evaluate cautiously if you should change this values directly on any maintenance or feature implementation

Value change Rules

If you manage to work on value change, take in consideration that the value change is treated as a special property on the configuration, let's take in consideration what happens on a component in react:

const [value, setValue] = useState('');

const handleChange = (e) => {
  const val = e.currentTarget.value;
  val ? (setValue = val) : '';
};

<input onChange={handleChange} value={value} />;

The value prop is binded to the value state and the onChange prop is binded to the function that will change this value.

In form-engine, the property that holds the value and the property that changes this value are:

class FormField
  private _value: unknown;
  (...)
  get value(): unknown {
    return this._value;
  }

  set value(value: unknown) {
    /*
      too much unstable, if the valueChangeEvent parses the template event
      value, might occur unexpected results
    */
    let val;
    if (this.valueChangeEvent) {
      try {
        val = this.valueChangeEvent(value, { props: this.props });
      } catch (e) {
        val = value;
      }
    } else {
      val = value;
    }
    if (typeof val === 'undefined' || val === null) return;
    if (typeof val === 'object' && '_value' in val && '_metadata' in val) {
      this._value = this.formatValue(val['_value']);
      this._stateValue = this.mapper.events?.setValue
        ? {
            [this.mapper.events.setValue]: this.maskValue(
              this.formatValue(val['_value'])
            ),
          }
        : {};
      this._metadata = val._metadata;
    } else {
      this._value = this.formatValue(val);
      this._stateValue = this.mapper.events?.setValue
        ? {
            [this.mapper.events?.setValue]: this.maskValue(
              this.formatValue(val)
            ),
          }
        : {};
      this.maskValue(this.formatValue(val));
      this._metadata = val;
    }
    this.stateValue && this.valueSubject$.next(this.stateValue);
    this.templateSubject$.next({ key: this.name, event: 'ON_VALUE' });
  }

In this case, the value passed to this setter method can have multiple formats (it's the valueChangeEvent mapper function that is responsible to pass the value on each of this formats):

value: unknown;

The value is formatted and stored onto fieldinstance._value and the value prop name with the value into fieldinstance._stateValue

{ _value: unknown, _metadata: unknown }

The _value key is formatted and stored onto fieldinstance._value and the value prop name with the value into fieldinstance._stateValue

The _metadata key is stored at fieldinstance._metadata as it is, and can be retrieved using fieldinstance.metadata readonly property

But, this change can have sideEffects defined on the Schema, like validations, visibilityConditions and so on, so proper way to change this value and trigger all side effects in order to update the component that is using this instance is using the fieldinstance.emitValue class function:

fieldinstance.emitValue({ event: 'ON_FIELD_CHANGE', value: 'hello' });

This function will set the value along with triggering all the side effects and triggers the onData callbacks defined:

  emitValue(prop: {
    value: unknown | { _value: unknown; _stateValue: unknown };
    event: TEvents;
  }): void {
    this.value = prop.value;
    this.emitEvents({ event: prop.event });
    this.dataSubject$.next({ key: this.name, event: prop.event });
  }
  emitEvents({ event }: { event: TEvents }): void {
    this.setFieldValidity({ event });
    this.validateVisibility({ event, key: this.name });
    this.resetValue({ event, key: this.name });
    this.apiEventQueueSubject$.next({ event });
    this.fieldEventSubject$.next({
      event,
      fieldName: this.name,
      fieldInstance: this,
    });
  }

To emit a capture of this value change, value setter emits on the end, setting the value on the configured property on the mapper -> events -> setValue when the value is being retrieved, an object is created with the correspondent key configured to be passed as props on the component.

Also, there is an helper function to help register this callback function that is responsible to change this value on the adapter called subscribeValue.

So, to compare the basic react implementation from a simple binded state along with a change handler to a form-engine controlled input handler, the implementation goes like this:

const [valueState, setValueState] = useState<Record<string, unknown>>(fieldinstance.stateValue || {});

fieldInstance = new FormField(
  (...)
  mapper: {
    events: {
      setValue: 'value'
    }
  }
  (...)
)

useEffect(() => {
    fieldInstance.subscribeValue((value) => setValueState(value));
    return () => {
      fieldInstance?.destroyField();
    };
  }, []);

  const handleChange = useCallback((event: unknown) => {
    fieldInstance?.emitValue({ value: event, event: 'ON_FIELD_CHANGE' });
  }, []);

return <input onChange={handleChange} {...valueState}>

The listener is registered when the field mounts calling subscribeValue on the field instance that will update the state.

The handleChange calls the field instance emitValue

The rest of the process to manage the validation, visibility and so on occurs internally on form-engine-core, the only thing that the adapter needs to do is inform the value that is set as input, the output is handled and delivered on the callback function defined on the effect.

Note: this is for demonstration purposes, the final implementation of the adapter only resembles to this

Props change and visibility rules

Props change can be defined on the schema or can be changed with templating, let's take into consideration what happens in react:

return <input label={props.label} placeholder={props.placeholder}>

React will rerender this component each time props are changed, in the case of form-engine, this props are passed on the schema and can have templating that will change based on any field side effect, what manages the props change is an RXJS subject, to properly register the props and let the form-engine manage the side effects, this is needed to be done:

const [state, setState] = useState<Partial<IState>>({
    visibility: fieldInstance?.visibility || true,
    props: fieldInstance?.props || props,
  });

useEffect(() => {
    fieldInstance.subscribeState((props) => {
        setState((prev) => ({
          ...prev,
          ...props,
        }))
    })

    return () => {
      fieldInstance?.destroyField();
    };
},[])

return {state.visibility && <input {...props}>}

Field instance has an helper function to register the callback function subscribeState to be called each time props change internally on form-engine, and on this callback function, you need to change the state of the props in order to occur onto the adapter, also the visibility control is made by the visibility prop given by the callback function, so the condition to show or hide the component is made onto the adapter depending on this prop value.

NOTE: trying to pass the value sending the value prop will not work except if this value is a template, also, templates that will change based on the same field will not work either

Note: this is for demonstration purposes, the final implementation of the adapter only resembles to this

Error messages display

For error message display, based on validation logic occuring in the field configured onto the schema, the component need to have a property configured onto the mappers configuration onto mappers -> events -> setErrorMessage, then, each time an error occurs, the field will automatically have an error displayed on the corresponding prop.

Example of a basic component in react:

const [errorMessage, setErrorMessage] = useState('')

return <Input errorMessage={errorMessage}>

In form-engine, the error message will be passed onto the subscribedState on errors with the prop name passed onto the mapper config and all the error messages that were triggered

Example:

fieldInstance = new FormField(
  (...)
  mapper: {
    events: {
      setErrorMessage: 'errorMessage'
    }
  }
  (...)
)

const [state, setState] = useState<Partial<IState>>({
    visibility: fieldInstance?.visibility || true,
    props: fieldInstance?.props || props,
  });

useEffect(() => {
    fieldInstance.subscribeState((props) => {
        setState((prev) => ({
          ...prev,
          ...props,
        }))
    })

    return () => {
      fieldInstance?.destroyField();
    };
},[])

return <Input {...state.errors}>

The error will contain an object with a key named errorMessage with all the errors that occured separated with a comma, the key configured onto the mapper varies based on the component props, if another component sets error messages on another prop, needs to be configured onto the mapper config onto setErrorMessage inside events.

Note: this is for demonstration purposes, the final implementation of the adapter only resembles to this

subject dependencies

field

templateSubject$: Subject<TTemplateEvent>;
fieldEventSubject$: Subject<TFieldEvent>;
dataSubject$: Subject<{ key: string; event: TEvents }>;
formValidNotification$: Subject<Pick<TFormValidationPayload, 'fieldTrigger'>>;

this subjects needs to be passed as a field constructor parameters, and the field invoker needs to instanciate them, they are part of the form instance implementation

templateSubject$

This subject needs to be invoked on any field mutation, this will notify the form that a field changed and any other field that has a template dependency needs to be recomputed,

Ex: field1 label props depends on field2 value, so: field1.props.label has a template ${field2.props.value}, each time field2 value changes, the templateSubject$ emits, the form gets the notification and checks that field2 has a field1 dependency and updates the label with the value of field2

fieldEventSubject$

Each time a basic event mapped from a component occurs like onChange or onBlur the field emits this subject with it's event type and field name, on form, if adapter invokes subscribeFieldEvent on an instanciated form with a callback function, this function is called, each time the field emitEvents is invoked, otherwise it's ignored

dataSubject$

This subject triggers each time emitValue is called, this subject handles field value changes on any event type, if the adapter invokes subscribeData on an instanciated form and passes a callback function, this functions will be executed each time a field emitValue is invoked

formValidNotification$

This subject triggers each time a field validation status change, if the adapter invokes subscribeFormValidation with a callback function on an instanciated form, this function will execute, each time a field validity changes, giving the form validity status

build fields with a schema

this is a brief explanation of how building fields with a schema works, the constructor method of the form takes care of the process if a schema is passed as a parameter

fields

every schema is parsed with the form instance serializeStructure method, this will pick all fields, instanciates all form fields on a map and they are ready to interact with the form with the subjects passed on the constructors, it's the responsability of the adapter to pick this fields map and build the component tree, you can check the react adapter BuildTree method

templates

to register the templates, the form instance subscribeTemplates needs to be invoked, this will create a list of dependencies to be checked each time a templateSubject$ is triggered, note that this needs to be called when all fields are instantiated, otherwise all templates might not be registered accordingly

build fields without schema

if you want to add a field without a schema, the form instance has addField method, this will add a field onto the form instance and handles a couple of pre requisites, like checking if the name doesn't exists and regists the templates, it's the responsability of the adapter to figure out how to render this field on

form group event callback register

FormGroup instance provides methods to add and remove form instances, also they let you regist onData and onValid events on form groups, the focus will be this two events that can be used by the adapters:

form group onData

formGroup onDataSubscription instance method let's you pass a list of indexes along with a callback function to be evoked each time any form instance emits onData, this is useful to handle groups of forms

form group onValid

formGroup onValidSubscription instance method let's you pass a list of indexes along with a callback function to be evoked each time any form changes it's validity status, this is useful to handle groups of forms, it will returns the form group validity status and each individual form validity status and it's triggered each time a form validity status changes

版本与发布记录

@bolttech/form-engine-core 共发布 110 个版本,首次发布于 2024/06/13;过去 12 个月发布了 13 个版本。

版本发布日期距今
1.2.3latest2026/10/021 个月前
1.2.22026/09/291 个月前
1.2.12026/08/242 个月前
1.2.02026/07/273 个月前
1.1.12026/06/254 个月前
1.1.02026/06/025 个月前

其他发布通道:beta → 1.1.0-beta.0。

关于 @bolttech/form-engine-core 的常见问题

@bolttech/form-engine-core 是什么?

Development maintenance documentation

如何安装 @bolttech/form-engine-core?

在项目目录运行 npm install @bolttech/form-engine-core,或使用 pnpm add / yarn add / bun add。

@bolttech/form-engine-core 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@bolttech/form-engine-core 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@bolttech/form-engine-core 可以商用吗?

@bolttech/form-engine-core 使用 未声明 许可。package.json 没有声明许可证。没有许可证的代码默认保留全部权利,商用前请联系作者或查看仓库中的 LICENSE 文件。以上为概括,不构成法律意见,请以许可原文为准。

@bolttech/form-engine-core 的下载量有多少?

根据 npm 官方下载统计,@bolttech/form-engine-core 在 2026/10/09 – 2026/11/07 这 30 天内被下载 1,731 次,日均约 58 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@bolttech/form-engine-core 还在维护吗?

最新版本 1.2.3 发布于 2026/10/02(1 个月前);至今共发布 110 个版本,其中过去 12 个月发布了 13 个;首次发布于 2024/06/13。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。