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

sketch-n-sketch

v2.0.17

Published

Bidirectional programming library that sketch-n-sketch relies on

Readme

Sketch-n-sketch Application Programming Interface

This npm packages offers a quick but simple API for dealing with the Sketch-n-sketch bidirectional evaluator in Javascript. We hope to provide enough functionalities to enable the creation of applications server that serve the Leo language, which is a superset of Elm and HTML, for web applications.

Get familiar with Sketch-n-sketch

If you want to get familiar with Sketch-n-sketch for the language, first visit the following link:

github.com/ravichugh/sketch-n-sketch

API usage

In node.js projects, run the following command to install sketch-n-sketch for your project.

npm install sketch-n-sketch

Then, in your javascript files, place the following declaration:

const sns = require("sketch-n-sketch");

Browser projects (not described here), simply include sns-core.js as an external script file.

The evaluation of this line will take 3 seconds the first time, the time necessary to evaluate the prelude.

Basic API documentation

Given that JSObj represents the native Javascript object type to store key/value pairs, and JSAny any javascript value (in our case: numbers, booleans, arrays and objects), we provide the following API along with their types.

parse   : String -> Result String Exp
evaluate: Exp -> Result String (Val, ComputationCache)
update  : Exp -> (Val, ComputationCache) -> Val -> Result String (LazyList Exp)
unparse : Exp -> String
process : Result err a -> (a -> Result err b) -> Result err b
valToNative: Val -> JSAny
nativeToVal: JSAny -> Val
valToString: Val -> String
valToHTMLSource: Val -> Result String String

evaluateWithoutCache: Exp -> Result String Val
updateWithoutCache: Exp -> Val -> Result String (LazyList Exp)

lazyList: {
  nonEmpty: LayList a -> Bool
  isEmpty: LayList a -> Bool
  head: LayList a -> a
  tail: LayList a -> LazyList a
}
fromOk: Result err a -> a
foldResult: (err -> b) -> (a -> b) -> Result err a -> b
first: (a, b) -> a
second (a, b) -> b

Specialized API

Plug-in an environment of constants (as a JavaScript object), that can be modified in return:

objEnv.evaluate: JSObj -> Exp -> Result String (Val, ComputationCache)
objEnv.update  : JSObj -> Exp -> (Val, ComputationCache) -> Val -> Result String (LazyList (JSObj, Exp))
objEnv.evaluateWithoutCache: JSObj -> Exp -> Result String Val
objEnv.updateWithoutCache  : JSObj -> Exp -> Val -> Result String (LazyList (JSObj, Exp))

Combine parse and evaluate steps directly:

string.evaluate: String -> Result String (Val, ComputationCache)
string.update  : String -> (Val, ComputationCache) -> Val -> Result String (LazyList String)
string.evaluateWithoutCache: String -> Result String Val
string.updateWithoutCache  : String -> Val -> Result String (LazyList String)

Plug-in an environment of constants AND combine parse and evaluate (string.objEnv. is also a valid prefix):

objEnv.string.evaluate: JSObj -> String -> Result String (Val, ComputationCache)
objEnv.string.update  : JSObj -> String -> (Val, ComputationCache) -> Val -> Result String (LazyList (JSObj, String))
objEnv.string.evaluateWithoutCache: JSObj -> String -> Result String Val
objEnv.string.updateWithoutCache  : JSObj -> String -> Val -> Result String (LazyList (JSObj, String))

update and updateWithoutCache are doing the same thing, except that the first (Val, ComputationCache) of update should be the previously computed output and the second Val the new value, whereas updateWithoutCache only takes in the new value and recomputes the old value and the cache itself, which might take longer.

Basic API Example usage

Here is a detailed workflow using variable assignments, expression parsing and unparsing:

// sns.parse returns either
//    {ctor: "Ok", _0: SNS AST of this program}
// or {ctor:"Err", _0: Parse error message}
var prog = sns.fromOk(sns.parse(
  `let f x = x + 1 in
   let who = "world" in
   [f 2, """Hello @who""", <span>Nice</span>]`)); // Could replace fromOk(x) by x._0

// sns.evaluate returns either 
//    {ctor: "Ok",  _0: Val}
// or {ctor: "Err", _0: Evaluation error message}
var outValCache = sns.fromOk(sns.evaluate(prog));
var outVal = sns.first(outValCache);

console.log(sns.valToString(outVal))
/* Prints
    [ 3,
      "Hello world",[ "span",[],[[ "TEXT",
        "Nice"
      ]]]
    ]
*/

// Returns either
//    {ctor: "Ok",  _0: native val}
// or {ctor: "Err", _0: Conversion error message}
// Here nativeOutVal is equal to [3, "Hello world", ["span", [], [["TEXT", "Nice"]]]]
var nativeOutVal = sns.fromOk(sns.valToNative(outVal));
console.log(nativeOutVal);

