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

@opcheese/marshal

v0.1.0

Published

Admissibility and record: allocators, pick(), explained refusals

Downloads

30

Readme

Marshal

Decides whether work may start, and records that it decided — capacity, exclusivity, order, and a refusal that names its reason. It knows nothing about what your jobs are, and by construction it cannot: boundary.test.ts fails the build if any file here imports anything but node: and its own siblings, or if an exported name carries one of six domain nouns.

Extracted from Galatea, where it runs the admission half of an agent scheduler.

  • How it works — the model: what it is for, the two layers, the resource families, and what is not guaranteed
  • Putting it in your project — the loop, naming your resources, budgets, ranking, and what changes as you add machines
  • API reference — every export, with runnable examples
  • Changelog — keyed by commit, since there are no releases
  • Ludentes/sieve — a worked reference: an agent app that uses this package to keep two sessions off one workspace

Install

pnpm add @opcheese/marshal

The npm scope is @opcheese; the repository lives under Ludentes. The two names disagree because the scope is an account that exists and the org scope is not registered. Nothing about the package depends on the difference — it is worth one line here so you do not go looking for @ludentes/marshal and conclude you have the wrong name.

No build step: dist/ ships in the tarball. ESM, no dependencies, verified on Node 20, 22 and 24 — CommonJS consumers can require() it on Node 22 and later.

Pre-1.0, and semver applies below 1.0 the way it always does: a minor may break you. There is no release cadence — versions appear when the upstream copy changes enough to be worth one. Pin exactly (@opcheese/[email protected], no caret) if you would rather read the changelog than be surprised by it. Adding an entry to BLOCKED_KINDS is the one change that would break a consumer silently, so it is guarded by a compile-time tie between the const and the union.

A git install still works if you want to track main ahead of a release: pnpm add git+https://github.com/Ludentes/marshal.git.

Sixty seconds

Two jobs want one repository. One gets it; the other is told who has it.

import { pick } from "@opcheese/marshal/pick"

const rank = (jobs) => jobs.map((job) => ({ job, rank: 0, why: { base: 0 } }))

const result = pick({
  jobs: [
    { id: "job-1", needs: [{ resource: "repo:cms" }] },
    { id: "job-2", needs: [{ resource: "repo:cms" }] },
  ],
  capacity: { exclusive: { "repo:cms": {} } },
  rank,
  now: Date.now(),
})

for (const g of result.granted) console.log("granted", g.job.id, g.holds)
for (const r of result.refused) console.log("refused", r.job.id, r.blocked)
granted job-1 [ 'repo:cms' ]
refused job-2 {
  kind: 'resource-held',
  resource: 'repo:cms',
  by: { id: 'job-1', what: 'job-1', since: '1788175651109' }
}

"repo:cms" means nothing to Marshal. You name your own resources, hand in your own clock, and supply your own ranking; the package decides and explains, and never learns what the strings mean.

The resource model

Four kinds, and a refusal for each.

| Kind | Question | Refusal | |---|---|---| | counting | are all N in use? | no-capacity, naming the holders | | exclusive | is this one taken? | resource-held, naming the holder | | unavailable | may it be taken yet? | not-ready, with an optional until | | budget | is there allowance left in every window? | budget-exhausted, with resets |

A need says how much, not just what: { resource: "lane", units: 2 } takes two of a counting resource, and { resource: "provider", amount: 4 } debits four of a budget. A bare { resource } is one unit at whatever your CostFn says — the plain-string case, spelled out. Naming a resource twice in one job is one resource; naming it twice two different ways throws, because there is no honest answer to which of the two you meant.

pick() is all-or-nothing per job: one that took two of three resources and then refused would leak the two, and nothing would release them because no grant was returned to release. Ranking is injected — an aging curve is policy wearing mechanism's clothes, and left inside pick() every consumer that disagrees with it forks the package. A default sits one import away rather than inside: agingRank() builds the curve out of accessors you supply, so Job never learns what a priority is.

The two layers

Merging these is the mistake most worth naming first, because both are about "who may go" and they answer different questions.

pick() — which job may start. Pure: no clock, no filesystem. Capacity is an argument, so the state lives in your bookkeeping, not in this package.

acquirePermit() — whether this process may run at all. Lease files and pid liveness across unrelated OS processes on one machine, over openSync(file, "wx") — never a TTL. A job legitimately runs far longer than any timeout we would dare set, so a TTL either strands the resource after a crash or steals it from work still in progress. It has no refusal kinds; it throws NoPermit.

Neither half ever queues. A refusal names a holder and returns immediately; waiting is the caller's decision, made with the caller's information.

Three things that will bite you

Each of these is a real defect that reached a real system.

An unpriced job is free. A need is priced by its own amount, else by the injected cost(job, resource), else zero — and a zero-cost job is admitted by a completely exhausted window. Guessing a number would be a policy, so the package refuses to guess. If you configured budget and never see a refusal, this is why. See cost for the same window answering both ways.

rank must be a total permutation. Return every job exactly once or pick() throws. A rank that filters loses a job into neither list, where nothing can notice it; one that concatenates grants the same job twice.

Take the process lease from your entry point, not at module scope. If your framework evaluates the module twice in one process, the second call finds a live holder and the process refuses itself. There is deliberately no same-pid exemption — the lease cannot tell which evaluation is the real one.

The finding this package exists to carry

On a measured two-lane run, the provider's weekly allowance window bound throughput at roughly 13 jobs per week while every other resource sat idle. The 5-hour window that is easy to mistake for the constraint is 6.7× looser — pacing to it drains the week in a day.

That is why budget is a set of windows rather than a gauge, and why admission must satisfy every one of them rather than the loosest. It is also why reconcile() exists: a budget debited only by the estimate never learns the estimate is wrong, and a CostFn that systematically underestimates keeps granting while the real window drains. The failure then surfaces as a provider outage rather than as the admission error it is.

Status

Published, and built before it is. dist/ is committed and the subpath exports point at it. That is not a preference: pnpm gates install-time build scripts behind onlyBuiltDependencies, so a prepare script would leave a consumer with no dist/ and no error. Exports onto TypeScript source do not work either — measured against an agent runtime that marks anything under node_modules external, where Node then refuses to strip types inside node_modules at all. CI rebuilds dist/ and fails if it differs from what is committed, and imports every export in plain Node so an unloadable build cannot pass.

Generated, not edited. Galatea holds the authoritative copy at scripts/marshal/, and this repository is regenerated from it by git filter-repo over both the old and the current paths. upstream is pure filter-repo output and only ever fast-forwards; main is upstream merged with root-only scaffolding — the two sides touch disjoint paths, so no refresh has ever needed a force-push.

The practical consequence: an edit made directly to src/ here is discarded by the next refresh. Issues and discussion are welcome; a patch to src/ has to be applied upstream to survive, so say what should change and why rather than sending the diff.

License

MIT. See LICENSE.