Skip to main content
The @temelj/value package provides utilities for checking value types, deep equality comparison, and record/object manipulation in TypeScript.

Installation

npm install @temelj/value

Type checking

isValuePrimitive

Checks if a value is a JavaScript primitive.
function isValuePrimitive(value: unknown): value is Primitive
import { isValuePrimitive } from "@temelj/value";

console.log(isValuePrimitive("hello")); // true
console.log(isValuePrimitive(42)); // true
console.log(isValuePrimitive(true)); // true
console.log(isValuePrimitive(null)); // true
console.log(isValuePrimitive(undefined)); // true
console.log(isValuePrimitive(Symbol("id"))); // true
console.log(isValuePrimitive(BigInt(123))); // true

console.log(isValuePrimitive({})); // false
console.log(isValuePrimitive([])); // false
console.log(isValuePrimitive(new Date())); // false

isObjectPrimitive

Checks if a value is a plain object (created with {} or Object.create(null)).
function isObjectPrimitive(obj: unknown): obj is PrimitiveObject
import { isObjectPrimitive } from "@temelj/value";

console.log(isObjectPrimitive({})); // true
console.log(isObjectPrimitive({ a: 1 })); // true
console.log(isObjectPrimitive(Object.create(null))); // true

console.log(isObjectPrimitive([])); // false
console.log(isObjectPrimitive(new Date())); // false
console.log(isObjectPrimitive(new Map())); // false
console.log(isObjectPrimitive("string")); // false

isObjectDeepPrimitive

Checks if a value is a plain object with only primitive values (recursively).
function isObjectDeepPrimitive(obj: unknown): obj is PrimitiveObject
import { isObjectDeepPrimitive } from "@temelj/value";

console.log(isObjectDeepPrimitive({ a: 1, b: "text" })); // true
console.log(isObjectDeepPrimitive({ a: 1, b: { c: 2 } })); // true

console.log(isObjectDeepPrimitive({ a: new Date() })); // false
console.log(isObjectDeepPrimitive({ a: [1, 2] })); // false

isPrimitiveValue

Checks if a value is either a primitive or a deeply primitive object.
function isPrimitiveValue(value: unknown): value is PrimitiveValue
import { isPrimitiveValue } from "@temelj/value";

console.log(isPrimitiveValue(42)); // true
console.log(isPrimitiveValue("hello")); // true
console.log(isPrimitiveValue({ a: 1 })); // true
console.log(isPrimitiveValue([1, 2, 3])); // true

console.log(isPrimitiveValue(new Date())); // false
console.log(isPrimitiveValue({ a: new Map() })); // false

Value operations

deepEquals

Compares two values for deep equality.
function deepEquals(a: unknown, b: unknown): boolean
import { deepEquals } from "@temelj/value";

console.log(deepEquals(42, 42)); // true
console.log(deepEquals("hello", "hello")); // true
console.log(deepEquals(null, null)); // true
console.log(deepEquals(42, "42")); // false
deepEquals uses the react-fast-compare library internally, which provides efficient deep comparison while handling edge cases correctly.

primitivize

Converts a value to a primitive value. Maps and Sets are converted to objects and arrays respectively.
function primitivize(value: unknown): PrimitiveValue
import { primitivize } from "@temelj/value";

const map = new Map([
  ["name", "Alice"],
  ["age", 30],
]);

const result = primitivize(map);
// Result: { "name": "Alice", "age": 30 }
primitivize throws an error if it encounters a non-primitive value that cannot be converted (like class instances, functions, etc.).

Record operations

recordEquals

Compares two records for equality.
function recordEquals<V>(
  a: Record<string, V>,
  b: Record<string, V>,
  compare?: (a: V, b: V) => boolean,
): boolean
import { recordEquals } from "@temelj/value";

const a = { x: 1, y: 2 };
const b = { x: 1, y: 2 };
const c = { x: 1, y: 3 };

console.log(recordEquals(a, b)); // true
console.log(recordEquals(a, c)); // false

recordIsEmpty

