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

subshape

v0.14.0

Published

subShape provides primitives and patterns for crafting composable shapes featuring cohesive typing, validation, serialization, and reflection.

Downloads

794

Readme

subShape  composable shapes for cohesive code

one shape can do them all; one shape defined them

subShape provides primitives and patterns for crafting composable shapes featuring cohesive typing, validation, serialization, and reflection.

Setup

Deno

import * as $ from "https://deno.land/x/subshape/mod.ts"

Node

npm install subshape
import * as $ from "subshape"

Demo

Craft a Composable Shape

import * as $ from "https://deno.land/x/subshape/mod.ts"

const $superhero = $.object(
  $.field("pseudonym", $.str),
  $.optionalField("secretIdentity", $.str),
  $.field("superpowers", $.array($.str)),
)

And Get...

Typing

type Superhero = $.Output<typeof $superhero>
// type Superhero = {
//   pseudonym: string;
//   secretIdentity?: string | undefined;
//   superpowers: string[];
// }

Validation

const spiderMan = {
  pseudonym: "Spider-Man",
  secretIdentity: "Peter Parker",
  superpowers: ["does whatever a spider can"],
}

$.assert($superhero, spiderMan) // ok!

const bob = {
  pseudonym: "Bob",
  secretIdentity: "Robert",
  superpowers: null,
}

$.assert($superhero, bob) // ShapeAssertError: !(value.superpowers instanceof Array)

Serialization

const encoded = $superhero.encode(spiderMan)
// encoded: Uint8Array

const decoded = $superhero.decode(spiderMan)
// decoded: Superhero

console.log(decoded)
// Prints:
//   {
//     pseudonym: "Spider-Man",
//     secretIdentity: "Peter Parker",
//     superpowers: [ "does whatever a spider can" ]
//   }

Reflection

$superhero.metadata // Metadata<Superhero>

console.log($superhero)
// Prints:
//   $.object(
//     $.field("pseudonym", $.str),
//     $.optionalField("secretIdentity", $.str),
//     $.field("superpowers", $.array($.str))
//   )

Examples

Further examples can be found in the examples directory.

Shape Naming Convention

This library adopts a convention of denoting shapes with a $$.foo for built-in shapes, and $foo for user-defined shapes. This makes shapes easily distinguishable from other values, and makes it easier to have shapes in scope with other variables:

interface Person { ... }
const $person = $.object(...)
const person = { ... }

Here, the type, shape, and a value can all coexist without clashing, without having to resort to wordy workarounds like personShape.

The main other library this could possibly clash with is jQuery, and its usage has waned enough that this is not a serious problem.

While we recommend following this convention for consistency, you can, of course, adopt an alternative convention if the $ is problematic – $.foo can easily become s.foo or subshape.foo with an alternate import name.

Asynchronous Encoding

Some shapes require asynchronous encoding. Calling .encode() on a shape will throw if it or another shape it calls is asynchronous. In this case, you must call .encodeAsync() instead, which returns a Promise<Uint8Array>. You can call .encodeAsync() on any shape; if it is a synchronous shape, it will simply resolve immediately.

Asynchronous decoding is not supported.

Custom Shapes

If your encoding/decoding logic is more complicated, you can create custom shapes with createShape:

const $foo = $.createShape<Foo>({
  metadata: $.metadata("$foo"),

  // A static estimation of the encoded size, in bytes.
  // This can be either an under- or over- estimate.
  staticSize: 123,
  subEncode(buffer, value) {
    // Encode `value` into `buffer.array`, starting at `buffer.index`.
    // A `DataView` is also supplied as `buffer.view`.
    // At first, you may only write at most as many bytes as `staticSize`.
    // After you write bytes, you must update `buffer.index` to be the first unwritten byte.

    // If you need to write more bytes, call `buffer.pushAlloc(size)`.
    // If you do this, you can then write at most `size` bytes,
    // and then you must call `buffer.popAlloc()`.

    // You can also call `buffer.insertArray()` to insert an array without consuming any bytes.

    // You can delegate to another shape by calling `$bar.subEncode(buffer, bar)`.
    // Before doing so, you must ensure that `$bar.staticSize` bytes are free,
    // either by including it in `staticSize` or by calling `buffer.pushAlloc()`.
    // Note that you should use `subEncode` and not `encode`.

    // See the `EncodeBuffer` class for information on other methods.

    // ...
  },

  subDecode(buffer) {
    // Decode `value` from `buffer.array`, starting at `buffer.index`.
    // A `DataView` is also supplied as `buffer.view`.
    // After you read bytes, you must update `buffer.index` to be the first unread byte.

    // You can delegate to another shape by calling `$bar.subDecode(buffer)`.
    // Note that you should use `subDecode` and not `decode`.

    // ...
    return value
  },

  subAssert(assert) {
    // Validate that `assert.value` is valid for this shape.
    // `assert` exposes various utility methods, such as `assert.instanceof`.
    // See the `AssertState` class for information on other methods.

    // You can delegate to another shape by calling `$bar.subAssert(assert)` or `$bar.subAssert(assert.access("key"))`.
    // Any errors thrown should be an instance of `$.ShapeAssertError`, and should use `assert.path`.

    // ...
  },
})