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

@wral/form

v0.1.0

Published

Lit web component for standalone form pages on WRAL sites: renders an api-forms document as a plain accessible form, submits it anonymously (e-mail only, no My WRAL account), and shows the confirmation. First page: Brain Game Registration.

Readme

<wral-form>

Lit web component for standalone page forms on WRAL sites: one api-forms document rendered as a plain, accessible form and submitted anonymously, with no My WRAL account. The first page is Brain Game Registration, which replaces the legacy Diesel form at legacy.wral.com/wral-tv/page/2503123/ and keeps its seven fields.

It is deliberately not Report It. Report It (wral-report-it) is the guided newsroom intake with sign-in, media and a Studio queue. A page form is a registration, sign-up or contact form that lives on its own page, is e-mailed to one team, and must never change how Report It looks or works. api-forms guarantees that with the standalone flag (below).

Usage

From the CDN (self-contained, registers the element):

<script type="module"
	src="https://cdn.wral.com/@wral/form/v0/define.standalone.js"></script>
<wral-form form-id="braingame" api="https://api.wral.com/forms" noun="registration"></wral-form>

From npm:

import '@wral/form/define';        // registers wral-form
// or
import { WralForm } from '@wral/form';

Pre-size it in the host page's <head> to avoid layout shift: <style>wral-form { display: block; min-height: 60vh; }</style>.

Attributes and properties

| Name | Kind | Default | Purpose | | --- | --- | --- | --- | | form-id | attribute | — | The api-forms document to render and submit (its formId) | | api | attribute | https://api.wral.com/forms | api-forms base URL (dev: https://api.wral.com/dev/forms) | | noun | attribute | form | What one submission is called in copy: "registration", "entry", "request" | | submit-label | attribute | Submit | The submit button's text | | save-content | attribute | false | true asks api-forms to write a content-stage record as well as e-mailing. Leave it off for page forms: a record would appear in the Report It queue | | formsClient | property | — | Any object with getForm, createAnonSession, putRequest (see src/lib/forms-client.mjs); wins over api. The demo client implements it offline |

Events: wral-form:submitted ({formId, requestId, reference}) and wral-form:error ({formId, kind, status}), both bubbling and composed.

Styling: design tokens are read as var(--wral-…, fallback), the same layer the Report It page inlines, with literal fallbacks, so the form renders correctly with no token stylesheet on the page. --wral-form-max-width overrides the column width (default 680px).

How it works

The fields come from the document. On load the component fetches GET {api}/v1/forms/{formId} — the single-form read api-forms added for page forms — and walks the document (src/lib/flow.mjs) exactly as the server re-walks it at submit: each collect node is a fieldset of typed fields (text, textarea, select, consent, datetime, url, location), each choice node a radio group (a select past eight options) whose answer extends the path, a gate is passed through, a redirect terminal is shown as a notice. Labels, limits, patterns, autocomplete tokens and the confirmation copy all come from the document, and the server validates against the same document. A document with a media field is refused: an anonymous, unsaved submission can carry no files, and uploads belong to Report It.

The response also says what the stage allows. The read carries the same anonymous: {enabled, humanCheck, saveContent} block as the listing. When anonymous submission is off, the form's allowAnonymous is false, or a proof-of-human provider is required (this component draws no captcha widget yet), the page says the form is unavailable at load rather than at submit.

Client-side validation mirrors the server (src/lib/validate.mjs): required fields with the field's label in the message, lengths from the document, pattern on text fields, option membership on selects, consent must be checked, a real web address, a date and time. Errors render inline under the field, the first invalid control takes focus, and a server 400 that names a field is pointed back at that field.

Submit is api-forms' anonymous flow (DEV-1270):

  1. Mint a uuid requestId once per submission and keep it across retries.
  2. POST /v1/anon/sessions with {formId, requestId, saveContent: false} → a session token, cached until shortly before it expires.
  3. PUT /v1/requests/{requestId} with the token and the keyed answers for the walked path. The response carries the RI-… reference and the confirmation copy the done view shows, with a summary of the answers.

