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

@jaypie/vocabulary

v0.1.7

Published

Jaypie standard application component vocabulary

Readme

Jaypie Vocabulary ⛲️

Philosophies

Fabric

  • Smooth, pliable
  • Things that feel right should work
  • Catch bad passes

Concepts

  • Fluid scalar coercion, string to object conversion
  • Primitives: Boolean, String, Number (scalars); Objects, Arrays (considered different)
  • Undefined always available
  • "Resolvable" as scalar or function with potential promise
  • Treat native types and their string representation equally (type: String or type: "string")

Coercion

Arrays

  • Non-arrays become arrays of that value
  • Arrays of a single value become that value
  • Multi-value arrays throw bad request

Objects

  • Scalars become { value: Boolean | Number | String }
  • Arrays become { value: [] }
  • Objects with a value attribute attempt coercion with that value
  • Objects without a value throw bad request

Scalars

  • String "" becomes undefined
  • String "true" becomes true or 1
  • String "false" becomes false or 0
  • Strings that parse to numbers use those numeric values to convert to number or boolean
  • Strings that parse to NaN throw bad request
  • Boolean true becomes "true" or 1
  • Boolean false becomes "false" or 0
  • Numbers to String are trivial
  • Numbers convert to true when positive
  • Numbers convert to false when zero or negative

Entity Model

{
  "model": "job",
  "type": "command",
  "job": "evaluation",
  "plan": "hello"
}

Service Handler

import { serviceHandler } from "@jaypie/vocabulary";

const divisionHandler = serviceHandler({
  alias: "division",
  description: "Divides two numbers",
  input: {
    numerator: {
      default: 12,
      description: "Number 'on top', which is to be divided",
      type: Number,
    },
    denominator: {
      default: 3,
      description: "Number 'on bottom', how many ways to split the value",
      type: Number,
      validate: (value) => value !== 0,
    }
  },
  service: ({ numerator, denominator }) => (numerator / denominator),
});

await divisionHandler(); // =4
await divisionHandler({ numerator: 24 }); // =8
await divisionHandler({ numerator: 24, denominator: 2 }); // =12
await divisionHandler({ numerator: "14", denominator: "7" }); // =2
await divisionHandler({ numerator: 1, denominator: 0 }); // throws BadRequestError(); does not validate
await divisionHandler({ numerator: 1, denominator: "0" }); // throws BadRequestError(); does not validate
await divisionHandler('{ "numerator": "18" }'); // =3; String parses as JSON
await divisionHandler({ numerator: "ONE" }); // throws BadRequestError(); cannot coerce NaN to Number
await divisionHandler({ denominator: "TWO" }); // throws BadRequestError(); cannot coerce NaN to Number
await divisionHandler(12, 2); // throws BadRequestError(); future argument coercion may allow

Service Handler builds a function that initiates a "controller" step that:

  • Parses the input if it is a string to object
  • Coerces each input field to its type
  • Calls the validation function or regular expression or checks the array
    • The validation function may return false or throw
    • A regular expression should be used as a matcher
    • An array should validate if any scalar matches when coerced to the same type, any regular expression matches, or any function returns true (pocket throws in this case)
  • Calls the service function and returns the response (or returns the processed input if no service is provided)
  • Parameters are assumed required unless (a) they have a default or (b) they are required: false

Handler Properties

The returned handler exposes config properties directly for introspection, useful for building CLI adapters and documentation:

const handler = serviceHandler({
  alias: "division",
  description: "Divides two numbers",
  input: {
    numerator: { type: Number, default: 12 },
    denominator: { type: Number, default: 3 },
  },
  service: ({ numerator, denominator }) => numerator / denominator,
});

handler.alias;       // "division"
handler.description; // "Divides two numbers"
handler.input;       // { numerator: {...}, denominator: {...} }

Validation Only (No Service)

When no service function is provided, the handler returns the coerced and validated input:

const validateUser = serviceHandler({
  input: {
    age: { type: Number, validate: (v) => v >= 18 },
    email: { type: [/^[^@]+@[^@]+\.[^@]+$/] },
    role: { default: "user", type: ["admin", "user", "guest"] },
  },
  // no service - returns processed input
});

await validateUser({ age: "25", email: "[email protected]" });
// → { age: 25, email: "[email protected]", role: "user" }

await validateUser({ age: 16, email: "[email protected]" });
// throws BadRequestError - age validation fails

Natural Types

  • Array
    • [] same as Array
    • [Boolean] same as Array<boolean>
    • [Number] same as Array<number>
    • [Object] same as [{}] same as Array<object>
    • [String] same as [""] same as Array<string>
  • Boolean
  • Number
  • Object
    • {} same as Object
  • String
    • "" same as String

Typed Array Coercion

Typed arrays ([String], [Number], [Boolean], [Object]) coerce each element to the specified type.

coerce([1, 2, 3], [String])        // → ["1", "2", "3"]
coerce(["1", "2"], [Number])       // → [1, 2]
coerce([1, 0, -1], [Boolean])      // → [true, false, false]
coerce([1, "hello"], [Object])     // → [{ value: 1 }, { value: "hello" }]

