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

@visualtools/namespace

v2.4.1

Published

Dotted-path namespace utilities for JavaScript — 8-verb point-contract API, path algebra, and batch contracts

Readme

@visualtools/namespace

Zero-dependency dotted-path namespace utilities for JavaScript. Safety-by-default verbs, auto-vivification, and a NotFound sentinel that distinguishes "missing" from "undefined".

Works in Node.js, browsers, and TypeScript.

Install

npm install @visualtools/namespace
# or
yarn add @visualtools/namespace

Quick start

import namespace from "@visualtools/namespace";
// or: const namespace = require("@visualtools/namespace");

const ctx = {};

// Bare call: ensure a path exists as a plain object
namespace(ctx, "app.config");
// ctx is now { app: { config: {} } }

// Create-only write (throws if path already holds something)
namespace.setNotExists(ctx, "app.config.port", 3000);

// Read — returns the value, or the NotFound sentinel if absent
const val = namespace.getIfExists(ctx, "app.config.host");
if (namespace.isNotFound(val)) {
  console.log("host not configured yet");
}

// Read — throw if absent (great for fail-fast validation)
const port = namespace.getMustExist(ctx, "app.config.port");

// Read — return a fallback if absent, never write
const host = namespace.getOrDefault(ctx, "app.config.host", "localhost");

// Convergence — write only if absent; return whichever now holds
const cache = namespace.setOrDefault(ctx, "app.cache", new Map());

Why namespace?

The verb name IS the contract. Each call encodes a claim about what the rest of the codebase has promised at that point. When the claim is wrong, the error names the exact path and verb — no hunting through stack traces.

NotFound is not undefined. getIfExists() returns a frozen sentinel for absent paths, so you can distinguish "not found" from "found and set to undefined". Most path libraries conflate these.

Safety by default. setNotExists() refuses to overwrite. You must reach for the longer name (setOverwrite) to clobber — the verbosity is the signal that you mean it.

The bare call: namespace(obj, path)

Ensures every segment of path exists as a plain object.

  • Absent — vivify as {}
  • Plain object — return it
  • Anything else (array, string, number, ...) — throw
const ctx = {};
const db = namespace(ctx, "services.database");
// ctx is { services: { database: {} } }
// db === ctx.services.database

// Idempotent — second call returns the same object
namespace(ctx, "services.database") === db; // true

// Collision detection — won't silently coerce non-objects
ctx.services.cache = [1, 2, 3];
namespace(ctx, "services.cache"); // throws: non-object value exists at "cache"

Read verbs

Read verbs never write to the object.

getIfExists(object, path)

Returns the value at path, or the NotFound sentinel if any segment is absent.

namespace.getIfExists(obj, "a.b.c");   // value or NotFound
namespace.isNotFound(result);          // true if absent

getMustExist(object, path, options?)

Returns the value, or throws. Use for fail-fast validation.

namespace.getMustExist(obj, "required.field");
namespace.getMustExist(obj, "required.field", {
  errorMessage: "Config error: field is required"
});

getOrDefault(object, path, standIn)

Returns the value if present, otherwise standIn. Never writes.

const timeout = namespace.getOrDefault(config, "http.timeout", 5000);

getOrDefault.syncFunc(object, path, fn)

Like getOrDefault, but calls fn() only when absent. Never writes.

const config = namespace.getOrDefault.syncFunc(ctx, "app.config", () => loadConfig());

getOrDefault.asyncFunc(object, path, fn)

Like getOrDefault, but calls async fn() only when absent. Always returns a promise. Never writes.

const config = await namespace.getOrDefault.asyncFunc(ctx, "app.config", () => fetchConfig());

getMustEmpty(object, path)

Throws if a value is present at path. Use as a guard before writing to a slot you know is new.

namespace.getMustEmpty(obj, "slot.that.should.be.new");
namespace.setNotExists(obj, "slot.that.should.be.new", value);

Write verbs

setNotExists(object, path, value)

Create-only. Writes value, throws if path already holds something. Auto-vivifies missing intermediates.

namespace.setNotExists(obj, "users.alice.role", "admin");
namespace.setNotExists(obj, "users.alice.role", "user"); // throws: cannot overwrite

setMustExist(object, path, value)

Update-only. Writes value, throws if path is absent. Does NOT auto-vivify — the whole hierarchy must already exist.

namespace.setMustExist(obj, "users.alice.role", "user"); // update existing

setOrDefault(object, path, value)

Convergence: writes value only if absent; returns whichever now holds. Auto-vivifies intermediates.

// Many routes may initialize this — first one wins
const db = namespace.setOrDefault(ctx, "connections.db", createPool());

setOrDefault.syncFunc(object, path, fn)

