npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@bolttech/form-engine-core

v1.0.3

Published

Development maintenance documentation

Readme

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