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

@fulcro/reflect

v1.0.0

Published

Type aware nameOf, typeOf and defaultOf, with the compile time transformer that resolves them in the box.

Readme

@fulcro/reflect

nameOf, typeOf, defaultOf, is, as, sizeOf and the rest — utilities that answer questions TypeScript erases on its way to JavaScript.

npm install @fulcro/reflect

One package. The compile time transformer ships inside it, as @fulcro/reflect/transformer, so there is nothing else to install and no way to end up with the utilities but not the thing that resolves them.

It used to be a second package, installed as a peer dependency, and that went wrong in both of the ways it could. npm installs peers and Yarn does not, so a project could get the utilities alone — quietly, because these degrade rather than crash. And @fulcro/[email protected] with @fulcro/[email protected] was an installable, broken combination. What a call means and what it compiles to are now released together, because they are the same package.

The transformer is not optional, and it is not a menu. These utilities are named for what they read, and what they read is the type — which exists only while the compiler is running. Without the transformer the package still loads and still answers, but it answers from the value in front of it: typeOf reports a runtime shape with declared reading null, nameOf falls back to parsing the closure and is at the mercy of a minifier, and defaultOf throws, because a default it cannot compute would be a lie.

That is a fallback, not a mode to choose. The transformer is a build time concern and lives behind its own entry point, so a bundle that only imports these functions at runtime never pulls the compiler machinery in — but it is always there to be wired up.

import { defaultOf, nameOf, typeOf } from '@fulcro/reflect';

What each utility gains from the transformer is spelled out below, and summarised in a table at the end. Wiring it into a build takes one entry in a tsconfig:

{
	"plugins": [{ "transform": "@fulcro/reflect/transformer", "type": "program" }]
}

or one plugin in a bundler, from @fulcro/reflect/unplugin. See docs/reflect.md for the full setup.

Looking for switchFor or tryCatch? They moved to @fulcro/functions. Neither has anything to do with the compiler, and keeping them here blurred what this package is for.

nameOf

Reads a name as it was written in the source.

const email = '[email protected]';

nameOf(() => email); // 'email'
nameOf(() => user.profile.theme); // 'theme'  (last segment of a path)
nameOf(() => user['email']); // 'email'
nameOf(() => user.save); // 'save'   (never calls it)
nameOf(User); // 'User'
nameOf(42); // 'Number'

The accessor is never invoked — the name is read out of the source of the closure, so nameOf(() => user.save) costs nothing and is safe on a getter with side effects.

Two limits, both from the language rather than this implementation:

  • Interfaces and type aliases have no runtime existence, so the runtime form cannot name them. With the transformer, nameOf<UserContract>() can.
  • A minifier renames local variables, so nameOf(() => email) may report a mangled name in a bundled build. Property names, method names and the form taking a class normally survive. With the transformer the name is resolved before minification ever runs, so this stops being a concern.

typeOf

A replacement for the native typeof, which answers with eight strings and collapses most of what a program needs to tell apart.

typeof null; // 'object'
typeOf(null).typeId; // 'null'

typeof [1, 2]; // 'object'
typeOf([1, 2]).typeId; // 'array'

typeof NaN; // 'number'
typeOf(NaN).typeId; // 'nan'

typeOf(new Admin()); // { typeId: 'instance', name: 'Admin', … }

The result carries typeId (a discriminant usable in a switch), name, lineage (the prototype chain, e.g. ['Admin', 'User', 'Object']), and the flags primitive, nullish and iterable.

lineage is as close as runtime inspection gets to "where does this type come from". It is a chain of constructors, never a module path — the compiler keeps no record of where a class was declared.

With the transformer, a declared field is filled in with the written type: its rendered text, its name, how it was declared (interface, enum, union, …) and the file, line and column it came from. Without the transformer declared reads null and everything else keeps working.

typeOf(user).declared;
// { text: 'UserContract', name: 'UserContract', kind: 'interface',
//   site: { path: 'src/models/user.ts', line: 12, column: 18 } }

defaultOf

Produces the emptiest value that still fully inhabits a type.

interface Order {
	id: number;
	customer: { name: string; active: boolean };
	items: string[];
	note?: string;
}

defaultOf<Order>();
// { id: 0, customer: { name: '', active: false }, items: [] }

defaultOf<string>(); // ''
defaultOf<'dark' | 'light'>(); // 'dark'

One rule covers every case: the result is always a valid T. Required properties are filled, optional ones are left out (absence already satisfies them), a literal type yields its only inhabitant, and a union yields null or undefined when it admits one, falling back to the default of its first member. Tuples are filled position by position, arrays come back empty, Set and Date are instantiated rather than described, and a circular type is closed off instead of nesting forever.

defaultOf requires the transformer. A type has no runtime existence, so there is genuinely nothing for a plain function to inspect. Without the transformer the call throws, deliberately — a default it cannot compute would be a lie, and failing loudly at the call site beats handing back a wrong value.

is and as

The language's as asserts without checking — payload as Order compiles whatever payload turns out to be, and the mistake surfaces later, somewhere else, as a property of undefined. These two do the check it cannot.

if (is<Order>(payload)) {
	payload.total; // narrowed, and actually verified
}

const order = as<Order>(await response.json());

is is a type guard, for branching. as returns the value unchanged — the same object, not a copy — and throws when it does not match, for stopping.

The transformer writes the check out from the type: every property, nested objects, every element of an array. A failing as names where it stopped matching rather than only that it did:

TypeError: FULCRO4007: as<Order>() refused a value: customer.email: expected string, got number

That message comes from a second walker emitted beside the check, which runs only once the check has already refused — so a value that passes never pays for it, and is does not carry one at all.

Index signatures and unresolved generics are refused, loudly. Write the test yourself and pass it in as a second argument for those.

With and without the transformer

| | Without | With the transformer | | ----------------------------- | ----------------------------------- | ------------------------------------------------------------- | | nameOf(() => user.email) | 'email' — parsed from the closure | 'email' — emitted as a literal, minifier-proof | | nameOf<UserContract>() | not available | 'UserContract' | | typeOf(value) | runtime shape; declared is null | runtime shape + the declared type and its source location | | defaultOf<T>() | throws | the built value, emitted inline | | sizeOf<T>(), alignOf<T>() | throws | the number of bytes the type declares, emitted inline |

Wiring the transformer is a build time concern only; this package stays a plain runtime dependency either way, and nothing extra is installed to get it.


Full guide: docs/reflect.md — scenarios, worked examples and the failure modes worth knowing before you meet them.