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

@artemjs/vfskit

v1.2.1

Published

Universal abstraction over any virtual file system - memory, disk, S3 and more - with composable adapters, encryption and a remote bridge. Backend kit.

Readme


vfskit wraps any kind of storage behind a single, small VFS interface, then lets you compose behavior on top of it - encryption, caching, a remote bridge - and drive it from the browser exactly as you would on the server. Anything you can read, write, and list becomes a structured file system with files and metadata.

It ships in two faces under one brand:

  • @artemjs/vfskit (npm, Node) - the full kit: core + memory + node-fs + s3 + sqlite + kv + encrypt + cache + serve + remote.
  • @artemjs/vfskit-front (npm + jsDelivr, browser) - core + memory + opfs + kv + encrypt + cache + a remote client.

Both expose identical API names, so your code looks the same on either side.

Install

npm i @artemjs/vfskit              # backend / Node
// browser, no build step
import { remote, wsTransport } from 'https://cdn.jsdelivr.net/npm/@artemjs/vfskit-front/+esm'

Everything is a VFS

interface VFS {
  read(path): Promise<Uint8Array>
  write(path, data, opts?): Promise<void>
  list(path, opts?): Promise<Entry[]>
  stat(path): Promise<Stat>
  exists(path): Promise<boolean>
  mkdir(path, opts?): Promise<void>
  remove(path, opts?): Promise<void>
  move(from, to): Promise<void>
  copy(from, to): Promise<void>
  getMeta(path): Promise<Meta>
  setMeta(path, meta): Promise<void>
  watch(path, cb): Unsubscribe
  capabilities(): Capabilities
}
  • Adapters implement VFS over a backend: memory(), nodeFs(dir), s3({ client }).
  • Middleware wraps a VFS and returns a VFS: encrypt(vfs, { passphrase }), cache(vfs).
  • Bridge connects them across the wire: serve(vfs) on the server, remote(transport) on the client.

Compose freely. encrypt(remote(transport)) is end-to-end encryption - the server only ever stores ciphertext.

Quick start

import { memory, nodeFs, encrypt, serve, remote, toText } from '@artemjs/vfskit'

const store = encrypt(nodeFs('./data'), { passphrase: 'hunter2' })
await store.write('/notes/todo.md', '# buy milk', { meta: { tag: 'home' } })
console.log(toText(await store.read('/notes/todo.md')))

Expose a backend, drive it from anywhere:

// server
const server = serve(nodeFs('./data'))
// wire server.fetch (HTTP) or server.socket (WebSocket) into your runtime
// client (browser or Node)
import { remote, wsTransport } from '@artemjs/vfskit-front'
const fs = remote(wsTransport('ws://localhost:3000'))
await fs.write('/hello.txt', 'hi')

Adapters

| Adapter | Where | Metadata | Notes | | --- | --- | --- | --- | | memory() | anywhere | native | reference implementation; synchronous watch | | nodeFs(dir) | Node | sidecar .vfskit/meta.json | rooted at dir; native streaming; watch via fs.watch | | s3({ client, prefix?, pollMs? }) | Node | native object metadata | inject any S3Like client; POSIX dirs emulated with markers; watch by polling | | sqlite(file) | Node | native column | whole VFS in one SQLite file via built-in node:sqlite; conditional writes | | opfs(root?) | browser | sidecar manifest | persistent Origin Private File System; native file streaming | | kv({ store, prefix? }) | anywhere | native record | over any async key-value store; ships memKv() and localStorageKv() |

Every adapter passes the same conformance suite, so a new one "just works" once it does too.

// browser-persistent storage, no server
import { opfs, kv, localStorageKv } from '@artemjs/vfskit-front'
const disk = opfs()                          // Origin Private File System
const ls = kv({ store: localStorageKv() })   // or back any KV: Redis, Cloudflare KV, Deno KV

