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

@stele-climbing/stele1-directory-handle

v0.0.2

Published

browser interface for stele1 catalogs over any FileSystemDirectoryHandle (File System Access picker or OPFS)

Readme

@stele-climbing/stele1-directory-handle

Browser interface for stele1 catalogs, backed by any FileSystemDirectoryHandle — from the File System Access picker or from OPFS.

A Catalog reads and writes a catalog directory as described by the stele1 spec: one JSON file per record, photo images alongside their records, metadata in stele1.json at the root.

All methods are async. Iterators are async generators, consumed with for await.

This package is ESM-only.

Getting a directory handle

// File System Access picker (user-chosen directory; Chromium, secure context)
const handle = await window.showDirectoryPicker({ mode: 'readwrite' });

// OPFS (origin-private, broadly supported including Firefox/Safari)
const handle = await navigator.storage.getDirectory();

The catalog takes an already-permissioned handle; it does not prompt.

Use

import { Catalog } from '@stele-climbing/stele1-directory-handle';

const catalog = Catalog.plain(handle);

if (!(await catalog.exists())) await catalog.create();

await catalog.setClimb({
  uuid: '550e8400-e29b-41d4-a716-446655440000',
  name: 'The Mandala',
  rating: { difficulty: 'V12' },
});

for await (const climb of catalog.eachClimb()) {
  console.log(climb.name);
}

Each datatype (climb, area, photo, trail, parking) has matching methods:

  • {type}Uuids() lists the uuids of stored records
  • get{Type}(uuid) reads one record (rejects on a missing file, invalid JSON, or a uuid that doesn't match its filename)
  • set{Type}(record) writes one record, keyed by its uuid
  • each{Type}(onError) lazily iterates parsed records; a file that fails to read or parse is skipped, and onError(err, uuid) is called if provided

Catalog metadata (stele1.json) is read and written with getMetadata() and setMetadata(metadata).

Creating a catalog

await catalog.create();                             // mints a catalog uuid, lays down dirs
await catalog.create({ 'stele1.txt': introText });  // with custom asset contents

create refuses a directory that already contains anything. Unlike the Node backend there are no bundled asset files in the browser, so create takes asset contents as an argument, with brief stub texts as the default.

Deleting records

Trails and parking delete directly: dropTrail(uuid), dropParking(uuid).

Photos can be referenced by nothing, so dropPhoto(uuid) deletes the record and its image file.

Climbs and areas can be referenced by photo layers, so deletion is explicit about what happens to those references:

  • purgeClimb(uuid) removes the climb and strips it from every photo's climb_layers.
  • dropClimbNoPurge(uuid) removes only the record, leaving any photo layers dangling.

purgeArea / dropAreaNoPurge mirror this for areas. Renderers should tolerate a layer whose reference no longer resolves.

Photo images

await catalog.setPhotoImage(uuid, blob, 'jpg');   // store a Blob or File
await catalog.getPhotoImageBlob(uuid);            // File (a Blob), or null

Images are stored as {uuid}_image.{ext} next to the photo's JSON. Setting a new image removes the old one, including one with a different extension.

Adapters

A Catalog doesn't interpret records itself; an adapter supplies parse, stringify, and getUuid per record type (metadata needs only parse and stringify).

Catalog.plain(handle) uses the built-in plain adapter: records are plain objects, no validation, output formatted with sorted keys and stable indentation so catalogs diff cleanly under version control.

To validate records on read and write, use the adapter shipped by @stele-climbing/stele1-datatypes:

import { Catalog } from '@stele-climbing/stele1-directory-handle';
import { adapter } from '@stele-climbing/stele1-datatypes/adapter';

const catalog = new Catalog(handle, adapter);

// eachClimb() now yields Climb instances, validated on read;
// setClimb accepts them, validated on write

Adapters travel across backends: the same adapter works with the Node filesystem package, and all shipped adapters produce identically formatted output, so a catalog moves between environments unchanged and diffs cleanly whichever backend wrote it. The backends share the on-disk format and the adapter contract, not their APIs; each is idiomatic for its environment.

Persistence (OPFS)

OPFS is best-effort by default and may be evicted under disk pressure. Request durable storage once if the catalog should be kept:

await navigator.storage.persist();

Requirements

  • A secure context (HTTPS or localhost).
  • The File System Access picker is Chromium-only; OPFS is broadly supported.
  • showDirectoryPicker must be called from a user gesture.

Install

npm install @stele-climbing/stele1-directory-handle

Development

Source and issues live at codeberg.org/stele-climbing/stele1.

License

MIT