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

@suss/resolution

v0.3.2

Published

Language-neutral rules for following a value to the function it comes down to.

Readme

@suss/resolution

The rules for following a value to the function it comes down to.

What this package is

A list of Datalog rules and nothing else. No parser, no language, no files. An adapter reads source into facts, concatenates its own rules onto these, and evaluates on @suss/datalog.

The rules are about programming languages rather than about any one of them. A name binds to a value. A call puts an argument in a parameter. A module exports a name and another module can forward it. A function that returns a function calling its parameter hands back the argument it was given, which is a decorator in Python, a closure in Go, and a handler factory in TypeScript.

The facts an adapter supplies

func(f)                     f is a function
objectValue(o)              o is an object written out literally
writtenValue(x)             x is an expression written out in source
                            rather than a name for one
holdsProperty(o, n, x)      object o holds x under the name n
readsProperty(x, o, n)      x is the expression o.n
binds(x, y)                 the name x is declared as y
endsHolding(x, y)           the name x is written more than once and
                            holds y once the writes have run
paramOf(f, k, p)            p is f's parameter at position k
returnsValue(f, v)          f returns v
bodyCalls(f, c)             f's body calls c
containsFn(f, g)            g is declared inside f
call(r, c)                  r is a call whose callee is c
callArg(r, k, a)            r passes a at position k
imports(x, m, n)            x is the name n imported from module m
exportsAs(m, n, v)          module m exports v under the name n
reExports(m, n, m2, n2)     m's n is m2's n2
reExportsAll(m, m2)         m forwards everything m2 exports

Two more come from a pack rather than from source, for a wrapper whose body nobody can read: unwrapsByName(name, k) and wrapperModule(name, module), checked against calleeName and calleeOrigin so a local function spelled like the library's does not match.

Node identity is the adapter's business. The rules only join on it.

What comes out

comesTo(x, z)               following x arrives at the value z
resolves(x, z)              comesTo narrowed to functions
isWrittenAs(x, z)           x is written as the expression z
comesFrom(x, m, n)          following x arrives at m's export n
callsInto(f, m, n)          calling f ends up calling m's n

resolves is the question most callers ask. comesTo is the one underneath it, and it can answer with an object, since a chain has to pass through objects for routes.list to reach what list holds.

isWrittenAs follows the same names to the expression a value is written as, whatever kind of expression that turns out to be. A GraphQL document is neither a function nor an object, so comesTo never reaches one.

comesFrom answers the direction comesTo cannot. Every comesTo chain ends at something written out in the source being read, so a name for a library's own function ends nowhere: the library's body is not here. comesFrom follows the same aliases, imports and barrels and answers with the module and the name that module exports.

callsInto puts that together with the calls a function makes. A project writes its own decorator that calls Resolver() and applies that one to its classes, and the class is a resolver even though nothing about it says Resolver. Several answers for one function is the normal case rather than an ambiguity, since a wrapper composing two library decorators applies both, so a caller asks whether the one it cares about is among them.

One relation comes out for the rules' own use rather than for callers:

objectOf(x, obj)            x stands for the object literal obj

An object arrives two ways, through a name or as what a factory call returns, and objectOf names that step so the rule for routes.list and the rule for make(body).handle are the same rule. A factory call gets an objectOf answer without getting a comesTo answer, since a factory call usually is the wrapper and answering with the function it returns would fight the unwrapping answer.

Why rules and not a walker

Each rule describes one hop. The chains people write are longer than that, and nobody writes a rule for them: a factory handing off to another factory, a closure three levels down calling the argument, a barrel re-exporting a wrapper. The engine composes what it has, so those work without being named.

The corollary is where to look when something comes back empty. Suspect the facts before the rules. Against one production service suss resolved 11 handlers and missed most of what the template declared. The fix was one condition in fact extraction, and the existing rules found the rest unchanged.

What is not modelled

A handler passed as one property of a config. make({ body: handler }), where the factory reads opts.body, does not resolve. A rule for it was tried and taken out: a wrapper often reads several callbacks off the same config object, the rule made each one a candidate for the whole call, and the resulting ambiguity nulled out handlers that used to resolve. Saying which property is the handler takes something structure does not carry, either "the only property called" (which needs negation) or a pack naming the property. Until one of those exists, this shape stays unresolved on purpose.

An element of an array. all[0] has no fact for what an array holds.

Which write a read sees, once control flow decides it. A name written twice in a module's own statement list is answered: those statements run once each, top to bottom, so the last write is what anything importing the name gets, and the adapter says so with endsHolding. A write inside a branch, a loop, or a function body is a different claim, and the adapter stays quiet. The name then comes to nothing, which is the answer until there are control-flow facts to reason over.

Answering it in general is reaching definitions, per use rather than per name. That needs facts saying which statement follows which and which branch each sits on, and no adapter emits any today.

The adapter picking the write rather than a rule picking it is a cost decision. Ordering writes inside the rules means asking which writes have no later write, negation says that in one line, and this evaluator has to throw its last fixpoint away and start over whenever a rule set uses negation. The store evaluates after every wave of facts, so one negated rule turned a 66 second run on the Saleor dashboard into one that had not finished in ten minutes. Source order is something every adapter already knows.

Ambiguity is the caller's problem. When the rules reach two different functions the store returns nothing, since picking one would make the answer depend on the order facts arrived in.

License

Licensed under Apache 2.0. See LICENSE.