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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@moatamed-sce/form-schema

v0.1.5

Published

Angular-free JSON contract for SCE form definitions.

Readme

@moatamed-sce/form-schema

The JSON contract for a form on the SCE Platform. A form here is data, not TypeScript: a versioned document that can be stored, diffed, returned by an API and — eventually — written by a designer, because nothing in it is a function.

This package holds the contract, the version-migration chain, and the parser that decides whether an untrusted document may be trusted. It does not build a FormGroup and it does not render anything; those are @moatamed-sce/form-engine and the sce-dynamic-form organism in @moatamed-sce/ui-components.

Nothing in it imports @angular/*. That is the point rather than an accident: the same document has to be validatable on a server, previewable in a designer, and renderable in the app.

Status — not published

This package is not currently on npmjs.org. It exists in the SCE monorepo and in the build output, but no version has been pushed to the public registry — and neither has @moatamed-sce/form-engine, which declares this package as a peer. Inside the monorepo both resolve through TypeScript path aliases (@moatamed-sce/form-schema and @libs/form-schema both point at libs/form-schema/src/index.ts).

What's in it

The contractFormDefinition, and the node tree beneath it: FormField, FormGroupNode, FormArrayNode, FormPage, FormStep, plus FieldLayout, FieldPii, FormChrome and DataSourceRef. Narrowing helpers (isFormField, isFormGroupNode, isFormArrayNode) and flattenFormFields come with it.

Bilingual copyI18nText is { ar, en, key? } rather than a bare string. A Saudi government form renders in two languages, and a schema carrying one label: string pushes that problem onto every consumer.

Named validatorsNamedValidator is a name (saudiId, iban, saudiMobile05, min, pattern, …) that the engine resolves against libs/core/src/lib/validation/. A ValidatorFn is not serializable, so the moment one appears on a document the document stops being storable. SerializedPattern splits a regex into { source, flags } for the same reason.

Rules — a closed operator set (eq, neq, in, truthy, falsy, and, or) driving seven actions (show, hide, enable, disable, require, unrequire, readonly). Never a string expression: an expression language accepted from an API is an injection surface, and there is no eval or new Function anywhere in the platform.

VersioningFORM_DEFINITION_VERSION, the FORM_DEFINITION_MIGRATIONS chain and migrateFormDefinition. A version field with no migrator is a version field nobody can ever bump.

The trust boundaryparseFormDefinition / isFormDefinition. See below.

Legacy conversionfromLegacyConfig turns an existing DynamicFormConfig into a FormDefinition, reporting anything it could not represent instead of dropping it silently.

Reference fixturesREFERENCE_DEFINITIONS, and the four documents in it, each mirroring a real screen.

The trust boundary

Everything downstream — buildFormGroup, the rule evaluator, the renderer — is written against FormDefinition and trusts it completely. That trust is earned exactly once, in parseFormDefinition, or it is not earned at all.

import { parseFormDefinition } from '@moatamed-sce/form-schema';

const { definition, problems, fromVersion, migrations } = parseFormDefinition(await response.text());

if (!definition) {
  // Nothing throws. Every problem is located by a JSON path into the document:
  //   pages[0].nodes[2].validators[1].name: "sqlInject" is not a named validator …
  return renderError(problems);
}

It accepts a JSON string or an already-parsed value, migrates the document forward first, and checks:

| Check | What it stops | | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Forbidden keys and path segments | __proto__ / constructor / prototype as an object key, or as a segment of any path the engine walks — a rule's field, a rule's target, a data source's dependsOn | | Round-trip safety | Functions, Dates, RegExps, NaN, undefined inside an array — anything JSON.stringify would destroy or change | | Closed vocabularies | Field types, validator names, rule actions, condition operators, PII categories, data-source types — each against a Record<Union, true> table the compiler forces to stay complete | | Cross-references | A rule targeting a field that exists, a step naming a page that exists, a field naming a declared data source, a dependsOn naming a real control | | lookupKey is a key, never a URL | A document that could name its own endpoint would make the app fetch from anywhere | | Bounds | PATTERN_SOURCE_MAX_LENGTH (ReDoS), ARRAY_ITEMS_MAX (minItems is an allocation the document chooses), COL_SPAN_MAX, PAGE_COLUMNS_MAX, nesting depth | | Undeclared members | validatorz: [...] is valid JSON that silently does nothing — the renderer reads validators, finds nothing, and shows an unvalidated field. Every object is checked against a Record<keyof Interface, true> table, so the compiler keeps the tables complete. props is exempt: it is the renderer's open bag | | Spacing is a token | chrome.gap must be var(--…). --space-N is N pixels in this repository, so a raw "12px" reads as deliberate while meaning something else |

Nothing throws: a hostile or malformed document yields definition: null plus a list of problems, because the caller has an error state to render and an exception in a resolver just blanks the screen.

Peer dependencies

None. The package depends on tslib and nothing else — no Angular, no RxJS, no NgRx.

Quick start

Author a definition as data:

import { FORM_DEFINITION_VERSION, type FormDefinition } from '@moatamed-sce/form-schema';

const definition: FormDefinition = {
  id: 'account-user-verify',
  version: FORM_DEFINITION_VERSION,
  title: { ar: 'تحقق', en: 'Verify' },
  localeDefaults: { primary: 'ar', fallback: 'en' },
  pages: [
    {
      id: 'main',
      columns: 2,
      nodes: [
        {
          kind: 'field',
          id: 'fld-national-id',
          key: 'nationalId',
          type: 'text',
          label: { ar: 'رقم الهوية', en: 'National ID', key: 'dashboard.accountUsers.dialog.nationalId' },
          validators: [{ name: 'required' }, { name: 'saudiId' }],
          pii: { category: 'personal' },
        },
      ],
    },
  ],
  chrome: { showSubmit: true, gap: 'var(--space-12)' },
};

Reveal a field with a rule rather than a flag, so one condition can drive several targets:

const rules = [
  {
    id: 'reveal-cr',
    when: { op: 'and', clauses: [{ op: 'eq', field: 'entityType', value: 'company' }] },
    then: [{ action: 'show', target: 'crNumber' }],
  },
];
// and the field itself carries `hiddenByDefault: true` — otherwise a first render
// with no data shows every conditional branch at once.

Convert an existing DynamicFormConfig instead of rewriting it:

import { fromLegacyConfig } from '@moatamed-sce/form-schema';

const { definition, issues } = fromLegacyConfig(legacyConfig, 'lookup-create');

// A non-empty `issues` must fail your test rather than be ignored: it means a custom
// ValidatorFn could not be identified, or a value fell outside what the schema allows.
expect(issues).toEqual([]);

Conventions worth knowing

  • id and key are different. key is the FormControl name and the API field name — the contract with the backend. id is the node's stable identity for a designer's move/undo history, which has to survive renaming the key.
  • Key uniqueness is per scope, not global: applicant.name and a partners[] row's name are different controls. Pages share one namespace, because buildFormGroup merges them into a single flat group.
  • hiddenByDefault inverts the legacy showWhen. A conditional field starts absent and a rule reveals it.
  • props is the renderer's open bag of widget options (rows, calendarSystem, accept). Its contents are deliberately unchecked — but it must be an object, since it is read by key.

What this package deliberately does not do

  • Build a FormGroup, resolve a validator name, or evaluate a rule — that is @moatamed-sce/form-engine.
  • Render — that is sce-dynamic-form.
  • Fetch anything. A DataSourceRef names a lookupKey; the host resolves it.
  • Enforce PDPL policy. FieldPii is a place to record sensitivity so an inventory has something to read. Recording it is not a compliance claim.
  • Generate Angular, HTML or SCSS. The definition is the artefact; there is no codegen.

FormStep is declared and validated here. sce-dynamic-form exposes step / stepPlan; the form designer preview hosts sce-stepper when the definition declares steps.

Docs

License

MIT