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

@frt-platform/report-core

v1.5.1

Published

Core report template engine: schema, validation, normalization, and migration.

Readme

@frt-platform/report-core

Core engine for defining, validating, migrating, diffing, and serializing dynamic report templates.

This package is:

  • 🧠 Framework-agnostic
  • 🧩 UI-agnostic
  • 🔒 Safe by design (Zod-based)
  • 🧱 The foundation of the FRT incident & reporting platform

It contains no React, no database code, and no styling.
You can use it in Node, Next.js, backend services, or custom form engines.


✨ Features

📄 Template Schema

  • Sections → fields

  • Field IDs, labels, descriptions, placeholders

  • Built-in field types:

    • shortText, longText
    • number
    • date
    • checkbox
    • singleSelect, multiSelect
    • repeatGroup (nested fieldsets)

🎛 Field Constraints

  • min/max length (shortText, longText)
  • min/max value (number)
  • min/max selections (multiSelect)
  • allowed options
  • checkbox required semantics
  • default values

🔀 Conditional Logic

  • visibleIf
  • requiredIf
  • Supports: equals, any, all, not
  • Fully integrated into validation.

🔥 Validation Engine

  • Build response schema dynamically with conditions

  • DX helper: buildResponseSchema(template) (no response needed)

  • Throwing API: validateReportResponse()

  • Non-throwing API: validateReportResponseDetailed()

  • Rich ResponseFieldError objects with:

    • section title
    • field label
    • error code
    • full message
    • nested repeatGroup row context

🔄 Template Migration & Normalization

  • Legacy format migration (fields → sections)
  • Automatic ID normalization & uniqueness enforcement
  • Stable "lowercase_with_underscores"-style IDs
  • Fallback IDs for missing values: section_1, field_1, etc.

🔍 Schema Diffing

Detect:

  • Added / removed / reordered sections
  • Added / removed / reordered fields
  • Modified fields
  • Nested diffs inside repeat groups

📦 JSON Schema Export

  • Export a template as a valid JSON Schema (2020-12 draft)

  • Includes vendor extensions:

    • x-frt-visibleIf
    • x-frt-requiredIf
    • x-frt-minIf / x-frt-maxIf for repeatGroup
    • placeholders
  • Useful for OpenAPI, Postman, or other backend runtimes.

🏗 Field Registry

Extend the system at runtime:

  • Add custom types (richText, fileUpload, etc.)
  • Override validation logic
  • Provide metadata for UI packages
  • Unknown/unregistered field types safely fall back to z.any() so they never break the core engine.

🧬 Type Inference

Get a fully typed response type from a template:

type MyResponse = InferResponse<typeof template>;

🧾 Serialization Helpers

Deterministic JSON output with sorting options:

serializeReportTemplateSchema(template, {
  pretty: true,
  sortSectionsById: true,
  sortFieldsById: true,
});

Perfect for Git diffs and storage.


📦 Installation

npm install @frt-platform/report-core zod

zod is a peer dependency.


🧹 Parsing & Normalization

The core exposes helpers for safely parsing, migrating, and normalizing templates.

import {
  parseReportTemplateSchema,
  parseReportTemplateSchemaFromString,
} from "@frt-platform/report-core";

// From a raw JS object (e.g. loaded from DB, file, etc.)
const template = parseReportTemplateSchema(rawTemplateObject);

// From a JSON string
const templateFromString = parseReportTemplateSchemaFromString(jsonString);

Under the hood, parseReportTemplateSchema does:

  1. Legacy migration

    • Accepts both the new { sections: [...] } format and legacy flat:

      {
        version: 1,
        fields: [ /* ... */ ]
      }
    • Legacy fields are automatically wrapped into a single section_1.

  2. Schema validation

    • Uses a Zod validator for ReportTemplateSchema to ensure the template is structurally valid.
  3. Normalization

    • Section and field IDs are normalized to lowercase with spaces → underscores:

      • "My Field!""my_field"
    • Invalid characters are stripped (only [a-z0-9_-] are kept).

    • IDs are made unique per namespace by appending -1, -2, … as needed:

      • my_field, my_field-1, my_field-2, …
    • Missing IDs get deterministic fallbacks:

      • Sections: section_1, section_2, …
      • Fields: field_1, field_2, …

This makes templates safe to store, diff, and round-trip in a stable way.


🚀 Quickstart

1. Define a template

import type { ReportTemplateSchema } from "@frt-platform/report-core";

const template: ReportTemplateSchema = {
  version: 1,
  sections: [
    {
      id: "general",
      title: "General Info",
      fields: [
        {
          id: "title",
          type: "shortText",
          label: "Incident title",
          required: true,
        },
        {
          id: "severity",
          type: "singleSelect",
          label: "Severity",
          required: true,
          options: ["Low", "Medium", "High"],
        },
        {
          id: "details",
          type: "longText",
          label: "Details",
          minLength: 10,
        },
      ],
    },
  ],
};

2. Validate a response (throwing API)

import { validateReportResponse } from "@frt-platform/report-core";

