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

@pallad/cascade

v1.1.0

Published

Allows to run cascade operations like deletions or updates

Downloads

10

Readme


CircleCI npm version License: MIT

@pallad/cascade is a library to run cascade actions on entities.

For example action delete on an Article entity containing images triggers removal of Image entities. If Image entity has anything else to remove as well they might trigger deletion of other entities, and so on and so forth.

Use cases

  • cascade deletion on databases not supporting relations or cascade actions like DynamoDB, MongoDB, Redis etc.
  • cascade updates of cached views
  • running extra logic on creation of new entries
  • achieving cascade logic between multiple persistence models (key/value database <-> filesystem <-> RDBMS etc)
  • great for apps that tries to leverage polyglot persistence

Features

  • 👷 Built with Typescript
  • 📏 Simple but powerful 🔥
  • 🧑‍🤝‍🧑 Context friendly for forwarding extra information (like transaction handle)
  • ❤️ Integration with @pallad/modules

Installation

npm install @pallad/cascade

Modules

If you already use alpha-dic and would like to integrate with @pallad/modules install @pallad/cascade-module

npm install @pallad/cascade-module

Why should I use it?

If you can (and want to) handle all cascade actions within a database then great and you probably don't need @pallad/cascade at all :)

However it is still a great tool for

  • databases that does not support cascade actions like DynamoDB, MongoDB, Redis etc.
  • cascade updates of cached views
  • running extra logic on creation of new entries
  • handling cascade multiple persistence modesl (key/value database <-> filesystem <-> RDBMS etc)
  • apps that tries to leverage polyglot persistence

Concepts

The basic conceptions are very simple but it very important to understand them to get in speed with handling cascade actions.

Rule

Rule performs an action on a target. It decides whether is able to handle given target, if not then Action will not ask it to handle it.

import {Rule} from '@pallad/cascade';

const deleteImageRule: Rule = {
	supports(target) {
		return target instanceof Image;
	},
	run(target: Image): Rule.Result | Promise<Rule.Result> {
		// perform image deletion
		// at this stage we're sure that target is an instanceof Image
		// since otherwise it would not be called
	}
}

Action

Aggregates rules and controls the flow of execution. Once action starts the following steps are being performed:

  1. iterates over the rules one by one
  2. check if target is supported by the rule (via supports). If not then moved to another rule, otherwise calls run on it.
  3. If rule returns a promise then awaits for its resolution.
  4. If the final result of rule handling returns anything iterable then action is ran on every target.

Manager

Just aggregates all actions in one place.

Examples

Basic example

import {Rule} from '@pallad/cascade';

class Article {
	id!: string;
	images!: Image[];
}

class Image {
	id!: string;
}

const deleteArticleRule: Rule = {
	supports(target) {
		return target instanceof Article;
	},
	run(target: Article) {
		console.log('Removing article', target.id);

		return target.images;
	}
}
const deleteImageRule: Rule = {
	supports(target) {
		return target instanceof Image;
	},
	run(target: Image) {
		console.log('Removing image', target.id);
	}
}
const deleteAction = new Action([
	deleteArticleRule,
	deleteImageRule
]);

const article = Object.assign(new Article, {
	id: 'a1',
	images: [
		Object.assign(new Image(), {id: 'i1'}),
		Object.assign(new Image(), {id: 'i2'}),
	]
});
deleteAction.run(article);

// Removing article a1
// Removing image i1
// Removing image i2

More entities + counter invalidation

A little bit more advanced with more entity types and articles counter invalidation. Note the order of execution:

  1. First Stash is being deleted
  2. then Article with id: a1
  3. then images for that article
  4. then Article with id: a2
  5. and so on and so forth
import {Rule, Action} from '@pallad/cascade';

class Article {
	id!: string;
	images!: Image[];
}

class Image {
	id!: string;
}

class Stash {
	id!: string;
	entries!: Article[];
}

const deleteArticleRule: Rule = {
	supports(target) {
		return target instanceof Article;
	},
	run(target: Article) {
		console.log('Removing article', target.id);
		return target.images;
	}
}
const deleteImageRule: Rule = {
	supports(target) {
		return target instanceof Image;
	},
	run(target: Image) {
		console.log('Removing image', target.id);
	}
}

const deleteStashRule: Rule = {
	supports(target) {
		return target instanceof Stash;
	},
	run(target: Stash) {
		console.log('Removing stash', target.id);
		return target.entries;
	}
}

const updateArticlesCounterRule: Rule = {
	supports(target) {
		return target instanceof Article;
	},
	run(target: Article) {
		console.log('Updating articles counter of', target.id);
	}
}
const deleteAction = new Action([
	deleteArticleRule,
	deleteImageRule,
	deleteStashRule,
	updateArticlesCounterRule,
]);

const article1 = Object.assign(new Article, {
	id: 'a1',
	images: [
		Object.assign(new Image(), {id: 'i1'}),
		Object.assign(new Image(), {id: 'i2'}),
	]
});

const article2 = Object.assign(new Article, {
	id: 'a1',
	images: [
		Object.assign(new Image(), {id: 'i3'}),
		Object.assign(new Image(), {id: 'i4'}),
	]
});

const stash = Object.assign(new Stash, {
	id: 's1',
	entries: [article1, article2]
});

deleteAction.run(stash);

// Removing stash s1
// Removing article a1
// Removing image i1
// Removing image i2
// Updating articles counter of a1
// Removing article a1
// Removing image i3
// Removing image i4
// Updating articles counter of a1

Wrapping in transactions

import {Rule, Action} from '@pallad/cascade';
import {Knex} from 'knex';

interface DeleteActionContext {
	transaction: Knex.Transaction;
}

class Article {
	id!: string;
	images!: Image[];
}

class Image {
	id!: string;
}

const deleteArticleRule: Rule<DeleteActionContext> = {
	supports(target) {
		return target instanceof Article;
	},
	run(target: Article, context) {
		console.log('Removing article', target.id);
		// now you 
		context.transaction('articles').delete().where('id', target.id);
		return target.images;
	}
}
const deleteImageRule: Rule<DeleteActionContext> = {
	supports(target) {
		return target instanceof Image;
	},
	run(target: Image, context) {
		console.log('Removing image', target.id);
		context.transaction('images').delete().where('id', target.id);
	}
}

const deleteAction = new Action<DeleteActionContext>([
	deleteArticleRule,
	deleteImageRule,
]);

const article = Object.assign(new Article, {
	id: 'a1',
	images: [
		Object.assign(new Image(), {id: 'i1'}),
		Object.assign(new Image(), {id: 'i2'}),
	]
});

const transaction = knex.transaction()

knex.transaction((trx) => {
	return deleteAction.run(article, {
		transaction: trx
	});
});

// Removing article a1
// Removing image i1
// Removing image i2

Tips

How to distinguish targets if my entities are just pure javascript objects (POJO)?

You have to wrap them within extra object to indicate its type or use @pallad/entity-ref