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

promptdef

v0.1.0

Published

Typed, versioned prompt templates with variable interpolation, token estimation, and diff tracking

Readme

promptdef

Typed, versioned prompt templates for LLM applications — with variable interpolation, token estimation, pre-flight validation, diff tracking, and a registry for storing and managing prompt versions.

npm install promptdef

Why

Most teams store prompts as raw strings in code or config files. There is no type safety on variables (you find out at runtime that {{userName}} was never filled), no token budget awareness before sending, and no history of what changed between versions.

promptdef makes prompts first-class citizens in a codebase.


Quick start

import { definePrompt, renderPrompt } from 'promptdef'

const summarize = definePrompt({
  id: 'summarize-article',
  version: '1.0.0',
  template: `
    You are a {{tone}} summarizer.
    Summarize this article in {{maxSentences}} sentences.

    Article: {{article}}
  `,
  variables: {
    tone:         { type: 'string', default: 'neutral' },
    maxSentences: { type: 'number', required: true, min: 1, max: 10 },
    article:      { type: 'string', required: true },
  },
})

const result = renderPrompt(summarize, {
  maxSentences: 3,
  article: 'The Federal Reserve raised interest rates by 25bp...',
})

result.text        // final string, ready to send
result.tokenCount  // estimated tokens (no API call needed)
result.variables   // the values that were interpolated
result.meta        // { id, version }

Type safety

Variable types are inferred at compile time. TypeScript catches problems before you run anything.

renderPrompt(summarize, {
  maxSentences: 'three',  // ❌ Type error: expected number
  // article omitted      // ❌ Type error: required field missing
})

A variable is required when it has no default and no required: false. A variable is optional when it has a default value or required: false — and gets a ? in the inferred type.

Supported variable types:

| Type | TypeScript type | Extra constraints | |-----------|-----------------|-------------------------| | string | string | | | number | number | min, max | | boolean | boolean | | | array | string[] / number[] | items: 'string' \| 'number' |


Token estimation

import { estimateTokens, assertFitsInContext } from 'promptdef'

estimateTokens('Hello, world!')
// { tokens: 4, method: 'tiktoken', encoding: 'cl100k_base' }
// Falls back to { tokens: 4, method: 'approximation' } if tiktoken is not installed.

// Throws ContextWindowExceededError if the rendered prompt won't fit.
assertFitsInContext(result, {
  model: 'claude-sonnet-4-6',
  contextWindow: 200_000,
  reserveForResponse: 2_000,
})

Install tiktoken as an optional peer dependency for accurate counts:

npm install tiktoken

Without it, promptdef falls back to a character-based approximation (ceil(chars / 4)) — no API calls required either way.


Pre-flight validation

validatePrompt collects all problems at once without rendering — useful for form validation or CI checks before a deploy.

import { validatePrompt } from 'promptdef'

const result = validatePrompt(summarize, {
  maxSentences: 'three',  // wrong type
  // article missing
})

result.valid   // false
result.errors
// [
//   { variable: 'maxSentences', message: '"maxSentences" must be a number, got string' },
//   { variable: 'article',      message: 'Missing required variable "article"' },
// ]
result.warnings
// e.g. declared variable never used in template

Unlike renderPrompt, validatePrompt never throws. It accepts partial values so you can call it with no second argument to discover what is required.


Diff tracking

import { diffPrompts } from 'promptdef'

const diff = diffPrompts(v1, v2)

diff.templateChanges  // line-level added / removed
diff.variablesAdded   // ['date', 'focusAreas']
diff.variablesRemoved // []
diff.tokenDelta       // +13
diff.breakingChange   // true if a required variable was removed

breakingChange: true is the signal to block a merge in CI — existing callers would be missing a newly-required variable or would pass a variable that no longer exists.


Composition

Combine a system prompt and a task prompt into one, merging their variable schemas.

import { composePrompt } from 'promptdef'

const systemPrompt = definePrompt({
  id: 'system',
  version: '1.0.0',
  template: 'You work for {{company}}. Be concise.',
  variables: { company: { type: 'string', required: true } },
})

const composed = composePrompt([systemPrompt, summarize], { separator: '\n\n---\n\n' })
// composed.template   — both templates joined with the separator
// composed.variables  — merged from both (throws on conflicting types)

Registry

Store, version, and retrieve prompts — in a directory, in memory, in S3, or in any SQL database.

File registry

import { createRegistry } from 'promptdef'

