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

@noy-db/in-pwa

v0.6.0

Published

PWA shell helpers for noy-db — storage persistence with a clear grant/deny signal, fail-closed eviction guard for the local vault, install-prompt capture, display-mode context detection, online/offline watcher, and the app-shell service-worker recipe.

Readme

@noy-db/in-pwa

npm

Installable/offline shell helpers for noy-db SPAs

Part of @noy-db/hub — the zero-knowledge, offline-first, encrypted document store.

Install

pnpm add @noy-db/hub @noy-db/in-pwa

The premise: the PWA starts empty

An installed PWA lives in its own storage partition. On first open it holds no data: the app runs the online enrollment (unlock + re-invite via @noy-db/on-oidc) and hydrates the vault from the firm cloud store into @noy-db/to-browser-idb. From then on the installed app is the offline-capable home of the vault: offline boot = cached app shell (service worker) + local IndexedDB vault.

This package is the browser-shell plumbing around that lifecycle. It holds no keys and sees no plaintext — every helper here operates on browser shell APIs or on opaque ciphertext-store presence, so it adds nothing to the threat model.

Persistence & eviction

Eviction of the local vault is the top PWA risk. iOS ITP evicts all script-writable storage (IndexedDB included) of non-installed web content after ~7 days without interaction; installed home-screen apps are safer, and navigator.storage.persist() exempts an origin from best-effort eviction where granted. Two helpers cover the risk:

requestPersistence()

import { requestPersistence } from '@noy-db/in-pwa'

const res = await requestPersistence()
// { persisted: boolean, quota?: number, usage?: number,
//   grantedBy: 'already' | 'granted' | 'denied' | 'unsupported' }

Wraps navigator.storage.persist() + estimate() with a clear grant/deny signal. Never throws — browsers without the Storage API resolve to grantedBy: 'unsupported'. Call it during enrollment; warn the user on 'denied'.

guardLocalVault() — fail closed at boot

If the partition was evicted, the app must never crash and never present a silent empty vault as truth. The guard probes the local store before the vault is opened and routes a missing vault into re-enrollment:

import { guardLocalVault } from '@noy-db/in-pwa'
import { browserIdb } from '@noy-db/to-browser-idb'

const store = browserIdb()
const { healthy } = await guardLocalVault(store, 'firm', (why) => {
  // why: { present: false, reason: 'empty' | 'probe-failed', cause? }
  routeToReEnrollment()
})
if (healthy) {
  /* open the vault normally */
}

The probe (probeLocalVault(store, vaultId)) is store-agnostic — it speaks only the 6-method NoydbStore contract (@noy-db/hub/to), never to-browser-idb internals. It checks the vault's _keyring marker records first (every encrypted vault persists its owner keyring at creation — the same signal the hub uses for "vault provisioned"), then falls back to a loadAll() envelope scan for plaintext-mode vaults. A broken store is treated exactly like a missing vault: probe errors resolve to { present: false, reason: 'probe-failed' } and trigger onEvicted — fail closed, never a throw from the probe path.

Install UX

import { captureInstallPrompt, getDisplayContext, isIosSafari } from '@noy-db/in-pwa'

// Android/desktop Chromium: defer the browser prompt, re-fire it from your UI.
const install = captureInstallPrompt() // call once at boot
// later, on a user gesture:
const outcome = await install.promptInstall() // 'accepted' | 'dismissed' | 'unavailable'

getDisplayContext() // 'pwa' (standalone display-mode / iOS navigator.standalone) | 'browser'
isIosSafari()       // true → show the "Add to Home Screen" share-sheet interstitial
                    // (iOS never fires beforeinstallprompt)

The shared shell-context contract with @noy-db/in-liff is exported here as a plain string union (no runtime coupling):

export type AppShellContext = 'liff' | 'browser' | 'pwa'

Online/offline transitions

import { watchOnline } from '@noy-db/in-pwa'

const stop = watchOnline((online) => syncStrategy.setOnline(online))

Invokes the callback immediately with navigator.onLine, then on every online/offline event — shaped for feeding the sync engine's isOnline flag (reconnect replay of the dirty queue lives in with-sync; this package does not import it).

Service-worker recipe (copy, don't import)

This package ships no service-worker runtime — the SW below is a recipe you copy into your app and version yourself.

Hard rule: vault data NEVER goes in the SW cache. The vault lives encrypted in to-browser-idb; the service worker caches the app shell only (HTML/JS/CSS/icons). A SW cache of vault responses would create a second, unmanaged copy of ciphertext outside the store contract — and a plaintext copy if you cached decrypted API responses. Don't.

// sw.js — app-shell caching only. Bump SHELL_CACHE on every deploy.
const SHELL_CACHE = 'app-shell-v1'
const SHELL_ASSETS = ['/', '/index.html', '/assets/app.js', '/assets/app.css', '/icons/192.png']

self.addEventListener('install', (event) => {
  event.waitUntil(caches.open(SHELL_CACHE).then((cache) => cache.addAll(SHELL_ASSETS)))
  self.skipWaiting()
})

self.addEventListener('activate', (event) => {
  // Drop caches from previous shell versions.
  event.waitUntil(
    caches
      .keys()
      .then((keys) => Promise.all(keys.filter((k) => k !== SHELL_CACHE).map((k) => caches.delete(k))))
      .then(() => self.clients.claim()),
  )
})

self.addEventListener('fetch', (event) => {
  const url = new URL(event.request.url)
  // App shell only: same-origin GET navigations and static assets.
  // Everything else (sync traffic, APIs) passes straight to the network —
  // vault data is never cached here.
  if (event.request.method !== 'GET' || url.origin !== self.location.origin) return
  event.respondWith(
    caches.match(event.request).then((hit) => hit ?? fetch(event.request)),
  )
})

Offline boot then composes as: SW serves the cached shell → guardLocalVault() confirms the local vault is present → the app opens it from to-browser-idb with no network at all.

Detaching from LINE / re-enrollment

Storage partitions are never transferable: LINE's WebView, the browser tab, and the installed PWA each have isolated storage, so moving the vault into the installed app is a re-enroll + re-sync ceremony, never a data transfer — the firm re-invites the device via the @noy-db/on-oidc unlock flow and the fresh partition hydrates from the firm cloud store, exactly like first enrollment. guardLocalVault()'s onEvicted hook is where that ceremony is (re-)entered.

Status

Pre-release (0.4.0-pre.1). API may change before 1.0.

Documentation

See the main repository for setup, examples, and the full subsystem catalog.

License

MIT © vLannaAi