hoek

General-purpose Node.js utilities for deep cloning, merging, and object traversal, built for the hapi ecosystem.

Library
npm
v11.0.7
482stars
BSD 3-Clause License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
67/100Good
Development Activity56
Maintenance48
Community84
Maturity60
Momentum20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
70/100Good
Architecture78
Code Quality82
Innovation45
Learning Curve75

@hapi/hoek is a small, dependency-free utility library that grew out of the hapi web framework’s need for consistent object manipulation helpers. It provides deep cloning, destructive and non-destructive merging, safe object-path traversal, deep equality checks, and a handful of string-escaping and assertion helpers that show up repeatedly across server-side Node.js code.

While hoek is maintained as part of the hapi family and is a direct dependency of hapi, joi, and other hapi-ecosystem packages, it works fine standalone in any Node.js project. It deliberately does not try to be a full utility belt like lodash — its scope is narrow and shaped by what hapi’s own internals repeatedly needed: safe deep clones that preserve prototypes and non-enumerable properties, config merging with explicit null-handling semantics, and dot-path object access with configurable strictness.

The library has been rewritten in TypeScript with a modern ESM-only build (tsdown), full type definitions shipped alongside the compiled output, and a Vitest-based test suite with coverage reporting, replacing what was historically a plain CommonJS module.

What You Get

  • clone() — deep clones objects and arrays, including non-enumerable properties, symbols, and special types like Date, RegExp, and Buffer, with optional shallow-copy key paths
  • merge() / applyToDefaults() — destructive and non-destructive object merging with configurable null-override and array-merge semantics, purpose-built for config-defaults patterns
  • reach() / reachTemplate() — safe dot-path (or array-path) traversal into nested objects, with strict mode, custom separators, and default fallback values
  • deepEqual() / intersect() / contain() — structural equality checks and array/set comparison utilities for validation and comparison logic
  • escapeHtml() / escapeJson() / escapeRegex() / escapeHeaderAttribute() — output-escaping helpers for safely interpolating untrusted strings into HTML, JSON, regex, and HTTP headers
  • assert() / AssertError / once() / wait() / isPromise() — small assertion and async-control-flow helpers used throughout hapi’s own internals

Common Use Cases

  • Config merging in server frameworks — layering user-supplied options onto framework defaults with applyToDefaults(), preserving explicit null/undefined semantics
  • Safe nested config/data access — using reach() to read deeply nested request or config values without throwing on missing intermediate keys
  • Deep cloning request/response objects — cloning objects that carry Dates, Buffers, or RegExps (e.g. validation schemas, cached responses) without losing type fidelity
  • Input sanitization before output — escaping strings destined for HTML attributes, JSON payloads, or HTTP headers to prevent injection
  • Building joi-style validation libraries — deepEqual/intersect/contain provide the comparison primitives that schema-validation libraries build on top of

Under The Hood

Architecture hoek is a flat collection of single-purpose modules under src/, one file per exported function (clone.ts, merge.ts, reach.ts, deepEqual.ts, etc.), re-exported through a single index.ts barrel file. There is no internal class hierarchy or shared runtime state — each function is independently testable and most take an explicit options object rather than relying on module-level configuration. Cross-function dependencies are minimal and deliberate: merge() calls clone() internally to deep-copy array elements and mismatched-type values, and several functions share small helpers from utils.ts (key enumeration respecting symbols and enumerability). This flat, function-per-file structure means changing one utility (e.g. clone()‘s handling of a new object type) has a narrow, predictable blast radius limited to its direct callers (merge, applyToDefaults).

Tech Stack The package is written in TypeScript targeting Node.js 22+, built with tsdown into a single ESM-only bundle (dist/index.mjs) with accompanying .d.mts type declarations — there is no CommonJS output, a deliberate break from hoek’s historical CJS-only distribution. Linting and formatting are handled by oxlint and oxfmt (the Oxc-based Rust toolchain) rather than ESLint/Prettier, and tests run under Vitest with @vitest/coverage-v8 for coverage reporting. CI is delegated to a shared reusable workflow (hapijs/.github/.github/workflows/ci-module.yml) rather than a repo-local pipeline, reflecting hoek’s status as one of many uniformly-maintained packages in the hapi organization.

Code Quality Each exported function has a corresponding test file under test/, and the suite runs with coverage instrumentation via Vitest, giving reasonable confidence that edge cases (null options, symbol properties, array-vs-object merge conflicts) are exercised. Type safety is strong: complex generic types like MergeTypes<T1, T2> compute the merged object’s resulting shape at the type level, and functions are annotated with detailed JSDoc covering parameters, defaults, and return semantics. Error handling favors explicit assert() calls with descriptive messages over silent failure (e.g. merge() asserts both target and source are valid objects before proceeding). Naming is consistent and each option field documents its default inline via JSDoc @default tags.

What Makes It Unique hoek’s value isn’t novel algorithms — clone, merge, and reach are common utility patterns — but the specific semantics it encodes for hapi’s own needs: merge()‘s explicit nullOverride and mergeArrays flags exist because hapi’s config-layering (defaults → user options → route options) needs precise, documented behavior for how null and array values propagate, which general-purpose utility libraries like lodash don’t standardize the same way. Its narrow, dependency-free scope and willingness to be superseded by lodash for generic use cases (stated directly in its own README) is itself a design choice: hoek optimizes for being a small, auditable, zero-dependency building block for framework internals rather than a broad utility belt.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search