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

hazo_env

v0.10.1

Published

Canonical environment resolver — typed env names, per-env DB/file/secret config, doctor and CLI for hazo apps

Readme

hazo_env

Canonical environment resolver for hazo apps. Typed env names, per-env DB/file/secret config, a local Postgres/files sync engine (envsync), a doctor diagnostic command, and a hazo-env CLI.

What it does

  • Env resolution — typed HazoEnv (dev | test | staging | prod), role mapping, pattern declaration, and assertEnv() to fail fast at boot if HAZO_ENV is invalid.
  • Per-env DB configresolveConnectConfig() maps the current env to its hazo_connect config (SQLite or PostgREST) with zero hardcoded connection strings in app code.
  • Per-env file configresolveFilesConfig() maps the current env to a hazo_files config rooted at the declared data_root.
  • SecretsgetSecret() resolves from .env.local only; placeholders in hazo_env_config.ini are substituted at runtime without storing secrets.
  • envsync — local Postgres/files sync engine (hazo-env sync / hazo-env serve). Dumps/restores a database via pg_dump/pg_restore and archives/restores a files root via tar, entirely on one host — no SSH, no PostgREST row-copy. See "envsync" below.
  • Doctordoctor() / hazo-env doctor validates pattern, DB reachability, required secrets (no values printed), data_root writability, schema level, and (with --probe --all) migration parity across envs.
  • CLIhazo-env current | doctor | sync | serve.

Installation

npm install hazo_env

Peer deps (required): hazo_core, hazo_config. Peer deps (optional): hazo_connect (for resolveConnectConfig), hazo_files (for resolveFilesConfig).

Quick start

1. Create hazo_env_config.ini (copy from config/hazo_env_config.ini.sample)

[env]
pattern = dev, prod
app     = myapp

[db.dev]
type          = sqlite
database_path = ${DATA_ROOT}/dev.sqlite

[db.prod]
type    = postgrest
base_url = ${POSTGREST_PROD_URL}
api_key  = ${POSTGREST_API_KEY}

2. Create .env.local (gitignored, one per deployment)

POSTGREST_PROD_URL=https://db.myapp.com
POSTGREST_API_KEY=...

3. Boot

import { assertEnv } from 'hazo_env';
import { resolveConnectConfig } from 'hazo_env';
import { createHazoConnect } from 'hazo_connect';

assertEnv();                                           // throws if HAZO_ENV not in pattern
const adapter = await createHazoConnect(resolveConnectConfig());

API

Env resolution (client-safe — also in hazo_env/client)

import { getEnv, getEnvRole, getRoleMap, getPattern, listEnvs, assertEnv, describeEnv,
         isDev, isTest, isStaging, isProd } from 'hazo_env';

getEnv()        // 'dev' | 'test' | 'staging' | 'prod'
getEnvRole()    // 'development' | 'test' | 'staging' | 'production'
getRoleMap()    // EnvRoleMap — full env→role mapping (config-driven; safe defaults)
getPattern()    // e.g. 'dev_prod'
listEnvs()      // ['dev', 'prod'] — valid envs for the declared pattern
assertEnv()     // throws HazoError(ENV_INVALID) if HAZO_ENV is outside the pattern
describeEnv()   // { env, role, pattern, app, dataRoot, hostHint, roles? }
isDev() / isTest() / isStaging() / isProd()

getEnvRole() uses a config-driven role map so custom env names (e.g. preview, qa) resolve to the correct broad role. Unknown names default to 'development' — never silently treated as production. Configure custom mappings in [env.roles]:

[env.roles]
preview = staging
qa      = test

Configuring environment roles

Canonical env names (dev, test, staging, prod) are pre-mapped and require no configuration. Non-canonical names (anything else) must declare their role in the [env.roles] INI section, or doctor will flag an error. Unknown names without a declaration fall back to 'development' (safe: no masking, no prod-protection).

[env.roles]
; Map arbitrary env names to the four fixed roles:
;   development | test | staging | production
qa   = test
live = production
rc   = staging

Valid role values: development, test, staging, production.

Client-side global injection

On the client (browser), the INI file is not available. Inject the resolved values at render time using globalThis globals — the same pattern used for the env pattern:

// In your Next.js layout server component or _app (server-side):
// globalThis.__HAZO_ENV_PATTERN__ = 'dev,prod';                  // comma-separated pattern
// globalThis.__HAZO_ENV_ROLES__   = { live: 'production', qa: 'test' };

// Client bundle reads these automatically:
import { getPattern, getRoleMap } from 'hazo_env/client';
getPattern()   // reads __HAZO_ENV_PATTERN__
getRoleMap()   // merges DEFAULT_ROLE_MAP → __HAZO_ENV_ROLES__

Both globals are optional; omitting them makes the client fall back to canonical defaults.

Utility

