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

patent-query-shortcuts

v0.2.2

Published

Shortcut expansion + template system for patent search queries (e.g., TAC -> Title/Abstract/Claims)

Readme

patent-query-shortcuts

Shortcut expansion + template system for patent search queries. Type TAC and get TAC_ALL:(Title, Abstract, Claims) with smart autocomplete, date range snippets, and usage-based ranking.

Install

npm i patent-query-shortcuts

Table of Contents


Quick Start

import { createRegistry, patsnapTemplate, nuvoaiTemplate } from "patent-query-shortcuts";

// Step 1: Create a registry with templates
const reg = createRegistry([patsnapTemplate, nuvoaiTemplate]);

// Step 2: Get autocomplete suggestions (for frontend)
const result = reg.getCompletions("TA", 2, { templateId: "patsnap" });
// Returns: { token: { start: 0, end: 2, prefix: "TA" }, items: [...] }

// Step 3: Expand shortcuts in a query (for backend)
const expanded = reg.expand(`TAC:(stent) AND APD:[20260101 TO 20261231]`, { 
  templateId: "patsnap" 
});
// => TAC_ALL:(stent) AND APD:[20260101 TO 20261231]

Frontend Usage

Use this package in your frontend to provide autocomplete suggestions as users type patent query shortcuts.

React Example

Step-by-step implementation:

  1. Install and import the package:
import { createRegistry, patsnapTemplate } from "patent-query-shortcuts";
import { useState, useMemo, useCallback } from "react";
  1. Create a registry instance (do this once, outside component or in useMemo):
