Skip to content

Operations.clone and Hash.value

Pure static utilities that work on any value without a schema.


Operations.clone

Declaration. Deep-copies a value using structuredClone. Returns an independent copy with no shared object references. Type-preserving - clone<T>(v: T): T.

Use this when you need an independent copy before passing a value to a mutating operation, or before Value.diff when you want to keep the original. instantiate() already clones internally; only call clone when doing your own mutation.

Don't use this when you just need a shallow copy (use { ...obj } instead). Don't use it for non-JSON-serializable values (functions, class instances with methods - structuredClone may throw or strip those).

Examples

Example 1: Clone an order before adding a line item

/**
 * Operations.clone / Hash.value — Example 1: Deep copy and deterministic hash
 * Demonstrates: clone independence, hash key-order invariance
 *
 * Order is the canonical Bastian-orders-Neverending-Story fixture. The
 * clone gets a second line item appended (a Walter Moers paperback)
 * without disturbing the original.
 */

import {
  Hash, Operations
} from '../../../src/index.js';
import {
  aboxFixtures, bookstoreEntities, OrderSchema
} from '../bookstore/index.js';

const order = bookstoreEntities.instantiate(OrderSchema.$id, aboxFixtures.order);

// clone — deep copy; mutations don't affect original.
const copy = Operations.clone(order);

// clone produces a deep copy — orderLines arrays are distinct references.
console.assert(order.orderLines.length === 1);
console.assert(copy.orderLines !== order.orderLines, 'clone must produce distinct orderLines reference');

// Spread the branded readonly orderLines tuple into a plain mutable array so a new
// line item can be appended without satisfying the element brands at compile time.
const copyItems: unknown[] = structuredClone([...copy.orderLines]);

copyItems.push({
  // Walter Moers — Die Stadt der Träumenden Bücher (Piper, 2004).
  'bookIsbn': '9783492045490',
  'quantity': 1,
  'unitPrice': {
    'amount': 24.9,
    'currency': 'EUR'
  }
});
console.assert(copyItems.length === 2);

// hash — deterministic, key-order invariant.
const h1 = Hash.value({
  'isbn': aboxFixtures.rareBook.isbn,
  'title': aboxFixtures.rareBook.title
});
const h2 = Hash.value({
  'isbn': aboxFixtures.rareBook.isbn,
  'title': aboxFixtures.rareBook.title
});

console.assert(h1 === h2);
console.assert(typeof h1 === 'string' && h1.length > 0);

console.log('original orderLines:', order.orderLines.length);
console.log('copy orderLines after push:', copyItems.length);
console.log('hash (key-order invariant):', h1);
console.log('hashes equal:', h1 === h2);
Output
Press Execute to run this example against the real library.

Example 2: Clone nested addresses

/**
 * Operations.clone — Example 2: Clone nested addresses
 * Demonstrates: deep copy preserves independence of nested arrays
 *
 * Bastian Balthazar Bux's customer record has a nested addresses array.
 * After cloning, the addresses array in the copy is a distinct object —
 * mutations to the copy do not affect the original.
 */

import {
  Operations
} from '../../../src/index.js';
import {
  aboxFixtures, bookstoreEntities, CustomerSchema
} from '../bookstore/index.js';

const customer = bookstoreEntities.instantiate(CustomerSchema.$id, aboxFixtures.customer);

const copy = Operations.clone(customer);

// Deep copy — arrays are distinct references.
const custAddresses = (customer as Record<string, unknown>).addresses;
const copyAddresses = (copy as Record<string, unknown>).addresses;

console.assert(copyAddresses !== custAddresses);
console.assert(Array.isArray(copyAddresses));
console.assert((copy as { 'name': string }).name === aboxFixtures.customer.name);

console.log('copy.name:', (copy as { 'name': string }).name);
console.log('addresses are distinct references:', copyAddresses !== custAddresses);
Output
Press Execute to run this example against the real library.

Comparison

ts
const copy = Operations.clone(order); // deep copy via structuredClone
ts
// No built-in clone utility.
const copy = structuredClone(order);
ts
// Limitation: Valibot has no clone utility - use structuredClone.
const copy = structuredClone(order);
ts
// Limitation: io-ts has no clone utility - use structuredClone.
const copy = structuredClone(order);
ts
import { Value } from '@sinclair/typebox/value';
const copy = Value.Clone(order);
ts
// No built-in clone  - use structuredClone.
const copy = structuredClone(order);
py
copy = order.model_copy(deep=True)
ts
// Limitation: feature not directly supported in Yup. See /comparisons for the matrix.
ts
// Limitation: feature not directly supported in Joi. See /comparisons for the matrix.
ts
// Limitation: feature not directly supported in Effect Schema. See /comparisons for the matrix.
ts
// Limitation: feature not directly supported in ArkType. See /comparisons for the matrix.
ts
// Limitation: feature not directly supported in Runtypes. See /comparisons for the matrix.

Hash.value

