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

promptix

v1.0.0

Published

Three-tier prompt loading with Redis caching and LRU cache support

Readme

Promptix (@sno/promptix)

Three-tier prompt loading system with Redis caching, user-specific prompts, and multilingual support.

Architecture

┌─────────────────────────────────────────────────────────────────────┐
│                          Promptix                                   │
├─────────────────────────────────────────────────────────────────────┤
│  L1: LRU Cache (0.1ms)  →  L2: Redis (1-2ms)  →  L3: File (5-10ms)  │
└─────────────────────────────────────────────────────────────────────┘

Features:

  • Dependency injection (no hardcoded config)
  • Optional Redis (works file-only if unavailable)
  • Pub/Sub cache invalidation
  • User-specific prompts via userId
  • Multilingual support (en, zh, es)
  • Graceful fallback chain

Installation

# As npm package (private)
npm install @sno/promptix

# Or as git submodule
git submodule add https://github.com/sno-ai/promptix.git package/promptix

Peer Dependencies:

{
  "ioredis": "^5.0.0"  // Optional - only needed for Redis features
}

How to Use

Basic Usage

import { createPromptLoader } from "@sno/promptix";

// Create loader with file-only mode (no Redis)
const loader = createPromptLoader({
  promptDir: "./prompts",
});

// Load English prompt
const prompt = await loader.loadPrompt("memory", "extract", 1);

With Redis

const loader = createPromptLoader({
  promptDir: "./prompts",
  redisUrl: "redis://localhost:6379",
});

// Initialize Redis connection
await loader.initRedis();

// Start cache invalidation listener (optional)
await loader.startInvalidationListener();

// Load prompt
const prompt = await loader.loadPrompt("memory", "extract", 1);

// Cleanup on shutdown
await loader.close();

With User-Specific Prompt

// Load user-specific prompt
const prompt = await loader.loadPrompt("memory", "extract", 1, {
  userId: "user_12345",
});
// Falls back to generic prompt if user-specific not found

With Language

// Load Chinese prompt
const prompt = await loader.loadPrompt("memory", "extract", 1, {
  language: "zh",
});
// Falls back to English if Chinese not found

// Load Spanish prompt for specific user
const prompt = await loader.loadPrompt("memory", "extract", 1, {
  userId: "user_12345",
  language: "es",
});

Full Options

const prompt = await loader.loadPrompt("memory", "extract", 2, {
  fallbackVersion: 1,      // Try v1 if v2 not found
  context: "coding",       // App context (default: "default")
  userId: "user_123",      // User-specific prompt
  language: "zh",          // Language (default: "en")
});

Directory Structure

Filename Convention: {promptName}_v{version}.md (version suffix required)

{promptDir}/
├── {context}/                              # Context (e.g., "default", "coding")
│   └── {category}/                         # Category (e.g., "memory", "graph")
│       │
│       │── {promptName}_v{version}.md      # English (root - no subdirectory)
│       │
│       ├── zh/                             # Chinese (optional)
│       │   └── {promptName}_v{version}.md
│       │
│       ├── es/                             # Spanish (optional)
│       │   └── {promptName}_v{version}.md
│       │
│       └── {userId}/                       # User-specific (optional)
│           │── {promptName}_v{version}.md  # User English
│           ├── zh/
│           │   └── {promptName}_v{version}.md  # User Chinese
│           └── es/
│               └── {promptName}_v{version}.md  # User Spanish

Example Directory

prompts/
├── default/
│   ├── memory/
│   │   ├── extract_v1.md                   # English v1 (generic)
│   │   ├── extract_v2.md                   # English v2 (generic)
│   │   ├── zh/
│   │   │   └── extract_v1.md               # Chinese v1
│   │   ├── es/
│   │   │   └── extract_v1.md               # Spanish v1
│   │   └── user_12345/
│   │       ├── extract_v1.md               # User-specific English v1
│   │       └── zh/
│   │           └── extract_v1.md           # User-specific Chinese v1
│   │
│   └── graph/
│       └── build_v1.md
│
└── coding/                                 # Context for coding assistants
    └── memory/
        └── extract_v1.md                   # Coding-specific prompt

Input Parameters

createPromptLoader(config)

| Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | promptDir | string | Yes | - | Base directory for prompt files | | redisUrl | string | No | - | Redis connection URL | | cacheSize | number | No | 100 | LRU cache max entries | | cacheTtlSeconds | number | No | 21600 | Local cache TTL (6 hours) | | invalidationChannel | string | No | "prompt:invalidate" | Redis pub/sub channel | | redisConnectTimeoutMs | number | No | 5000 | Redis connect timeout | | redisCommandTimeoutMs | number | No | 5000 | Redis command timeout | | redisMaxRetries | number | No | 3 | Max retries per request | | logger | PromptLoaderLogger | No | console | Custom logger |