import { normalize } from 'hazo_env';

normalize('  Dev ')  // → 'dev'  (trim + lowercase)

Resolvers (server-only)

import { resolveConnectConfig } from 'hazo_env';
import { resolveFilesConfig }   from 'hazo_env';
import { getSecret }            from 'hazo_env';

// Returns the hazo_connect config for the current env
resolveConnectConfig()

// Returns the hazo_files config for the current env (base_path from data_root)
resolveFilesConfig()

// Read a value from .env.local — throws HazoError(SECRET_MISSING) when required + absent
getSecret('POSTGREST_API_KEY', { required: true })

Doctor

import { doctor } from 'hazo_env';

const report = await doctor({ probe: true, all: true });
// report.passed    boolean
// report.checks    DoctorCheck[] — { label, status: 'ok'|'warn'|'error', detail? }
// Migration-readiness checks (--probe --all): _migrations parity across envs,
// masking ruleset column validation against live schema.

envsync — local Postgres/files sync (server/ops-only)

envsync copies a database or files root between environments that live on the same host (or where you're willing to move a dump/archive by hand) using plain pg_dump/pg_restore/tar — no SSH, no PostgREST row-copy loop, no masking pass. It's driven via the CLI or an optional local HTTP service; the engine functions (downloadDb, uploadDb, downloadFiles, uploadFiles) live at hazo_env/dist/envsync/engine.js and are intentionally not re-exported from the package's main index.ts — reach them through that subpath or through the CLI.

# Config: [envsync] section in hazo_env_config.ini — source_db, target_db, files_root, work_dir,
#         keep, prod_db_names, prod_db_pattern, allow_prod

hazo-env sync download-db                          # pg_dump source_db → work_dir
hazo-env sync upload-db --dump <path> --confirm     # pg_restore into target_db (--allow-prod-target for a prod target)
hazo-env sync download-files                        # tar files_root (+ current .env.local) → work_dir
hazo-env sync upload-files --archive <path>         # dry-run: prints the .env.local diff it would apply
hazo-env sync upload-files --archive <path> --confirm  # actually restores files + merges env overrides
hazo-env sync upload-files --archive <path> --confirm --skip-env  # restore files only; leave .env.local untouched
hazo-env sync upload-files --archive <path> --confirm --prune-env # also DELETE .env.local keys the archive lacks
hazo-env sync upload-files --archive <path> --show-values          # print env values in the diff (hidden by default)

hazo-env serve [--port <n>] [--bind <host>]         # HTTP service over the same engine — requires HAZO_ENVSYNC_TOKEN

Safety model (see CHANGE_LOG.md 0.7.0 for the original design and 0.10.0 for the hardening pass):

  • Single writer. Every destructive op runs under withLock.
  • Validate, then destroy. The dump/archive is fully checked (exists, non-empty, inside work_dir, and — for archives — lists cleanly under tar -t) before dropdb or the files_root wipe runs. A typo'd path can never delete anything.
  • Confined paths. --dump/--archive must resolve (symlinks included) inside work_dir, must not start with -, and are passed after a -- end-of-options separator.
  • Production guard. A target is refused if it is on prod_db_names or matches prod_db_pattern (default: a prod/production/live token anywhere in the name). Override with --allow-prod-target on the CLI, or allow_prod = true in [envsync]. The HTTP service reads the override only from config — a request body can never set it.
  • .env.local keys are preserved. Keys that exist only in the target survive a restore by default; deleting them requires --prune-env / pruneEnv: true. The diff preview lists every key with an added/changed/unchanged/removed status.
  • No secrets in output. Connection-string credentials are stripped from every progress line, and env diffs are reported as key + status — the HTTP API and control page never carry .env.local values.

CLI

hazo-env current                       # prints env, role, pattern, app, data_root
hazo-env doctor [--env <e>] [--all]    # red/green validation table

hazo-env sync download-db | upload-db --dump <p> [--confirm] [--allow-prod-target]
hazo-env sync download-files | upload-files --archive <p> [--confirm] [--allow-prod-target]
                                            [--skip-env] [--prune-env] [--show-values]
hazo-env serve [--port <n>] [--bind <host>]   # requires HAZO_ENVSYNC_TOKEN

Config reference (hazo_env_config.ini)

See config/hazo_env_config.ini.sample for the full annotated template.

Key sections:

  • [env]pattern (comma-separated env names), app (app identifier)
  • [data]root (data_root; default app_data)
  • [db.<env>]type (sqlite | postgrest), database_path / base_url / api_key
  • [host.<env>]location (local | remote)

Secret placeholders use ${ENV_VAR_NAME} syntax — hazo_env substitutes them from .env.local at runtime.

For envsync's own [envsync] / [migrate.env_overrides] config sections, see the envsync section above and CHANGE_LOG.md (0.7.0 entry).

License

MIT