Checks if a record has no keys.
function recordIsEmpty<V>(value: Record<string, V>): boolean
import { recordIsEmpty } from "@temelj/value";

console.log(recordIsEmpty({})); // true
console.log(recordIsEmpty({ a: 1 })); // false

recordMerge

Merges multiple records into a single record using deep merge.
function recordMerge<T>(
  values: Partial<T>[],
  options?: RecordMergeOptions,
): T
interface RecordMergeOptions {
  clone?: boolean;
  arrayMerge?: <T, S>(target: T[], source: S[]) => (T & S)[];
  isMergable?: (value: unknown) => boolean;
}
import { recordMerge } from "@temelj/value";

const defaults = { port: 3000, host: "localhost" };
const config = { port: 8080 };

const merged = recordMerge([defaults, config]);
// Result: { port: 8080, host: "localhost" }
recordMerge uses the deepmerge library internally, providing flexible deep merging with customizable behavior.

Type definitions

Primitive

A JavaScript value that is not an object and has no methods or properties.
type Primitive =
  | string
  | boolean
  | number
  | bigint
  | symbol
  | null
  | undefined

PrimitiveObject

A plain JavaScript object with primitive values.
type PrimitiveObject = { [key: string]: PrimitiveValue }

PrimitiveValue

A value that is either a primitive, a primitive object, or an array of primitive values.
type PrimitiveValue =
  | Primitive
  | PrimitiveObject
  | Array<PrimitiveValue>

JsonValue

A valid JSON value.
type JsonValue =
  | string
  | number
  | boolean
  | null
  | JsonObject
  | Array<JsonValue>

type JsonObject = { [key: string]: JsonValue }

Constructor

A constructor function type.
type Constructor<T> = new (...args: unknown[]) => T

Practical examples

Configuration merging

import { recordMerge } from "@temelj/value";

interface AppConfig {
  server: {
    port: number;
    host: string;
  };
  database: {
    url: string;
    poolSize: number;
  };
  features: {
    auth: boolean;
    logging: boolean;
  };
}

const defaultConfig: AppConfig = {
  server: { port: 3000, host: "localhost" },
  database: { url: "postgresql://localhost", poolSize: 10 },
  features: { auth: true, logging: false },
};

const envConfig: Partial<AppConfig> = {
  server: { port: parseInt(process.env.PORT || "8080") },
  features: { logging: true },
};

const finalConfig = recordMerge<AppConfig>([defaultConfig, envConfig]);

Comparing complex objects

import { deepEquals } from "@temelj/value";

function hasUserChanged(oldUser: User, newUser: User): boolean {
  return !deepEquals(oldUser, newUser);
}

const user1 = {
  id: 1,
  profile: { name: "Alice", preferences: { theme: "dark" } },
  permissions: ["read", "write"],
};

const user2 = {
  id: 1,
  profile: { name: "Alice", preferences: { theme: "light" } },
  permissions: ["read", "write"],
};

if (hasUserChanged(user1, user2)) {
  console.log("User has been modified");
}

Type-safe value validation

import { isPrimitiveValue, isObjectPrimitive } from "@temelj/value";

function validateApiResponse(data: unknown): boolean {
  if (!isObjectPrimitive(data)) {
    console.error("Response is not a plain object");
    return false;
  }

  for (const [key, value] of Object.entries(data)) {
    if (!isPrimitiveValue(value)) {
      console.error(`Invalid value for key "${key}": not serializable`);
      return false;
    }
  }

  return true;
}

Serialization helper

import { primitivize } from "@temelj/value";

function serializeForStorage(data: unknown): string {
  const primitive = primitivize(data);
  return JSON.stringify(primitive);
}

const data = {
  users: new Map([
    ["1", { name: "Alice", roles: new Set(["admin", "user"]) }],
    ["2", { name: "Bob", roles: new Set(["user"]) }],
  ]),
};

const json = serializeForStorage(data);
// JSON string with Maps and Sets converted to objects/arrays
The @temelj/value package is particularly useful when working with configuration objects, API responses, and state management where deep comparison and merging are common operations.

Build docs developers (and LLMs) love