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

@safeaccess/inline

v0.1.5

Published

Safe nested data access with dot notation - JavaScript/TypeScript

Readme


TypeScript/JavaScript library for safe nested data access with security validation on by default — JSON, YAML, XML, INI, ENV, NDJSON, arrays and objects. Includes a full PathQuery engine with filters, wildcards, slices, and projections. ESM, zero production dependencies.

The problem

Reading nested data from external sources requires more than optional chaining. You also need to defend against prototype pollution in JSON, URI injection, Node.js global shadowing, and payload size attacks. Without a tool for this, that validation is boilerplate you write manually for every format and every endpoint.

Without this library (JSON from an external API):

const parsed = JSON.parse(input); // no depth limit, no key validation
// check for __proto__, constructor, prototype manually...
// enforce maxDepth, maxKeys...
// check for javascript:, blob:, node: URIs...
const host = parsed?.database?.host ?? null;

With this library:

const host = Inline.fromJson(input).get('database.host');
// prototype pollution blocked, forbidden keys validated, depth enforced — by default

Installation

npm install @safeaccess/inline

Requirements: Node.js 22+

Quick start

import { Inline } from '@safeaccess/inline';

const accessor = Inline.fromJson('{"user": {"name": "Alice", "age": 30}}');

accessor.get('user.name'); // 'Alice'
accessor.get('user.email', 'N/A'); // 'N/A' (default when missing)
accessor.has('user.age'); // true
accessor.getOrFail('user.name'); // 'Alice' (throws if missing)

// Immutable writes - original is never modified
const updated = accessor.set('user.email', '[email protected]');
updated.get('user.email'); // '[email protected]'
accessor.has('user.email'); // false (original unchanged)

Security

All public entry points validate input by default. Every key passes through SecurityGuard and SecurityParser.

What gets blocked

| Category | Examples | Reason | | ----------------------------- | ------------------------------------------------------------------------------------------------------------ | --------------------------------------------------- | | Prototype pollution | __proto__, constructor, prototype | Prevents prototype pollution attacks | | Legacy prototype manipulation | __defineGetter__, __defineSetter__, __lookupGetter__, __lookupSetter__ | Prevents legacy prototype tampering | | Property shadow | hasOwnProperty | Overriding it can bypass guard checks | | Node.js globals | __dirname, __filename | Prevents path-injection via dynamic property access | | Protocol / stream URIs | javascript:, blob:, ws://, wss://, node:, file://, http://, https://, ftp://, data:, ... | Prevents URI injection and XSS vectors |

Format-specific protections

| Format | Protection | | ------ | ------------------------------------------------ | | XML | Rejects <!DOCTYPE — prevents XXE attacks | | YAML | Blocks unsafe tags, anchors, aliases, merge keys | | All | Forbidden key validation on every parsed key |

Structural limits

| Limit | Default | Description | | ------------------------ | ------- | ------------------------------------- | | maxPayloadBytes | 10 MB | Maximum raw string input size | | maxKeys | 10,000 | Maximum total key count | | maxDepth | 512 | Maximum structural nesting depth | | maxResolveDepth | 100 | Maximum recursion for path resolution | | maxCountRecursiveDepth | 100 | Maximum recursion when counting keys |

Custom forbidden keys

const guard = new SecurityGuard(512, ['secret', 'internal_token']);
const accessor = Inline.withSecurityGuard(guard).fromJson(data);

Disabling validation for trusted input

const accessor = Inline.withStrictMode(false).fromJson(trustedInput);

Warning: Disabling strict mode skips all validation. Only use with application-controlled input.

For vulnerability reports, see SECURITY.md.

Dot notation syntax

Basic syntax

| Syntax | Example | Description | | ----------------- | ------------------ | ------------------------------- | | key.key | user.name | Nested key access | | key.0.key | users.0.name | Numeric key (array index) | | key\.with\.dots | config\.db\.host | Escaped dots in key names | | $ or $.path | $.user.name | Optional root prefix (stripped) |