function PatentQueryInput() {
  // Create registry once - this is expensive, so use useMemo
  const registry = useMemo(() => {
    return createRegistry([patsnapTemplate]);
  }, []);

  const [query, setQuery] = useState("");
  const [cursorPosition, setCursorPosition] = useState(0);
  const [suggestions, setSuggestions] = useState([]);
  const [selectedIndex, setSelectedIndex] = useState(0);
  1. Handle text input and get completions:
  const handleInputChange = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {
    const text = e.target.value;
    const cursor = e.target.selectionStart;
    
    setQuery(text);
    setCursorPosition(cursor);
    
    // Get completions for current cursor position
    const result = registry.getCompletions(text, cursor, {
      templateId: "patsnap",
      limit: 10,
      trailingSpace: true, // Add space after completion
      allowEmptyPrefix: false // Only show suggestions when typing
    });
    
    setSuggestions(result.items);
    setSelectedIndex(0);
  }, [registry]);
  1. Handle keyboard navigation (Arrow keys, Enter, Tab):
  const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
    if (suggestions.length === 0) return;
    
    if (e.key === "ArrowDown") {
      e.preventDefault();
      setSelectedIndex(prev => (prev + 1) % suggestions.length);
    } else if (e.key === "ArrowUp") {
      e.preventDefault();
      setSelectedIndex(prev => (prev - 1 + suggestions.length) % suggestions.length);
    } else if (e.key === "Enter" || e.key === "Tab") {
      e.preventDefault();
      applySuggestion(suggestions[selectedIndex]);
    }
  }, [suggestions, selectedIndex]);
  1. Apply selected suggestion:
  const applySuggestion = useCallback((item: CompletionItem) => {
    // Apply the completion
    const result = registry.applyCompletion(query, item);
    
    setQuery(result.text);
    setCursorPosition(result.cursor);
    setSuggestions([]);
    
    // Record usage for better ranking
    registry.recordUsage("patsnap", item.key, 1);
    
    // Focus back to textarea and set cursor position
    setTimeout(() => {
      const textarea = document.querySelector("textarea");
      if (textarea) {
        textarea.focus();
        textarea.setSelectionRange(result.cursor, result.cursor);
      }
    }, 0);
  }, [query, registry]);
  1. Render the UI:
  return (
    <div className="query-input-container">
      <textarea
        value={query}
        onChange={handleInputChange}
        onKeyDown={handleKeyDown}
        placeholder="Type TAC, APD, etc. for suggestions..."
        rows={5}
        style={{ width: "100%", padding: "8px" }}
      />
      
      {/* Suggestions dropdown */}
      {suggestions.length > 0 && (
        <div className="suggestions-dropdown">
          {suggestions.map((item, idx) => (
            <div
              key={item.key}
              className={`suggestion-item ${idx === selectedIndex ? "selected" : ""}`}
              onClick={() => applySuggestion(item)}
              onMouseEnter={() => setSelectedIndex(idx)}
            >
              <strong>{item.label}</strong>
              {item.description && (
                <span className="suggestion-desc">{item.description}</span>
              )}
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

Complete React Component:

import { createRegistry, patsnapTemplate, type CompletionItem } from "patent-query-shortcuts";
import { useState, useMemo, useCallback } from "react";

function PatentQueryInput() {
  const registry = useMemo(() => createRegistry([patsnapTemplate]), []);
  const [query, setQuery] = useState("");
  const [cursorPosition, setCursorPosition] = useState(0);
  const [suggestions, setSuggestions] = useState<CompletionItem[]>([]);
  const [selectedIndex, setSelectedIndex] = useState(0);

  const handleInputChange = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {
    const text = e.target.value;
    const cursor = e.target.selectionStart;
    setQuery(text);
    setCursorPosition(cursor);
    
    const result = registry.getCompletions(text, cursor, {
      templateId: "patsnap",
      limit: 10,
      trailingSpace: true
    });
    
    setSuggestions(result.items);
    setSelectedIndex(0);
  }, [registry]);

  const applySuggestion = useCallback((item: CompletionItem) => {
    const result = registry.applyCompletion(query, item);
    setQuery(result.text);
    setCursorPosition(result.cursor);
    setSuggestions([]);
    registry.recordUsage("patsnap", item.key, 1);
  }, [query, registry]);

  const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
    if (suggestions.length === 0) return;
    if (e.key === "ArrowDown") {
      e.preventDefault();
      setSelectedIndex(prev => (prev + 1) % suggestions.length);
    } else if (e.key === "ArrowUp") {
      e.preventDefault();
      setSelectedIndex(prev => (prev - 1 + suggestions.length) % suggestions.length);
    } else if (e.key === "Enter" || e.key === "Tab") {
      e.preventDefault();
      applySuggestion(suggestions[selectedIndex]);
    }
  }, [suggestions, selectedIndex, applySuggestion]);

  return (
    <div className="query-input-container">
      <textarea
        value={query}
        onChange={handleInputChange}
        onKeyDown={handleKeyDown}
        placeholder="Type TAC, APD, etc. for suggestions..."
        rows={5}
      />
      {suggestions.length > 0 && (
        <div className="suggestions-dropdown">
          {suggestions.map((item, idx) => (
            <div
              key={item.key}
              className={idx === selectedIndex ? "selected" : ""}
              onClick={() => applySuggestion(item)}
            >
              <strong>{item.label}</strong>
              {item.description && <span>{item.description}</span>}
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

Vue.js Example

Step-by-step implementation:

  1. Install and setup:
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue';
import { createRegistry, patsnapTemplate, type CompletionItem } from 'patent-query-shortcuts';

// Step 1: Create registry (do this once)
const registry = createRegistry([patsnapTemplate]);

// Step 2: Reactive state
const query = ref('');
const cursorPosition = ref(0);
const suggestions = ref<CompletionItem[]>([]);
const selectedIndex = ref(0);
  1. Handle input changes:
const handleInput = (e: Event) => {
  const target = e.target as HTMLTextAreaElement;
  query.value = target.value;
  cursorPosition.value = target.selectionStart;
  
  // Step 3: Get completions
  const result = registry.getCompletions(query.value, cursorPosition.value, {
    templateId: 'patsnap',
    limit: 10,
    trailingSpace: true
  });
  
  suggestions.value = result.items;
  selectedIndex.value = 0;
};
  1. Apply suggestion:
const applySuggestion = (item: CompletionItem) => {
  const result = registry.applyCompletion(query.value, item);
  query.value = result.text;
  cursorPosition.value = result.cursor;
  suggestions.value = [];
  registry.recordUsage('patsnap', item.key, 1);
  
  // Update textarea cursor
  nextTick(() => {
    const textarea = document.querySelector('textarea');
    if (textarea) {
      textarea.setSelectionRange(result.cursor, result.cursor);
    }
  });
};
  1. Template:
<template>
  <div class="query-input-container">
    <textarea
      v-model="query"
      @input="handleInput"
      @keydown="handleKeyDown"
      placeholder="Type TAC, APD, etc..."
      rows="5"
    />
    <div v-if="suggestions.length > 0" class="suggestions">
      <div
        v-for="(item, idx) in suggestions"
        :key="item.key"
        :class="{ selected: idx === selectedIndex }"
        @click="applySuggestion(item)"
      >
        <strong>{{ item.label }}</strong>
        <span v-if="item.description">{{ item.description }}</span>
      </div>
    </div>
  </div>
</template>

Vanilla JavaScript Example

Step-by-step implementation:

<!DOCTYPE html>
<html>
<head>
  <title>Patent Query Input</title>
</head>
<body>
  <textarea id="queryInput" rows="5" style="width: 100%;"></textarea>
  <div id="suggestions"></div>

  <script type="module">
    // Step 1: Import the package
    import { createRegistry, patsnapTemplate } from './node_modules/patent-query-shortcuts/dist/index.js';
    
    // Step 2: Create registry
    const registry = createRegistry([patsnapTemplate]);
    
    // Step 3: Get DOM elements
    const textarea = document.getElementById('queryInput');
    const suggestionsDiv = document.getElementById('suggestions');
    let selectedIndex = 0;
    let currentSuggestions = [];
    
    // Step 4: Handle input
    textarea.addEventListener('input', (e) => {
      const text = e.target.value;
      const cursor = e.target.selectionStart;
      
      // Get completions
      const result = registry.getCompletions(text, cursor, {
        templateId: 'patsnap',
        limit: 10,
        trailingSpace: true
      });
      
      currentSuggestions = result.items;
      selectedIndex = 0;
      renderSuggestions();
    });
    
    // Step 5: Handle keyboard
    textarea.addEventListener('keydown', (e) => {
      if (currentSuggestions.length === 0) return;
      
      if (e.key === 'ArrowDown') {
        e.preventDefault();
        selectedIndex = (selectedIndex + 1) % currentSuggestions.length;
        renderSuggestions();
      } else if (e.key === 'ArrowUp') {
        e.preventDefault();
        selectedIndex = (selectedIndex - 1 + currentSuggestions.length) % currentSuggestions.length;
        renderSuggestions();
      } else if (e.key === 'Enter' || e.key === 'Tab') {
        e.preventDefault();
        applySuggestion(currentSuggestions[selectedIndex]);
      }
    });
    
    // Step 6: Apply suggestion
    function applySuggestion(item) {
      const result = registry.applyCompletion(textarea.value, item);
      textarea.value = result.text;
      suggestionsDiv.innerHTML = '';
      currentSuggestions = [];
      registry.recordUsage('patsnap', item.key, 1);
      
      // Set cursor position
      setTimeout(() => {
        textarea.setSelectionRange(result.cursor, result.cursor);
        textarea.focus();
      }, 0);
    }
    
    // Step 7: Render suggestions
    function renderSuggestions() {
      if (currentSuggestions.length === 0) {
        suggestionsDiv.innerHTML = '';
        return;
      }
      
      suggestionsDiv.innerHTML = currentSuggestions.map((item, idx) => `
        <div class="suggestion ${idx === selectedIndex ? 'selected' : ''}" 
             onclick="applySuggestion(${JSON.stringify(item).replace(/"/g, '&quot;')})">
          <strong>${item.label}</strong>
          ${item.description ? `<span>${item.description}</span>` : ''}
        </div>
      `).join('');
    }
  </script>
</body>
</html>

Backend Usage

Use this package in your backend to expand user queries before sending them to your patent search API.

Express.js API Example

Step-by-step implementation:

  1. Install and create service:
// services/queryService.ts
import { createRegistry, patsnapTemplate, nuvoaiTemplate } from "patent-query-shortcuts";

// Step 1: Create registry with all templates
const registry = createRegistry([patsnapTemplate, nuvoaiTemplate]);

// Step 2: Export query processing functions
export class QueryService {
  /**
   * Expand shortcuts in a user query
   * @param query - User's query with shortcuts (e.g., "TAC:(stent)")
   * @param templateId - Which template to use (e.g., "patsnap")
   * @returns Expanded query ready for search API
   */
  static expandQuery(query: string, templateId: string): string {
    return registry.expand(query, {
      templateId,
      wrapBareValues: true // Wrap bare values in parentheses
    });
  }

  /**
   * Validate and expand query
   * @param query - User query
   * @param templateId - Template ID
   * @returns Object with expanded query and validation info
   */
  static processQuery(query: string, templateId: string) {
    const expanded = this.expandQuery(query, templateId);
    
    return {
      original: query,
      expanded: expanded,
      templateId: templateId,
      isValid: expanded.length > 0,
      timestamp: new Date().toISOString()
    };
  }

  /**
   * Get available templates
   */
  static getTemplates() {
    return registry.listTemplates();
  }
}
  1. Create Express routes:
// routes/queryRoutes.ts
import express from "express";
import { QueryService } from "../services/queryService.js";

const router = express.Router();

// Step 3: POST endpoint to expand query
router.post("/expand", (req, res) => {
  try {
    const { query, templateId = "patsnap" } = req.body;
    
    if (!query || typeof query !== "string") {
      return res.status(400).json({ 
        error: "Query is required and must be a string" 
      });
    }

    // Step 4: Process and expand the query
    const result = QueryService.processQuery(query, templateId);
    
    res.json({
      success: true,
      data: result
    });
  } catch (error) {
    res.status(500).json({ 
      error: "Failed to process query", 
      message: error.message 
    });
  }
});

// Step 5: GET endpoint for available templates
router.get("/templates", (req, res) => {
  try {
    const templates = QueryService.getTemplates();
    res.json({
      success: true,
      data: templates
    });
  } catch (error) {
    res.status(500).json({ 
      error: "Failed to get templates" 
    });
  }
});

export default router;
  1. Use in your Express app:
// app.ts
import express from "express";
import queryRoutes from "./routes/queryRoutes.js";

const app = express();

app.use(express.json());
app.use("/api/query", queryRoutes);

app.listen(3000, () => {
  console.log("Server running on http://localhost:3000");
});
  1. Example API usage:
# Expand a query
curl -X POST http://localhost:3000/api/query/expand \
  -H "Content-Type: application/json" \
  -d '{
    "query": "TAC:(stent) AND APD:[20260101 TO 20261231]",
    "templateId": "patsnap"
  }'

# Response:
{
  "success": true,
  "data": {
    "original": "TAC:(stent) AND APD:[20260101 TO 20261231]",
    "expanded": "TAC_ALL:(stent) AND APD:[20260101 TO 20261231]",
    "templateId": "patsnap",
    "isValid": true,
    "timestamp": "2024-01-15T10:30:00.000Z"
  }
}

Query Processing Service

Complete backend service example:

// services/patentSearchService.ts
import { createRegistry, patsnapTemplate } from "patent-query-shortcuts";

const registry = createRegistry([patsnapTemplate]);

export class PatentSearchService {
  /**
   * Step 1: Preprocess user query
   * - Expand shortcuts
   * - Validate syntax
   * - Log for analytics
   */
  static preprocessQuery(userQuery: string, templateId: string = "patsnap") {
    // Expand shortcuts
    const expandedQuery = registry.expand(userQuery, {
      templateId,
      wrapBareValues: true
    });
    
    return {
      original: userQuery,
      expanded: expandedQuery,
      templateId
    };
  }

  /**
   * Step 2: Execute search with expanded query
   */
  static async searchPatents(query: string, templateId: string = "patsnap") {
    // Preprocess
    const processed = this.preprocessQuery(query, templateId);
    
    // Step 3: Send to your patent search API
    const response = await fetch("https://your-patent-api.com/search", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        query: processed.expanded,
        template: templateId
      })
    });
    
    const results = await response.json();
    
    // Step 4: Return results with metadata
    return {
      results,
      query: processed.expanded,
      originalQuery: processed.original,
      templateId: processed.templateId
    };
  }

  /**
   * Step 5: Track which shortcuts users use (for analytics)
   */
  static trackShortcutUsage(templateId: string, shortcutKey: string) {
    registry.recordUsage(templateId, shortcutKey, 1);
    // Optionally save to database for analytics
    console.log(`Tracked usage: ${templateId}/${shortcutKey}`);
  }
}

// Usage in your API endpoint:
app.post("/api/search", async (req, res) => {
  const { query, templateId } = req.body;
  
  try {
    const results = await PatentSearchService.searchPatents(query, templateId);
    res.json(results);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

Features

✅ Smart Autocomplete

  • Prefix matching: Type TA to see TA, TAC, TACD suggestions
  • Smart ranking: Exact match → Most-used → Shorter key → Alphabetical
  • Cursor positioning: Snippets place cursor at the right spot (e.g., TAC:(|))

✅ Date/Range Snippets

Date fields automatically insert range syntax:

  • APD:[| TO ] → Application Date range
  • B_CITES_COUNT:[| TO *] → Citation count range

✅ Trailing Space Option

Insert space after completion while keeping cursor inside brackets:

const items = reg.complete("TAC", 3, { 
  templateId: "patsnap", 
  trailingSpace: true 
});
// Inserts: "TAC:() " with cursor still inside ()

✅ Usage Tracking

Record which shortcuts users select to improve ranking:

reg.recordUsage("patsnap", "TAC", 1);
// TAC will now rank higher in future suggestions

✅ JSON Template Loader

Load templates from JSON:

import { loadTemplateFromJson, loadTemplatesFromJson } from "patent-query-shortcuts";

const template = loadTemplateFromJson({
  id: "custom",
  name: "Custom Template",
  shortcuts: [
    { key: "TEST", kind: "field", field: "TEST_FIELD", snippet: "TEST:(|)" }
  ]
});

✅ Built-in Templates

  • patsnapTemplate: Full Patsnap field mappings (100+ shortcuts)
  • nuvoaiTemplate: NuvoAI Patent Search fields

API Reference

createRegistry(templates: Template[])

Creates a registry with one or more templates.

Example:

const reg = createRegistry([patsnapTemplate, nuvoaiTemplate]);

registry.getCompletions(text, cursor, options)

One-call UI helper. Returns { token, items }:

  • token: { start, end, prefix } - the token being completed
  • items: Array of CompletionItem with insertText, newCursor, etc.

Options:

  • templateId (required): Which template to use
  • limit?: Max number of suggestions (default: 10)
  • trailingSpace?: Add space after completion (default: false)
  • allowEmptyPrefix?: Show all shortcuts when prefix is empty (default: false)

registry.complete(text, cursor, options)

Get completion items for autocomplete UI. Returns CompletionItem[].

registry.suggest(prefix, templateId, limit?)

Get simple suggestions (just keys and labels). Returns Suggestion[].

registry.expand(query, options)

Expand all shortcuts in a query string.

Options:

  • templateId (required): Which template to use
  • wrapBareValues?: Wrap bare values in parentheses (default: true)

registry.recordUsage(templateId, key, delta?)

Record that a shortcut was used (for ranking). delta defaults to 1.

registry.applyCompletion(text, item)

Apply a completion item to text. Returns { text, cursor }.

registry.listTemplates()

Get list of all registered templates. Returns { id, name }[].


Template Format

{
  id: "patsnap",
  name: "Patsnap",
  normalizeKey: "upper", // or "none" - converts keys to uppercase
  shortcuts: [
    {
      key: "TAC",              // User types this
      kind: "field",           // "field" or "macro"
      field: "TAC_ALL",        // Actual field name (for kind="field")
      macro: "{{value}}",      // Template string (for kind="macro")
      label: "Title+Abstract+Claims",
      description: "Search in title, abstract, and claims",
      snippet: "TAC:(|)",      // | marks cursor position after insertion
      aliases: ["TAC_ALL"]     // Alternative keys that map to this shortcut
    }
  ]
}

Advanced Examples

Custom Template from JSON

import { loadTemplateFromJson, createRegistry } from "patent-query-shortcuts";

// Load from JSON string
const jsonString = `{
  "id": "custom",
  "name": "Custom Template",
  "normalizeKey": "upper",
  "shortcuts": [
    {
      "key": "TITLE",
      "kind": "field",
      "field": "title_field",
      "snippet": "TITLE:(|)"
    }
  ]
}`;

const template = loadTemplateFromJson(jsonString);
const reg = createRegistry([template]);

Multi-Template Support

const reg = createRegistry([patsnapTemplate, nuvoaiTemplate]);

// User selects template in UI
const selectedTemplate = "patsnap";

// Get suggestions for selected template
const suggestions = reg.suggest("TA", selectedTemplate);

// Expand query using selected template
const expanded = reg.expand("TAC:(test)", { templateId: selectedTemplate });

Usage Analytics

// Track which shortcuts users select
function onSuggestionSelected(templateId: string, key: string) {
  registry.recordUsage(templateId, key, 1);
  
  // Optionally save to your analytics service
  analytics.track("shortcut_used", {
    template: templateId,
    shortcut: key
  });
}

// Most-used shortcuts will now rank higher
const suggestions = registry.suggest("TA", "patsnap");
// TAC will appear before TACD if it's been used more

Date Range Handling

// Date fields automatically get range snippets
const items = registry.complete("APD", 3, { templateId: "patsnap" });
const apdItem = items.find(i => i.key === "APD");

// When applied, inserts: "APD:[ TO ]" with cursor at position 5
const result = registry.applyCompletion("APD", apdItem);
// result.text = "APD:[ TO ]"
// result.cursor = 5 (inside the bracket)

License

MIT

Contributing

Contributions welcome! Please open an issue or PR.