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

@ghostry/extern-extension-fabricator-v0

v0.0.2

Published

@ghostry/fabricator integration for @ghostry/extern — a typed extern block built from a fabricator schema produces its own value instead of requiring a mock.

Readme

@ghostry/extern-extension-fabricator-v0

@ghostry/fabricator integration for @ghostry/extern.

A typed extern block built from a fabricator schema becomes self-mocking: with no mock defined it fabricates a value from its own schema instead of throwing NotMockedError.

Setup

import { initialize as initializeExtern } from "@ghostry/extern";
import { initialize as initializeFabricator } from "@ghostry/fabricator";
import { fabricatorExtension } from "@ghostry/extern-extension-fabricator-v0";

const fabricator = initializeFabricator({ salt: "my-suite" });

export const extern = await initializeExtern({
  extensions: [fabricatorExtension({ instance: fabricator })],
});

The widening applies to this instance alone. An extern instance without the extension still rejects fabricator schemas as identities, exactly as before.

Using it

const { T } = fabricator;

const user = T.object({
  id: T.number,
  name: T.string.whereby({ length: { max: 10 } }),
});

const load = () => extern.typed.by(user).will(() => fetchUser());

Inside a testing block, load() fabricates a { id, name } with no mock written:

await extern.testing(() => {
  expect(load()).toMatchObject({ id: expect.any(Number) });
});

Overriding outright

await extern.testing((mock) => {
  mock(user).with({ id: 1, name: "Ada" });
});

Shaping the fabrication

produce() fabricates and hands back a spy. Given a callback, it hands you the built Fabricator, so overrides are expressed in fabricator's own vocabulary — this package models none of it:

await extern.testing((mock) => {
  mock(user).produce(({ via }) => via.fabricate({ name: "Ada" }));

  expect(load().name).toBe("Ada");
});

via is the built Fabricator, so an object's fabricate(overrides) carries fabricator's own Override<Definition> — nested objects, the Omitted sentinel, and all. Everything else it carries is reachable too, trace and schema included. Both produce() and produce(fn) cache per (identity, name): the callback runs once, and reading the block twice in one test agrees.

via is the built Fabricator for whichever kind the identity is, so the callback form is available on every schema — not object schemas alone. What differs is what that Fabricator accepts: an object's fabricate takes overrides, a T.number's takes none. That is fabricator's own type saying so, not a rule this package imposes.

Requiring an explicit mock

fabricatorExtension({ instance: fabricator, unmocked: "error" });

Blocks then throw NotMockedError again unless mocked, while produce() still fabricates on demand.

Determinism

Every testing block gets a fresh construction counter, inheriting the fabricator instance's salt and layering one foreknown constant onto it — never anything derived from where a block was written. That gives:

  • the same block fabricates the same value across independent testing() calls
  • the same schema exercised from a different test file draws the same value
  • construction order within one testing block determines unnamed values

A user's own new fabricator.Fabricator(schema) written inside a testing block shares that block's source, but is a separate draw — successive ordinals in one stream — not a second view of the block's value.

One written outside the block draws from the enclosing scope instead. The constant layer is what keeps those two streams disjoint: a fabrication either side of a testing block can never collide with one inside it, at any ordinal.

Two blocks over one schema in one test are a single production. Give them named(...) to make them distinct.

A name does more than disambiguate. It lifts a block out of positional identity altogether: a named block's value is a function of its identity and its name, and of nothing else — so it holds still against whatever else the test fabricates ahead of it. Add a second fixture above it, or a helper that fabricates on its way past, and a named block keeps its value where an unnamed one moves. That is the trade the two forms make, and it is why the bullet above is scoped to unnamed values.

With a test harness

@ghostry/fabricator/harnessing opens a wrap of its own per test, salted by that test's identity. This extension's scope then runs inside it, and the two nest rather than compete: blocks inside extern.testing inherit the enclosing test's salt, and its clock along with it.

That nesting is deliberate rather than automatic. A wrap lays its overlay over the instance it was called on, so a receiver bound outside the running block — which the configured instance always is — would restate from that instance and drop the enclosing scope. This extension composes against context.scope(), the frame in effect, which is why the per-test salt arrives.

The construction ordinal does not carry through — every testing block re-instantiates, as above — but the scope's constant salt layer keeps the block's stream disjoint from the enclosing test's, so a fabricate() written either side of extern.testing never collides with one inside it.

The harness's own context.fabricator stays usable inside a block. It holds the harness's scope rather than this extension's, but a construction resolves against the innermost active frame, so it draws in step with the blocks around it.

Both halves must come from one initialize():

const fabricator = initializeFabricator({ salt: "my-suite" });

// harness integration and extension, one lineage
integration(fabricator);
fabricatorExtension({ instance: fabricator });

A frame is visible only to instances on its own ancestral line. Two initialize() calls mint unrelated lineages and never see each other's frames — not even when handed the same stack, which selects a carrier and nothing more. So an extension pointed at a second instance never observes the harness's frame and falls back to its own configuration: the per-test salt is lost and every test fabricates identically, with no error raised. A fork() stays within the lineage and is fine to pass.

Determinism survives that; distinctness does not. The symptom is tests that write a fabricated id into shared state colliding with each other — passing individually, failing as a suite, and passing again under .only, which sends you looking for pollution rather than for the wiring.

Where both instances are in scope, you can assert it directly — root is fabricator's answer to "same lineage?":

if (integrationInstance.root !== extensionInstance.root) {
  throw new Error("fabricator instances are from different lineages");
}

Where they are not — the two halves wired in separate modules, which is how this goes wrong in practice — one guard test in the suite's own setup covers it: fabricate the same schema under two different test identities and assert the values differ.

Requirements

Needs an async stack carrier, which means any runtime with node:async_hooks — Bun, Node, and Deno. Extern's testing block is inherently asynchronous and this extension runs it inside fabricator's wrap, which refuses an async block under the synchronous carrier a browser bundle selects.