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

@workspec/topology-model

v0.1.0-alpha.1

Published

Pure loader/resolver for a WorkSpec Topology working tree — discovers, parses, and resolves resources, environments, and connections from any file-source port into one typed, environment-scoped ResolvedTopology plus network/resource-group lens trees, with

Readme

@workspec/topology-model

Pure loader/resolver for a WorkSpec Topology working tree. Discovers .workspec/ topologies, resources, environments, and .layout/ files through a small file-source port, parses and validates every one via @workspec/topology-schema, and provides the normative resolve() contract that turns a raw Topology into an environment-scoped ResolvedTopology — the shape every downstream consumer (the Studio UI, cost, traceability) takes instead of the raw artifact. Also builds both normative lens trees (network, resource-group) over a resolved topology. Never throws: every failure mode degrades to a diagnostic, and the model/result is always data-complete.

No DOM, no React, no IO assumptions — the root entry only knows about MemorySource. Node-only code (FsSource, node:fs/promises) lives behind the ./fs subpath export, so importing @workspec/topology-model itself never pulls in a node: module.

Usage

import {
  loadTopologyModel,
  resolve,
  buildNetworkTree,
  buildResourceGroupTree,
  createMemorySource,
} from '@workspec/topology-model';
import { createFsSource } from '@workspec/topology-model/fs';

// Server-side / CLI: read a real working tree from disk.
const model = await loadTopologyModel(createFsSource('/path/to/repo'));

if (model.topology) {
  const resources = new Map([...model.resources].map(([slug, r]) => [slug, r.resource]));
  const environments = new Map([...model.environments].map(([slug, e]) => [slug, e.environment]));

  const resolved = resolve(model.topology.topology, resources, environments, 'prod');
  const network = buildNetworkTree(resolved);
  const resourceGroups = buildResourceGroupTree(resolved);
}

The resolve() contract

Applied in order, for one target environment:

  1. Prune resources — drop any resource whose spec.environments is present and excludes the target environment. Omitted environments means present in every environment; this omission is meaningful and is never defaulted to an empty list.

  2. Prune connections — drop a connection whose own environments excludes the target environment (explicit scoping, enabling per-environment rewiring), OR whose from/to was pruned in step 1 (auto-prune).

  3. Merge overrides (S1) — each surviving resource's OWN spec.overrides[envSlug] patch is merged onto its config/cost/resourceGroup/network. resolve() itself doesn't validate the override key — an override for an environment this resource isn't present in, or that doesn't exist at all, is caught upstream by this package's checkOverrideEnvironmentRefs (verify-time; see its own doc comment for why BOTH of those rules live at the model layer, not schema-level) — it just applies whatever the target envSlug names, per field:

    • config — a SHALLOW, top-level merge: a key named in the override replaces the base value at that key wholesale, even when both are objects — this is NOT a deep/recursive merge, and a null override value SETS the merged key to null rather than removing it (there is no way to make an override key "disappear" from the merged result).
    • cost — field-by-field (sku/mode/schedule/qty/attribution); attribution is a whole-array replace, never an element-wise splice.
    • resourceGroup/network — full replace of the base ref when the override names one. Since both fields are Slug-typed (never nullable), an override can only REPLACE a resource's placement with a different one, never clear it to "no placement" for one specific environment while the base resource has one.

    (v0 sourced this patch from Environment.spec.overrides[slug] instead; S1 moved it onto the Resource — see @workspec/topology-schema's resource.ts.)

  4. Apply naming — a resource-group-kind resource's resolved display name becomes <rg-slug><suffix> when the matching Environment declares naming.resourceGroupSuffix; its own authored name is untouched. Naming is the one thing resolve() still reads from the Environment artifact.

Lens trees (spec §3.2)

A resource's kind alone decides whether it renders as a container box or a plain node, and this is lens-scoped: vnet/subnet are container boxes in the network lens (nested via each resource's own network ref) but plain nodes in the resource-group lens; resource-group is the container box in the resource-group lens (nested via resourceGroup) but a plain node in the network lens. buildNetworkTree/buildResourceGroupTree apply this one rule via isGroupingKindForLens — there is no separate case for either lens's "nodes inside the other kind's box" behaviour.