Declaration. Computes a deterministic FNV-1a hash of a JSON-serializable value. Property key order is normalized before hashing - two objects with the same keys/values but different key order produce identical hashes. Returns a hex string. Not cryptographically secure.

Use this when you need content-addressable caching, deduplication, ETag generation, or change detection without a full structural diff.

Don't use this when you need cryptographic security - this is not a secure hash. Don't use it for values that contain non-JSON-serializable data (functions, undefined, circular references - behavior is undefined).

Examples

Example 1: Generate an ETag for a book

/**
 * Hash.value — Example 1: Generate an ETag for a book
 * Demonstrates: deterministic hex string, key-order invariance
 *
 * The canonical rare book fixture — Michael Ende's Die unendliche Geschichte
 * (Thienemann Verlag, 1979) — produces the same hash regardless of property
 * key ordering. This property makes Hash.value suitable for ETag generation
 * and content-addressable caching.
 */

import {
  Hash
} from '../../../src/index.js';
import {
  aboxFixtures, bookstoreEntities, RareBookSchema
} from '../bookstore/index.js';

const book = bookstoreEntities.instantiate(RareBookSchema, aboxFixtures.rareBook);

const etag = Hash.value(book);

// Deterministic hex string — same value each time.
console.assert(typeof etag === 'string');
console.assert(etag.length > 0);

// Key-order invariant — same keys/values, different insertion order → identical hash.
// isbn before title in h1, title before isbn in h2 — hash is stable.
const h1 = Hash.value({
  'isbn': aboxFixtures.rareBook.isbn,
  'title': aboxFixtures.rareBook.title
});

const h2 = Hash.value({
  'isbn': aboxFixtures.rareBook.isbn,
  'title': aboxFixtures.rareBook.title
});

console.assert(h1 === h2);

console.log('book etag:', etag);
console.log('key-order invariant:', h1 === h2);
Output
Press Execute to run this example against the real library.

Example 2: Cache invalidation

/**
 * Hash.value — Example 2: Cache invalidation via content hash
 * Demonstrates: hash changes when value changes, stable when unchanged
 *
 * The canonical Bastian Balthazar Bux order is used as the tracked value.
 * After a total update the hash changes, signalling that cached views must
 * be invalidated.
 */

import {
  Hash, Operations
} from '../../../src/index.js';
import {
  aboxFixtures, bookstoreEntities, OrderSchema
} from '../bookstore/index.js';

const order = bookstoreEntities.instantiate(OrderSchema, aboxFixtures.order);
const prevHash = Hash.value(order);

// Simulate a total update — add a second line item price.
const updatedOrder = bookstoreEntities.instantiate(OrderSchema, {
  ...aboxFixtures.order,
  'orderLines': [
    ...aboxFixtures.order.orderLines,
    // Hermann Hesse — Siddhartha (Suhrkamp, 1951)
    {
      'bookIsbn': '9783518366820',
      'quantity': 1,
      'unitPrice': {
        'amount': 12,
        'currency': 'EUR'
      }
    }
  ],
  'orderTotal': {
    'amount': aboxFixtures.order.orderTotal.amount + 12,
    'currency': 'EUR'
  }
});

const newHash = Hash.value(updatedOrder);

// Changed — cache must be invalidated.
console.assert(newHash !== prevHash);

// Cloning without mutation preserves the hash.
const cloned = Operations.clone(order);

console.assert(Hash.value(cloned) === prevHash);

console.log('original hash:', prevHash);
console.log('updated hash:', newHash);
console.log('hash changed after update:', newHash !== prevHash);
console.log('clone hash matches original:', Hash.value(cloned) === prevHash);
Output
Press Execute to run this example against the real library.

Comparison

ts
Hash.value(book) // deterministic FNV-1a hex, key-order invariant
ts
// No built-in hash utility.
// Use a third-party library: object-hash, stable-hash, etc.
import hash from 'object-hash';
const h = hash(book);
ts
// Limitation: Valibot has no hash utility.
// Use a third-party library: object-hash, stable-hash, etc.
import hash from 'object-hash';
const h = hash(book);
ts
// Limitation: io-ts has no hash utility.
// Use a third-party library: object-hash, stable-hash, etc.
import hash from 'object-hash';
const h = hash(book);
ts
// No built-in hash utility.
ts
// No built-in hash utility.
py
import hashlib, json
data = book.model_dump()
h = hashlib.sha256(json.dumps(data, sort_keys=True).encode()).hexdigest()
ts
// Limitation: feature not directly supported in Yup. See /comparisons for the matrix.
ts
// Limitation: feature not directly supported in Joi. See /comparisons for the matrix.
ts
// Limitation: feature not directly supported in Effect Schema. See /comparisons for the matrix.
ts
// Limitation: feature not directly supported in ArkType. See /comparisons for the matrix.
ts
// Limitation: feature not directly supported in Runtypes. See /comparisons for the matrix.

See also

Released under the MIT License.