With saveContent: false nothing is written to the content stage: the submission is validated, rate-limited and e-mailed like a Report It submission, and the desk e-mail is its only copy. A delivery failure comes back as a 502 the visitor retries with the same requestId, and a retry sends a fresh e-mail. A 401 re-mints the session once and retries; a 409 (the document changed) refetches it and asks the visitor to check their answers; a 429 shows the server's message. "Submit another" mints a new requestId.

The api-forms contract for page forms

A page form is an ordinary form document with standalone: true, allowAnonymous not false, no gate node and budget.maxFiles: 0. api-forms keeps standalone forms out of GET /v1/forms, which is everything the Report It component knows about (entry screen, hand-offs, ?flow= links, drafts), and serves them by GET /v1/forms/{formId} alone. Its route may carry program and noun so the desk e-mail reads "[Brain Game] … registration" rather than "Report It submission". See api-forms' README, "Standalone page forms".

Adding a page form: a seed document and a route in api-forms, a Sailthru list for the team, and a page carrying <wral-form form-id="…">. Nothing about Report It changes.

Development

Requires Node 18+.

npm install
npm run dev     # harness on http://localhost:5176
npm test        # node --test over src/lib
npm run lint
npm run build   # dist/index.mjs + dist/define.mjs (lit external)
                # and dist/define.standalone.js (self-contained, for <script src>)

The harness (index.html) mounts the component against the in-memory demo client (src/demo/demo-client.mjs), which serves the Brain Game fixture (src/demo/fixtures/form-braingame.json, a copy of api-forms' seed) and logs submits to the console. ?demofail=1 makes the first send fail as a 502, ?demo401=1 expires the session once, ?anonoff=1 shows the stage-has-anonymous-off state. ?api=<base> talks to a real stage instead, for example a local api-forms at http://localhost:3011/forms or the dev stage at https://api.wral.com/dev/forms.

Release

Push a vX.Y.Z tag: the pipeline tests, builds, publishes to npm and uploads dist/ to cdn.wral.com/@wral/form/vX.Y.Z/ (immutable) plus the rolling aliases /vX/, /vX.Y/ and /latest/ (5-minute TTL). Repo variables: NPM_TOKEN, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_DEFAULT_REGION, S3_BUCKET=cdn.wral.com, S3_PREFIX=@wral/form, AWS_DISTRIBUTION_ID.

Published pages pin the exact version, so a release reaches a page only once its pin is bumped and the page republished.

Pages

pages/brain-game-registration.html is the wral.com page. It pins the component to an exact release and points api at the prod api-forms stage. Like the Report It page, it is a publisher publication whose template is an HTML asset in the editorial DAM (content: {}, template: {"$ref": "https://assets.wral.com/<asset id>"}).

| Page | api-forms stage | DAM asset | | --- | --- | --- | | www.wral.com/brain-game-registration/ (site wral.com) | prod (/forms) | not yet uploaded | | /test/brain-game-registration/ (site staging.wral.com) | dev (/dev/forms) for rehearsal | not yet uploaded |

To publish or change a page:

  1. Upload the HTML as a new asset: POST https://api.wral.com/dam/v1/asset/<new uuid>/upload-url with dam:write, then PUT the file with Content-Type: text/html.
  2. POST https://api.wral.com/publisher/v1/publications with publisher:write: {site, path, content: {}, template: {"$ref": …}, metadata: {httpStatusCode: 200}}.
  3. Invalidate the path on the site's CloudFront distribution (E2YSTU8KLPZ0ZR for wral.com, E2PUK7RNR73BP4 for staging).
  4. For the legacy path, a redirect publication: {site: "wral.com", path: "/wral-tv/page/2503123/", content: {url, title, description}, template: {"$ref": "https://api.wral.com/template/v1/templates/generic.redirect?satisfy=1.*"}, metadata: {httpStatusCode: 301, headers: [["Location", url]]}}.

Leave the previous asset in place. Republishing with its $ref is the rollback.

Before the page works on a stage

  • api-forms with the single-form read and the standalone exclusion deployed, with ANON_SUBMIT_ENABLED on (prod has had it since 2026-09-29).
  • api-content with the standalone flag on the form model, so the seed is accepted.
  • The braingame document seeded into that stage's content stage (node scripts/seed-forms.mjs braingame in api-forms).
  • The route's Sailthru list (reportit-braingame) with [email protected] subscribed, or the stage's list override for dev.

Until then the page says the form is temporarily unavailable.