const data = Inline.fromJson('{"users": [{"name": "Alice"}, {"name": "Bob"}]}');
data.get('users.0.name'); // 'Alice'
data.get('users.1.name'); // 'Bob'

Advanced PathQuery

| Syntax | Example | Description | | --------------- | ------------------- | ----------------------------------------- | | [0] | users[0] | Bracket index access | | * or [*] | users.* | Wildcard — expand all children | | ..key | ..name | Recursive descent — find key at any depth | | ..['a','b'] | ..['name','age'] | Multi-key recursive descent | | [0,1,2] | users[0,1,2] | Multi-index selection | | ['a','b'] | ['name','age'] | Multi-key selection | | [0:5] | items[0:5] | Slice — indices 0 through 4 | | [::2] | items[::2] | Slice with step | | [::-1] | items[::-1] | Reverse slice | | [?expr] | users[?age>18] | Filter predicate expression | | .{fields} | .{name, age} | Projection — select fields | | .{alias: src} | .{fullName: name} | Aliased projection |

Filter expressions

const data = Inline.fromJson(`[
    {"name": "Alice", "age": 25, "role": "admin"},
    {"name": "Bob",   "age": 17, "role": "user"},
    {"name": "Carol", "age": 30, "role": "admin"}
]`);

// Comparison: ==, !=, >, <, >=, <=
data.get('[?age>18]'); // Alice and Carol

// Logical: && and ||
data.get('[?age>18 && role=="admin"]'); // Alice and Carol

// Built-in functions: starts_with, contains, values
data.get('[?starts_with(@.name, "A")]'); // Alice
data.get('[?contains(@.name, "ob")]'); // Bob

// Arithmetic: +, -, *, /
const orders = Inline.fromJson('[{"price": 10, "qty": 5}, {"price": 3, "qty": 2}]');
orders.get('[[email protected] * @.qty > 20]'); // first order only

Supported formats

const accessor = Inline.fromJson('{"users": [{"name": "Alice"}, {"name": "Bob"}]}');
accessor.get('users.0.name'); // 'Alice'
const yaml = `database:
  host: localhost
  port: 5432
  credentials:
    user: admin`;

const accessor = Inline.fromYaml(yaml);
accessor.get('database.credentials.user'); // 'admin'
const accessor = Inline.fromXml('<config><database><host>localhost</host></database></config>');
accessor.get('database.host'); // 'localhost'
const accessor = Inline.fromIni('[database]\nhost=localhost\nport=5432');
accessor.get('database.host'); // 'localhost'
const accessor = Inline.fromEnv('APP_NAME=MyApp\nAPP_DEBUG=true\nDB_HOST=localhost');
accessor.get('DB_HOST'); // 'localhost'

Each line is parsed as an independent JSON object and indexed from 0 by its position in the input. Blank lines and trailing newlines are skipped. Security validation is applied to each line individually.

const accessor = Inline.fromNdjson('{"id":1,"name":"Alice"}\n{"id":2,"name":"Bob"}');
accessor.get('0.name'); // 'Alice'
accessor.get('1.name'); // 'Bob'
const accessor = Inline.fromArray({ users: [{ name: 'Alice' }, { name: 'Bob' }] });
accessor.get('users.0.name'); // 'Alice'

const objAccessor = Inline.fromObject({ name: 'Alice', age: 30 });
objAccessor.get('name'); // 'Alice'
import { Inline } from '@safeaccess/inline';
import type { ParseIntegrationInterface } from '@safeaccess/inline';

// Requires implementing ParseIntegrationInterface
const accessor = Inline.withParserIntegration(new MyCsvIntegration()).fromAny(csvString);
accessor.get('0.column_name');
import { Inline, TypeFormat } from '@safeaccess/inline';

const accessor = Inline.from(TypeFormat.Json, '{"key": "value"}');
accessor.get('key'); // 'value'

