Skip to content
PluginStats

@gnldev/scheduler weekly downloads statistics

By gnldev (15 listings) · npm · AI SDKs

Durable workflow scheduler on @gnldev/durable: cron/interval/at triggers, exactly-once fire (run-lock), backoff/retry. Time is data: the next fire time is resolved and frozen into the journal.

View on npm Stale · Oct 18, 2026

@gnldev/scheduler has 419 recorded weekly downloads on npm.

Weekly downloads
419
Official API · Oct 18, 2026
7-day growth
—
Insufficient history
30-day growth
—
Insufficient history
90-day growth
—
Insufficient history
Rank
—
Among tracked npm listings

About @gnldev/scheduler

@gnldev/scheduler

Cron, interval and one-shot triggers for durable workflows. Several workers can poll the same schedule: a run-lock picks one winner per fire and every write to the trigger's state is CAS-fenced, so a slot is never double-advanced or rolled back — see the lock section for what that does and does not promise about the workflow body.

Install

Install: pnpm add @gnldev/scheduler — or use it from a repo clone: pnpm install && pnpm -r build.

npm i @gnldev/scheduler

Schedule something

import { scheduleWorkflow, createScheduler, pollScheduler } from '@gnldev/scheduler';

await scheduleWorkflow(journal, {
  id: 'nightly-invoices',
  cron: '0 3 * * *',
  name: 'invoices',          // the workflow name handed to runner.runWorkflow
});

// in your worker loop. The runner is an object with runWorkflow — createGnl() is one.
setInterval(() => pollScheduler(journal, gnl), 30_000);

Give exactly one of at (epoch ms), every (ms, greater than 0) or cron (5 fields, UTC unless you give tz — see Time zones). A spec that could not fire — a cron with hour 25, every: 0, two of the three, 0 0 30 2 * (there is no 30 February) — is refused before anything is written. Whether a cron can fire does not depend on the date you ask: 0 0 29 2 * fires on every leap day, four (across 2100, eight) years apart.

Calling scheduleWorkflow again with the same spec for the same id is a no-op, so every boot can call it. A different spec for an id that already exists is refused, naming both, until you say which one you want:

import { scheduleWorkflow } from '@gnldev/scheduler';
import { InMemoryJournal } from '@gnldev/durable';
const journal = new InMemoryJournal();

await scheduleWorkflow(journal, { id: 'nightly', name: 'report', cron: '0 4 * * *', ifExists: 'replace' });

'replace' applies the new spec (next time from it, attempts reset, a fire count no run has used); 'keep' leaves the stored trigger as it is; or use a new id. A stored definition that cannot fire (left by 0.9.0) is replaced by the next valid registration whatever ifExists says, and the poller fails it instead of firing it.

Time zones

cron is read in UTC unless the spec names an IANA zone in tz:

import { scheduleWorkflow } from '@gnldev/scheduler';
import { InMemoryJournal } from '@gnldev/durable';
const journal = new InMemoryJournal();

await scheduleWorkflow(journal, { id: 'berlin-standup', name: 'standup', cron: '0 9 * * 1-5', tz: 'Europe/Berlin' });

That is 09:00 in Berlin all year. A UTC cron cannot say it: 0 8 * * * UTC is 09:00 in Berlin on 155 days of 2026 and 10:00 on the other 210. On the two DST days a year:

  • a wall time that does not exist (02:30 when clocks go 02:00 → 03:00) fires once, shifted forward by the gap — 03:30. A daily job still runs that day.
  • a wall time that happens twice (02:30 when clocks go 03:00 → 02:00) fires once, at the first 02:30 — unless the hour field is *. Then every real hour is its own occurrence: an hourly cron runs 25 times on a 25-hour day.

tz is only for cron (an every period and an at instant have no zone) and must be a zone the runtime knows; anything else is refused before a write. The canonical name is stored, so 'europe/berlin' and 'Europe/Berlin' are the same spec. Zone rules come from Node's own Intl.

Stop, pause, resume

import { unscheduleWorkflow, pauseTrigger, resumeTrigger } from '@gnldev/scheduler';
import { InMemoryJournal } from '@gnldev/durable';
const journal = new InMemoryJournal();

await pauseTrigger(journal, { id: 'nightly-invoices' });        // kept, not fired
await resumeTrigger(journal, { id: 'nightly-invoices' });       // fires again from the next slot
await unscheduleWorkflow(journal, { id: 'nightly-invoices' });  // gone
await unscheduleWorkflow(journal, { id: 'ayse-weekly', resourceId: 'ayse', orgId: 'acme' }); // an owned one

