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

@galaxy-foundry/kind-manifest

v0.4.0

Published

The shared kind-manifest format for Foundry-pattern instances: types, zod validator, and the zod-shape-to-manifest deriver.

Downloads

1,432

Readme

@galaxy-foundry/kind-manifest

The shared kind-manifest format for Foundry-pattern instances: the types, a zod validator for reading one, and the deriver that turns a kind's zod shape into its field table.

A kind manifest is what an instance publishes about its own kinds — enough that two Foundries can be diffed by machine instead of by eye.

{
  "instance": "galaxy-workflow-foundry",
  "version": 1,
  "kinds": [
    {
      "kind": "mold",
      "title": "Mold",
      "layer": "substrate",
      "summary": "…",
      "shape": "directory",
      "companions": [
        {
          "file": "eval.md",
          "requirement": "recommended",
          "purpose": "The properties a cast must satisfy.",
          "disposition": "foundry-only"
        }
      ],
      "locations": ["content/molds"],
      "fields": [{ "name": "tags", "required": true, "type": "string[]" }]
    }
  ]
}

Why this is a package

The format is declared shared by the pattern's own checklist — "the FORMAT is SHARED ACROSS INSTANCES, the kinds in it are yours". It had four independent encodings: the prose spec, a kind-manifest.ts in each of the two instances, and a fourth hand-written copy of the types in the pattern site that consumes them.

The two instance copies were character-for-character identical apart from quote style, and so were their test suites. That is not convergent evolution; that is one file living in two places.

The deriver

import { buildKindManifest } from '@galaxy-foundry/kind-manifest';

const manifest = buildKindManifest({
  instance: 'galaxy-workflow-foundry',
  kinds: KINDS.map((d) => ({
    kind: d.kind,
    title: d.title,
    layer: d.layer,
    summary: d.summary,
    shape: d.shape, //           note shape: 'file' | 'directory'
    companions: d.companions,
    frontmatter: d.build(ctx).shape, // the zod object the validator runs
    doc: docs[d.kind],
  })),
  source: { repo: 'galaxyproject/foundry', path: 'types/kinds.generated.json' },
});

A kind's layout travels beside its frontmatter: shape says whether its notes are files or directories, companions lists the non-note files a note may carry, additionalCompanions marks a kind whose companion set is genuinely open, locations names the collection bases routing to it, and example carries the worked example.md both instances already validate and then throw away. That is what lets a catalog say mold is a folder in both instances while pattern is a folder in one and a flat file in the other — a difference otherwise expressible only inside collection globs.

shape is why the zod-shape field is called frontmatter: one word cannot mean both a note's physical shape and the zod object validating its frontmatter, and a producer mapping d.shape and d.build(ctx).shape onto one key is a bug waiting to be written.

companions: [] means none, as an assertion — never absent-meaning-unmodelled. The reader requires both shape and companions, so a catalog never has to tell "this kind declares none" apart from "this producer did not say".

fields is derived from the zod shape, never hand-written. A hand-maintained required-metadata table is a second encoding of the schema and drifts the first week; this one cannot, because it is read off the same object the validator runs.

Note what the deriver does not take: a context, a registry, or a schema factory. Resolving a kind's schema needs an instance's registries and no two instances do it the same way, so that step stays on the instance's side of the line. What transfers is what happens to the shape once it exists.

required answers "must an author write this key", so a field carrying .default() counts as optional — it validates without the author writing anything.

If you define kinds with @galaxy-foundry/kind-schema, don't write that map by hand — manifestKinds is the bridge, and it derives locations from your collection table instead of asking you for a second copy of it.

The reader

import { parseKindManifest } from '@galaxy-foundry/kind-manifest';

const manifest = parseKindManifest(JSON.parse(await readFile(vendored, 'utf8')));

A cross-instance catalog consumes manifests it did not produce, from repos it does not control, at revisions it did not choose. Casting the parsed JSON to an interface makes a malformed manifest render a broken page instead of failing at the read. A manifest from a newer format version is rejected rather than guessed at.

Provenance

source is split by who actually knows each fact.

// the producer declares its own identity
buildKindManifest({ ..., source: { repo: 'owner/name', path: 'types/kinds.generated.json' } });

// whoever vendors a copy records which snapshot they took
withRevision(manifest, 'abc1234');

The pattern site used to bolt all of this on after reading the file — literally manifest.source = {...} in its vendoring script. That put the producer's identity in the consumer's hands and turned vendoring into a mutation.

revision stays on the consumer's side for two reasons. It is the wrong party: revision answers "which snapshot is this", which only whoever took the snapshot can say. And it is structurally impossible for the producer — a manifest is a committed artifact whose CI gate regenerates it and string-compares, so a file carrying the revision it was generated at never matches the revision CI regenerates it at, and --check fails on every commit. A test pins that: two builds of the same kinds are byte-identical.

The zod pin

describeType reads _def.typeName, which is zod 3 internals, and zod is a peer dependency pinned to ^3.25.

This is deliberate rather than an oversight: zod exposes no public reflection API, and the alternative — a hand-written field table beside each schema — is the second encoding this package exists to prevent. zod 4 replaces _def.typeName entirely; the test suite fails loudly rather than silently rendering every field as any.

The peer range also matters for a second reason: the deriver must read the same zod instance the schemas were built with. A duplicated zod in the tree renders every field any.

Why the tests are here

The pattern's checklist is explicit about why the manifest needs unit tests rather than a regeneration gate:

--check regenerates with the same code and string-compares, so a bug in the type renderer produces a wrong manifest that --check then blesses forever.

The gate can only catch a renderer that changed, never one that was always wrong. So the renderer is exercised against synthetic shapes — optional, defaulted, effects-wrapped, array-of-object, enum, literal, nullable, wide union — where a wrong answer is a wrong answer regardless of what the corpus happens to contain. That suite previously had to be written once per instance.

API

| Export | Purpose | | ------------------------------------------------------------------------------ | ------------------------------------------------------------ | | buildKindManifest(opts) | Derive a manifest from kinds and their built shapes | | withRevision(manifest, rev) | Record the snapshot revision on a vendored copy | | describeType(schema) | Render one zod type as a short readable string | | describeFields(shape) | Walk an object shape into the field table, required first | | parseKindManifest(data) | Validate an untrusted manifest, throwing with the path named | | kindManifestSchema and friends | The zod schemas, for composing into a larger check | | KIND_MANIFEST_VERSION | The current format version | | KindManifest, ManifestKind, ManifestField, ManifestSource, KindLayer | Types |

License

MIT