
Security News
Socket Releases Free Certified Patches for Critical vm2 Sandbox Escape
A critical vm2 sandbox escape can allow untrusted JavaScript to break isolation and execute commands on the host Node.js process.
@nan0web/db
Advanced tools
Every data becomes a database. Agnostic document database and data manipulation utilities.
Agnostic document database and data manipulation utilities. Designed to be flexible, minimal and powerful β the tool that supports any data format and nested hierarchy with reference resolution, inheritance and global variables.
Inspired by zero-is-not-a-number rule of nan0web:
Every data becomes a database.
Based on real use-cases, supports:
mount() composes multiple storage backends into one tree. Supports Root Mount (prefix: '') for transparent catchment of all relative paths.attach() provides failover with transparent notificationsSee how it works in playground.
DB is a VFS Router. Mount different storage backends, attach fallbacks, hydrate models:
[App] β db.fetch('/media/logo.png') β [S3 Driver]
db.fetch('/cache/user_1') β [Redis Driver]
db.fetch('/play/demo-app') β [FS Driver] β Document instance
How to install with npm?
npm install @nan0web/db
How to install with pnpm?
pnpm add @nan0web/db
How to install with yarn?
yarn add @nan0web/db
You can mount a database at an empty prefix (''). This instance will catch
all relative paths that don't match more specific mount points. Extremely
useful for isolated playground environments.
How to mount a database as virtual root?
import DB from "@nan0web/db"
const rootDB = new DB()
const targetDB = new DB({ data: new Map([['doc.json', { ok: true }]]) })
rootDB.mount('', targetDB)
// Accessing relative path transparently routes to targetDB
.nan0 Data ExtensionSince v1.4.4, .nan0 is a first-class citizen alongside .json. It is
automatically recognized as a data-containing file.
How to use native .nan0 data extension?
import DB from "@nan0web/db"
const db = new DB({ data: new Map([['vault.nan0', { secret: 42 }]]) })
const result = await db.get('vault.nan0')
console.info(result) // β { secret: 42 }
How to load Data document?
import DB from "@nan0web/db"
const db = new DB()
const doc = await db.loadDocumentAs('.json', 'doc', { key: 'value' })
console.info(doc) // β { key: "value" }
.jsonl, .csv, and .csv0 formats safely stream line-by-line handling
chunk fragmentation natively via the standard Driver Protocol.
How to stream lines from data files?
// Example driver implementation
class MockDriver extends DBDriverProtocol {
async stream(uri) {
return (async function* () {
yield '{"uid":1}'
yield '{"uid":2}'
yield '{"uid":3}'
})()
}
}
// Attach your custom or imported driver
const db = new DB({ driver: new MockDriver() })
const stream = await db.stream('demo.jsonl')
const lines = []
for await (const line of stream) {
lines.push(line)
}
console.info(lines.length) // β 3
console.info(lines) // β [ '{"uid":1}', '{"uid":2}', '{"uid":3}' ]
get() with default fallbackHow to get or return default?
import DB from "@nan0web/db"
const db = new DB()
const result = await db.get('missing-file.json', { defaultValue: {} })
console.info(result) // β {}
How to get specific document?
import DB from "@nan0web/db"
const db = new DB({ data: new Map([['file.txt', 'text']]) })
const result = await db.get('file.txt')
console.info(result) // β "text"
How to use document reference system?
import DB from "@nan0web/db"
const db = new DB({
data: new Map([
['_/index.json', { global: 'value' }],
['data.json', { $ref: '_/index.json', key: 'val' }],
]),
})
await db.connect()
const res = await db.fetch('data.json')
console.info(res) // β { global: "value", key: "val" }
CLI sandbox for safe experiments:
git clone https://github.com/nan0web/db.git
cd db
npm install
npm run play
The heart of the package includes core tools to manage hierarchical data structures.
db.get(uri, GetOpts)Loads/returns document content from its URI.
Parameters
uri (string) β Document URI.GetOpts.defaultValue (any) β fallback if doc not foundReturns
How to get document value?
import DB from "@nan0web/db"
const db = new DB({ data: new Map([['x.file', 'hello']]) })
const result = await db.get('x.file')
console.info(result) // β "hello"
db.fetch(uri, FetchOptions)Like get, plus advanced features: refs, vars, inherit rules processing.
Supports extension lookup, e.g. find .json even when omitted.
How to load extended data?
import DB from "@nan0web/db"
const db = new DB({ predefined: [['file.json', { value: 'loaded' }]] })
await db.connect()
const result = await db.fetch('file')
console.info(result) // β { value: "loaded" }
db.set(uri, data)Sets document content and marks metadata updates.
How to save new content?
import DB from "@nan0web/db"
const db = new DB()
const res = await db.set('file.text', 'save me!')
console.info(res) // β "save me!"
console.info(db.data.get('file.text')) // β "save me!"
Data.flatten(data)Flattens nested object into paths as keys.
How to flatten object?
import { Data } from "@nan0web/db"
const flat = Data.flatten({ x: { a: [1, 2, { b: 3 }] } })
console.info(flat) // β { 'x/a/[0]': 1, 'x/a/[1]': 2, 'x/a/[2]/b': 3 }
Data.unflatten(data)Reconstructs nested structure from flat keys.
How to unflatten data?
import { Data } from "@nan0web/db"
const nested = Data.unflatten({
'x/y/z': 7,
'arr/[0]/title': 'first',
'arr/[1]/title': 'second',
})
console.info(nested) // β { x: { y: { z: 7 } }, arr: [ { title: 'first' }, { title: 'second' } ] }
Since v1.4.2, keys containing the OBJECT_DIVIDER (default /) are automatically
escaped during flattening and restored during unflattening. This ensures that
i18n keys like "Manage / Update" are not incorrectly split into nested objects.
How to preserve literal slashes in keys?
import { Data } from "@nan0web/db"
const obj = { 'Manage / Update': 'ΠΠ΅ΡΡΠ²Π°Π½Π½Ρ' }
const flat = Data.flatten(obj)
// The slash is escaped to Unicode FRACTION SLASH 'β'
console.info(Object.keys(flat)[0]) // β "Manage β Update"
const unflat = Data.unflatten(flat)
console.info(unflat['Manage / Update']) // β "ΠΠ΅ΡΡΠ²Π°Π½Π½Ρ"
Data.merge(a, b)Deep merges two objects, handling array conflicts by replacing.
How to merge deeply?
import { Data } from "@nan0web/db"
const a = { x: { one: 1 }, arr: [0] }
const b = { y: 'two', x: { two: 2 }, arr: [1] }
const merged = Data.merge(a, b)
console.info(merged) // β { x: { one: 1, two: 2 }, y: 'two', arr: [ 1 ] }
Data.find(path, data)Finds value by string path or array path. Use array path to access keys containing /.
How to find value by path?
import { Data } from "@nan0web/db"
const data = { 'I/O': 'value', nested: { item: 1 } }
console.info(Data.find('nested/item', data)) // β 1
console.info(Data.find(['I/O'], data)) // β "value"
@nan0web/db/path provides URI/path resolution functions for cross-platform use.
Supports normalization, basename/dirname extraction, and absolute/relative resolution.
How to import path utilities?
import { normalize, basename, dirname, absolute, resolveSync } from '@nan0web/db/path'
console.info(normalize('a/b/../c')) // β a/c
console.info(basename('path/to/file.txt')) // β file.txt
console.info(dirname('path/to/file.txt')) // β path/to/
console.info(absolute('/base', 'root', 'file')) // β /base/root/file
console.info(resolveSync('/base', '.', 'file.txt')) // β file.txt
normalize(...segments)Normalizes path segments, handling ../, ./, and duplicate slashes.
How to normalize path segments?
import { normalize } from '@nan0web/db/path'
console.info(normalize('a/b/../c')) // β a/c
console.info(normalize('a//b///c')) // β a/b/c
console.info(normalize('dir/sub/')) // β dir/sub/
basename(uri, [suffix])Extracts basename, optionally removing suffix or extension.
How to extract basename?
import { basename } from '@nan0web/db/path'
console.info(basename('/dir/file.txt')) // β file.txt
console.info(basename('/dir/file.txt', '.txt')) // β file
console.info(basename('/dir/file.txt', true)) // β file (remove ext)
console.info(basename('/dir/')) // β dir/
dirname(uri)Extracts parent directory path.
How to extract dirname?
import { dirname } from '@nan0web/db/path'
console.info(dirname('/a/b/file')) // β /a/b/
console.info(dirname('/a/b/')) // β /a/
console.info(dirname('/file')) // β /
console.info(dirname('file.txt')) // β .
extname(uri)Extracts file extension with dot (lowercase). Since v1.5.3, correctly ignores
dots in directory names of absolute paths.
How to extract extension?
import { extname } from '@nan0web/db/path'
console.info(extname('file.TXT')) // β .txt
console.info(extname('/Users/user/src/nan.web/apps/t.json')) // β .json
console.info(extname('.gitignore')) // β ''
console.info(extname('/dir/')) // β ''
resolveSync(cwd, root, ...segments)Resolves segments relative to cwd/root (synchronous).
How to resolve path synchronously?
import { resolveSync } from '@nan0web/db/path'
console.info(resolveSync('/base', '.', 'a/b/../c')) // β a/c
relative(from, to)Computes relative path from from to to.
How to compute relative path?
import { relative } from '@nan0web/db/path'
console.info(relative('/a/b', '/a/c')) // β c
console.info(relative('/root/dir', '/root/')) // β dir
absolute(cwd, root, ...segments)Builds absolute path/URL from cwd, root, and segments.
How to build absolute path?
import { absolute } from '@nan0web/db/path'
console.info(absolute('/base', 'root', 'file')) // β /base/root/file
console.info(absolute('https://ex.com', 'api', 'v1')) // β https://ex.com/api/v1
isRemote(uri) & isAbsolute(uri)Checks if URI is remote or absolute.
How to check URI type?
import { isRemote, isAbsolute } from '@nan0web/db/path'
console.info(isRemote('https://ex.com')) // β true
console.info(isAbsolute('/abs/path')) // β true
console.info(isAbsolute('./rel')) // β false
Package is fully typed with jsdoc and d.ts.
How many d.ts files should cover the source?
Drivers extend DB with storage backends. Extend DBDriverProtocol for custom logic.
How to extend DBDriverProtocol?
import { DBDriverProtocol } from '@nan0web/db'
class MyDriver extends DBDriverProtocol {
async read(uri) {
// Custom read logic
return { data: 'from custom storage' }
}
}
const driver = new MyDriver()
console.log(await driver.read('/path')) // β { data: 'from custom storage' }
How to attach driver to DB?
import { DB, DBDriverProtocol } from '@nan0web/db'
class SimpleDriver extends DBDriverProtocol {
async read(uri) {
return `Read: ${uri}`
}
async write(uri, data) {
return true
}
}
class ExtendedDB extends DB {
constructor() {
super({ driver: new SimpleDriver() })
this.loadDocument = async (uri) => await this.driver.read(uri)
this.saveDocument = async (uri, data) => await this.driver.write(uri, data)
}
}
const db = new ExtendedDB()
await db.connect()
console.info(await db.get('/test')) // β Read: test
Use AuthContext for role-based access in DB operations.
How to create AuthContext?
import { AuthContext } from '@nan0web/db'
const ctx = new AuthContext({ role: 'user', roles: ['user', 'guest'] })
console.info(ctx.hasRole('user')) // β true
console.info(ctx.role) // β user
How to use AuthContext in DB?
import { DB, AuthContext } from '@nan0web/db'
const db = new DB()
const ctx = new AuthContext({ role: 'admin' })
await db.set('secure/file.txt', 'secret', ctx)
console.info(await db.get('secure/file.txt', {}, ctx)) // β secret
How to handle auth failures?
import { AuthContext } from '@nan0web/db'
const ctx = new AuthContext()
ctx.fail(new Error('Access denied'))
console.info(ctx.fails) // β [Error: Access denied]
console.info(ctx.hasRole('admin')) // β false
After mounting all databases, seal() locks the mount registry.
Any further mount() or unmount() calls will throw an error.
This prevents untrusted plugins from hijacking mount points at runtime.
How to seal mount registry?
import DB from '@nan0web/db'
const db = new DB()
const cache = new DB()
db.mount('cache', cache)
db.seal()
console.info(db.sealed) // β true
URIs starting with ~ or @ are reserved for mount points.
If accessed before mounting, DB throws a clear error with a hint:
How does DB handle unmounted reserved prefixes?
import DB from '@nan0web/db'
const db = new DB()
DBConfig and RevisionInfo provide standard data definitions.
DBConfigHow to securely serialize connection arguments?
import { DBConfig } from '@nan0web/db'
const config = new DBConfig('redis://yaro:pass123@redis.local:6379/cache')
console.info(config.protocol) // β redis
console.info(config.safeDsn) // β redis://yaro:***@redis.local:6379/cache
RevisionInfoHow to standardize document history?
import { RevisionInfo } from '@nan0web/db'
const ts = new Date('2026-04-06T00:00:00Z').toISOString()
const rev = new RevisionInfo({ sha: '1234567890abcdef', timestamp: ts })
console.info(rev.shortSha) // β 1234567
Directory.isConfig(path)Checks if a path represents a directory configuration file (_.yaml, _.nan0, _.json).
How to detect directory configuration file?
import { Directory } from '@nan0web/db'
console.info(Directory.isConfig('_.yaml')) // β true
console.info(Directory.isConfig('path/to/_.nan0')) // β true
console.info(Directory.isConfig('file.json')) // β false
How to participate? β see CONTRIBUTING.md
ISC LICENSE β see full text
FAQs
Every data becomes a database. Agnostic document database and data manipulation utilities.
We found that @nan0web/db demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago.Β It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
A critical vm2 sandbox escape can allow untrusted JavaScript to break isolation and execute commands on the host Node.js process.

Research
Five malicious NuGet packages impersonate Chinese .NET libraries to deploy a stealer targeting browser credentials, crypto wallets, SSH keys, and local files.

Security News
pnpm 11 turns on a 1-day Minimum Release Age and blocks exotic subdeps by default, adding safeguards against fast-moving supply chain attacks.