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

@tstsx/preventable

v0.0.2

Published

English | [한국어](./README.ko.md)

Downloads

14

Readme

English | 한국어

@tstsx/preventable

Intercept and conditionally prevent function execution with a flexible event-based API.

Why?

Sometimes you need to add pre-execution hooks to functions without modifying their core logic. This library provides:

  • Event-based interception: Add multiple handlers that execute before the original function
  • Conditional prevention: Stop function execution based on dynamic conditions
  • Parameter access: Read and modify parameters passed to the original function
  • Async support: Works seamlessly with both sync and async functions
  • Type-safe: Full TypeScript support with preserved function signatures

Perfect for: validation hooks, authorization checks, logging, analytics, form submission control, and conditional behavior.

Installation

npm install @tstsx/preventable

Usage

Basic Example

import { preventable } from '@tstsx/preventable';

const submitForm = (data: FormData) => {
  console.log('Submitting:', data);
};

const wrappedSubmit = preventable(submitForm, [
  (event) => {
    if (!event.params[0].has('email')) {
      event.preventDefault();
      console.log('Email is required');
    }
  }
]);

wrappedSubmit(new FormData()); // Logs "Email is required", doesn't submit

Multiple Handlers

const saveData = (data: any) => {
  console.log('Saving:', data);
};

const wrappedSave = preventable(saveData, [
  (event) => {
    console.log('Handler 1: Logging attempt');
  },
  (event) => {
    if (event.params[0].invalid) {
      event.preventDefault();
      console.log('Handler 2: Data is invalid');
    }
  },
  (event) => {
    console.log('Handler 3: Still runs even if prevented');
  }
]);

wrappedSave({ invalid: true });
// Logs:
// "Handler 1: Logging attempt"
// "Handler 2: Data is invalid"
// "Handler 3: Still runs even if prevented"
// saveData is NOT called

Async Functions

const saveToDatabase = async (user: User) => {
  await db.users.insert(user);
};

const wrappedSave = preventable(saveToDatabase, [
  async (event) => {
    const user = event.params[0];
    const exists = await db.users.findByEmail(user.email);
    
    if (exists) {
      event.preventDefault();
      throw new Error('User already exists');
    }
  }
]);

await wrappedSave({ email: '[email protected]', name: 'John' });

Custom Parameters

const logMessage = (message: string) => {
  console.log('Message:', message);
};

const wrappedLog = preventable(logMessage, [
  (event) => {
    // Call the original function with modified parameters
    event.default(`[${new Date().toISOString()}] ${event.params[0]}`);
    event.preventDefault(); // Prevent calling with original params
  }
]);

wrappedLog('Hello');
// Logs: "Message: [2024-01-01T00:00:00.000Z] Hello"

Checking Prevention State

const processData = (data: any) => {
  console.log('Processing:', data);
};

const wrappedProcess = preventable(processData, [
  (event) => {
    console.log('Handler 1');
  },
  (event) => {
    if (event.params[0].shouldPrevent) {
      event.preventDefault();
    }
  },
  (event) => {
    // Check if already prevented
    if (event.defaultPrevented) {
      console.log('Already prevented, skipping validation');
      return;
    }
    // Additional validation...
  }
]);

API

preventable(defaultAction, handlers)

Creates a wrapper function that executes handlers before the original function.

Parameters:

  • defaultAction: The function to wrap (sync or async)
  • handlers: Array of handler functions to execute before defaultAction

Returns:

  • A function with the same signature as defaultAction

Event Object

Each handler receives an event object with the following properties:

type Event<T> = {
  // Whether preventDefault() has been called
  defaultPrevented: boolean;
  
  // Parameters passed to the wrapped function
  params: Parameters<T>;
  
  // Call the original function (optionally with different params)
  default: T;
  
  // Prevent the original function from executing
  preventDefault: () => void;
};

Handler Function

type EventHandler<T> = (event: Event<T>) => void | Promise<void>;

Real-World Examples

Form Validation

function LoginForm() {
  const handleSubmit = preventable(
    async (email: string, password: string) => {
      await loginUser(email, password);
    },
    [
      (event) => {
        const [email, password] = event.params;
        
        if (!email || !password) {
          event.preventDefault();
          showError('Email and password are required');
        }
      },
      (event) => {
        const [email] = event.params;
        
        if (!email.includes('@')) {
          event.preventDefault();
          showError('Invalid email format');
        }
      }
    ]
  );
  
  return <form onSubmit={() => handleSubmit(email, password)}>...</form>;
}

Authorization Checks

const deleteUser = preventable(
  async (userId: string) => {
    await api.users.delete(userId);
  },
  [
    async (event) => {
      const currentUser = await getCurrentUser();
      
      if (!currentUser.isAdmin) {
        event.preventDefault();
        throw new Error('Unauthorized: Admin access required');
      }
    }
  ]
);

Analytics & Logging

const trackEvent = (eventName: string, data: any) => {
  analytics.track(eventName, data);
};

const wrappedTrack = preventable(trackEvent, [
  (event) => {
    // Log to console in development
    if (process.env.NODE_ENV === 'development') {
      console.log('Analytics:', event.params);
    }
  },
  (event) => {
    // Don't track in test environments
    if (process.env.NODE_ENV === 'test') {
      event.preventDefault();
    }
  }
]);

Rate Limiting

const apiCall = preventable(
  async (endpoint: string) => {
    return await fetch(endpoint);
  },
  [
    async (event) => {
      const [endpoint] = event.params;
      const canProceed = await rateLimiter.check(endpoint);
      
      if (!canProceed) {
        event.preventDefault();
        throw new Error('Rate limit exceeded');
      }
    }
  ]
);

License

MIT