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

occult

v0.0.2

Published

Painfully Powerful Reactive Array and Object

Readme

Occult

Painfully Powerful Reactive Arrays and Objects

npm version License: MIT

Transform your data into living, breathing entities that announce their changes. Welcome to reactive programming made simple.

Installation

npm install occult

The Dream of Reactive Data

Imagine if your arrays and objects could talk. Not with words, but with notifications. Every time something changes, they whisper to anyone who's listening. This isn't magic—it's reactive programming, and it's about to change how you think about data forever.

import { Arr, Obj } from 'occult';

// Create a living array that speaks when it changes
const fruits = new Arr(['apple', 'banana']);

// Listen to its whispers
fruits.subscribe(arr => {
  console.log('Fruits changed:', [...arr]);
});

// Make a change and watch the magic
fruits.push('orange');
// Output: Fruits changed: ['apple', 'banana', 'orange']

Why Occult Exists

Traditional programming is like being blindfolded in a room full of furniture. You know things are there, but you have to constantly check where everything is. Did the array change? Better check. Did someone update that user object? Better check again.

Reactive programming turns on the lights. Your data tells you when it changes. You don't check—you listen.

Your First Reactive Array

Let's start with something simple: a list of colors that knows when it changes.

import { Arr } from 'occult';

// Create a reactive array of colors
const colors = new Arr(['red', 'blue', 'green']);

// Subscribe to changes
const unsubscribe = colors.subscribe(arr => {
  console.log(`🎨 Colors updated: ${arr.join(', ')}`);
});

// Every operation triggers a notification
colors.push('yellow');        // 🎨 Colors updated: red, blue, green, yellow
colors[0] = 'crimson';       // 🎨 Colors updated: crimson, blue, green, yellow
colors.pop();                // 🎨 Colors updated: crimson, blue, green
colors.sort();               // 🎨 Colors updated: blue, crimson, green

// When you're done listening
unsubscribe();

Your First Reactive Object

Objects can be reactive too. Imagine a user profile that automatically updates the UI whenever it changes.

import { Obj } from 'occult';

// Create a reactive user object
const user = new Obj({
  name: 'Alice',
  age: 25,
  city: 'Wonderland'
});

// Listen for any changes
user.subscribe(obj => {
  console.log(`👤 User updated: ${obj.name}, ${obj.age} years old, from ${obj.city}`);
});

// Every property change is announced
user.age = 26;                    // 👤 User updated: Alice, 26 years old, from Wonderland
user.city = 'Looking Glass City'; // 👤 User updated: Alice, 26 years old, from Looking Glass City

The Power of Composition

Here's where things get beautiful. You can combine reactive arrays and objects to build complex, self-aware data structures.

import { Arr, Obj } from 'occult';

// Create a reactive shopping cart
const cart = new Arr([
  new Obj({ item: 'Coffee', price: 5, quantity: 2 }),
  new Obj({ item: 'Donut', price: 3, quantity: 1 })
]);

// Calculate total whenever cart changes
cart.subscribe(items => {
  const total = items.reduce((sum, item) =>
    sum + (item.price * item.quantity), 0
  );
  console.log(`🛒 Cart total: $${total}`);
});

// Add a new item
cart.push(new Obj({ item: 'Sandwich', price: 8, quantity: 1 }));
// Output: 🛒 Cart total: $21

// Update quantity of first item
cart[0].quantity = 3;
// Output: 🛒 Cart total: $26

Building a Live Train Schedule

Let's create something more ambitious—a train schedule that updates in real-time.

import { Arr, Obj } from 'occult';

// Create a reactive train schedule
const schedule = new Arr([
  new Obj({
    train: 'Express 101',
    departure: '09:00',
    platform: 3,
    status: 'On Time'
  }),
  new Obj({
    train: 'Local 202',
    departure: '09:30',
    platform: 1,
    status: 'On Time'
  })
]);

// Create a display that updates automatically
schedule.subscribe(trains => {
  console.clear();
  console.log('🚂 LIVE TRAIN SCHEDULE 🚂');
  console.log('─'.repeat(40));

  trains.forEach(train => {
    const statusEmoji = train.status === 'On Time' ? '✅' : '⚠️';
    console.log(`${train.departure} | Platform ${train.platform} | ${train.train}`);
    console.log(`Status: ${statusEmoji} ${train.status}`);
    console.log('─'.repeat(40));
  });
});

// Simulate real-time updates
setTimeout(() => {
  schedule[0].status = 'Delayed 5 min';
  schedule[0].departure = '09:05';
}, 2000);

setTimeout(() => {
  schedule.push(new Obj({
    train: 'Express 303',
    departure: '10:00',
    platform: 2,
    status: 'On Time'
  }));
}, 4000);

Creating a Reactive Drawing Canvas

Here's a beautiful example—a shape collection that could drive a drawing application.

import { Arr, Obj } from 'occult';

