
Security News
Axios Maintainer Confirms Social Engineering Attack Behind npm Compromise
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.
@startsimpli/funnels
Advanced tools
Brutally generic filtering pipeline package for any Simpli product
Brutally generic filtering pipeline package for any Simpli product
A reusable package for building multi-stage filtering funnels. Works with ANY entity type - investors, recipes, leads, tasks, GitHub issues, or whatever you dream up.
BRUTALLY GENERIC - No domain-specific types. No investor-specific fields. No recipe-specific logic.
It's just:
npm install @simpli/funnels
Filter investors for a Series A fundraise:
import { Funnel, FunnelEngine } from '@simpli/funnels';
interface Investor {
name: string;
firm: {
stage: string;
check_size_min: number;
check_size_max: number;
};
}
const funnel: Funnel<Investor> = {
id: 'series-a-funnel',
name: 'Series A Investor Qualification',
status: 'active',
input_type: 'contacts',
stages: [
{
id: 'stage-1',
order: 0,
name: 'Stage Filter',
filter_logic: 'OR',
rules: [
{ field_path: 'firm.stage', operator: 'eq', value: 'Series A' },
{ field_path: 'firm.stage', operator: 'eq', value: 'Multi-Stage' }
],
match_action: 'tag_continue',
no_match_action: 'exclude',
match_tags: ['qualified_stage']
},
{
id: 'stage-2',
order: 1,
name: 'Check Size',
filter_logic: 'AND',
rules: [
{ field_path: 'firm.check_size_min', operator: 'lte', value: 5000000 },
{ field_path: 'firm.check_size_max', operator: 'gte', value: 3000000 }
],
match_action: 'output',
no_match_action: 'exclude',
match_tags: ['qualified']
}
],
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
};
// Execute funnel
const engine = new FunnelEngine();
const investors = [/* your data */];
const results = engine.executeSync(funnel, investors);
console.log(`Matched: ${results.matched.length}`);
console.log(`Excluded: ${results.excluded.length}`);
Find quick, easy, vegetarian recipes:
import { Funnel } from '@simpli/funnels';
interface Recipe {
name: string;
prep_time_minutes: number;
difficulty: 'easy' | 'medium' | 'hard';
dietary_restrictions: string[];
}
const funnel: Funnel<Recipe> = {
id: 'quick-dinner',
name: 'Quick Weeknight Dinner',
status: 'active',
input_type: 'any',
stages: [
{
id: 'dietary',
order: 0,
name: 'Dietary Restrictions',
filter_logic: 'AND',
rules: [
{ field_path: 'dietary_restrictions', operator: 'has_all', value: ['vegetarian'] }
],
match_action: 'tag_continue',
no_match_action: 'exclude',
match_tags: ['vegetarian']
},
{
id: 'time',
order: 1,
name: 'Quick Prep',
filter_logic: 'AND',
rules: [
{ field_path: 'prep_time_minutes', operator: 'lte', value: 30 }
],
match_action: 'tag_continue',
no_match_action: 'exclude',
match_tags: ['quick']
},
{
id: 'difficulty',
order: 2,
name: 'Easy to Make',
filter_logic: 'OR',
rules: [
{ field_path: 'difficulty', operator: 'eq', value: 'easy' },
{ field_path: 'difficulty', operator: 'eq', value: 'medium' }
],
match_action: 'output',
no_match_action: 'exclude',
match_tags: ['beginner_friendly']
}
],
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
};
Score sales leads based on company size and engagement:
import { Funnel } from '@simpli/funnels';
interface Lead {
company: { size: number };
engagement: {
email_opens: number;
demo_requested: boolean;
};
tags: string[];
}
const funnel: Funnel<Lead> = {
id: 'lead-scoring',
name: 'Enterprise Lead Scoring',
status: 'active',
input_type: 'any',
stages: [
{
id: 'company-size',
order: 0,
name: 'Enterprise Size',
filter_logic: 'AND',
rules: [
{ field_path: 'company.size', operator: 'gte', value: 100 }
],
match_action: 'tag_continue',
no_match_action: 'tag_continue',
match_tags: ['enterprise'],
no_match_tags: ['smb']
},
{
id: 'engagement',
order: 1,
name: 'High Engagement',
filter_logic: 'OR',
rules: [
{ field_path: 'engagement.email_opens', operator: 'gte', value: 5 },
{ field_path: 'engagement.demo_requested', operator: 'is_true', value: null }
],
match_action: 'output',
no_match_action: 'output',
match_tags: ['hot_lead'],
match_context: { tier: 'A', score: 100 }
}
],
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
};
A sequential pipeline with multiple filtering stages. Each funnel has:
interface Funnel<TEntity = any> {
id: string;
name: string;
status: 'draft' | 'active' | 'paused' | 'archived';
stages: FunnelStage<TEntity>[];
// ... more fields
}
A single filtering step with rules, actions, and tags:
interface FunnelStage<TEntity = any> {
id: string;
order: number;
name: string;
filter_logic: 'AND' | 'OR';
rules: FilterRule[];
match_action: 'continue' | 'tag' | 'tag_continue' | 'output';
no_match_action: 'continue' | 'exclude' | 'tag_exclude';
match_tags?: string[];
no_match_tags?: string[];
custom_evaluator?: (entity: TEntity) => boolean;
}
A single condition with field path, operator, and value:
interface FilterRule {
field_path: string; // 'firm.stage', 'recipe.cuisine', 'tags'
operator: Operator; // 'eq', 'gt', 'contains', 'has_any', etc.
value: any; // Value to compare against
negate?: boolean; // Optional negation
}
Supported operators:
eq, negt, lt, gte, ltecontains, not_contains, startswith, endswith, matchesin, not_in, has_any, has_allisnull, isnotnullhas_tag, not_has_tagis_true, is_falseDefines what fields are available for filtering in your domain:
const investorRegistry: FieldRegistry = {
entity_type: 'investor',
fields: [
{
name: 'firm.stage',
label: 'Investment Stage',
type: 'string',
operators: ['eq', 'ne', 'in', 'not_in'],
category: 'Firm Details',
constraints: {
choices: ['Seed', 'Series A', 'Series B', 'Growth']
}
},
{
name: 'firm.check_size_min',
label: 'Min Check Size',
type: 'number',
operators: ['eq', 'ne', 'gt', 'lt', 'gte', 'lte'],
category: 'Firm Details'
}
]
};
Import only what you need for optimal bundle size:
// Full package (everything)
import { FunnelEngine, FunnelPreview, createFunnelStore } from '@simpli/funnels';
// Core only (NO React dependencies - perfect for workers, CLI, Node.js)
import { FunnelEngine, evaluateRule, applyOperator } from '@simpli/funnels/core';
// Components only (React UI)
import { FunnelCard, FunnelPreview, FunnelStageBuilder } from '@simpli/funnels/components';
// Hooks only (React hooks)
import { useDebouncedValue } from '@simpli/funnels/hooks';
// State only (Zustand store)
import { createFunnelStore } from '@simpli/funnels/store';
Use cases:
@simpli/funnels/core (no React, no DOM)@simpli/funnels (full package)@simpli/funnels/components (UI only)@simpli/funnels/store (Zustand store)See API_REFERENCE.md for complete API documentation.
import { FunnelEngine } from '@simpli/funnels/core';
const engine = new FunnelEngine();
// Synchronous execution
const results = engine.executeSync(funnel, entities);
// Get matched entities
const matched = results.matched.map(r => r.entity);
// Get excluded entities
const excluded = results.excluded.map(r => r.entity);
import { FunnelCard, FunnelPreview, FunnelStageBuilder } from '@simpli/funnels/components';
// Display funnel card
<FunnelCard funnel={myFunnel} onEdit={handleEdit} onDelete={handleDelete} />
// Preview funnel results
<FunnelPreview funnel={myFunnel} entities={myData} />
// Build funnel stages
<FunnelStageBuilder
funnel={myFunnel}
fieldRegistry={registry}
onChange={handleChange}
/>
import { createFunnelStore } from '@simpli/funnels/store';
const useFunnelStore = createFunnelStore();
function MyComponent() {
const { funnel, updateStage, addStage } = useFunnelStore();
// Use store methods
addStage(newStage);
updateStage(stage.id, { name: 'New Name' });
}
Traditional filtering systems hardcode domain models:
// ❌ Domain-specific (not reusable)
interface InvestorFilter {
firm_stage: string[];
check_size_min: number;
geography: string[];
}
interface RecipeFilter {
cuisine: string[];
prep_time_max: number;
dietary: string[];
}
With @simpli/funnels, one system handles everything:
// ✅ Brutally generic (reusable everywhere)
interface FilterRule {
field_path: string; // Works for ANY field
operator: Operator; // Works for ANY comparison
value: any; // Works for ANY value
}
Benefits:
Stages execute in order (0, 1, 2, ...). Each stage can:
Stage 0: Filter by investment stage
├─ Match → tag 'qualified_stage', continue
└─ No match → exclude
Stage 1: Filter by check size
├─ Match → tag 'qualified_check_size', continue
└─ No match → tag 'excluded_check_size', exclude
Stage 2: Filter by geography
├─ Match → output (final)
└─ No match → exclude
Tags and context accumulate across stages:
{
entity: { /* investor data */ },
matched: true,
accumulated_tags: ['qualified_stage', 'qualified_check_size', 'qualified_geography'],
context: {
stage: 'qualified',
tier: 'A',
score: 100
}
}
See EXAMPLES.md for 6+ real-world examples:
See INTEGRATION_GUIDE.md for step-by-step integration instructions.
Quick summary:
npm install @simpli/funnelsExplore 50+ interactive examples:
cd packages/funnels
npm run storybook
Or view the built Storybook in ./storybook-static/index.html
See STORYBOOK.md for details.
The package includes comprehensive test coverage:
npm run test # Run all tests
npm run test:watch # Watch mode
npm run test:coverage # Coverage report
Test stats:
See CONTRIBUTING.md for contribution guidelines.
Quick summary:
npm test and npm run type-checkSee CHANGELOG.md for version history.
MIT - See LICENSE for details.
Because it works for literally anything:
Zero domain-specific types. Just generic entity processing with rich filtering capabilities.
Built with ❤️ by the Simpli team
FAQs
Brutally generic filtering pipeline package for any Simpli product
We found that @startsimpli/funnels demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.

Security News
The Axios compromise shows how time-dependent dependency resolution makes exposure harder to detect and contain.