Reading & writing

const accessor = Inline.fromJson('{"a": {"b": 1, "c": 2}}');

// Read
accessor.get('a.b'); // 1
accessor.get('a.missing', 'default'); // 'default'
accessor.getOrFail('a.b'); // 1 (throws PathNotFoundException if missing)
accessor.has('a.b'); // true
accessor.all(); // { a: { b: 1, c: 2 } }
accessor.count(); // 1 (root keys)
accessor.count('a'); // 2 (keys under 'a')
accessor.keys(); // ['a']
accessor.keys('a'); // ['b', 'c']
accessor.getMany({
    'a.b': null,
    'a.x': 'fallback',
}); // { 'a.b': 1, 'a.x': 'fallback' }
accessor.getRaw(); // original JSON string

// Write (immutable - every write returns a new instance)
const updated = accessor.set('a.d', 3).remove('a.c').merge('a', { e: 4 }).mergeAll({ f: 5 });
updated.all(); // { a: { b: 1, d: 3, e: 4 }, f: 5 }

// Readonly mode - block all writes
const readonly = accessor.readonly();
readonly.get('a.b'); // 1 (reads work)
readonly.set('a.b', 99); // throws ReadonlyViolationException

Configure

Builder pattern

import { Inline, SecurityGuard, SecurityParser } from '@safeaccess/inline';

const accessor = Inline.withSecurityGuard(new SecurityGuard(512, ['secret']))
    .withSecurityParser(new SecurityParser({ maxDepth: 5 }))
    .withStrictMode(true)
    .fromJson(untrustedInput);

Builder methods

| Method | Description | | ------------------------------------ | ------------------------------------------------ | | withSecurityGuard(guard) | Custom forbidden-key rules and depth limits | | withSecurityParser(parser) | Custom payload size and structural limits | | withPathCache(cache) | Path segment cache for repeated lookups | | withParserIntegration(integration) | Custom format parser for fromAny() | | withStrictMode(false) | Disable security validation (trusted input only) |

Error handling

All exceptions extend AccessorException:

import {
    Inline,
    AccessorException,
    InvalidFormatException,
    SecurityException,
    PathNotFoundException,
    ReadonlyViolationException,
} from '@safeaccess/inline';

try {
    const accessor = Inline.fromJson(untrustedInput);
    const value = accessor.getOrFail('config.key');
} catch (e) {
    if (e instanceof InvalidFormatException) {
        /* malformed JSON, XML, INI, or NDJSON */
    }
    if (e instanceof SecurityException) {
        /* forbidden key, payload too large, depth exceeded */
    }
    if (e instanceof PathNotFoundException) {
        /* path does not exist */
    }
    if (e instanceof ReadonlyViolationException) {
        /* write on readonly accessor */
    }
    if (e instanceof AccessorException) {
        /* catch-all for any library error */
    }
}

Exception hierarchy

| Exception | Extends | When | | ---------------------------- | ------------------------ | ----------------------------------------- | | AccessorException | Error | Root — catch-all | | SecurityException | AccessorException | Forbidden key, payload, structural limits | | InvalidFormatException | AccessorException | Malformed JSON, XML, INI, NDJSON | | YamlParseException | InvalidFormatException | Unsafe or malformed YAML | | PathNotFoundException | AccessorException | getOrFail() on missing path | | ReadonlyViolationException | AccessorException | Write on readonly accessor | | UnsupportedTypeException | AccessorException | Unknown accessor class in make() | | ParserException | AccessorException | Internal parser errors |

Advanced usage

Strict mode

// Disable all security validation for trusted input
const accessor = Inline.withStrictMode(false).fromJson(trustedPayload);

Warning: Disabling strict mode skips all validation. Only use with application-controlled input.

Path cache

