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 🙏

© 2024 – Pkg Stats / Ryan Hefner

api-compiler

v1.0.0

Published

Treat APIs as declarative programming languages.

Downloads

4

Readme

API Compiler

API Compiler is a library that lets you treat your API as a declarative programming language, and compile API requests into JS code for optimized performance.

The API Compiler solves a very specific type of problem:

  1. You have an API that allows requesting various calculated values
  2. Some of these calculations can take a long time to run
  3. Some of these calculations can share intermediate values

Because some of your calculations take a long time to run, you only want to calculate what the client actually needs on any given request. And because some of your calculations can share intermediate values, you can save time by batching requests and only calculating the shared values once. And you want to figure out which values can be shared and the optimal order to compute them in as efficiently as possible.

In order to solve that kind of problem, API Compiler requires that you describe your calculations in terms of small functions that calculate intermediate or final values given some input values, and specify what the inputs and outputs are. If you provide the compiler with a bare function object, it will use reflection to extract the name of the function as the name of its calculated value, and the names of the formal parameters to the function, from that function's source code. As that can be rather brittle, however, you also have the option to more explicitly specify the output and input value names for any function in an OpSpec object, of the form { inputs: string[], outputs: string, fn: Function, async?: boolean }. At the moment, only single-value outputs are supported. Support for multivalue return is planned in a later version.

Once a Compiler object is constructed with a list of your calculation functions, the API Compiler can automatically construct a dependency graph for any set of values you might want to calculate, and

  1. Answer queries about which minimal inputs are required to produce certain outputs, and
  2. Compile an optimized function for calculating exactly those output values with minimal wasted computation.

API Compiler API

The package exports an OpSpec type (described above) and a Compiler constructor.

  • new Compiler(optable: Iterable<Function | OpSpec>) Given a list of operations, construct a compiler for the declarative language implied by that set of interdependent operations.
  • Compiler.prototype.getParams(reqs: Iterable<string>, precomputed?: Iterable<string>): { params: string[], intermediates: string[] } Returns the list of required input parameters and incidentally-computed intermediate values for a given set of requested values. If a set of precomputed value names is provided, this method will determine which of those values are useful in the computation of the requested values, and give back a modified parameter list that includes those available precomputed values that would be useful. This allows the compiler to create more optimized functions that avoid recomputing intermediate values unnecessarily if the client is known to be able to provide them.
  • Compiler.prototype.compile(reqs: Iterable<string>, precomputed?: Iterable<string>): (args: { [key: string]: unknown }) => Promise<{ [key: string]: unknown }> Returns a compiled function to calculate the requested values given appropriate inputs, possibly accounting for precomputed intermediate values available from the client.
  • Compiler.prototype.getCalculator(reqs: Iterable<string>, precomputed?: Iterable<string>): (args: { [key: string]: unknown }) => Promise<{ [key: string]: unknown }> This method is functionally identical to Compiler.prototype.compile, but it will cache the compiled functions so as to amortize the cost of compilation over multiple runs of the same calculation function.
  • Compiler.prototype.calculate(reqs: Iterable<string>, args: { [key: string]: unknown }): Promise<{ [key: string]: unknown }> Internally uses Compiler.prototype.getCalculator to acquire an optimal compiled function, accounting for any precomputed intermediate values that may have been provided in the args object along with basic required parameters, and immediately applies it to calculate the requested values. If any required arguments are missing, it throws an error describing which inputs are missing and which requested values require them, so that the request can be repeated either without the uncomputable requests or with the minimum additional required arguments provided.
  • Compiler.prototype.interpret(reqs: Iterable<string>, args: { [key: string]: unknown }): Promise<{ [key: string]: unknown }> As the compilation step has some overhead, this method provides access to an interpreter mode which will traverse the dependency graph computing any intermediate values it needs to immediately and returning the requested results. This is a good option for APIs that expect infrequent repetitions of the same type of request, such that we cannot expect to amortize the cost of compilation over multiple requests. However, it is unable to provide as detailed error messages as a compiled function would.

Roadmap

The following features are planned for future versions:

  • Support for multi-value returns from operator implementations, along with a solver to pick the best set of implementations if the same value is available via multiple routes (possibly in combination with different sets of other values).
  • Caching of serialized function sources so that compilation can be amortized across server restarts; currently, only JS function objects are cached, which are lost when the node process ends or the Compiler object is otherwise garbage collected.
  • Support for WASM operator implementations, with compilation to a single WASM module for better numnerical performance.
  • Support for mixed WASM and JS operations.