@question-forms/core
v0.4.10
Published
Core engine for Question Forms SDK — schema types, validation, conditions, formatters
Maintainers
Readme
@question-forms/core
Core engine for the Question Forms SDK — schema types, validation, conditional logic, formatters, and value helpers.
Zero dependencies. Works with any JavaScript runtime.
Installation
npm install @question-forms/coreQuick Start
import {
resolveVisibility,
validateForm,
setValue,
} from '@question-forms/core';
import type { FormSchema, FormValues } from '@question-forms/core';
const schema: FormSchema = {
id: 'my-form',
sections: [
{
id: 'section-1',
questions: [
{
id: 'q1',
key: 'name',
type: 'TEXT',
subtype: 'short',
label: 'Your Name',
required: true,
},
],
},
],
};
let values: FormValues = {};
values = setValue(values, 'name', 'Alice');
const visibility = resolveVisibility(schema, values);
const result = validateForm(schema, values, visibility);
console.log(result.valid); // trueFeatures
- Schema-driven — define forms as plain JSON objects
- 10 question types — text, date, time, select, rating, image, handwriting, radio grid, checkbox grid
- Conditional visibility — show/hide sections and questions based on answers
- Enabled state — enable/disable questions based on conditions
- Validation — required, min/max length, min/max value, regex pattern, custom rules
- Formatters — currency and percentage formatting
- Immutable value helpers — get, set, reset, dirty tracking
- License system — built-in plan-based licensing for commercial distribution
- Zero dependencies — pure TypeScript, works anywhere
- Tree-shakeable — ESM + CJS dual output
Question Types
| Type | Subtypes / Variants | Key Features |
|------|---------------------|--------------|
| TEXT | short, long, formatted | Min/max length, currency/percentage formatting |
| DATE | full-date, year, month | Min/max date constraints |
| TIME | — | Min/max time constraints |
| DATE_TIME | — | Combined date + time with min/max |
| SELECT | single-select, multi-select, dropdown | Radio, dropdown, checkbox, autocomplete variants; static or async data sources |
| HANDWRITING | — | Output as base64, blob, or URI |
| RATING | smile, star, heart | Configurable max value |
| IMAGE | — | Multiple files, max count, accepted formats |
| RADIO_GRID | — | Row/column matrix, mutually exclusive per row |
| CHECKBOX_GRID | — | Row/column matrix, multiple selections per row |
API Reference
Schema Types
import type {
FormSchema,
FormSection,
QuestionSchema,
TextQuestionSchema,
DateQuestionSchema,
TimeQuestionSchema,
DateTimeQuestionSchema,
SelectQuestionSchema,
HandwritingQuestionSchema,
RatingQuestionSchema,
ImageQuestionSchema,
RadioGridQuestionSchema,
CheckboxGridQuestionSchema,
SelectOption,
SelectDataSource,
TextFormat,
TextSubtype,
TextFormatKind,
DateSubtype,
SelectSubtype,
SelectVariant,
RatingVariant,
} from '@question-forms/core';Conditions & Visibility
import {
evaluateCondition,
evaluateRuleGroup,
resolveVisibility,
resolveEnabled,
} from '@question-forms/core';
import type {
Condition,
Comparator,
VisibilityRule,
EnabledRule,
FormValues,
VisibilityResult,
EnabledResult,
} from '@question-forms/core';Comparators: equals, notEquals, includes, notIncludes, isEmpty, isNotEmpty, greaterThan, lessThan
Rule groups combine conditions with AND / OR operators and can be nested.
const visibility: VisibilityRule = {
operator: 'AND',
conditions: [
{ questionKey: 'role', comparator: 'equals', value: 'manager' },
{ questionKey: 'experience', comparator: 'greaterThan', value: 5 },
],
};
const result = evaluateRuleGroup(visibility, values); // true/falseValidation
import { validateQuestion, validateForm } from '@question-forms/core';
import type { ValidationRule, ValidationError, FormValidationResult } from '@question-forms/core';Validation rule types: required, minLength, maxLength, min, max, pattern, custom
const visibility = resolveVisibility(schema, values);
const result = validateForm(schema, values, visibility);
if (!result.valid) {
console.log(result.errors);
// [{ questionId: 'q1', questionKey: 'name', rule: 'required', message: '...' }]
}Formatters
import { formatTextValue, parseFormattedNumber } from '@question-forms/core';
formatTextValue(1234.5, { kind: 'currency' }); // "$1,234.50"
formatTextValue(0.85, { kind: 'percentage' }); // "85%"
parseFormattedNumber('$1,234.50'); // 1234.5Value Helpers
All value operations return new objects (immutable).
import {
getValue,
setValue,
setValues,
resetValues,
getDirtyKeys,
isDirty,
} from '@question-forms/core';
let values: FormValues = {};
values = setValue(values, 'name', 'Alice');
values = setValues(values, { name: 'Bob', age: 30 });
getValue(values, 'name'); // 'Bob'
const initial = { name: 'Alice' };
getDirtyKeys(values, initial); // ['name', 'age']
isDirty(values, initial); // true
values = resetValues(values, initial, ['name']); // resets only 'name'License System
import {
validateLicense,
hasAccess,
generateLicenseKey,
} from '@question-forms/core';
import type { LicensePlan, LicenseInfo } from '@question-forms/core';Plans (ascending): FREE → BUILDER → PRO → ENTERPRISE
const key = generateLicenseKey('PRO', 365);
const info = validateLicense(key);
// { valid: true, plan: 'PRO', expired: false }
hasAccess(info, 'BUILDER'); // true — PRO includes BUILDER
hasAccess(info, 'ENTERPRISE'); // falseConditional Visibility Example
const schema: FormSchema = {
id: 'survey',
sections: [
{
id: 'followup',
visibility: {
operator: 'AND',
conditions: [
{ questionKey: 'interested', comparator: 'equals', value: 'yes' },
],
},
questions: [
{
id: 'q-details',
key: 'details',
type: 'TEXT',
subtype: 'long',
label: 'Tell us more',
required: true,
},
],
},
],
};
const visibility = resolveVisibility(schema, { interested: 'no' });
// visibility.sections['followup'] === false
// visibility.questions['q-details'] === falseLicense
MIT — Question Forms SDK
