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

@devfellowship/ux-paths-spec

v1.3.1

Published

The DFL UX Paths data contract: the v1 JSON Schema, TypeScript types generated from it, and a validate() with zero runtime dependencies and zero network I/O.

Readme

@devfellowship/ux-paths-spec

The DFL UX Paths data contract, on its own: the v1 JSON Schema, TypeScript types generated from it, and a validate() with zero runtime dependencies and zero network I/O.

pnpm add @devfellowship/ux-paths-spec
import { validate, assertValid, SCHEMA_V1 } from "@devfellowship/ux-paths-spec";
import type { UxPathsDoc } from "@devfellowship/ux-paths-spec";

const result = validate(JSON.parse(text));
if (!result.valid) {
  for (const error of result.errors) console.error(error.formatted);
  //  screens[0].id: must be string (expected type: string)
  process.exit(1);
}
result.doc.screens; // typed as UxPathsDoc

// Or the throwing form, which narrows in place.
assertValid(doc, "itera-player.flows.json");

Why this is a separate package

Its consumers are a CI validator, a capture job and an AI explorer. None of them should need React to find out whether a flows.json is well-formed, and one of them runs in a browser. The schema is a data contract, so it ships as data — not folded into a component library.

It also ends a vendored copy kept in step by a promise. dfl-components-cli carries its own copy of v1.schema.json with a comment asking a human to "mirror it in the same round". A promise fails quietly: the copy goes stale, the validator keeps exiting 0, and a document nobody should have accepted sails through. Here, every artifact that restates the schema is generated from it, and CI fails if any of them stops matching.

What it exports

| Export | What it is | |---|---| | validate(doc, options?) | { valid: true, doc } or { valid: false, errors }. No I/O. Synchronous. | | assertValid(doc, location?, options?) | Throws SpecValidationError; narrows doc to UxPathsDoc on success. | | formatErrors(errors) | One human-readable line per violation. | | SCHEMA_V1 | The schema itself, as an object. | | SCHEMA_ID | The schema's $id. A name, not an address — see below. | | SUPPORTED_SCHEMA_VERSIONS | ["1.0.0", "1.1.0", "1.2.0", "1.3.0"]. | | UxPathsDoc, Screen, Flow, Screenshot, SourceRef, … | Types generated from the schema. | | stepScreenId, flowScreenIds, danglingScreenIds | Helpers for rules JSON Schema cannot express. | | assertSchemaSupported, isDateTime, isUri | Internals, exported for testing. |

The raw schema also ships in the tarball, for consumers that are not TypeScript:

import schema from "@devfellowship/ux-paths-spec/schema/v1.json" with { type: "json" };

The $id is a name, not an address

SCHEMA_ID is https://raw.githubusercontent.com/devfellowship/dfl-ux-paths/main/schema/v1.json.

dfl-ux-paths is a private repo, so that URL 404s for anyone outside the org — and GitHub reports a private path as not found, never forbidden, so the failure disguises itself as a broken link. The $id identifies the schema. It is never fetched. This package exists in large part so that nothing has to try.

Zero runtime dependencies is a tested property

dependencies is absent from package.json, and src/__tests__/package-contract.test.ts fails the build if one appears, if shipped source imports anything non-relative, or if it references fetch / node:fs / any other host API.

tsconfig.build.json drops @types/node from the shipped compile, so a node: import is a compile error rather than a convention someone remembers.

So how does it validate without Ajv?

It interprets the schema at runtime. validate() reads SCHEMA_V1 and walks the document against it — the schema is the input, never a thing transcribed into TypeScript. Add a field upstream and this validator enforces it with no edit here. A hand-written validator would have re-created the exact drift this package exists to end.

Two guards keep that honest:

  • Ajv parity. Ajv is the reference implementation and stays a devDependency. src/__tests__/ajv-parity.test.ts runs both engines over every committed flows document plus ~30 single-rule mutations, and fails on any disagreement.
  • Unknown keywords are a hard stop. The interpreter implements a fixed keyword set. If a future schema uses one it does not know, assertSchemaSupported() throws naming the keyword, rather than skipping it and quietly accepting documents it should refuse. A v1.4 needing maxLength fails here loudly, in the round it lands.

Versioning

major.minor tracks the highest schema_version the package admits — 1.3.x ships schema v1.3. A schema v1.4 makes it 1.4.0. A fix in validate() is a patch bump. A test asserts the version and the schema agree.

Changing the schema

schema/v1.json at the repo root is the single source of truth. Edit it, then:

pnpm --filter @devfellowship/ux-paths-spec run generate

That rewrites spec/schema/v1.json (byte copy), spec/src/schema.generated.ts and spec/src/types.generated.ts. Never edit those three by hand. CI runs generate --check and fails, naming the stale files, if you forget.

Not in the schema yet (a v1.4 conversation)

The viewer app reads three fields the schema has never defined: requirements[], screen.fidelity and screen.archetype (plus screen.page_class). The schema sets additionalProperties: false at the top level and on $defs/screen, so a document carrying any of them is refused — this is a real gap, not a tolerated extra. Three committed viewer fixtures fail validation for exactly this reason, and ajv-parity.test.ts pins that fact with both engines so it stays visible until v1.4 closes it.

License

MIT.