{"_id":"@314owen/prompty","_rev":"8-5454b740ec32b323f135b4ffcad3b502","name":"@314owen/prompty","dist-tags":{"latest":"1.0.5"},"versions":{"1.0.1":{"name":"@314owen/prompty","version":"1.0.1","license":"ISC","_id":"@314owen/prompty@1.0.1","maintainers":[{"name":"314owen","email":"314owen@gmail.com"}],"dist":{"shasum":"a126346a3fe393f8d3c24c745b6f2aec9e115cdc","tarball":"https://registry.npmjs.org/@314owen/prompty/-/prompty-1.0.1.tgz","fileCount":17,"integrity":"sha512-0mqYnDlcLhwf2davJ8XjmePM+YH6Wn4GImFcmpZ4OnN0dHi7zVQGmDrCtAIjt6D8W2sOmaxHMqvVZEUSARuz0Q==","signatures":[{"sig":"MEUCIQDcn11UEl2PMggKzkX8MKJbPyZUsxbe/AMCt+RRs+lDEAIgWhwMqeFTg+Z7PMSitvNe/Kq3X91XvxW8RcP1mFC45zw=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":33401},"type":"module","exports":{".":{"import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"7fcde2135586aa931a0950dec7450ac8fcd8ecb8","scripts":{"test":"vitest run","build":"tsup","test:watch":"vitest","test:integration":"vitest run --config vitest.integration.config.ts"},"_npmUser":{"name":"314owen","email":"314owen@gmail.com"},"_npmVersion":"11.16.0","description":"Typed, validated prompting against Cloudflare Workers AI","directories":{},"_nodeVersion":"25.2.1","allowScripts":{"esbuild@0.27.7":true,"fsevents@2.3.3":true},"dependencies":{"zod":"^4.4.3"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","dotenv":"^17.4.2","vitest":"^4.1.9","typescript":"^6.0.3","@types/node":"^25.9.3"},"_npmOperationalInternal":{"tmp":"tmp/prompty_1.0.1_1781666344837_0.10084739745042581","host":"s3://npm-registry-packages-npm-production"}},"1.0.2":{"name":"@314owen/prompty","version":"1.0.2","license":"ISC","_id":"@314owen/prompty@1.0.2","maintainers":[{"name":"314owen","email":"314owen@gmail.com"}],"dist":{"shasum":"f6328a182b0445aafc2babad16288374e38edfb7","tarball":"https://registry.npmjs.org/@314owen/prompty/-/prompty-1.0.2.tgz","fileCount":6,"integrity":"sha512-T7Rowlt55IahTviH8U9XVsx30YEliHbfjF3Mh3GH+hvYLckneyxND913CYoeMmzB0f6mJhbOF+dwN6OcZgC8wg==","signatures":[{"sig":"MEUCIQDhYBcf40t9r42t73Wu44KGKuPPfoU3SFgCVfcrc+X7pQIgFrIS3P3E8LMMgCNlLf96Rup8U7IYMVhu+/IjkIeYUjk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":22532},"type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"7fcde2135586aa931a0950dec7450ac8fcd8ecb8","scripts":{"test":"vitest run","build":"tsup","test:watch":"vitest","test:integration":"vitest run --config vitest.integration.config.ts"},"_npmUser":{"name":"314owen","email":"314owen@gmail.com"},"_npmVersion":"11.16.0","description":"Typed, validated prompting against Cloudflare Workers AI","directories":{},"_nodeVersion":"25.2.1","allowScripts":{"esbuild@0.27.7":true,"fsevents@2.3.3":true},"dependencies":{"zod":"^4.4.3"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","dotenv":"^17.4.2","vitest":"^4.1.9","typescript":"^6.0.3","@types/node":"^25.9.3"},"_npmOperationalInternal":{"tmp":"tmp/prompty_1.0.2_1781667554144_0.38608447841697013","host":"s3://npm-registry-packages-npm-production"}},"1.0.3":{"name":"@314owen/prompty","version":"1.0.3","license":"ISC","_id":"@314owen/prompty@1.0.3","maintainers":[{"name":"314owen","email":"314owen@gmail.com"}],"dist":{"shasum":"b3a7e9881aa6032902247dda510ef43f0c05b00c","tarball":"https://registry.npmjs.org/@314owen/prompty/-/prompty-1.0.3.tgz","fileCount":6,"integrity":"sha512-CPvq1SOIMe5ajwlJyVnURpQSnCwYBUCA3j78E6ubSVTx/sa/AX7XavqbMqm7FfrypywFpIF2mG+0p0ypNVshBA==","signatures":[{"sig":"MEUCIQCBNjCRLfbQeX6QZAlzi5neQUb56WpUEMJVyYiV5biNrAIgTWdvq3CPRHHMUg9GIcZP64RSplMPoE1LMURbtWjzrqE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":22643},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}}},"gitHead":"7fcde2135586aa931a0950dec7450ac8fcd8ecb8","scripts":{"test":"vitest run","build":"tsup","test:watch":"vitest","test:integration":"vitest run --config vitest.integration.config.ts"},"_npmUser":{"name":"314owen","email":"314owen@gmail.com"},"_npmVersion":"11.16.0","description":"Typed, validated prompting against Cloudflare Workers AI","directories":{},"_nodeVersion":"25.2.1","allowScripts":{"esbuild@0.27.7":true,"fsevents@2.3.3":true},"dependencies":{"zod":"^4.4.3"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","dotenv":"^17.4.2","vitest":"^4.1.9","typescript":"^6.0.3","@types/node":"^25.9.3"},"_npmOperationalInternal":{"tmp":"tmp/prompty_1.0.3_1781667802926_0.7487269103715442","host":"s3://npm-registry-packages-npm-production"}},"1.0.4":{"name":"@314owen/prompty","version":"1.0.4","license":"ISC","_id":"@314owen/prompty@1.0.4","maintainers":[{"name":"314owen","email":"314owen@gmail.com"}],"dist":{"shasum":"7082b3b20c215314abda1c418d1b593c74d41450","tarball":"https://registry.npmjs.org/@314owen/prompty/-/prompty-1.0.4.tgz","fileCount":18,"integrity":"sha512-4S/hX0badBIBuJxdIs8K8vn4dADxaXxmQXhxBmUJeQ62GkXpNH/nu8WnRNAHQRKroQuQDzIRSDc30NE7v2G+rg==","signatures":[{"sig":"MEQCICBFBAQDHdPzF+QNo3/iHbHlCdiZ6qSm2ebQsUOt1uWBAiAWyTRtChtkmLlL/XG+3ilQalUR6y9CI77xS9d+FShsWQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":49852},"type":"module","exports":{".":{"import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"7fcde2135586aa931a0950dec7450ac8fcd8ecb8","scripts":{"test":"vitest run","build":"tsup","test:watch":"vitest","test:integration":"vitest run --config vitest.integration.config.ts"},"_npmUser":{"name":"314owen","email":"314owen@gmail.com"},"_npmVersion":"11.16.0","description":"Typed, validated prompting against Cloudflare Workers AI","directories":{},"_nodeVersion":"26.3.0","allowScripts":{"esbuild@0.27.7":true,"fsevents@2.3.3":true},"dependencies":{"zod":"^4.4.3"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","dotenv":"^17.4.2","vitest":"^4.1.9","typescript":"^6.0.3","@types/node":"^25.9.3"},"_npmOperationalInternal":{"tmp":"tmp/prompty_1.0.4_1781806131401_0.23889054788436437","host":"s3://npm-registry-packages-npm-production"}},"1.0.5":{"name":"@314owen/prompty","version":"1.0.5","description":"Typed, validated prompting against Cloudflare Workers AI","license":"ISC","type":"module","main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"dependencies":{"zod":"^4.4.3"},"devDependencies":{"@types/node":"^25.9.3","dotenv":"^17.4.2","tsup":"^8.5.1","typescript":"^6.0.3","vitest":"^4.1.9"},"publishConfig":{"access":"public"},"allowScripts":{"fsevents@2.3.3":true,"esbuild@0.27.7":true},"scripts":{"build":"tsup && tsc -p tsconfig.build.json","test":"vitest run","test:integration":"vitest run --config vitest.integration.config.ts","test:watch":"vitest"},"_nodeVersion":"22.14.0","_id":"@314owen/prompty@1.0.5","dist":{"integrity":"sha512-6E843+GkrpyyurjqKQqLXJ3f9s5xABDFsuFyKdtdOzmIJ7Ml4JQggTO/Y3Zi6T75VboifzguZOB9+6n9fX7G/A==","shasum":"5332919112fdf53c445b8368ad87b8c659a1d6df","tarball":"https://registry.npmjs.org/@314owen/prompty/-/prompty-1.0.5.tgz","fileCount":10,"unpackedSize":31821,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIEN2Vmbke3aQGhyKVGhfmXsuNDK6ED/YeWbz/AETTpmRAiEAimCo7fz7mTgYwqksbBX2vPJGrRnuuqDb23n5GemWwPM="}]},"_npmUser":{"name":"314owen","email":"314owen@gmail.com"},"directories":{},"maintainers":[{"name":"314owen","email":"314owen@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/prompty_1.0.5_1785725576078_0.57090372718383"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-17T03:19:04.666Z","modified":"2026-08-03T02:52:56.364Z","1.0.0":"2026-06-17T03:11:54.825Z","1.0.1":"2026-06-17T03:19:05.014Z","1.0.2":"2026-06-17T03:39:14.281Z","1.0.3":"2026-06-17T03:43:23.091Z","1.0.4":"2026-06-18T18:08:51.548Z","1.0.5":"2026-08-03T02:52:56.225Z"},"license":"ISC","description":"Typed, validated prompting against Cloudflare Workers AI","maintainers":[{"name":"314owen","email":"314owen@gmail.com"}],"readme":"# prompty\r\n\r\nTyped, validated prompting against the [Cloudflare Workers AI](https://developers.cloudflare.com/workers-ai/) REST API.\r\n\r\n- **Input/output validation** via Zod — bad LLM responses are reprompted automatically\r\n- **Retry loop** — failed schema parses append the error to the conversation and retry\r\n- **KV caching** — pass any `KVNamespace`-compatible binding; cache keys are SHA-256 of `(model, system, input)`\r\n- **Personas** — shared defaults (model, system prompt, retries) composed with per-prompt overrides\r\n- **Zero runtime deps** — only `zod`; uses native `fetch` and Web Crypto\r\n\r\n---\r\n\r\n## Setup\r\n\r\n```bash\r\nnpm install prompty\r\n```\r\n\r\nCopy the environment template and fill in your Cloudflare credentials:\r\n\r\n```bash\r\ncp .env.example .env\r\n```\r\n\r\n| Variable | Where to find it |\r\n|---|---|\r\n| `CLOUDFLARE_ACCOUNT_ID` | Cloudflare dashboard → right sidebar |\r\n| `CLOUDFLARE_API_KEY` | [dash.cloudflare.com/profile/api-tokens](https://dash.cloudflare.com/profile/api-tokens) → create token with **Workers AI** permission |\r\n\r\n---\r\n\r\n## Quick start\r\n\r\n```ts\r\nimport { definePrompt } from 'prompty';\r\nimport { z } from 'zod';\r\n\r\nconst config = {\r\n  accountId: process.env.CLOUDFLARE_ACCOUNT_ID!,\r\n  apiKey: process.env.CLOUDFLARE_API_KEY!,\r\n};\r\n\r\nconst classify = definePrompt({\r\n  system: 'Classify the sentiment of the input text.',\r\n  input: z.object({ text: z.string() }),\r\n  output: z.object({\r\n    sentiment: z.enum(['positive', 'negative', 'neutral']),\r\n    confidence: z.number().min(0).max(1),\r\n  }),\r\n}, config);\r\n\r\nconst result = await classify.run({ text: 'The product is surprisingly good!' });\r\n// → { sentiment: 'positive', confidence: 0.95 }\r\n```\r\n\r\n---\r\n\r\n## Personas\r\n\r\nShare defaults across multiple prompts:\r\n\r\n```ts\r\nimport { createPersona } from 'prompty';\r\n\r\nconst analyst = createPersona({\r\n  system: 'You are a financial analyst. Be precise and cite figures.',\r\n  model: '@cf/meta/llama-3.3-70b-instruct-fp8-fast',\r\n  maxRetries: 5,\r\n}, config);\r\n\r\nconst summarise = analyst({\r\n  system: 'Summarise the following earnings report.',\r\n  input: z.object({ report: z.string() }),\r\n  output: z.object({\r\n    summary: z.string(),\r\n    keyFigures: z.array(z.string()),\r\n  }),\r\n});\r\n\r\nconst result = await summarise.run({ report: '...' });\r\n```\r\n\r\nTask-level values always override persona defaults. System prompts concatenate as `persona.system + '\\n\\n' + task.system`.\r\n\r\n---\r\n\r\n## Caching\r\n\r\nPass any object matching the `KVNamespace` interface — a Cloudflare KV binding in Workers, or an in-memory store in tests:\r\n\r\n```ts\r\nconst prompt = definePrompt({\r\n  system: 'Answer questions.',\r\n  input: z.object({ q: z.string() }),\r\n  output: z.object({ answer: z.string() }),\r\n  cache: {\r\n    enabled: true,\r\n    kv: env.MY_KV,        // Cloudflare KV binding\r\n    ttlSeconds: 3600,     // optional; omit for no expiry\r\n  },\r\n}, config);\r\n\r\n// Skip cache for a specific call:\r\nawait prompt.run({ q: '...' }, { skipCache: true });\r\n```\r\n\r\n---\r\n\r\n## Defaults\r\n\r\n| Setting | Default | Description |\r\n|---|---|---|\r\n| `model` | `@cf/meta/llama-3.1-8b-instruct` | LLM model to use |\r\n| `maxRetries` | `3` | Max retry attempts on validation failure |\r\n| `maxContextTokens` | `8000` | Max cumulative message history size (characters) |\r\n| `maxRequestSizeBytes` | `51200` | Max input size before API call (50KB) |\r\n| `requestTimeoutMs` | `30000` | Request timeout in milliseconds |\r\n| `cache.enabled` | `false` | Enable KV caching |\r\n\r\n---\r\n\r\n## Safety Features\r\n\r\n### Message History Limits\r\n\r\nTo prevent unbounded conversation growth during retries, a `maxContextTokens` limit (default 8000) caps the conversation history size. If retries exceed this limit, the prompt throws a `max_retries` error with details on the last attempt.\r\n\r\n```ts\r\nconst prompt = definePrompt({\r\n  system: 'Answer questions.',\r\n  input: z.object({ q: z.string() }),\r\n  output: z.object({ answer: z.string() }),\r\n  maxContextTokens: 12000,  // Increase for complex schemas\r\n  maxRetries: 5,\r\n}, config);\r\n```\r\n\r\n### Input Size Validation\r\n\r\nInput is validated against `maxRequestSizeBytes` (default 50KB) before the API call to prevent oversized requests:\r\n\r\n```ts\r\nconst prompt = definePrompt({\r\n  maxRequestSizeBytes: 100000,  // 100KB limit\r\n  // ...\r\n}, config);\r\n```\r\n\r\n### Request Timeout\r\n\r\nAPI requests have a default timeout of 30 seconds. Customize it per prompt:\r\n\r\n```ts\r\nconst prompt = definePrompt({\r\n  requestTimeoutMs: 60000,  // 60 seconds\r\n  // ...\r\n}, config);\r\n```\r\n\r\n### Schema Constraints in Prompts\r\n\r\nArray size constraints are communicated to the LLM in the system prompt:\r\n\r\n```ts\r\nconst prompt = definePrompt({\r\n  system: 'Generate a list.',\r\n  input: z.object({ topic: z.string() }),\r\n  output: z.object({\r\n    items: z.array(z.string()).min(3).max(10),\r\n  }),\r\n}, config);\r\n\r\n// LLM sees in system prompt:\r\n// \"items\": array of string (at least 3 items, at most 10 items)\r\n```\r\n\r\n### Cache Validation\r\n\r\nCached values are re-validated against the output schema before returning. This ensures stale cache data (e.g., after schema changes) isn't returned without validation:\r\n\r\n```ts\r\n// If the schema changes but cache still has old format, \r\n// the old data is rejected and a fresh API call is made\r\nconst prompt = definePrompt({\r\n  cache: { enabled: true, kv: env.MY_KV },\r\n  output: z.object({ updated_field: z.string() }),\r\n}, config);\r\n```\r\n\r\n### Cache Key Canonicalization\r\n\r\nCache keys are generated from canonicalized JSON, so semantically identical inputs with different key ordering hash to the same key:\r\n\r\n```ts\r\n// Both inputs below cache to the same key\r\nawait prompt.run({ topic: 'dogs', count: 5 });\r\nawait prompt.run({ count: 5, topic: 'dogs' });\r\n```\r\n\r\n---\r\n\r\n## Error Handling\r\n\r\nAll errors are instances of `PromptyError` with a `kind` field for discrimination:\r\n\r\n```ts\r\nimport { PromptyError } from 'prompty';\r\n\r\ntry {\r\n  await prompt.run(input);\r\n} catch (err) {\r\n  if (err instanceof PromptyError) {\r\n    if (err.kind === 'validation') {\r\n      // Input validation failed — check err.message for field details\r\n      console.error('Bad input:', err.message);\r\n    } else if (err.kind === 'api') {\r\n      // Cloudflare Workers AI API error\r\n      console.error('API error:', err.message);\r\n    } else if (err.kind === 'max_retries') {\r\n      // Exhausted retries (validation or context limit)\r\n      console.error('Max retries exceeded:', err.message);\r\n    }\r\n  }\r\n}\r\n```\r\n\r\nInput validation errors include field-level details:\r\n```\r\nInput validation failed: \"user.email\": Invalid email; \"user.age\": Expected number\r\n```\r\n\r\n---\r\n\r\n## Running tests\r\n\r\n```bash\r\n# Unit tests (no credentials needed)\r\nnpm test\r\n\r\n# Integration tests (requires .env with real credentials)\r\nnpm run test:integration\r\n\r\n# Watch mode\r\nnpm run test:watch\r\n```\r\n","readmeFilename":""}