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

@seifer-webapp-factory/user-settings

v0.1.1

Published

user-settings — vijfde capability-module: een herbruikbare preferences-laag (theme/locale/dir, density, layout, dismissed hints, onboarding) met één useSettings()-seam over twee runtime-adapters (client: cookie+localStorage anoniem; server: NestJS+persist

Downloads

197

Readme

@seifer-webapp-factory/user-settings

A reusable preferences layer capability-module: one useSettings() seam the UI reads/writes, backed by a client store (cookie + localStorage, no login) or a per-user server store (auth), chosen at runtime and transparent to the UI — with graceful guest→login sync. Drops into a no-auth product (client only) and an auth product (client + server) without changing the surface.

It is schema-parameterised: the module ships the engine, the host declares its keys as SettingDescriptor[]. Consent is not a preference — analytics/marketing opt-in/out belongs to the account module; a consent-namespace descriptor is rejected here.

See ../user-settings.md for the full build plan, locked decisions, and the best-practice validation (2026 research).

Install

// the app's package.json
"dependencies": {
  "@seifer-webapp-factory/user-settings": "^0.1.0",
  "@seifer-webapp-factory/kits": "^0.1.0"
}

Subpath exports: ./contract · ./backend · ./frontend · ./manifest · ./scaffolder. The surface (backend/templates/**, frontend/templates/**) is materialized into the project and becomes project-owned.

Requires-ports (the host provides)

| Port | Used by | Default the module ships | |---|---|---| | SettingsStore (client) | anonymous path | createClientAdapter (cookie + localStorage) | | SettingsStore (server) | auth path | pgSettingsStore over the persistence kit | | SubjectProvider | server adapter | none — host injects (resolve the auth subject) | | Database | server adapter | host's persistence pool | | DesignSystem | reference page | plain semantic HTML (the page is ejectable) |

No requires.modules — this module composes kits only (decoupled from account).

Config (config fragment)

| Knob | Default | Meaning | |---|---|---| | serverSync | true | server persistence when auth is present; a no-auth product never selects the server adapter | | cookiePrefix | us_ | prefix for the render-key cookies | | renderKeys | ['theme','locale','dir'] | SSR-critical keys stored on a server-readable cookie | | cookieMaxAgeDays | 365 | render-cookie lifetime | | onboardingScope | server | onboardingSeen follows the user across devices (or device = localStorage) |

The host also passes its descriptors: SettingDescriptor[] (the key set) to UserSettingsModule.forRoot and to the client/server adapters. Each descriptor carries a key, namespace (render|pref), a coded default, a Zod schema, and optionally version + migrate (load-time migration) and deprecated.

The seam — one API, two adapters

// no-auth (client only)
const api = createUserSettings({ descriptors, initialAdapter: createClientAdapter({ ... }) });
provideUserSettings(app, api);

// in a component
const theme = useSetting<string>('theme'); // reactive two-way binding

On login, merge the anonymous map into the server store and repoint the seam:

await api.syncOnLogin({ from: clientAdapter, to: serverAdapter, client, opId, renderKeys });

syncOnLogin is idempotent via opId (a retry after a lost response is a no-op). Per-key conflict policy: server-wins for keys the account already has, client-fills for keys it lacks; unknown/invalid keys are reported in conflicts, never silently dropped. Render keys stay cookie-mirrored so SSR keeps working post-login. See sample/app/assemble.ts.

SSR — first-paint-safe theming

ssr.ts resolves render keys server-side from the cookie (returning visitor → correct <html class> in the initial HTML, no flash). First visit: ship color-scheme: light dark + light-dark() as the zero-JS default, plus renderHeadScript(cookiePrefix) (a synchronous <head> script, injected with a CSP nonce, with suppressHydrationWarning on <html>) that only overrides when a stored preference contradicts the OS. Accept-Language is used as a first-visit locale hint only — an explicit choice always wins.

Data model & sync semantics

The server owns one table, user_settings, keyed on (subject_id, key) — a keyed map, never one blob, so two devices editing different keys never conflict. This is an LWW-Element-Map CRDT: the server stamps a monotonic version per (subject,key) (the ordering authority — dodges client clock skew), the client carries a baseVersion so a stale overwrite is detectable (stale_write, 409), and deletes are versioned tombstones (deleted column), never a physical row-delete.

v0.1 scope note: there is no DELETE /settings/:key endpoint; SettingsStore.clear() on the server adapter resets to the descriptor default. The deleted tombstone column is used by merge and reserved for a future DELETE endpoint. Server versioning is receive-order LWW (documented consciously — not intent-order); if intent-time ordering of offline edits ever matters, the server version upgrades to a hybrid logical clock behind the contract.

Security & compliance

  • Consent is not a preference — delegated to account (GDPR Art. 7: consent must be demonstrable). A consent-namespace descriptor is rejected. Jurisdiction note: EU = opt-in for analytics; UK DUAA 2025 (5 Feb 2026) = info + opt-out for narrow aggregate analytics — modelled by account's consent store, not here.
  • Tiny cookies — only render keys on the cookie (~4 KB cap); never localStorage for render keys.
  • The theme cookie is client-read → also XSS-readable (not HttpOnly); it holds no secrets. Set SameSite=Lax; Secure; never Partitioned.
  • Functional-only exemption for pref keys holds only while user-set and never repurposed for tracking.
  • Server writes are authenticated + CSRF-guarded (double-submit); every read/write is schema-validated (junk → default, never a crash); events carry no values.

Divergence & eject surfaces

  • Configure (level 1): the key set, serverSync, renderKeys, cookie names.
  • Extend (level 2): add a SettingDescriptor, register a custom client SettingsStore.
  • Materialize & edit (level 3): eject frontend/templates/pages/settings.vue, the pg store, the NestJS controller/module — project-owned, reconciled on upgrade via three-way merge.
  • Fork (level 4): replace the adapter/merge mechanism — last resort.

Testing

npm test runs the unit suite (contract, manifest, backend services, frontend client/store/useSettings/ssr, the reference page, scaffolder). npm run test:e2e runs the backend testcontainers e2e (needs Docker). The vertical Playwright proof (tests/e2e/vertical/) assembles a sample app (needs a browser + running app).