// Implement PathCacheInterface for repeated lookups
const cacheMap = new Map();
const cache: PathCacheInterface = {
    get: (path) => cacheMap.get(path) ?? null,
    set: (path, segments) => {
        cacheMap.set(path, segments);
    },
    has: (path) => cacheMap.has(path),
    clear: () => {
        cacheMap.clear();
        return cache;
    },
};

const accessor = Inline.withPathCache(cache).fromJson(data);
accessor.get('deeply.nested.path'); // parses path
accessor.get('deeply.nested.path'); // cache hit

Custom format integration

// Implement ParseIntegrationInterface for custom formats
const csvIntegration: ParseIntegrationInterface = {
    assertFormat: (raw: unknown) => typeof raw === 'string' && raw.includes(','),
    parse: (raw: unknown) => {
        // Parse CSV to object
        return parsed;
    },
};

const accessor = Inline.withParserIntegration(csvIntegration).fromAny(csvString);

API reference

Inline facade

Static factory methods

| Method | Input | Returns | | ----------------------------- | ---------------------------------------- | -------------------- | | fromArray(data) | Record<string, unknown> or unknown[] | ArrayAccessor | | fromObject(data) | object | ObjectAccessor | | fromJson(data) | JSON string | JsonAccessor | | fromXml(data) | XML string | XmlAccessor | | fromYaml(data) | YAML string | YamlAccessor | | fromIni(data) | INI string | IniAccessor | | fromEnv(data) | dotenv string | EnvAccessor | | fromNdjson(data) | NDJSON string | NdjsonAccessor | | fromAny(data, integration?) | unknown | AnyAccessor | | from(typeFormat, data) | TypeFormat enum | AccessorsInterface | | make(AccessorClass, data) | Accessor constructor | AbstractAccessor |

Accessor read methods

| Method | Returns | | --------------------------- | --------------------------------------- | | get(path, default?) | Value at path, or default | | getOrFail(path) | Value or throws PathNotFoundException | | getAt(segments, default?) | Value at key segments | | has(path) | boolean | | hasAt(segments) | boolean | | getMany(paths) | Record<string, unknown> | | all() | Record<string, unknown> | | count(path?) | number | | keys(path?) | string[] | | getRaw() | unknown |

Accessor write methods (immutable)

| Method | Description | | ------------------------ | ---------------------- | | set(path, value) | Set at path | | setAt(segments, value) | Set at key segments | | remove(path) | Remove at path | | removeAt(segments) | Remove at key segments | | merge(path, value) | Deep-merge at path | | mergeAll(value) | Deep-merge at root |

Modifier methods

| Method | Description | | ----------------- | -------------------------- | | readonly(flag?) | Block all writes | | strict(flag?) | Toggle security validation |

TypeFormat enum

Array · Object · Json · Xml · Yaml · Ini · Env · Ndjson · Any

Exports

The package uses named exports only (no default exports). All public types are available from the main entry point:

import {
    Inline,
    TypeFormat,
    SecurityGuard,
    SecurityParser,
    // Accessors
    AbstractAccessor,
    ArrayAccessor,
    ObjectAccessor,
    JsonAccessor,
    XmlAccessor,
    YamlAccessor,
    IniAccessor,
    EnvAccessor,
    NdjsonAccessor,
    AnyAccessor,
    // Exceptions
    AccessorException,
    SecurityException,
    InvalidFormatException,
    YamlParseException,
    ParserException,
    PathNotFoundException,
    ReadonlyViolationException,
    UnsupportedTypeException,
} from '@safeaccess/inline';

// Contracts (type-only imports)
import type {
    AccessorsInterface,
    ReadableAccessorsInterface,
    WritableAccessorsInterface,
    FactoryAccessorsInterface,
    SecurityGuardInterface,
    SecurityParserInterface,
    PathCacheInterface,
    ParseIntegrationInterface,
} from '@safeaccess/inline';

Contributing

See CONTRIBUTING.md for development setup, commit conventions, and pull request guidelines.

License

MIT © Felipe Sauer