Name the trigger as you scheduled it: the id (or the workflow name, if you gave no id) and the same owner. Each returns true when the trigger is in the asked-for state and false when there was nothing to act on (no such trigger; or, for pause and resume, one that is removed, done or failed).

  • Removing makes the trigger never fire again and drops it from listTriggers. A fire already in flight finishes (the run is not aborted), and its result is discarded, so it cannot bring the trigger back. Scheduling the id again later is a new trigger, with run ids the old one never used. If your boot code registers the trigger, remove that call too, or the next boot schedules it again.
  • Pausing keeps everything (schedule, fire count, attempts) and shows status: 'paused'. The boot-time scheduleWorkflow call does not lift it, and neither does ifExists: 'replace'. A fire in flight when you pause is recorded under the pause, so resuming never runs that occurrence again.
  • Resuming does not catch up. Occurrences that fell inside the pause are not run, whatever misfire says: the pause was the decision not to run them (misfire is for occurrences missed because no poller ran). The next fire is the first slot after the resume, on the same grid. A one-shot at whose time passed during the pause fires once, right away: skipping its only occurrence would delete it.

Each is a single write to the trigger's state, compared against the state it read, so it never overwrites a fire it did not see.

Triggers for an end user

A trigger can work for one user in one organization. Each fire's run belongs to that user: they can see it and approve it.

import { scheduleWorkflow, createScheduler } from '@gnldev/scheduler';
import { createGnl, scopeConfigToOrg } from '@gnldev/durable';…

Description from the npm listing.

  • ai
  • agent
  • llm
  • typescript
  • ai-sdk
  • durable
  • exactly-once
  • cron
  • triggers

README badge

Show your rank in a README or on your website. The badge updates automatically.

Charts

Images: 30d · 1y · all time · 1600×900 dark

Adoption over time

@gnldev/scheduler weekly downloads on npm, last 90 days: 419 weekly downloads as of Oct 18, 2026.@gnldev/schedulerWeekly downloads on npm · Last 90 days419downloads / week360380400420440460480Jul 20Aug 4Aug 19Sep 3Sep 18Oct 3Oct 18downloads / weekOct 18, 2026 – Oct 18, 2026 · 1 observations · Updated Oct 18, 2026pluginstats.com
@gnldev/scheduler weekly downloads on npm, last 90 days: 419 weekly downloads as of Oct 18, 2026.

7/30/90-day growth

@gnldev/scheduler weekly downloads growth on npm: 7-day growth unavailable, 30-day growth unavailable, 90-day growth unavailable.@gnldev/schedulerWeekly downloads growth on npm419downloads / week now7-day growthinsufficient historyInsufficient history30-day growthinsufficient historyInsufficient history90-day growthinsufficient historyInsufficient historyGrowth vs. nearest observation to each baseline date · Updated Oct 18, 2026pluginstats.com
@gnldev/scheduler weekly downloads growth on npm: 7-day growth unavailable, 30-day growth unavailable, 90-day growth unavailable.

Ranking history

@gnldev/scheduler has no ranking history on npm yet.@gnldev/schedulernpm ranking · by weekly downloads · Last 90 daysNo ranking observations in this periodno observations · 0 observations · Updated Oct 18, 2026pluginstats.com
@gnldev/scheduler has no ranking history on npm yet.

Listing placement

All npm2,422 listings · 122 pages · 1,213 shown · page 83, #20 (overall #1,660)
All npm: every listing in weekly downloads order

Listing placement

AI SDKs270 listings · 14 pages · page 10, #8 (overall #188)
AI SDKs: every listing in weekly downloads order

Filled points are observed values; hollow points come from Internet Archive snapshots of the listing page. Dashed segments span periods with no observations — values between points are not interpolated.

Each band is one page of 20. Weekly downloads on a linear scale. Placement is projected from weekly downloads, so it can differ from the marketplace's own sort order. Hover a dot to see the listing.

Recent observations

Full history (1)
Recent weekly downloads observations for @gnldev/scheduler
Date (UTC)Weekly downloadsChange vs. previous
419—

Similar npm AI SDKs plugins

All 20 @gnldev/scheduler alternatives

Data source

Weekly downloads (trailing 7 complete days) come from the official npm downloads API. Downloads include CI and mirrors and are not equivalent to installs or users. npm publishes no ratings.

Listing: https://www.npmjs.com/package/@gnldev/scheduler. First seen Oct 22, 2026; last successful collection Oct 22, 2026. See the methodology.