const parsed = validateReportResponse(template, {
  title: "Broken fire alarm",
  severity: "High",
  details: "Triggered after smoke test",
});

If invalid → throws a ZodError.


3. Validate without throwing (UI-friendly)

import { validateReportResponseDetailed } from "@frt-platform/report-core";

const result = validateReportResponseDetailed(template, {
  title: "",
  severity: "High",
});

if (!result.success) {
  console.log(result.errors);
}

Produces:

[
  {
    fieldId: "title",
    sectionId: "general",
    sectionTitle: "General Info",
    label: "Incident title",
    code: "field.too_small",
    message:
      'Section "General Info" → Field "Incident title": String must contain at least 1 character(s).'
  }
]

🔀 Conditional Logic Example

{
  id: "follow_up_notes",
  type: "longText",
  label: "Follow-up notes",
  visibleIf: { equals: { follow_up_required: true } },
  requiredIf: { equals: { follow_up_required: true } },
}

Behavior:

  • If follow_up_required = false → field is hidden and ignored
  • If true → field becomes required

🔁 Repeat Group Example

{
  id: "injured",
  type: "repeatGroup",
  label: "Injured people",
  min: 1,
  max: 5,
  fields: [
    { id: "name", type: "shortText", label: "Name", required: true },
    { id: "injury", type: "longText", label: "Injury description" }
  ]
}

Response shape:

injured: Array<{ name: string; injury?: string }>;

📝 repeatGroup behavior & limitations

  • Base constraints

    • min / max are always enforced on the row array.
    • Each row is an object keyed by nested field IDs.
  • Conditional minIf / maxIf

    • If minIf / maxIf are present, they are evaluated against the current response.
    • When the condition is true, the conditional value overrides the static min / max for that validation pass.
    • When the condition is false, the engine falls back to the static min / max (if any).
  • Conditional logic inside rows

    • Nested fields in a repeatGroup support the same visibleIf / requiredIf semantics as top-level fields.
    • Hidden nested fields are treated as optional and are stripped from the parsed response, just like hidden top-level fields.
    • For now, row-level conditions see the full response object, not just the row. This matches top-level behavior and keeps the logic model simple.
  • JSON Schema export

    • repeatGroup constraints and conditions are exported via x-frt-* vendor extensions (e.g. x-frt-minIf, x-frt-maxIf, x-frt-visibleIf, x-frt-requiredIf), so you can mirror this behavior in other runtimes.

🧩 Field Registry (Custom Types)

import { FieldRegistry } from "@frt-platform/report-core";
import { z } from "zod";

FieldRegistry.register("richText", {
  defaults: { label: "Details" },
  buildResponseSchema(field) {
    let schema = z.string();
    if (field.minLength) schema = schema.min(field.minLength);
    return field.required ? schema : schema.optional();
  },
});

Now templates may include fields like:

{ id: "body", type: "richText", label: "Report body" }

If a template uses a field type that is not registered and not one of the built-in core types, the engine safely falls back to z.any() so unknown types never crash validation.


🧾 JSON Schema Export

import { exportJSONSchema } from "@frt-platform/report-core";

const jsonSchema = exportJSONSchema(template);

Produces JSON Schema with:

  • field types
  • enums
  • min/max constraints
  • default values
  • conditional logic preserved as custom x-frt-* properties

🔍 Diff Templates

import { diffTemplates } from "@frt-platform/report-core";

const diff = diffTemplates(oldTemplate, newTemplate);

Detects:

  • added/removed/reordered sections
  • added/removed/reordered fields
  • modified fields
  • nested diffs for repeat groups

Perfect for:

  • Version history
  • Audit logs
  • Template editing UI

🧬 Type Inference

Given a template:

export const myTemplate = {
  version: 1,
  sections: [
    {
      id: "s",
      fields: [
        { id: "title", type: "shortText", required: true },
        { id: "tags", type: "multiSelect", options: ["A", "B"] },
      ]
    }
  ]
} as const;

Infer response type:

type MyResponse = InferResponse<typeof myTemplate>;

Produces:

type MyResponse = {
  title: string;
  tags?: ("A" | "B")[];
};

🧾 Serialization

import { serializeReportTemplateSchema } from "@frt-platform/report-core";

const json = serializeReportTemplateSchema(template, {
  pretty: true,
  sortSectionsById: true,
  sortFieldsById: true,
});

Useful for deterministic output in Git and stable diffs across environments.


🧱 Roadmap

Phase 1 — Core Maturation (✔️ COMPLETE)

  • Validation
  • Conditional logic
  • Diffing
  • Field Registry
  • Error helpers
  • Serialization features
  • Parsing & normalization helpers

Phase 2 — Advanced Field System (IN PROGRESS)

  • Richer repeatGroup UX
  • Computed fields (design)
  • RichText / FileUpload via registry

Phase 3 — Reactions & Analytics (Planned)

  • Scoring rules
  • Auto-tagging
  • Suggested outcomes

Phase 4 — React UI Package (Planned)

  • Form renderer
  • Template builder
  • Field palette
  • Full ShadCN integration

📄 License

MIT — feel free to use, extend, or contribute.