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

@pcd/podspec

v0.0.2

Published

A library for validating the structure of [POD](https://pod.org)s in TypeScript, providing static types and runtime validation.

Downloads

6

Readme

Podspec

A library for validating the structure of PODs in TypeScript, providing static types and runtime validation.

Usage

Install the package:

npm install @pcd/podspec

Create a POD spec:

import { p } from "@pcd/podspec";

const spec = p.entries({
  name: p.string(),
  email: p.string(),
  high_score: p.int()
});

Then use the spec to validate POD entries:

const entries = {
  name: { type: "string", value: "Bob Dugnutt" },
  email: { type: "string", value: "[email protected]" },
  high_score: { type: "int", value: 999999 }
};

const result = spec.parse(entries);
if (result.isValid) {
  // Ready to use the POD
  result.value; // Contains the POD entries
} else {
  // Handle the error
  result.errors; // Contains information about the errors
}

If the entries are valid, result.value will be a PODEntries object, with static types for the entries which are part of the spec. Otherwise, result.errors will contain information about the errors encountered during parsing.

Use cases

Validating PODEntries before signing a POD

When signing a POD, we want to make sure that the POD's entries meet our expectations. This means not just that the POD is well-formed, but that it also has the right structure and types.

For example, if we have a POD that represents a weapon in a game, we might have a spec that looks like this:

const weaponSpec = p.entries({
  name: p.string(),
  damage: p.int(),
  durability: p.int(),
  price: p.int()
});

Now we can use the spec to validate POD entries before signing a POD:

const entries = {
  name: { type: "string", value: "Narsil" },
  damage: { type: "int", value: 10n },
  durability: { type: "int", value: 100n },
  price: { type: "int", value: 100n }
};

const result = weaponSpec.parse(entries);
if (result.isValid) {
  // Ready to sign the POD
} else {
  // Handle the error
  result.errors; // Contains information about the errors
}

Turning JavaScript objects into PODEntries

In the above example, we assumed that already had POD entries to validate. However, we might have a JavaScript object that we want to turn into POD entries. podspec can do some simple transformations to turn JavaScript objects into POD entries.

First we specify that we want to coerce JavaScript objects into POD entries:

const coercingWeaponSpec = p.entries({
  name: p.coerce.string(),
  damage: p.coerce.int(),
  durability: p.coerce.int(),
  price: p.coerce.int()
});

Then we can use the spec to parse a JavaScript object:

const javascriptObject = {
  name: "Narsil",
  damage: 10,
  durability: 100,
  price: 100
};

const result = coercingWeaponSpec.parse(javascriptObject);
if (result.isValid) {
  // Ready to sign the POD
} else {
  // Handle the error
  result.errors; // Contains information about the errors
}

Here, regular JavaScript objects are turned into POD entries. In particular, numbers are turned into ints, and strings are turned into strings.

Validating an existing POD

If you have a POD that is already signed, you can use podspec to validate the POD, including both the entries and the signer public key.

const pod = getPodFromSomewhere();
const pubKey = "expected_public_key";
const podSpec = p
  .POD({
    eventId: p.string(),
    productId: p.string()
  })
  .signer(pubKey);

const result = podSpec.parse(pod);
if (result.isValid) {
  // Ready to use the POD
} else {
  // Handle the error
  result.errors; // Contains information about the errors
}

This will check that the POD has the right structure and types, and that the signer is the expected signer.

You can also provide a list of valid signers:

const podSpec = p
  .POD({
    eventId: p.string(),
    productId: p.string()
  })
  .signerList([pubKey1, pubKey2]);

Querying an array of PODs for matches

If you have an array of PODs, you can use the spec to query the array for PODs that match the spec:

const pods = [pod1, pod2, pod3];
const podSpec = p.entries({
  eventId: p.string(),
  productId: p.string()
});
const result = podSpec.query(pods);
result.matches; // Contains the PODs that match the spec
result.matchingIndexes; // Contains the array indexes of the PODs that match the spec

Serializing Podspecs

To serialize a podspec to a JavaScript object, you can use the serialize method:

const serialized = podSpec.serialize();

Bear in mind that this will not serialize the podspec to a string, but rather to a JavaScript object. Since the object may contain bigint values, you may need to serialize it using a specialized library like json-bigint.

Other constraints

Range checks

As well as ensuring the existence and type of POD entries, podspec also provides some additional constraints, such as range and list checks.

const rangeSpec = p.entries({
  value: p.int().range(0n, 100n)
});

const entries = {
  value: { type: "int", value: 50n }
};

const result = rangeSpec.parse(entries);

This will parse successfully. This will not:

const entries = {
  value: { type: "int", value: 200n }
};

const result = rangeSpec.parse(entries);

List checks

Entries can be checked against a list of values:

const listSpec = p.entries({
  value: p.int().list([1n, 2n, 3n])
});

const entries = {
  value: { type: "int", value: 2n }
};

const result = listSpec.parse(entries);

This will parse successfully because the value 2n in the list.

Lists can also be used to specify invalid values which should not be allowed:

const listSpec = p.entries({
  value: p.int().list([1n, 2n, 3n], { exclude: true })
});

const entries = {
  value: { type: "int", value: 2n }
};

const result = listSpec.parse(entries);

This will not parse successfully because the value 2n is in the list of excluded values.

Tuple checks

Multiple entries can be checked against a list of valid tuples.

const tupleSpec = p
  .entries({
    foo: p.string(),
    bar: p.int()
  })
  .tuple({
    name: "test",
    exclude: false,
    entries: ["foo", "bar"],
    members: [
      [
        { type: "string", value: "test" },
        { type: "int", value: 1n }
      ],
      [
        { type: "string", value: "test2" },
        { type: "int", value: 2n }
      ]
    ]
  });

In this example, we will accept any set of POD entries which has either a foo entry with value "test" and a bar entry with value 1n or a foo entry with value "test2" and a bar entry with value 2n.

const entries = {
  foo: { type: "string", value: "test" },
  bar: { type: "int", value: 1n }
};

const result = tupleSpec.parse(entries);

This matches the first tuple in the list, so the result will be valid.

const entries = {
  foo: { type: "string", value: "test2" },
  bar: { type: "int", value: 2n }
};

const result = tupleSpec.parse(entries);

This matches the second tuple in the list, so the result will be valid.

const entries = {
  foo: { type: "string", value: "test" },
  bar: { type: "int", value: 2n }
};

const result = tupleSpec.parse(entries);

This has a foo entry which matches the first tuple, and a bar entry which matches the second tuple, but does not match either tuple as a whole. Therefore, the result will be invalid.