String Splitting: Strings containing commas or tabs are automatically split into arrays.

coerce("1,2,3", [Number])          // → [1, 2, 3]
coerce("a, b, c", [String])        // → ["a", "b", "c"] (whitespace trimmed)
coerce("true\tfalse", [Boolean])   // → [true, false]

Priority order:

  1. JSON parsing: "[1,2,3]"[1, 2, 3]
  2. Comma splitting: "1,2,3"["1", "2", "3"]
  3. Tab splitting: "1\t2\t3"["1", "2", "3"]
  4. Single element wrap: "42"["42"]

RegExp Type Shorthand

A bare RegExp as type coerces to String and validates against the pattern:

const handler = serviceHandler({
  input: {
    email: { type: /^[^@]+@[^@]+\.[^@]+$/ },
    url: { type: /^https?:\/\/.+/ },
  },
  service: ({ email, url }) => ({ email, url }),
});

await handler({ email: "[email protected]", url: "https://example.com" });  // ✓
await handler({ email: "invalid", url: "https://example.com" });          // ✗ BadRequestError

Validated Type Shorthand

Arrays of literals validate a value against allowed options.

String validation - array of strings and/or RegExp:

const sendMoneyHandler = serviceHandler({
  input: {
    amount: { type: Number },
    currency: { type: ["dec", "sps"] },  // Must be "dec" or "sps"
    user: { type: String },
  },
  service: ({ amount, currency, user }) => ({ amount, currency, user }),
});

await sendMoneyHandler({ amount: 100, currency: "dec", user: "bob" });  // ✓
await sendMoneyHandler({ amount: 100, currency: "usd", user: "bob" });  // ✗ BadRequestError

Number validation - array of numbers:

const taskHandler = serviceHandler({
  input: {
    priority: { type: [1, 2, 3, 4, 5] },  // Must be 1-5
    title: { type: String },
  },
  service: ({ priority, title }) => ({ priority, title }),
});

await taskHandler({ priority: 1, title: "Urgent" });   // ✓
await taskHandler({ priority: 10, title: "Invalid" }); // ✗ BadRequestError

Mixed string and RegExp validation:

const handler = serviceHandler({
  input: {
    value: { type: [/^test-/, "special"] },  // Matches /^test-/ OR equals "special"
  },
  service: ({ value }) => value,
});

await handler({ value: "test-123" });  // ✓
await handler({ value: "special" });   // ✓
await handler({ value: "other" });     // ✗ BadRequestError

Commander Adapter

The vocabulary package includes utilities for integrating service handlers with Commander.js CLIs.

registerServiceCommand

The simplest way to register a service handler as a Commander command:

import { Command } from "commander";
import { serviceHandler } from "@jaypie/vocabulary";
import { registerServiceCommand } from "@jaypie/vocabulary/commander";

const handler = serviceHandler({
  alias: "greet",
  description: "Greet a user",
  input: {
    userName: { type: String, flag: "user", letter: "u" },
    loud: { type: Boolean, letter: "l", default: false },
  },
  service: ({ loud, userName }) => {
    const greeting = `Hello, ${userName}!`;
    return loud ? greeting.toUpperCase() : greeting;
  },
});

const program = new Command();
registerServiceCommand({ handler, program });
program.parse();
// Usage: greet --user Alice -l

Configuration options:

  • handler - The service handler to register
  • program - The Commander program or command
  • name - Override command name (defaults to handler.alias)
  • description - Override description (defaults to handler.description)
  • exclude - Field names to exclude from options
  • overrides - Per-field option overrides

Input Flag and Letter Properties

Input definitions support flag and letter for Commander.js integration:

input: {
  userName: {
    type: String,
    flag: "user",     // Long flag: --user (instead of --user-name)
    letter: "u",      // Short flag: -u
  },
  verbose: {
    type: Boolean,
    letter: "v",      // Short flag: -v
  },
}
// Generates: --user <userName>, -u and --verbose, -v

createCommanderOptions

Generates Commander.js Option objects from handler input definitions:

import { createCommanderOptions } from "@jaypie/vocabulary/commander";

const { options } = createCommanderOptions(handler.input, {
  exclude: ["internalField"],      // Fields to skip
  overrides: {
    userName: {
      short: "u",                   // Add short flag: -u
      description: "Override desc", // Override description
      hidden: true,                 // Hide from help
    },
  },
});
options.forEach((opt) => program.addOption(opt));

parseCommanderOptions

Converts Commander.js options back to handler input format with type coercion:

import { parseCommanderOptions } from "@jaypie/vocabulary/commander";

const input = parseCommanderOptions(program.opts(), {
  input: handler.input,         // For type coercion and flag mapping
  exclude: ["help", "version"], // Fields to skip
});

Manual Integration Example

For more control, you can use createCommanderOptions and parseCommanderOptions directly:

