@rcompat/dict
v0.3.0
Published
Standard library dictionaries
Readme
@rcompat/dict
Dictionaries for JavaScript runtimes.
What is @rcompat/dict?
A cross-runtime module providing utilities for working with dictionaries (plain objects). Includes chainable entry manipulation, deep merging, path-based access, and more. Works consistently across Node, Deno, and Bun.
Installation
npm install @rcompat/dictpnpm add @rcompat/dictyarn add @rcompat/dictbun add @rcompat/dictUsage
empty
Checks if an object has no keys.
import dict from "@rcompat/dict";
dict.empty({}); // true
dict.empty({ a: 1 }); // false
dict.empty({ a: undefined }); // false (key exists)entries
Chainable wrapper around Object.entries with filter, map, and transform
methods.
import entries from "@rcompat/dict/entries";
const obj = { a: 1, b: 2, c: 3 };
// filter entries
entries(obj)
.filter(([key, value]) => value > 1)
.get(); // { b: 2, c: 3 }
// map values
entries(obj)
.valmap(([key, value]) => value * 2)
.get(); // { a: 2, b: 4, c: 6 }
// map keys
entries(obj)
.keymap(([key]) => key.toUpperCase())
.get(); // { A: 1, B: 2, C: 3 }
// chain operations
entries(obj)
.filter(([, v]) => v > 1)
.valmap(([, v]) => v * 10)
.get(); // { b: 20, c: 30 }
// iterate
for (const [key, value] of entries(obj)) {
console.log(key, value);
}exclude
Creates a new object excluding specified keys.
import dict from "@rcompat/dict";
const user = { id: 1, name: "Bob", password: "secret" };
dict.exclude(user, ["password"]);
// { id: 1, name: "Bob" }
dict.exclude(user, ["id", "password"]);
// { name: "Bob" }get
Gets a nested value using dot notation.
import dict from "@rcompat/dict";
const data = {
user: {
profile: {
name: "Bob",
},
},
};
dict.get(data, "user.profile.name"); // "Bob"
dict.get(data, "user.profile"); // { name: "Bob" }
dict.get(data, "user.missing"); // undefinedinflate
Creates a nested object from a dot-notation path.
import dict from "@rcompat/dict";
dict.inflate("a.b.c");
// { a: { b: { c: {} } } }
dict.inflate("a.b.c", "value");
// { a: { b: { c: "value" } } }
dict.inflate("a/b/c", "value", "/");
// { a: { b: { c: "value" } } }override
Deep merges objects recursively. Values from the second object override the first.
import dict from "@rcompat/dict";
dict.override({ a: 1 }, { b: 2 });
// { a: 1, b: 2 }
dict.override({ a: 1 }, { a: 2 });
// { a: 2 }
// Deep merge
dict.override(
{ user: { name: "Bob", age: 30 } },
{ user: { age: 31 } }
);
// { user: { name: "Bob", age: 31 } }proper
Type guard that checks if a value is a non-null object.
import dict from "@rcompat/dict";
dict.proper({}); // true
dict.proper([]); // true
dict.proper(null); // false
dict.proper(undefined); // false
dict.proper("string"); // falsenullproto
Creates objects with null prototype (no inherited properties like toString).
import dict from "@rcompat/dict";
// Empty null-prototype object
const empty = dict.nullproto();
empty.toString; // undefined (no inherited methods)
// With initial values
const obj = dict.nullproto({ a: 1, b: 2 });
obj.a; // 1
obj.toString; // undefined
// Frozen (immutable)
const frozen = dict.nullproto({ a: 1 }, { frozen: true });
frozen.a = 2; // TypeError in strict modetoQueryString
Converts an object to a URL query string.
import dict from "@rcompat/dict";
dict.toQueryString({ page: "1", sort: "name" });
// "page=1&sort=name"
dict.toQueryString({ q: "hello", limit: "10" });
// "q=hello&limit=10"API Reference
empty(object)
declare function empty(object: object): boolean;Returns true if the object has no own keys.
entries(dict)
declare function entries<K extends string, V>(dict: dict<K, V>): Entries<K, V>;
interface Entries<K, V> {
filter(predicate: (entry: [K, V]) => boolean): Entries<K, V>;
map<U>(mapper: (entry: [K, V]) => [K, U]): Entries<K, U>;
keymap(mapper: (entry: [K, V]) => K): Entries<K, V>;
valmap<U>(mapper: (entry: [K, V]) => U): Entries<K, U>;
get(): dict<K, V>;
[Symbol.iterator](): IterableIterator<[K, V]>;
}exclude(object, keys)
declare function exclude<T extends object, K extends keyof T>(
object: T,
keys: readonly K[]
): Omit<T, K>;get(object, path)
declare function get<T extends object, P extends string>(
object: T,
path: P
): unknown;inflate(path, initial?, separator?)
declare function inflate<P extends string, T>(
path: P,
initial?: T,
separator?: string
): object;override(base, over)
declare function override<T extends object, U extends object>(
base: T,
over: U
): T & U;proper(value)
declare function proper(value: unknown): value is NonNullable<object>;nullproto(init?, options?)
declare function nullproto<T extends object>(
init?: T,
options?: { frozen?: boolean }
): T;toQueryString(dict)
declare function toQueryString(dict: dict<string, string>): string;Examples
Sanitize API response
import dict from "@rcompat/dict";
function sanitizeUser(user) {
return dict.exclude(user, ["password", "ssn", "creditCard"]);
}
function transformResponse(data) {
return dict.entries(data)
.filter(([key]) => !key.startsWith("_"))
.valmap(([, value]) => value ?? "N/A")
.get();
}Configuration with defaults
import dict from "@rcompat/dict";
const defaults = {
host: "localhost",
port: 3000,
db: {
host: "localhost",
port: 5432,
},
};
function createConfig(userConfig) {
return dict.override(defaults, userConfig);
}
createConfig({ port: 8080, db: { port: 5433 } });
// { host: "localhost", port: 8080, db: { host: "localhost", port: 5433 } }Build URL with query params
import dict from "@rcompat/dict";
function buildUrl(base, params) {
const query = dict.toQueryString(params);
return query ? `${base}?${query}` : base;
}
buildUrl("/api/users", { page: "1", limit: "10" });
// "/api/users?page=1&limit=10"Safe configuration objects
import dict from "@rcompat/dict";
// Prevent prototype pollution
const config = dict.nullproto({
apiKey: "secret",
endpoint: "https://api.example.com",
}, { frozen: true });
// Safe from prototype attacks
config.__proto__; // undefined
config.constructor; // undefinedCross-Runtime Compatibility
| Runtime | Supported | |---------|-----------| | Node.js | ✓ | | Deno | ✓ | | Bun | ✓ |
No configuration required — just import and use.
License
MIT
Contributing
See CONTRIBUTING.md in the repository root.