Like setOrDefault, but calls fn() only when absent. Writes the result.

const pool = namespace.setOrDefault.syncFunc(ctx, "db.pool", () => createPool());

setOrDefault.asyncFunc(object, path, fn)

Like setOrDefault, but calls async fn() only when absent. Awaits, writes, returns a promise.

const pool = await namespace.setOrDefault.asyncFunc(ctx, "db.pool", () => connectAsync());

setOverwrite(object, path, value, options?)

Unconditional write. Clobbers any existing value. Auto-vivifies intermediates.

namespace.setOverwrite(obj, "status.updated", Date.now());

// By default, throws if an intermediate is a non-object.
// Pass { overwriteStructure: true } to clobber structure too.
namespace.setOverwrite(obj, "a.b.c", 1, { overwriteStructure: true });

Test verbs

exists(object, path)

Returns true if the path holds any value — including 0, false, "", null.

if (namespace.exists(config, "feature.enabled")) { ... }

isNotFound(value)

Returns true if value is the NotFound sentinel.

const result = namespace.getIfExists(obj, "maybe.missing");
if (namespace.isNotFound(result)) { ... }

Remove verbs

rm(object, path)

Removes the value at path. No-op if absent. Returns the removed value, or NotFound if the path was absent.

const old = namespace.rm(obj, "temp.token");
if (!namespace.isNotFound(old)) {
  console.log("removed:", old);
}

rmMustExist(object, path)

Removes the value at path. Throws if absent. Returns the removed value.

const token = namespace.rmMustExist(ctx, "pending.token");

Path algebra: namespace.path

Pure string operations — no tree argument.

namespace.path.join("users", userId, "entries");
// "users.alice.entries"

namespace.path.join("a.b", ["c", "d"]);
// "a.b.c.d"

namespace.path.joinSlash("api", "v2", "users");
// "api/v2/users"

namespace.path.split("a.b.c");
// ["a", "b", "c"]

namespace.path.isRootOf("users.alice", "users.alice.entries");
// true

namespace.path.tween("a.b.c");
// "a.children.b.children.c"

namespace.path.tween("a.b.c", "items");
// "a.items.b.items.c"

Batch operations: namespace.batch

Multi-path contracts in one call.

// Destructure from tree — throws if any path absent
const { db, port } = namespace.batch.destructureMustExist(config, {
  db:   "connections.database.url",
  port: "server.port",
});

// Assert multiple paths exist — keyed by dotted path
const vals = namespace.batch.allMustExist(config, [
  "auth.secret",
  "auth.issuer",
]);

// Extract: assert exists, delete from tree, return value
const token = namespace.batch.extractMustExist(ctx, "pending.token");

Immutability: namespace.freeze

Object.freeze is one level deep. These walk the whole reachable tree.

// Freeze in place — same reference back, everything below it frozen
const config = namespace.freeze.deep(loadConfig());
config.server.port = 9090;              // TypeError in strict mode

// Freeze one point in a tree that stays otherwise open
namespace.freeze.at(state, "constants");        // subtree
namespace.freeze.at(state, "config.port");      // primitive leaf → property locked

// Frozen copy — the original stays mutable
const snapshot = namespace.freeze.clone(state);
state.a.b = 2;                          // snapshot.a.b unchanged

// Mutable copy back out
const draft = namespace.freeze.thaw(snapshot);

namespace.freeze.isDeep(snapshot);      // true

Traversal rules, shared by deep / at / isDeep:

  • Cycle-safe — a node already visited is not revisited
  • Getters are never invoked — accessor properties are skipped, only data properties are descended
  • Symbol and non-enumerable keys included — Reflect.ownKeys
  • Functions left alone unless { freezeFunctions: true }
  • Map/Set contents are not frozen — freezing the container does not stop .set()/.add(); JS offers no way to make those immutable in place

freeze.clone / freeze.thaw copy plain objects and arrays; Date, Map, Set, and class instances are carried across by reference.

Configuration

// Append object JSON to error messages (truncated at 200 chars)
namespace.configure({ errorContext: true });

namespace.getMustExist({}, "missing");
// Error: namespace.getMustExist: property not found at "missing"
//   object: {}

Design philosophy

  1. The verb name is the contract — setNotExists means create-only; setOverwrite means you intend to clobber
  2. NotFound is not undefined — the sentinel distinguishes absence from a stored undefined
  3. Fail fast — MustExist verbs throw with the exact path, not a silent undefined
  4. Safety by default — reaching for the destructive verb requires the longer name
  5. Functions applied to data, never resident in it — the tree is always plain {}
  6. Zero dependencies

See METALAND/ for the full applied philosophy.

License

MIT