@msbci/form-core
v1.3.3
Published
Core form engine — types, condition evaluation, validation, navigation
Readme
@msbci/form-core
Pure TypeScript form engine — zero external dependencies. The logic core shared by all @msbci/form-* packages.
Installation
npm install @msbci/form-coreConcepts
The form hierarchy follows an explicit composition model:
IFormDefinition
└── pages: IFormPage[] ← Navigation units
├── variables: IFormVariable[] ← Input fields
└── rosters: IFormRoster[] ← Pilot-driven sub-sections
└── variables: IFormVariable[]- IFormPage — A navigation step containing fields and rosters
- IFormVariable — A single input field (text, number, date, select, etc.)
- IFormRoster — A repeating section driven by
pilotVariableCode(no add/remove — row count comes from a numeric variable) - IFormDefinition — The top-level form schema containing pages
Rosters are sub-components of pages, never at the same level. The relationship is modeled via composition (IFormPage.rosters), not parentId.
Engines
| Engine | Purpose |
|--------|---------|
| ConditionEngine | Evaluate ${VAR} expressions with 20+ built-in functions, caching, iteration support |
| ValidationEngine | Rule-based validation (required, min, max, pattern, email, url) + ConditionEval |
| NavigationEngine | Page ordering, visibility filtering, instance management, progress tracking |
| FormTree | Centralized visibility and jump management — evaluates all conditions in one pass |
| InstanceManager | Add/remove instances for repeatable pages with min/max constraints |
| DependencyResolver | Inter-variable dependency graph with transitive resolution |
| SchemaValidator | Design-time structural validation of form definitions |
Usage
import {
ConditionEngine,
FormTree,
NavigationEngine,
type IFormDefinition,
type IFormPage,
type IFormVariable,
} from '@msbci/form-core'
// Define a form
const form: IFormDefinition = {
id: 'demo',
code: 'REGISTRATION',
name: 'Registration Form',
version: '1.0.0',
isPublished: true,
pages: [
{
id: 'p1',
code: 'INFO',
name: 'Personal Information',
order: 0,
isRepeatable: false,
variables: [
{
id: 'v1', code: 'NAME', name: 'Full Name', type: 'text',
order: 0, isRequired: true, isReadonly: false, isHidden: false,
},
{
id: 'v2', code: 'AGE', name: 'Age', type: 'number',
order: 1, isRequired: true, isReadonly: false, isHidden: false,
},
],
rosters: [
{
id: 'r1', code: 'CHILDREN', name: 'Children',
rosterType: 'collection', order: 2,
pilotVariableCode: 'NB_CHILDREN',
variables: [
{
id: 'rv1', code: 'CHILD_NAME', name: 'Child Name', type: 'text',
order: 0, isRequired: true, isReadonly: false, isHidden: false,
},
],
},
],
},
],
}
// Evaluate conditions
const engine = new ConditionEngine({
AGE: { variableCode: 'AGE', value: 25 },
})
engine.evaluate('${AGE} >= 18') // true
engine.evaluate('show(${AGE} < 65)') // true
// Build form tree for visibility
const tree = new FormTree()
tree.build(form, { AGE: { variableCode: 'AGE', value: 25 } })
const visible = tree.getVisibleVariables('INFO')Variable Types
text · textarea · number · date · datetime · time · select · multiselect · checkbox · radio · file · gps · calculated · hidden · label
v1.3.3
- Multi-upload
file/image— nouveaux typesIFileConfig(maxFiles,minFiles,accept) etIImageConfig(maxImages,minImages,allowCamera) exposés surIFormVariable.fileConfig/imageConfigET surIVariableTemplate(donc surchargeable viaIVariableTemplateOverrides). - Contrat de valeur rétrocompatible :
maxFiles/maxImages === 1(ou absent) → la valeur reste unstringsimple. Au-delà de 1 →string[]. Aucune migration de schéma requise. - Nouveau
ValidationRuleType: 'minFiles'— règle explicite testant la longueur dustring[]. Une valeurstringlegacy compte comme 1 item (pas de cassure). - Garde implicite —
validateVariablelitfileConfig.minFiles/imageConfig.minImagesautomatiquement, sans avoir à wirer unevalidationRulesentry. - 9 nouveaux tests (
file-validation.test.ts) — 308 tests passants.
v1.3.2
- DataSourceConfig dynamique — nouveau type
IDataSourceConfigqui décrit une source de données stockée en base (code,url,method,queryParam,valueField,labelField,headersOverride,dependencies). Permet à l'admin de gérer ses connecteurs sans recompiler le host. IScopeHeaders— type de transport pour la réponse agrégéeGET /forms/:id/datasources({ scopeId, headers }). Les headers arrivent déjà décryptés côté renderer (le serveur fait le travail).IScope.headers?: Record<string, string>— surface optionnelle pour les GET de scope. Le champ reflète les headers décryptés (ou{}si la clé d'encryption manque).- Pas de breaking change — purement additif.
- 0 nouveau test côté core (la logique vit dans server/renderer/editor), 299 tests inchangés.
v1.3.1
IFormDefinition.scopeIds?: string[]— un formulaire peut désormais appartenir à plusieurs scopes, ordonnés par priorité (le premier scope l'emporte en cas de conflit decodelors de la résolution des templates).migrateFormScopeId(form)— migration transparente du champ legacyscopeId: stringversscopeIds: [string]. Câblée dansdeserializeForm: tout payload v1.3.0 est upgradé à la lecture.- Si
scopeIdetscopeIdssont tous deux présents,scopeIdsest conservé tel quel et le champ legacy est dropé. IFormDefinition.scopeIdest supprimé du type — utiliserscopeIds[].- 5 nouveaux tests (299 total)
v1.3.0
IScope,IVariableTemplate,IVariableTemplateOverrides— référentiel de variables réutilisables par scoperesolveVariableTemplate,resolveFormTemplates,collectTemplateIds— résolution Option B (in-memory)IFormPage.items[]— variables et rosters unifiés dans une liste ordonnée commune- Helpers
getPageVariables,getPageRosters,isPageVariable,isPageRoster,migratePageToItems,migrateFormToItems IFormVariable.templateId,templateOverrides— liaison optionnelle vers le référentielIFormDefinition.scopeId— appartenance d'un formulaire à un scope (remplacé parscopeIds[]en v1.3.1)- Compatibilité ascendante : les schémas legacy avec
variables[]+rosters[]sont migrés automatiquement à la désérialisation - 30 nouveaux tests (294 total)
v1.2.0
LocalizedString: support multilingue natif (string | Record<string, string>)resolveLabel,hasTranslation,getAvailableLangsIFormLangConfig+DEFAULT_LANG_CONFIGIFormDefinition.langConfig(stratégies :prop/browser/selector/auto)- 13 champs de schéma passés en
LocalizedString(name,description,placeholder,option.label,errorMessage,instanceLabel, ...) - Compatibilité ascendante : les schémas string simples existants fonctionnent sans modification
- 20 nouveaux tests (264 total)
What's new in v1.1.0
- New
RosterConditionEnginefor row-scoped condition evaluation with backward-jump, scope, self-reference and circular-dependency detection (DFS tricolor — beyond the original RSU baseline). - New
enrichResponsesWithContextutility resolves option labels, interpolates${VAR}placeholders in variable labels, and attaches page/roster context metadata to each response. FormTree.executeAutoActionsnow cascades until stable (max 10 iterations, guards against circular dependencies).VariableTypeextended with 4 new values:panel,richtext,listradio,photo.IFormRoster.collectionConfig?: { min?, max? }added for dynamic row counts.IFieldResponseMetadataextended with 8 optional fields (displayValue,variableLabel, page / roster context, ...).- All changes are backwards-compatible — optional types and new exports only.
License
Copyright (c) 2026 MOSOBI — All rights reserved. Commercial license required. Contact: [email protected]
