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

@johntalton/cyclic-fs

v1.0.2

Published

A circular buffer overtop of the EEPROM providing "latest-bucket" storage.

Readme

Cyclic FS

A circular buffer overtop of the EEPROM providing "latest-bucket" storage.

EEPROM have a wider range of use cases. For logging structured data where the "latest" version is the only value needed, a circular buffer can be used.

This has the benefit of ware-leveling the device in a predictable way.

npm Version GitHub package.json version CI

Concept

By breaking the memory space into Fixed-Size blocks (or "Slots" of stride length) these device can be predictably indexed and accessed.

Each "Slot" contains a "Header" and "Data". The "Data" section is the used defined payload and is stored and retrieved transparently (no size information is stored and if the full "Data" section is not use it is up to the user to account for).

Each "Slot" also contains a "Header" consisting of the "Version" reference.

Each time a new "Slot" is written the current" Version" is incremented and written down into the "Slot".

When the data fills the buffer, it wraps around to the beginning (first "Slot") and overwrites that value (the "oldest" version).

Note that the stride defines the total size of teach "Slot". Thus, each "Data" section that is available to use for the user is stride - HEADER_SIZE (where the current configured HEADER_SIZE is 32-bits / 4 bytes). Thus a stride of 8 would result in 4 usable bytes for the user.

    ----------------------------------------------
    |  Ver Data  |  Ver Data  | ... |  Ver Data  |
    ----------------------------------------------

A similar implementation for Arduino commonly referenced, which should be compatible.

Example

const eeprom = /* ... */
const byteSize = (32 * 1024 / 8) /* eeprom length - 32K-bits */

// format first
await CyclicFS.format(eeprom, byteSize, handle)
// initialize handle token
const handle = await CyclicFS.init(eeprom, byteSize, { stride: 8 })

// write data into FS
await CyclicFS.write(eeprom, handle, Uint8Array.from([ 1, 2, 3, 4 ]))
await CyclicFS.write(eeprom, handle, Uint8Array.from([ 5, 6, 7, 8 ]))
// ... etc
// last
await CyclicFS.write(eeprom, handle, Uint8Array.from([ 42, 77, 00, 37 ]))

// read (the one and only) latest buffer
const ab = await CyclicFS.read(eeprom, handle) // [ 42, 77, 00, 37 ]

Options

Both format and init take in an options object which cna configure the FS's use of the EEPROMs memory.

  • baseAddress start address in eeprom terms to create the FS (default: 0)
  • stride size of each "Slot" (includes Header width) (default: 32)
  • littleEndian "Header" byte ordering (does not effect user "Data") (default: false)

A "Slot" count is calculated as byteLength / stride (where byteLength is the allocated space for the FS, usually equal to the EEPROM total size)

Note: it is highly recommended to use stride that is a power-of-two, to align data to the EEPROM, though not required

Example (partitioned)

It is not required to have the entire EEPROM memory use, and multiple (or alternative) FS instances can be run along side (assuming they also respect the partition space). Such as EEFS

const eeprom = /* ... */
const totalEEPROMSize = 64 * 1024 / 8 // 64K-bit
const halfSize = totalEEPROMSize / 2

// create two unique "partitions" of half of the total size with different options
const partition1Options = {
  baseAddress: 0,
  stride: 8, // 4 bytes of user Data
  littleEndian: true
}
const partition1Options = {
  baseAddress: halfSize,
  stride: 16 // 12 bytes of user Data
}

// format both
await CyclicFS.format(eeprom, halfSize, partition1Options)
await CyclicFS.format(eeprom, halfSize, partition2Options)

// initialize the two partitions
const handleP1 = await CyclicFS.init(eeprom, halfSize, partition1Options)
const handleP2 = await CyclicFS.init(eeprom, halfSize, partition2Options)

// write a bunch of data to each
await CyclicFS.write(eeprom, handleP1, /* ... */)
await CyclicFS.write(eeprom, handleP1, /* ... */)
await CyclicFS.write(eeprom, handleP1, /* ... */)
// ...
await CyclicFS.write(eeprom, handleP2, /* ... */) // write to partition 2
// ...
await CyclicFS.write(eeprom, handleP1, /* ... */)
await CyclicFS.write(eeprom, handleP1, /* ... */)

// read the two independent values
const p1LatestValue = await CycleFS.read(eeprom, handleP1)
const p2LatestValue = await CycleFS.read(eeprom, handleP2)

Example (listing)

In descending order

cost handle = /* see above init() */

// ... add data

// iterate over slots in use starting with current
for await (const { version, data } of CyclicFS.list(eeprom, handle)) {
  console.log(version, data)
}

All slots in memory ordering

const handle = /* see above init() */

// returns all slots (even unused ones)
for await (const slot of CyclicFS.listSlots(eeprom, handle)) {
  const { version, data } = slot

  // assuming formatted with standard values
  if(version === HEADER_INIT_VALUE32) {
    // empty slot
  }
  else {
    // ... slit in-use
  }
}

The iteration of slots also can be used without a handle to inspect the FS (unlike list call which requires a handle), allowing to inspect potential devices at location or strides that may not be valid.

By bypassing the init call, the devices is not scanned to determine the current value

// your custom inspection configuration
const options = {
  baseAddress: /* some custom value */
  littleEndian: /* some custom value */
  byteLength: /* some custom value */
  stride: /* some custom value */
}
for await (const slot of CyclicFS.listSlots(eeprom, options)) {
  // ...
}