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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@squeakyrobot/fsrs

v1.0.0

Published

A TypeScript library for spaced repetition scheduling using the FSRS v4.5 algorithm with optional v6 support

Readme

@squeakyrobot/fsrs

A pure TypeScript implementation of the Free Spaced Repetition Scheduler (FSRS) v4.5 algorithm with optional v6 support.

Overview

FSRS is a modern, evidence-based spaced repetition algorithm that optimizes review intervals based on the DSR (Difficulty, Stability, Retrievability) model of memory. Unlike traditional algorithms like SM-2, FSRS uses trainable parameters optimized on real user data to achieve superior retention prediction.

Key Features

  • FSRS v4.5 Core: Proven algorithm with stable defaults (17 parameters)
  • 81% Better Than SM-2: Captures 98% of v6's improvement with fewer parameters
  • Edge Runtime Ready: Works in Cloudflare Workers, Vercel Edge, Deno Deploy
  • Continuous Grading: Supports both discrete (1-4) and continuous (1.0-4.0) ratings
  • Auto-Rating: Built-in response time to grade conversion
  • Optional v6 Support: Accepts optimized 21-parameter sets for advanced users
  • Type-Safe: Full TypeScript support with strict typing
  • Zero Dependencies: Minimal bundle size
  • Pure Functions: Immutable API, no side effects

Installation

npm install @squeakyrobot/fsrs

Quick Start

Basic Usage with Discrete Ratings

import { FSRS, Rating } from '@squeakyrobot/fsrs';

// Initialize with default v4.5 parameters
const fsrs = new FSRS();

// Create a new card
let card = fsrs.createEmptyCard();

// Simulate a review session - get all 4 possible outcomes
const now = new Date();
const reviewResults = fsrs.repeat(card, now);

// User rates the card as "Good"
const { card: updatedCard, log } = reviewResults[Rating.Good];

console.log(`Next review in ${updatedCard.scheduled_days} days`);
console.log(`Current stability: ${updatedCard.stability} days`);
console.log(`Difficulty: ${updatedCard.difficulty}/10`);

Auto-Rating with Continuous Grades

import { FSRS } from '@squeakyrobot/fsrs';

const fsrs = new FSRS();
let card = fsrs.createEmptyCard();

// Track response time
const start = Date.now();
// ... user answers flashcard ...
const responseTime = Date.now() - start;

// Convert response time to grade (1.0-4.0)
const averageTime = 2000; // Expected average response time (ms)
const grade = fsrs.autoRating(responseTime, averageTime, card.difficulty);

// Schedule with continuous grade
const { card: updatedCard, log } = fsrs.scheduleWithGrade(card, grade);

Edge Runtime Example (Cloudflare Workers)

// worker.ts
import { FSRS } from '@squeakyrobot/fsrs';

const fsrs = new FSRS();

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const { card, responseTime } = await request.json();

    const grade = fsrs.autoRating(responseTime, 2000);
    const result = fsrs.scheduleWithGrade(card, grade);

    return Response.json(result);
  }
};

Algorithm Background

For complete mathematical details, see the Algorithm Reference.

The DSR Model

FSRS models memory using three distinct variables:

  1. Difficulty (D): Inherent complexity of the item (1.0 = easiest, 10.0 = hardest)
  2. Stability (S): Time interval (days) for retrievability to decay from 100% to 90%
  3. Retrievability (R): Instantaneous probability of successful recall (0-1)

Why V4.5?

V4.5 provides the optimal balance for a minimal implementation:

  • Proven defaults: Works excellently without requiring parameter optimization
  • 98% of v6's benefit: Only 3% RMSE difference from v6 on benchmark data
  • Simpler: No same-day review complexity
  • Stable: Widely deployed with deterministic behavior

V6 support is available for advanced users who:

  • Run the FSRS optimizer on their review history
  • Need personalized parameters (w0-w20)
  • Want the marginal 2-3% accuracy improvement

API Reference

For complete API documentation, see the Full API Reference.

FSRS Class

Constructor

constructor(params?: Partial<FSRSParameters>)

Creates a new FSRS scheduler instance with optional parameter overrides.

Methods

repeat(card: Card, now?: Date): SchedulingCards

Calculates scheduling outcomes for all possible ratings. Useful for showing users all 4 button options with predicted intervals.

Parameters:

  • card: Current card state
  • now: Current timestamp (default: current time)

Returns: Map of Rating -> { log, card } for all 4 ratings

scheduleWithGrade(card: Card, grade: Rating | ContinuousRating, now?: Date): SchedulingResult

Schedules a review with a specific grade. Primary method for auto-rating scenarios and manual reviews.

Parameters:

  • card: Current card state
  • grade: Rating (discrete 1-4) or continuous (1.0-4.0)
  • now: Current timestamp (default: current time)

Returns: { log, card } with updated card state and review log

autoRating(responseTime: number, averageTime: number, difficulty?: number): ContinuousRating

Auto-rating utility that converts response time to a continuous grade.

Parameters:

  • responseTime: Time taken to respond (milliseconds)
  • averageTime: Expected average response time (milliseconds)
  • difficulty: Optional card difficulty (1-10) for adjustment (default: 5.5)

