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

@rrulenet/events

v0.1.8

Published

Event-set projection layer for contextual scheduling with @rrulenet/recurrence.

Readme

@rrulenet/events is a small event-set projection layer. It combines explicit event sets supplied by an application with transforms that either project a Recurrence from @rrulenet/recurrence during or around those events, or generate event-relative triggers from those events.

It is not an event catalogue, a data client, or an event-discovery service. Applications own event sourcing, persistence, metadata, permissions, and product-specific availability rules.

Install

npm install @rrulenet/events @rrulenet/recurrence

Role

The package answers this question:

given explicit event dates/windows + a transform,
which occurrences should run?

For example:

Every day at 09:00 during Black Friday and Cyber Monday

or:

Every day at 09:00 from 10 days before Black Friday through 2 days after Cyber Monday

or:

Trigger each regional audience 7 days before a maintenance window at 09:00 local time

Boundary With @rrulenet/recurrence

@rrulenet/recurrence remains the recurrence engine. It owns recurrence parsing, recurrence JSON, point occurrence generation, and recurrence algebra.

@rrulenet/events does not extend that API. It depends on @rrulenet/recurrence and uses Recurrence as the engine for during and window transforms. Event-relative transforms are not recurrence filters; they generate explicit occurrences from event anchors.

The event layer owns only:

  • event-set JSON validation
  • date, point, and interval member semantics
  • projection of recurrence occurrences onto event dates or event windows
  • event-relative trigger occurrences in a target timezone
  • deterministic serialization of event schedules

Data Boundary

Runtime event data does not live in this package.

Applications pass explicit EventSetJson objects into the library. Event ids, versions, sources, metadata, curation status, persistence, permissions, and availability rules belong to the application layer.

This package does not fetch event data and does not ship a runtime event catalogue.

Tests include deterministic fixtures such as black_friday_2026 and cyber_monday_2026. They are fixtures, not bundled catalogue data.

JSON Model

EventSetJson contains explicit members:

type EventSetMemberJson =
  | { kind: 'date'; id: string; date: '2026-11-27' }
  | { kind: 'point'; id: string; at: '2026-11-27T09:00:00Z' }
  | { kind: 'interval'; id: string; start: string; end: string };

For recurrence projection, the schedule combines events, recurrence, and transform:

type EventScheduleJson = {
  kind: 'event-schedule';
  timezone: string;
  events: EventSetJson;
  recurrence: RecurrenceJson;
  transform:
    | { kind: 'during' }
    | { kind: 'window'; before?: { days?: number }; after?: { days?: number } };
};

For event-relative triggers, recurrence is intentionally absent:

type EventScheduleJson = {
  kind: 'event-schedule';
  timezone: string;
  events: EventSetJson;
  transform: {
    kind: 'event-relative';
    anchor?: 'start';
    triggers: Array<
      | { before: { days: number }; time: string }
      | { after: { days: number }; time: string }
      | { before: ElapsedDuration }
      | { after: ElapsedDuration }
      | { at: 'start' | 'end' }
    >;
  };
};

type ElapsedDuration =
  | { hours: number; minutes?: number }
  | { hours?: number; minutes: number };

Date members are calendar dates. They are projected into the schedule timezone as half-open local-day windows:

[YYYY-MM-DDT00:00, next local midnight)

Point members are exact temporal points. Interval members are half-open windows:

[start, end)

Event-relative transforms deliberately distinguish calendar projection from elapsed-time arithmetic.

Calendar triggers use integer days plus a required local time:

  • date members anchor on their date
  • point members anchor on the point instant converted to the schedule timezone
  • interval before triggers anchor on the interval start instant converted to the schedule timezone
  • interval after triggers anchor on the interval end instant converted to the schedule timezone

They apply calendar days to the local anchor date, then combine the resulting date with the supplied wall-clock time. The local time therefore remains stable across daylight-saving transitions.

Exact elapsed triggers use non-negative integer hours, minutes, or both, with a positive total duration, and do not accept time. They subtract from the start instant for before, and add to the interval end or point instant for after. Exact { at: 'start' } and { at: 'end' } triggers project the corresponding instant directly.

Date members support only calendar triggers because a date does not identify an exact instant. Point members support elapsed triggers and at: 'start'. Interval members support every trigger, including at: 'end'. Every trigger must contain exactly one of before, after, or at; calendar days cannot be mixed with elapsed hours or minutes.

Example

import { Temporal } from 'temporal-polyfill';
import { Recurrence } from '@rrulenet/recurrence';
import { EventSchedule } from '@rrulenet/events';

const recurrence = Recurrence.rule({
  freq: 'DAILY',
  byHour: [9],
  start: Temporal.ZonedDateTime.from('2026-11-01T09:00:00[America/New_York]'),
  until: Temporal.ZonedDateTime.from('2026-12-10T09:00:00[America/New_York]'),
});

const schedule = EventSchedule.fromJSON({
  kind: 'event-schedule',
  timezone: 'America/New_York',
  events: {
    kind: 'event-set',
    id: 'campaign_events_2026',
    version: 'app-supplied-v1',
    members: [
      { kind: 'date', id: 'black_friday_2026', date: '2026-11-27' },
      { kind: 'date', id: 'cyber_monday_2026', date: '2026-11-30' },
    ],
  },
  recurrence: recurrence.toJSON(),
  transform: {
    kind: 'window',
    before: { days: 10 },
    after: { days: 2 },
  },
});

console.log(schedule.occurrences().map((value) => value.toString()));

Event-Relative Example

import { EventSchedule } from '@rrulenet/events';

const schedule = EventSchedule.fromJSON({
  kind: 'event-schedule',
  timezone: 'Australia/Perth',
  events: {
    kind: 'event-set',
    id: 'inline.maintenance.2026-09-15',
    version: 'user-snapshot-1',
    source: 'inline',
    timezone: 'UTC',
    members: [
      {
        kind: 'interval',
        id: 'maintenance-window',
        start: '2026-09-15T02:00:00Z',
        end: '2026-09-15T04:00:00Z',
      },
    ],
  },
  transform: {
    kind: 'event-relative',
    triggers: [
      { before: { days: 7 }, time: '09:00' },
      { before: { hours: 2 } },
      { before: { minutes: 15 } },
      { at: 'start' },
      { at: 'end' },
      { after: { minutes: 30 } },
      { after: { days: 2 }, time: '09:00' },
    ],
  },
});

console.log(schedule.occurrences().map((value) => value.toString()));
// [
//   '2026-09-08T09:00:00+08:00[Australia/Perth]',
//   '2026-09-15T08:00:00+08:00[Australia/Perth]',
//   '2026-09-15T09:45:00+08:00[Australia/Perth]',
//   '2026-09-15T10:00:00+08:00[Australia/Perth]',
//   '2026-09-15T12:00:00+08:00[Australia/Perth]',
//   '2026-09-15T12:30:00+08:00[Australia/Perth]',
//   '2026-09-17T09:00:00+08:00[Australia/Perth]'
// ]

Future VEVENT Relationship

@rrulenet/vevent is intentionally out of scope for this package.

This package keeps date sets, point sets, and interval/window semantics explicit, so a future VEVENT-oriented package can map richer event components into event sets later without forcing VEVENT concepts into this smaller projection layer.

Development

npm install
npm test