// node: a whole file system in one .db
import { sqlite } from '@artemjs/vfskit'
const db = sqlite('./app.db')

Bring your own storage

vfskit is just an interface. To put any backend behind the same API, write a function that returns a VFS - a plain object literal implementing the methods above - over your store (a database, a KV cache, localStorage, a blob service, whatever):

import { type VFS, normalize, toBytes, notFound } from '@artemjs/vfskit'

export function myVfs(store: MyStore): VFS {
  return {
    capabilities: () => ({ streaming: false, watch: false, atomicMove: false, nativeMeta: true, randomAccess: false, conditionalWrite: false }),
    async read(path) { /* ... */ },
    async write(path, data, opts) { /* ... */ },
    // ...the rest of the interface
  }
}

Then validate it against the exact same battery every built-in adapter must pass:

import { conformanceCases } from '@artemjs/vfskit/conformance'
import { describe, it } from 'vitest'

describe('my adapter', () => {
  for (const c of conformanceCases) it(c.name, () => c.run(() => myVfs(new MyStore())))
})

If it passes, your storage now works everywhere vfskit works - behind encrypt(...), behind serve(...), driven by a browser remote(...). A complete worked example (a key-value backend) lives in examples/custom-adapter. conformanceCases is framework-agnostic ({ name, run(makeVfs) }[]), so you can drive it from any test runner.

Encryption

AES-256-GCM via WebCrypto. A raw key, or a passphrase derived per file with PBKDF2 (random salt, 210k iterations). Tamper fails closed with a typed error. Content is encrypted by default; metadata stays as the backend stores it.

const vault = encrypt(memory(), { passphrase: 'open sesame' })

Caching

cache(vfs, { ttlMs? }) serves reads from an in-memory store (write-through, subtree-invalidated on write/remove/move/copy). Wrap a remote(...) to avoid round-trips for hot files:

import { cache, remote, wsTransport } from '@artemjs/vfskit-front'
const fs = cache(remote(wsTransport(url)), { ttlMs: 5000 })

Pass your own store to back the cache with anything (e.g. localStorage).

Concurrent writes

Adapters that report conditionalWrite give every file an opaque version token (via stat). Pass it back as ifMatch to make a write succeed only if nobody changed the file in between - otherwise it fails with a typed CONFLICT. ifAbsent makes a create-only write.

const { version } = await fs.stat('/doc')
await fs.write('/doc', next, { ifMatch: version })   // CONFLICT if it moved on
await fs.write('/new', data, { ifAbsent: true })     // ALREADY_EXISTS if it exists

Supported by memory, nodeFs, s3, and transparently over remote(...).

Streaming

readStream(vfs, path) and writeStream(vfs, path) give Web ReadableStream / WritableStream over any adapter - native where supported (nodeFs streams real file handles), buffered otherwise, so the API is uniform:

import { readStream, writeStream, collect, toBytes } from '@artemjs/vfskit'

const w = (await writeStream(fs, '/big.log')).getWriter()
await w.write(toBytes('line 1\n')); await w.close()
const all = await collect(await readStream(fs, '/big.log'))

encrypt and cache buffer through their own read/write, so streaming stays correct behind them (the stream still yields plaintext; the disk still holds ciphertext).

Transports

  • httpTransport(url) - request/response; works on serverless/edge. No watch.
  • wsTransport(url) - multiplexed; enables watch/events.

Errors

Typed hierarchy with stable wire codes, reconstructed on the client across the bridge: NOT_FOUND, ALREADY_EXISTS, NOT_A_DIRECTORY, IS_A_DIRECTORY, PERMISSION_DENIED, UNSUPPORTED, CONFLICT, IO. Detect with isVfsError(e) (brand-based - survives bundling and the RPC round-trip).

Example

examples/cloud-ide - Monaco editing files on a real-disk VFS over a WebSocket bridge, with per-user isolation. Swapping the backend to S3 is one line.

License

MIT