@dynamic-entity/core
v2.1.0
Published
Shared model + framework-agnostic form logic for the dynamic-entity ecosystem.
Maintainers
Readme
@dynamic-entity/core
Framework-agnostic core models, pure form logic, rules evaluation engine, and field type vocabulary for the @dynamic-entity ecosystem.
Contains no Angular and no RxJS — it is plain TypeScript and can be used from any framework, or on a server.
📦 Installation
npm install @dynamic-entity/core✨ Features
- Nested entity form model (
EntityFormConfig) — tabbed hierarchies, sub-tabs, nested groups, arrays, and field table display metadata. - Pure form logic — label resolution, display value formatting (
setDateFormattersfordate/datetime/time), nested data access, and masking, all as side-effect-free functions. - Rules engine — condition evaluation over 18 operators (
EQUAL,NOT_EQUAL,CONTAINS,NOT_CONTAINS,STARTS_WITH,ENDS_WITH,IS_EMPTY,IS_NOT_EMPTY,LESS_THAN,MORE_THAN,LESS_THAN_EQUAL,MORE_THAN_EQUAL,DATE_BEFORE,DATE_AFTER,IN,NOT_IN,HAS_ITEMS,VALUE_CHANGED) producing three action types:visibility,validation, andinfo. - Canonical field catalog —
FIELD_TYPE_CATALOGis the single source of truth for the 21 field type keys (text,textarea,markdown,number,currency,email,password,date,datetime,time,monthYear,dropdown,radio,checkbox,boolean,multiSelect,entity-ref,group,array,image,file), consumed by both the renderer and the builder. - Entity reference contracts —
EntityReferenceLoader, option normalisation, and pure cascade filtering (lookupFilter/lookupPath). - File contracts — canonical
FileRefandFileUploadHandler, shared by the image and file field types. - Config validation —
validateConfigchecks structure, field types against the catalog, ids unique per scope, and references that would never resolve — including bracketed field paths and, when passed,FormRules. A JSON Schema for editor completion ships alongside it at@dynamic-entity/core/schema. The same check is thedynamic-entity validatecommand, for gating configs in CI. - Record migration —
migrateRecord,needsMigration,stampRecordandvalidateMigrationsmove a saved record forward as a config'sversionchanges. Pure, so the same steps run in the browser and on a server.
🚀 Quick Start
import {
evaluateFormRules,
resolveLabel,
FIELD_TYPE_CATALOG,
type FormRule,
} from '@dynamic-entity/core';
// 1. Resolve a localized label
const label = resolveLabel({ en: 'First Name', de: 'Vorname' }, 'en'); // "First Name"
// 2. Inspect the field type vocabulary
console.log(FIELD_TYPE_CATALOG.length); // 21
// 3. Raise an info banner on the `annualBudget` field when it exceeds 5,000,000
const rules: FormRule[] = [
{
formConfigId: 'client',
fieldId: 'annualBudget',
conditions: [{ operator: 'MORE_THAN', value: 5_000_000, compareType: 'value' }],
action: { type: 'info', value: 'Budget exceeds $5,000,000' },
targets: [{ id: 'annualBudget', type: 'field' }],
enabled: true,
priority: 0,
},
];
const result = evaluateFormRules(rules, { annualBudget: 6_000_000 });
console.log(result.infoBanners); // { annualBudget: 'Budget exceeds $5,000,000' }evaluateFormRules returns a RuleEvaluationResult:
interface RuleEvaluationResult {
hiddenFields: string[]; // field ids hidden by a visibility rule
hiddenTabs: string[]; // tab ids hidden by a visibility rule
validationErrors: Record<string, string>; // target id → message
validationWarnings: Record<string, string>; // target id → message
infoBanners: Record<string, string>; // target id → message
}Each map is keyed by the target id the rule points at, not by rule id. Pass a baseline record as the third argument to enable the VALUE_CHANGED operator:
import { evaluateFormRules, type FormRule } from '@dynamic-entity/core';
declare const rules: FormRule[];
declare const currentValues: Record<string, unknown>;
declare const originalValues: Record<string, unknown>;
const changed = evaluateFormRules(rules, currentValues, originalValues);Date display
formatDisplayValue formats date, datetime and time through the runtime's locale
(toLocaleDateString and friends), not the form's language. language selects which
LocalizedText key to read; tying the two would change the punctuation for every consumer
whose browser (or Node Intl) is set to something else.
import { setDateFormatters } from '@dynamic-entity/core';
setDateFormatters({
date: (value, lang) => value.toLocaleDateString(lang ?? []),
});Every caller of formatDisplayValue honours it — which, in the renderer, means every
read-only date, datetime and time field as well as the record summary.
A partial object overrides one kind and leaves the rest. setDateFormatters() with no
argument restores the defaults. It is module-level rather than an injection token because
formatDisplayValue is a pure function — the renderer, the builder and the CLI all call it,
and only one of those has an injector.
Validating a config
validateConfig is the API. The package also ships a dynamic-entity bin so the same
check can run in CI without writing a script:
npx dynamic-entity validate ./form-config.json--additional-field-types signature,rating is the command-line form of
additionalFieldTypes. --rules rules.json is a FormRule[] checked against the same
path/id rule as showWhen. --fail-on-warnings treats a warning as a failure. Exit 0
means no errors, 1 means the config is unusable, 2 means the file or the JSON itself is.
