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

@open-spaced-repetition/binding

v0.6.1

Published

Node.js bindings for the FSRS Optimizer implemented in Rust and compiled to WASI.

Readme

@open-spaced-repetition/binding

npm version monthly downloads total downloads

Introduction | 简体中文 | はじめに

@open-spaced-repetition/binding provides high-performance FSRS tooling powered by fsrs-rs and napi-rs.

Use it when you need:

  • parameter optimization from review history
  • CSV-to-FSRS item conversion
  • learning and relearning step recommendation
  • WASI-based execution in Node.js or custom browser pipelines

Public beta notice: this package is in public testing and its API may change between releases.

Requirements

  • Node.js >=24

Installation

npm install @open-spaced-repetition/binding
pnpm install @open-spaced-repetition/binding
yarn add @open-spaced-repetition/binding
bun add @open-spaced-repetition/binding

Optimize Parameters

import { readFileSync } from 'node:fs'
import {
  computeParameters,
  convertCsvToFsrsItems,
} from '@open-spaced-repetition/binding'

const csvBuffer = readFileSync('./revlog.csv')
const modelVersion = 'FSRS-7' // 'FSRS-7' (default) or 'FSRS-6'
const items = convertCsvToFsrsItems(csvBuffer, 4, 'Asia/Shanghai', modelVersion)

const parameters = await computeParameters(items, {
  enableShortTerm: true,
  modelVersion,
  numRelearningSteps: 1,
  timeout: 500,
  progress: (current, total) => {
    console.log(`${current}/${total}`)
  },
})

console.log(parameters)

Pass the same modelVersion to convertCsvToFsrsItems(data, nextDayStartsAt, timezoneOrOffset, modelVersion) and computeParameters. The default is FSRS7, using fractional study days normalized to each study day’s actual duration between local rollover boundaries (including DST). Select FSRS6 explicitly to use whole study days.

With either WASI Preview 1 target (wasm32-wasip1 or wasm32-wasip1-threads), computeParameters and evaluateWithTimeSeriesSplits run synchronously on the current worker and wrap the completed result in a Promise for API compatibility. In browser applications, call these methods from a dedicated Worker so the main thread stays responsive. Progress callbacks are supported; return false to stop the operation.

CSV Timezone Input

The third convertCsvToFsrsItems argument is timezoneOrOffset.

  • Pass an IANA timezone name, such as 'Asia/Shanghai' or 'America/New_York', when you want the conversion to account for DST for each review at the time it was reviewed.
  • Pass a fixed UTC offset in minutes, such as 480 for UTC+08:00 or -300 for UTC-05:00, when the source data should use the same offset for every review timestamp.
const itemsWithTimezone = convertCsvToFsrsItems(csvBuffer, 4, 'Asia/Shanghai')
const itemsWithFixedOffset = convertCsvToFsrsItems(csvBuffer, 4, 480)

Recommend Learning Steps

import { readFileSync } from 'node:fs'
import { computeOptimalSteps } from '@open-spaced-repetition/binding'

const csvBuffer = readFileSync('./revlog.csv')
const stepStats = computeOptimalSteps(csvBuffer, 0.9, 0.5)

console.log(stepStats.recommendedLearningSteps)
console.log(stepStats.recommendedRelearningSteps)

The third argument must be a numeric decay in the range of [0.1, 1]: FSRS-4 uses 1, FSRS-4.5/5 uses 0.5, and FSRS-6 uses weights[20]. Parameter arrays are not accepted. This estimator is not usable with FSRS-3 (exponential curve) or FSRS-7 (dual-trace curve).

Due to technical limitations, at most two learning steps and one relearning step can be recommended. This does not mean you need that many steps, and it does not mean they are always sufficient. It is also possible that no steps are recommended when the data is not suitable.

If you are already using more steps with FSRS, be careful when changing the number of steps. Reducing the number of steps can significantly lower retention, and FSRS may need a long time to adapt to the new step configuration.

Dynamic WASI Initialization

Before using this setup, install the WASI asset package manually:

pnpm add @open-spaced-repetition/binding-wasm32-wasi

In Vite, you can load the WASM asset with ?url and the worker with ?worker, then initialize the optimizer through the dynamic entry:

import { initOptimizer } from '@open-spaced-repetition/binding/dynamic-wasi'
import wasmUrl from '@open-spaced-repetition/binding-wasm32-wasi/fsrs-binding.wasm32-wasi.wasm?url'
import WasiWorker from '@open-spaced-repetition/binding-wasm32-wasi/wasi-worker-browser.mjs?worker'

const binding = await initOptimizer({
  wasm: wasmUrl,
  worker: () => new WasiWorker(),
})

const item = new binding.FSRSBindingItem([
  new binding.FSRSBindingReview(3, 0),
  new binding.FSRSBindingReview(4, 1),
])

const parameters = await binding.computeParameters([item], {
  enableShortTerm: true,
  numRelearningSteps: 1,
})

console.log(parameters)

For browser deployments, enable cross-origin isolation to avoid worker and WASM loading issues. In practice, serve the page with both of these headers:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

Example plugin using dynamic WASI for in-browser training:

WASI Without Dynamic Loading

If you do not need dynamic WASI loading, you can let pnpm install the WASI package automatically by enabling wasm32 in supportedArchitectures:

{
  "pnpm": {
    "supportedArchitectures": {
      "cpu": ["current", "wasm32"]
    }
  }
}

When using this approach, make sure your framework does not prebundle or tree-shake @open-spaced-repetition/binding in a way that drops the WASM asset.

For example:

  • In Next.js, add @open-spaced-repetition/binding to serverExternalPackages
  • In Vite, add @open-spaced-repetition/binding to optimizeDeps.exclude
// vite.config.ts
import { defineConfig } from 'vite'

export default defineConfig({
  optimizeDeps: {
    exclude: ['@open-spaced-repetition/binding'],
  },
})

Notes

  • Edge runtimes are not supported by the default WASI path.
  • For browser usage, prefer bundling the generated WASM and worker assets explicitly.
  • In browsers, set Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp so worker and WASM resources can load without cross-origin isolation issues.
  • The Vite example above requires @open-spaced-repetition/binding-wasm32-wasi to be installed explicitly.
  • If you rely on pnpm supportedArchitectures instead of dynamic loading, exclude @open-spaced-repetition/binding from framework optimization when needed so the WASM asset is not dropped.
  • For scheduler-only workloads, use ts-fsrs.

Examples

Documentation