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.6.0

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 DB+files migration engine with PII masking, 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.
  • Migration enginerunMigration({ from, to }) copies a DB + files between envs. Validates → snapshots target → copies tables (paged, schema-checked) → copies files → verifies → writes hazo_audit entry. Prod target refused without an explicit confirm token.
  • PII masking — masking rules declared per table/column in hazo_env_masking.ini (seed) and synced to hazo_app_config at runtime. Built-in transforms: mask_email, fake_name, mask_phone, jitter_date, hash, tokenize, drop, nullify. Custom transforms via registerMask(). Applied automatically when the migration target role is test or staging.
  • 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 + masking ruleset column validation.
  • CLIhazo-env current | doctor | snapshot | migrate | verify | restore | mask.

Installation

npm install hazo_env

Peer deps (required): hazo_core, hazo_config. Peer deps (optional): hazo_connect (for resolveConnectConfig), hazo_files (for resolveFilesConfig), hazo_secure (for masking transforms), hazo_pdf (for mask_pdf transform), hazo_audit (for migration audit entries).

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.

Migration engine (server-only)

import { runMigration, verifyFiles, takeSnapshot, restoreSnapshot,
         writeMigrationProgress, readMigrationProgress, clearMigrationProgress } from 'hazo_env';

// Copy prod → staging (with PII masking applied automatically)
const result = await runMigration({
  from: 'prod',
  to: 'staging',
  dryRun: false,                         // set true for a plan with counts, no writes
  // allowProdTarget: true,              // required when `to` is 'prod'
  // confirmToken: '<token>',            // required together with allowProdTarget
  onProgress: (p) => console.log(p.phase, p.message),
  // jobId: '<id>',                      // optional — persist progress to progressDir/<id>.json
  // progressDir: '/tmp/hazo_jobs',      // required when jobId is set
});
// result.ok, result.db, result.files, result.snapshotId, result.warnings, result.durationMs

// Stand-alone verification (with optional hazo_connect adapter for L2–L5 checks)
await verifyFiles('staging', dataRoot, { hash: 'sample', checkOrphans: true, adapter });
// report.ok, report.checked, report.hashed, report.missing[], report.sizeMismatch[],
// report.hashMismatch[], report.orphans[], report.skippedReason?

// Manual snapshot / restore
const snap = takeSnapshot(connectConfig);   // returns { snapshotId }
restoreSnapshot(connectConfig, snapshotId);

// Filesystem progress store (used by hazo_jobs integration)
writeMigrationProgress(dir, jobId, progress);
readMigrationProgress(dir, jobId);    // → MigrationProgress | null
clearMigrationProgress(dir, jobId);

Masking — customize per table/column in config/hazo_env_masking.ini. Sync to the DB with hazo-env mask sync. Register custom transforms with registerMask(name, fn).

Masking ruleset API — load, parse, and sync rulesets programmatically:

import { loadRuleset, syncRulesetFromIni, parseIniRules } from 'hazo_env';
import type { MaskRule } from 'hazo_env';

const rules: MaskRule[] = parseIniRules(iniText);           // parse INI text → rules array
const loaded  = await loadRuleset(adapter);                 // read from hazo_app_config DB
await syncRulesetFromIni(adapter, '/path/to/masking.ini'); // write parsed rules to DB

CLI

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

hazo-env migrate --from <env> --to <env> [options]
  --transport auto|local|ssh|api       # default auto
  --no-db | --no-files                 # skip DB or file copy
  --scrub auto|none                    # default auto (mask when target is test/staging)
  --tables a,b,c                       # restrict to specific tables
  --dry-run                            # plan + counts, no writes
  --allow-prod-target --confirm <tok>  # required when writing prod

hazo-env verify <env> [--hash none|sample|full] [--no-orphans]
hazo-env restore <env> --snapshot <snapshot-id>

hazo-env mask sync                     # sync hazo_env_masking.ini → hazo_app_config
hazo-env mask list                     # print active masking rules

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.

dump-restore transport

The dump-restore transport copies a database via SSH pg_dump/pg_restore (binary format). Use it when the source and target share the same Postgres server (e.g. prod→dev on the same VPS) and you want a fast binary copy without going through the PostgREST row-copy loop.

Config: [backup.<env>]

Add a section to config/hazo_env_config.ini:

[backup.dev]
ssh_env         = prod         ; reuse [transport.ssh.prod] SSH connection
target_db       = myapp_dev    ; DB to drop/recreate
owner           = adminuser    ; createdb -O <owner>
dump_source     = /var/backups/myapp-*.pgdump  ; glob (newest used) or "fresh"
fresh_dump_db   = myapp        ; source DB for pg_dump when dump_source=fresh
pre_restore_cmd = sudo systemctl stop myapp-postgrest-dev
post_restore_cmd= sudo systemctl start myapp-postgrest-dev

No PII scrubbing on this path. The binary pg_restore copies raw rows; hazo_env's scrubHook only runs inside the PostgREST row-copy loop. If you need scrubbed data in dev, use the default api transport instead.

CLI

npx hazo-env migrate --from prod --to dev --transport dump-restore

Programmatic API

import { resolveBackupConfig, restoreDbViaDump } from 'hazo_env';

const backup = resolveBackupConfig('dev'); // reads [backup.dev] from INI
if (backup) {
  const result = await restoreDbViaDump(backup, {
    onProgress: (msg) => console.log(msg),
  });
  // result.mode === 'dump-restore'
}

License

MIT