Returns: Continuous rating (1.0-4.0)

Formula:

  • Much faster than average: approaches 4.0 (Easy)
  • Around average: approaches 3.0 (Good)
  • Slower than average: approaches 2.0 (Hard) or 1.0 (Again)
getRetrievability(card: Card, now?: Date): number

Calculates current retrievability (probability of recall).

Parameters:

  • card: Card to evaluate
  • now: Current timestamp (default: current time)

Returns: Retrievability (0-1)

createEmptyCard(now?: Date): Card

Creates a new empty card ready for first review.

Parameters:

  • now: Optional timestamp (default: current time)

Returns: New card in "New" state

getNextInterval(card: Card, rating: Rating, now?: Date): number

Gets the next interval for a given rating (preview mode).

Parameters:

  • card: Card to evaluate
  • rating: Rating to preview
  • now: Current timestamp (default: current time)

Returns: Interval in days

Types

Rating

type Rating = 1 | 2 | 3 | 4; // Again | Hard | Good | Easy

const Rating = {
  Again: 1,
  Hard: 2,
  Good: 3,
  Easy: 4,
} as const;

State

type State = 0 | 1 | 2 | 3; // New | Learning | Review | Relearning

const State = {
  New: 0,
  Learning: 1,
  Review: 2,
  Relearning: 3,
} as const;

Card

interface Card {
  due: Date;              // Next scheduled review timestamp
  stability: number;      // Days to 90% retention
  difficulty: number;     // Complexity (1-10)
  elapsed_days: number;   // Days since last review
  scheduled_days: number; // Assigned interval
  reps: number;           // Total review count
  lapses: number;         // Failure count
  state: State;           // Current learning phase
  last_review: Date | null; // Previous review timestamp
}

FSRSParameters

interface FSRSParameters {
  request_retention: number;  // 0.7-0.97, default 0.9
  maximum_interval: number;   // Default 36500 days
  w: number[];                // 17 (v4.5) or 21 (v6) weights
  enable_fuzz: boolean;       // Random interval jitter
  enable_short_term?: boolean; // V6 same-day logic
}

Utility Functions

isLapse(grade: Rating | ContinuousRating): boolean

Checks if a grade represents a lapse (failure to recall). Useful for analytics, streak tracking, and UI badges.

Parameters:

  • grade: Rating (discrete or continuous)

Returns: true if grade < 1.5 (Again)

Configuration

Default v4.5 Parameters

const fsrs = new FSRS({
  request_retention: 0.9,
  maximum_interval: 36500,
  enable_fuzz: true
});

Custom V6 Parameters (Advanced)

For users with optimized parameters from the FSRS optimizer:

const fsrs = new FSRS({
  request_retention: 0.9,
  w: [
    // Your 21 optimized parameters from FSRS optimizer
    0.4, 0.6, 2.4, 5.8, 4.93, 0.94, 0.86, 0.01,
    1.49, 0.14, 0.94, 2.18, 0.05, 0.34, 1.26,
    0.29, 2.61, 0.48, 1.05, 0.36, -0.54
  ]
});

Note: V6 parameters only provide benefit when optimized on your specific review data. Generic v6 defaults offer minimal improvement over v4.5.

Integration Examples

React + Zustand

import create from 'zustand';
import { FSRS, Card, Rating } from '@squeakyrobot/fsrs';

const fsrs = new FSRS();

interface FlashcardStore {
  cards: Map<string, Card>;
  reviewCard: (id: string, rating: Rating) => void;
}

const useFlashcards = create<FlashcardStore>((set) => ({
  cards: new Map(),
  reviewCard: (id, rating) => set((state) => {
    const card = state.cards.get(id);
    if (!card) return state;

    const result = fsrs.repeat(card, new Date());
    const updatedCard = result[rating].card;

    return {
      cards: new Map(state.cards).set(id, updatedCard)
    };
  })
}));

Next.js API Route

import { FSRS, Rating } from '@squeakyrobot/fsrs';
import { NextRequest, NextResponse } from 'next/server';

const fsrs = new FSRS();

export async function POST(req: NextRequest) {
  const { card, rating } = await req.json();

  const result = fsrs.repeat(card, new Date());
  const updated = result[rating as Rating];

  return NextResponse.json({
    card: updated.card,
    log: updated.log
  });
}

Performance Benchmarks

Based on the fsrs-benchmark dataset (1.7B reviews from 20K users):

| Algorithm | Log Loss | RMSE | Improvement vs SM-2 | |-----------|----------|------|---------------------| | SM-2 (Anki Default) | 0.7317 | 0.4066 | - | | FSRS v4.5 | 0.3624 | 0.0764 | ~81% | | FSRS v6 | 0.3460 | 0.0653 | ~84% |

Key Insight: FSRS v4.5 captures 98% of v6's improvement with 19% fewer parameters, making it ideal for applications that don't have user-specific optimization.

Contributing

Contributions are welcome! See the CONTRIBUTING guide for development guidelines.

License

MIT License - see LICENSE for details.

References