// Convert this Javascript value to a SNS value. This always succeed for base types.
var newOutVal = sns.nativeToVal([2, "Hello earth", ["span", [], [["TEXT", "Nice"]]]]);

// Invokes our update procedure with the given new out value.
// The result is either
//    {ctor: "Ok",  _0: Lazy list of new exps}
// or {ctor: "Err", _0: Update error message}
var newProgs = sns.update(prog)(outValCache)(newOutVal);

var solutions = sns.fromOk(newProgs);
// sns.lazyList.nonEmpty(solutions) == true
var solutionExp1 = sns.lazyList.head(solutions);

// Display the first solution to the update problem.
console.log(sns.unparse(solutionExp1));
/*let f x = x + 1 in
  let who = "earth" in
  [f 1, """Hello @who""", <span>Nice</span>]
*/

// Tail of solutions, computed lazily.
var solutionsTail = sns.lazyList.tail(solutions);

var solutionExp2 = sns.lazyList.head(solutionsTail);
console.log(sns.unparse(solutionExp2))
/*let f x = x + 0 in
  let who = "earth" in
  [f 2, """Hello @who""", <span>Nice</span>]
 */
// Third solution, etc.
// sns.lazyList.isEmpty(sns.lazyList.tail(solutionsTail)) == true

We can also encode this workflow in a way that carries errors until the result must be recovered.

var resProg = sns.parse(
  `let f x = x + 1 in
   let who = "world" in
   [f 2, """Hello @who""", <span>Nice</span>]`);

var resOutValCache = sns.process(resProg)(prog =>
  sns.evaluate(prog));

// Returns either {ctor: "Ok", _0: native val} or {ctor: "Err", _0: Conversion error message}
// Here nativeOutVal._0 is equal to [3, "Hello world", ["span", [], [["TEXT", "Nice"]]]]
var nativeOutVal = sns.process(resOutValCache)(outValCache =>
  sns.valToNative(sns.first(outValCache)));
console.log(nativeOutVal);

// Convert this Javascript value to a SNS value. This always succeed if we don't use functions.
var newOutVal = sns.nativeToVal([2, "Hello earth", ["span", [], [["TEXT", "Nice"]]]]);

// Invokes our update procedure with the given new out value.
// The result is either {ctor: "Ok", _0: Lazy list of new exps} or {ctor: "Err", _0: Update error message}
var newProgs =
  sns.process(resProg)(prog =>
  sns.process(resOutValCache)(outValCache =>
    sns.update(prog)(outValCache)(newOutVal)
  ));

// The remaining works as before.

sns.valToHTMLSource converts a value to a string in such a way it can be served as a web page (e.g. as the content of a http response). It takes care of

  • Special characters in attributes.
  • < > & in the script and style nodes.

Here is how you can use it.

var prog = sns.parse(
  `let x = "world" in
   <span>Hello @x</span>`)._0;

var outVal = sns.evaluateWithoutCache(prog)._0; // or sns.first

// Returns either a
//    {ctor:"Ok", _0: The HTML string}
// or {ctor:"Err", _0: the error message}.
var outHTML = sns.valToHTMLSource(outVal);

// Displays '<span>Hello world</span>'
console.log(outHTML._0);

Contributing: Package Sketch-n-sketch for npm (node.js):

After building Sketch-n-sketch, go to build/out and run

npm version patch
npm publish

You might need to register a user to do the last command. After publishing, make sure you update the dependency to SNS in your project via

npm update sketch-n-sketch

How to speed up update?

There are a few kinds of expressions that Sketch-n-sketch remembers without having to recompute them, and some that it automatically recomputes. Recomputation can take a quadratic time, so beware of some pitfalls.

  • Consecutive let definitions are cached. Every time you assign compute and assign a value to a variable, the original value of the expression can be retrieved from the variable itself. Except if

    • the pattern matching is a record pattern, because record extraction makes it impossible to retrieve the original value, e.g., from the cached value of a, we cannot obtain the original value of A in let {a} = A in X (A could evaluate to more fields). A work-around for this is either to define
      let a = A.a
      or add the "as" keyword to redefine A:
      let {a} as x = A
    • the pattern matching is a wilcard pattern like _. This will only trigger the recomputation of the right-hand-side, but it won't propagate to anything else. To avoid that, just put a dummy variable to ensure it caches the result.
  • Intermediate results of pipelines In the expression S |> F |> G, the intermediate results are cached because the expression is rewritten to a let definition like above. But the expressions F and G are not cached and will be recomputed.

  • List literals, record literals, branches of if-then-else (NOT the conditional), branches of case statement (NOT the input)

So in general, it's better to name any intermediate computation result. It's a bad idea (for now) to pipeline anything inside a branch (if or case) because the pipeline will need to be recomputed.