import { Command } from "commander";
import { serviceHandler } from "@jaypie/vocabulary";
import { createCommanderOptions, parseCommanderOptions } from "@jaypie/vocabulary/commander";

const handler = serviceHandler({
  input: {
    userName: { type: String, description: "User name" },
    maxRetries: { type: Number, default: 3, description: "Max retries" },
    verbose: { type: Boolean, description: "Verbose output" },
  },
  service: (input) => console.log(input),
});

const program = new Command();

// Create Commander options from handler input
const { options } = createCommanderOptions(handler.input);
options.forEach((opt) => program.addOption(opt));

program.action(async (opts) => {
  // Parse Commander options back to handler input
  const input = parseCommanderOptions(opts, { input: handler.input });
  await handler(input);
});

program.parse();

Lambda Adapter

The vocabulary package includes utilities for integrating service handlers with AWS Lambda.

lambdaServiceHandler

Wraps a service handler for use as an AWS Lambda handler with full lifecycle management:

import { serviceHandler } from "@jaypie/vocabulary";
import { lambdaServiceHandler } from "@jaypie/vocabulary/lambda";

const evaluationsHandler = serviceHandler({
  alias: "evaluationsHandler",
  input: {
    count: { type: Number, default: 1 },
    models: { type: [String], default: [] },
    plan: { type: String },
  },
  service: ({ count, models, plan }) => ({
    jobId: `job-${Date.now()}`,
    plan,
  }),
});

// Config object style
export const handler = lambdaServiceHandler({
  handler: evaluationsHandler,
  secrets: ["ANTHROPIC_API_KEY", "OPENAI_API_KEY"],
});

// Or handler with options style
export const handler2 = lambdaServiceHandler(evaluationsHandler, {
  secrets: ["ANTHROPIC_API_KEY"],
  setup: [async () => { /* initialization */ }],
  teardown: [async () => { /* cleanup */ }],
});

Features:

  • Uses getMessages() from @jaypie/aws to extract messages from SQS/SNS events
  • Calls the service handler once for each message
  • Returns single response if one message, array of responses if multiple
  • Uses handler.alias as the logging handler name (overridable via name option)
  • Supports all lambdaHandler options: chaos, name, secrets, setup, teardown, throw, unavailable, validate

Before/After Comparison

Before (manual lambdaHandler wrapping):

export const handler = lambdaHandler(
  async (event: unknown) => {
    const messages = getMessages(event);
    const results = [];
    for (const message of messages) {
      const result = await evaluationsHandler(message);
      results.push({ status: "success", jobId: result.job.id, plan: message.plan });
    }
    return { status: "success", results };
  },
  {
    name: "evaluationsHandler",
    secrets: ["ANTHROPIC_API_KEY", "OPENAI_API_KEY"],
  },
);

After (using lambdaServiceHandler):

export const handler = lambdaServiceHandler({
  handler: evaluationsHandler,
  secrets: ["ANTHROPIC_API_KEY", "OPENAI_API_KEY"],
});

Serialization Formats

Complete Formats

Flat Model JSON

{
  "model": "job",
  "type": "command",
  "job": "evaluation",
  "plan": "hello"
}
{ errors: [] }

Response Model JSON

{
  data: {
    "model": "entity",
    "id": "identifier"
  }
}
{
  data: [
    { "model": "entity", "id": "identifier" },
    { "model": "entity", "id": "identifier" },
  ]
}
{ 
  errors: [] 
}

Lookup Shorthand

JSON

{ id: "identifier", model: "entity" }

String

${model}-${id}

Defined Vocabulary

Attributes

Defined Attributes

Should be treated as "strictly" defined:

  • abbreviation: ""
  • alias: ""
  • createdAt: ""
  • deletedAt: ""
  • description: ""
  • id: ""
  • input: {}
  • label: ""
  • metadata: {}
  • mock: {} | Boolean
  • model: ""
  • name: ""
  • ou: ""
  • output: {}
  • state: {}
  • type: ""
  • updatedAt: ""
  • xid: ""
Known Attributes

Ideally optimize for reusability:

  • default: *
  • input: {}
  • service: ()
  • value: ""
  • validate: [()] | { "": () | // }
Assumed Attributes

Future intentions planned:

  • authentication: () | [()]
  • authorization: () | [()]
  • chaos: ""
  • class: ""
  • context: {}
  • controller: () => input | { input, context }
  • env: ""
  • history: {}
  • locals: {}
  • message: () | ""
  • parameters: {}
  • required: []
  • role: ""
  • seed: ""
  • serializer: ()
  • setup: [()]
  • tags: [""]
  • teardown: [()]
Avoidable Attributes
  • body, data; prefer state
  • subtype; prefer class

Models

  • item

    • Not used directly
  • record

    • types: text, json
    • attributes: content
  • collection

    • types: models; e.g., records, jobs
    • attributes: items, subject
  • job

    • types: api, call, command, event, schedule
    • attributes: job, plan, status

Extending Vocabulary

  • The easiest way to extend the base vocabulary is with namespacing

Style Guide

  • Alphabetize anything