loadPrompt(category, promptName, version, options?)

| Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | category | string | Yes | - | Prompt category (e.g., "memory", "graph") | | promptName | string | Yes | - | Prompt name without version (e.g., "extract") | | version | number | Yes | - | Primary version to load (1-9999) | | options.fallbackVersion | number | No | - | Version to try if primary not found | | options.context | string | No | "default" | Application context | | options.userId | string | No | - | User ID for user-specific prompts | | options.language | "en" \| "zh" \| "es" | No | "en" | Language code |

Validation Rules

| Field | Pattern | Max Length | Notes | |-------|---------|------------|-------| | category | ^[a-z][a-z0-9_]{0,63}$ | 64 | Lowercase, alphanumeric, underscores | | promptName | ^[a-z][a-z0-9_]{0,63}$ | 64 | Lowercase, alphanumeric, underscores | | context | ^[a-z][a-z0-9_-]{0,63}$ | 64 | Also allows hyphens | | userId | ^[a-zA-Z0-9_-]{1,64}$ | 64 | Alphanumeric, underscores, hyphens | | version | Integer 1-9999 | - | - |


Fallback Chain

Prompts are searched in priority order until found:

With userId + language

1. {context}/{category}/{userId}/{lang}/{promptName}_v{version}.md
2. {context}/{category}/{userId}/{promptName}_v{version}.md          # English fallback
3. {context}/{category}/{lang}/{promptName}_v{version}.md            # Generic fallback
4. {context}/{category}/{promptName}_v{version}.md                   # English generic
5. default/{category}/{userId}/{lang}/{promptName}_v{version}.md     # Default context
6. default/{category}/{userId}/{promptName}_v{version}.md
7. default/{category}/{lang}/{promptName}_v{version}.md
8. default/{category}/{promptName}_v{version}.md

With language only (no userId)

1. {context}/{category}/{lang}/{promptName}_v{version}.md
2. {context}/{category}/{promptName}_v{version}.md                   # English fallback
3. default/{category}/{lang}/{promptName}_v{version}.md              # Default context
4. default/{category}/{promptName}_v{version}.md

With userId only (English)

1. {context}/{category}/{userId}/{promptName}_v{version}.md
2. {context}/{category}/{promptName}_v{version}.md                   # Generic fallback
3. default/{category}/{userId}/{promptName}_v{version}.md            # Default context
4. default/{category}/{promptName}_v{version}.md

Basic (no userId, English)

1. {context}/{category}/{promptName}_v{version}.md
2. default/{category}/{promptName}_v{version}.md                     # Default context

Output

loadPrompt() Return Value

Returns: Promise<string> - The prompt template content.

Cache Statistics

const stats = loader.getStats();

Returns CacheStats:

{
  localCache: {
    size: 45,        // Current cached items
    maxSize: 100,    // Max capacity
    hits: 1250,      // Cache hits
    misses: 48,      // Cache misses
    hitRate: 96.3    // Hit rate percentage
  },
  redisAvailable: true,   // Redis connection status
  pubsubActive: true      // Invalidation listener status
}

Errors

| Error Class | When Thrown | |-------------|-------------| | PromptNotFoundError | Prompt file not found in any fallback path | | InvalidPromptError | Template content invalid (empty, too short, too large) | | SecurityError | Path traversal or dangerous characters detected |


Redis Key Format

prompt:{context}:{category}:{userId}:{lang}:{promptName}:v{version}

Where:

  • userId = "_" when no user specified
  • lang = "en" for English (always explicit in Redis)

Examples

prompt:default:memory:_:en:extract:v1           # Basic English
prompt:default:memory:_:zh:extract:v1           # Chinese
prompt:default:memory:user_123:en:extract:v1    # User-specific English
prompt:coding:memory:user_123:zh:extract:v2     # User-specific Chinese in coding context

Cache Invalidation

Programmatic

// Clear all
loader.invalidate("*");

// Clear category
loader.invalidate("memory:*");

// Clear specific
loader.invalidate("default:memory:extract:v1");

Via Redis Pub/Sub

# Publish invalidation message
redis-cli PUBLISH prompt:invalidate "memory:*"

Best Practices

  1. Version Management: Start with v1, increment for breaking changes
  2. Fallback Versions: Always provide fallbackVersion for critical prompts
  3. User-Specific Prompts: Use userId for per-user fine-tuning, not user data storage
  4. Languages: Only create localized files when translation is complete
  5. Context: Use for app modes (coding, chat), not user segmentation

Migration from subdirectory

The parameter subdirectory has been renamed to category for clarity:

// Before
loader.loadPrompt("memory_agent", "prompt", 1);

// After
loader.loadPrompt("memory", "extract", 1);

Directory structure remains the same.