const registry = createRegistry({ storage: './prompts' })

await registry.save(summarize)
// writes ./prompts/[email protected]

await registry.load('summarize-article', '1.0.0')
await registry.list()                        // latest version of every prompt
await registry.history('summarize-article')  // all versions, semver-sorted
await registry.rollback('summarize-article', '1.0.0')

Memory registry (testing)

import { createMemoryAdapter, createRegistry } from 'promptdef'

const registry = createRegistry({ storage: createMemoryAdapter() })

S3 registry

Requires @aws-sdk/client-s3 (optional peer dep):

npm install @aws-sdk/client-s3
import { S3Client } from '@aws-sdk/client-s3'
import { createAwsS3Backend, createS3Adapter, createRegistry } from 'promptdef'
// or: import { createAwsS3Backend, createS3Adapter } from 'promptdef/adapters/s3'

const backend = createAwsS3Backend(
  new S3Client({ region: 'us-east-1' }),
  'my-prompts-bucket'
)

const registry = createRegistry({
  storage: createS3Adapter(backend, { prefix: 'prompts/' }),
})

SQL registry

Works with any database — pass a query function that matches your driver.

import { createSqlAdapter, createRegistry } from 'promptdef'
// or: import { createSqlAdapter } from 'promptdef/adapters/sql'

// Create the table once:
// CREATE TABLE prompts (
//   id      TEXT NOT NULL,
//   version TEXT NOT NULL,
//   data    TEXT NOT NULL,
//   PRIMARY KEY (id, version)
// );

// mysql2 / better-sqlite3 (? placeholders)
const registry = createRegistry({
  storage: createSqlAdapter({
    query: async (sql, params) => {
      const [rows] = await pool.execute(sql, params)
      return rows as Record<string, unknown>[]
    },
  }),
})

// pg (convert ? → $N)
const registry = createRegistry({
  storage: createSqlAdapter({
    query: async (sql, params) => {
      let i = 0
      const pgSql = sql.replace(/\?/g, () => `$${++i}`)
      const { rows } = await pool.query(pgSql, params)
      return rows
    },
  }),
})

CLI

Install globally or use via npx:

npm install -g promptdef
# or
npx promptdef <command>

diff — compare two versions

promptdef diff v1.json v2.json
Comparing summarize-article:  1.0.0 → 2.0.0

Template changes (2):
  +  [line 1]  You are a {{tone}} summarizer. Today is {{date}}.
  -  [line 1]  You are a {{tone}} summarizer.

Variables added (1):
  +  date (string, required)

Token delta:  +13

✓  No breaking changes

Exits 1 when a breaking change is detected — use this to block merges in CI:

- run: promptdef diff prompts/v1.json prompts/v2.json

Use --json for machine-readable output:

promptdef diff v1.json v2.json --json | jq .diff.breakingChange

validate — pre-flight check

promptdef validate prompt.json --values '{"maxSentences": 3, "article": "..."}'

Exits 0 when valid, 1 when there are errors.

render — interpolate and print

promptdef render prompt.json --values '{"maxSentences": 3, "article": "..."}'
Rendered  [email protected]  (~30 tokens)

You are a neutral summarizer.
Summarize this article in 3 sentences.

Article: ...

Use --raw to print only the interpolated text (for piping):

promptdef render prompt.json --values '...' --raw | pbcopy

list — show all prompts in a registry

promptdef list ./prompts
3 prompts in ./prompts

  answer-question     1.0.0
  classify-sentiment  1.0.0  [classification]
  summarize-article   2.0.0  [summarization]

history — version timeline for one prompt

promptdef history summarize-article ./prompts
summarize-article  2 versions

  1.0.0  7 tokens
  2.0.0  16 tokens  +9

Global options

| Flag | Description | |---------------|------------------------------------------| | --json | Machine-readable JSON output | | --no-color | Disable ANSI colour | | --values | JSON string of variable values | | --raw | Text-only output (render command) |

Exit codes

| Code | Meaning | |------|------------------------------------------------------| | 0 | Success | | 1 | Breaking change / validation failure | | 2 | Usage error (bad args, file not found, invalid JSON) |


Subpath imports

Import only what you need to keep bundles lean:

import { createS3Adapter }  from 'promptdef/adapters/s3'
import { createSqlAdapter } from 'promptdef/adapters/sql'
import { createFileAdapter } from 'promptdef/adapters/file'
import { createMemoryAdapter } from 'promptdef/adapters/memory'

License

MIT