// Create a reactive canvas with shapes
const canvas = new Arr([
  new Obj({
    type: 'circle',
    x: 100,
    y: 100,
    radius: 50,
    color: '#ff0000'
  }),
  new Obj({
    type: 'rectangle',
    x: 200,
    y: 150,
    width: 100,
    height: 60,
    color: '#00ff00'
  })
]);

// Create a render function that redraws when data changes
canvas.subscribe(shapes => {
  console.log('🎨 Redrawing canvas...');
  shapes.forEach(shape => {
    if (shape.type === 'circle') {
      console.log(`  Drawing ${shape.color} circle at (${shape.x}, ${shape.y})`);
    } else if (shape.type === 'rectangle') {
      console.log(`  Drawing ${shape.color} rectangle at (${shape.x}, ${shape.y})`);
    }
  });
});

// Animate a shape
let frame = 0;
const animate = setInterval(() => {
  canvas[0].x = 100 + Math.sin(frame * 0.1) * 50;
  canvas[0].y = 100 + Math.cos(frame * 0.1) * 50;
  frame++;

  if (frame > 30) clearInterval(animate);
}, 100);

The Philosophy Behind Reactive Programming

Reactive programming isn't just about technical convenience—it's about structuring your thoughts correctly. Instead of thinking "How do I check if data changed?", you think "What should happen when data changes?"

This shift is profound. It's the difference between:

  • Polling vs. Subscribing
  • Asking vs. Listening
  • Pulling vs. Pushing
  • Checking vs. Knowing

Your program becomes a series of relationships, not procedures. When X changes, Y happens. It's declarative, it's clean, it's beautiful.

Managing Multiple Subscriptions

As your application grows, you'll manage multiple subscriptions. Here's a pattern for organizing them:

import { Arr, Obj } from 'occult';

class GameState {
  constructor() {
    // Reactive game data
    this.players = new Arr([]);
    this.score = new Obj({ team1: 0, team2: 0 });
    this.timer = new Obj({ minutes: 10, seconds: 0 });

    // Store all unsubscribe functions
    this.subscriptions = [];
  }

  start() {
    // Subscribe to all reactive data
    this.subscriptions.push(
      this.players.subscribe(players => {
        console.log(`👥 Players: ${players.length} active`);
      }),

      this.score.subscribe(score => {
        console.log(`⚽ Score: Team 1 [${score.team1}] - Team 2 [${score.team2}]`);
      }),

      this.timer.subscribe(time => {
        console.log(`⏱️ Time: ${time.minutes}:${String(time.seconds).padStart(2, '0')}`);
      })
    );
  }

  stop() {
    // Clean up all subscriptions
    this.subscriptions.forEach(unsub => unsub());
    this.subscriptions = [];
  }
}

// Use the game state
const game = new GameState();
game.start();

// Make changes and watch the updates
game.players.push('Alice', 'Bob', 'Charlie');
game.score.team1 = 1;
game.timer.seconds = 30;

// Clean up when done
game.stop();

Private Properties

Properties starting with underscore are considered private and don't trigger notifications:

import { Obj } from 'occult';

const config = new Obj({
  theme: 'dark',
  language: 'en'
});

config.subscribe(obj => {
  console.log('Settings changed!');
});

config.theme = 'light';      // Triggers: Settings changed!
config._internal = 'hidden'; // No notification (private property)

Manual Notifications

Sometimes you need to manually trigger a notification:

import { Arr } from 'occult';

const tasks = new Arr([
  { id: 1, title: 'Learn Occult', done: false },
  { id: 2, title: 'Build something amazing', done: false }
]);

tasks.subscribe(() => {
  const completed = tasks.filter(t => t.done).length;
  console.log(`✅ Completed: ${completed}/${tasks.length} tasks`);
});

// Modifying nested objects doesn't auto-trigger
tasks[0].done = true;

// Manually notify subscribers
tasks.notify();
// Output: ✅ Completed: 1/2 tasks

What Makes Occult Special

  1. Zero Learning Curve: If you know Arrays and Objects, you know Occult
  2. Natural Syntax: No special methods for getting/setting values
  3. Lightweight: Minimal overhead, maximum power
  4. Composable: Mix and match reactive arrays and objects freely
  5. Framework Agnostic: Works everywhere JavaScript works

A Message to Young Programmers

If you're just starting your programming journey and you've made it this far, know this: what seems like magic today will be the foundation of what you build tomorrow.

Reactive programming isn't just a pattern—it's a way of thinking that will serve you for your entire career. When data can announce its changes, you stop writing brittle update code and start declaring relationships. Your programs become more like poetry than prose.

Every great programmer started exactly where you are, amazed that an array could tell you when it changed. That amazement? Never lose it. It's what drives us to build beautiful things.

API Reference

Arr

const arr = new Arr([initial, values]);
const unsubscribe = arr.subscribe(callback);
arr.notify(); // Manually trigger notification
// All Array methods work: push, pop, shift, unshift, splice, etc.

Obj

const obj = new Obj({ initial: 'values' });
const unsubscribe = obj.subscribe(callback);
obj.notify(); // Manually trigger notification
// All Object operations work naturally