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

@graftjs/core

v0.7.0

Published

Type-safe bidirectional schema mapping - core engine

Readme

@graftjs/core

Type-safe bidirectional schema mapping — core engine

The foundation of Graft: a compile-time type-safe, runtime-validated library for mapping between two object schemas. @graftjs/core provides the low-level primitives that power Graft's schema mapping capabilities.

Overview

@graftjs/core is a dependency-free package that provides:

  • Type-level utilities for compile-time safety (no number → string wiring allowed)
  • Runtime mapping engine with precompiled specs for fast execution
  • Security guards to prevent prototype pollution
  • Collision detection to catch configuration mistakes
  • Bidirectional mapping from a single configuration

Most users will want @graftjs/zod instead, which provides automatic schema introspection and validation. Use @graftjs/core directly only if you're:

  • Building an adapter for another schema library (Valibot, TypeBox, etc.)
  • Need maximum control over the mapping process
  • Working without a schema validation library

Installation

npm install @graftjs/core
# or
pnpm add @graftjs/core
# or
yarn add @graftjs/core

Quick Example

import { compileSpec, executeForward, executeBackward } from '@graftjs/core';

// Define your mapping configuration
const config = {
  userId: 'user_id',
  email: 'email_address',
  createdAt: 'created_at'
};

// Compile once (at app startup)
const spec = compileSpec(config);

// Execute many times (hot path)
const apiUser = executeForward(dbUser, spec);
// { userId: '123', email: '[email protected]', createdAt: '2024-01-01' }

const dbUser = executeBackward(apiUser, spec);
// { user_id: '123', email_address: '[email protected]', created_at: '2024-01-01' }

Core Concepts

Deep Paths (v0.2)

Graft supports nested object mapping using dot notation. You can map flat fields to nested structures and vice versa:

// ✅ Map flat source to nested target
const config = {
  'profile.name': 'user_name',
  'profile.address.city': 'city',
  'profile.address.country': 'country_code'
};

// ✅ Map nested source to flat target
const config = {
  userName: 'user.profile.name',
  userCity: 'user.profile.address.city'
};

// ✅ Mix flat and nested paths
const config = {
  id: 'user_id',
  'profile.name': 'user_name'
};

Intermediate objects are automatically created with null prototypes for security.

Precompiled Specs

Graft separates compilation (expensive, done once) from execution (cheap, done many times):

// Compile once
const spec = compileSpec(config, {
  strict: 'to',
  validate: 'both'
});

// Execute many times in hot path
for (const dbRecord of thousands) {
  const apiRecord = executeForward(dbRecord, spec);
  // Fast! No repeated parsing or validation
}

Bidirectional Mapping

One configuration gives you both directions:

const spec = compileSpec({ userId: 'user_id' });

// Forward: Left → Right
const right = executeForward({ user_id: '123' }, spec);
// { userId: '123' }

// Backward: Right → Left
const left = executeBackward({ userId: '123' }, spec);
// { user_id: '123' }

The backward mapping is automatically inverted from the forward mapping.

Security First

Prototype pollution prevention is built-in:

// ❌ Throws GraftSecurityError immediately
const spec = compileSpec({
  '__proto__': 'evil'
});

// ❌ Also throws
const spec = compileSpec({
  normal: '__proto__'
});

All output objects use Object.create(null) to prevent pollution attacks.

API Reference

Type Utilities

MappingConfig<From, To>

Type for mapping configuration objects. Ensures type compatibility at compile time.

type UserDB = {
  user_id: string;
  email_address: string;
  age: number;
};

type UserAPI = {
  id: string;
  email: string;
  age: number;
};

// ✅ Valid - types are compatible
const config: MappingConfig<UserDB, UserAPI> = {
  id: 'user_id',        // string → string ✓
  email: 'email_address', // string → string ✓
  age: 'age'            // number → number ✓
};

// ❌ Invalid - would fail type check
const bad: MappingConfig<UserDB, UserAPI> = {
  id: 'age'  // number → string ✗
};

AssertAllToKeysMapped<From, To, Config>

Enforces that all required target fields are mapped (strict mode).

type From = { a: string; b: number };
type To = { x: string; y: number; z: boolean };

// ❌ Type error - missing mapping for 'z'
type Bad = AssertAllToKeysMapped<From, To, { x: 'a', y: 'b' }>;
// Shows: { __error: "Missing required mappings"; __missing: "z" }

// ✅ Valid - all required fields mapped
type Good = AssertAllToKeysMapped<From, To, { x: 'a', y: 'b', z: 'a' }>;

GraftOptions

Configuration options for compilation and execution:

type GraftOptions = {
  strict?: 'none' | 'to' | 'from' | 'both';  // Default: 'to'
  validate?: 'none' | 'from' | 'to' | 'both'; // Not used in core (for adapters)
};
  • strict: Enforce complete field coverage
    • 'to': All required target fields must be mapped
    • 'from': All required source fields must be reproducible
    • 'both': Both directions enforced
    • 'none': No enforcement (permissive)

Runtime Functions

compileSpec<From, To>(config, options?)

Compile a mapping configuration into an optimized specification.

Parameters:

  • config: MappingConfig<From, To> - Mapping configuration
  • options?: GraftOptions - Optional configuration

Returns: CompiledSpec - Frozen, immutable spec ready for execution

Throws:

  • GraftSecurityError - If dangerous keys detected (__proto__, etc.)
  • GraftCollisionError - If multiple sources map to same target
const spec = compileSpec(
  {
    userId: 'user_id',
    email: 'email_address'
  },
  {
    strict: 'to'
  }
);

executeForward<To>(input, spec)

Execute forward mapping (From → To).

Parameters:

  • input: Record<string, unknown> - Source object
  • spec: CompiledSpec - Compiled specification

Returns: To - Mapped target object

Throws:

  • GraftMappingError - If mapping fails
const output = executeForward({ user_id: '123', email_address: '[email protected]' }, spec);
// { userId: '123', email: '[email protected]' }

executeBackward<From>(input, spec)

Execute backward mapping (To → From).

Parameters:

  • input: Record<string, unknown> - Target object
  • spec: CompiledSpec - Compiled specification

Returns: From - Mapped source object

Throws:

  • GraftMappingError - If mapping fails
const output = executeBackward({ userId: '123', email: '[email protected]' }, spec);
// { user_id: '123', email_address: '[email protected]' }

Security Functions

validateConfigKeys(config)

Validate configuration keys for security issues.

Throws: GraftSecurityError if dangerous keys detected

validateConfigKeys({ userId: 'user_id' }); // ✓ Safe
validateConfigKeys({ '__proto__': 'evil' }); // ✗ Throws

createSafeObject()

Create a safe object with null prototype (prevents pollution).

const obj = createSafeObject();
obj['__proto__'] = { polluted: true };
console.log({}.polluted); // undefined - no pollution!

safeGet(obj, key) / safeSet(obj, key, value)

Safe property access with runtime guards.

safeGet(obj, 'name'); // ✓ Safe
safeGet(obj, '__proto__'); // ✗ Throws GraftSecurityError

Nested Grafts (v0.5)

GRAFT_BRAND

Symbol used to identify Graft instances for nested composition.

import { GRAFT_BRAND } from '@graftjs/core';

// Check if an object is a Graft instance
function isGraft(obj: unknown): boolean {
  return typeof obj === 'object' &&
         obj !== null &&
         GRAFT_BRAND in obj;
}

GraftLike

Minimal interface for nested graft detection:

type GraftLike = {
  readonly [GRAFT_BRAND]: true;
  readonly mapRight: (input: unknown) => unknown;
  readonly mapLeft: (input: unknown) => unknown;
};

NestedGraftConfig

Configuration for nested graft composition:

type NestedGraftConfig = {
  readonly from: string;   // Source path
  readonly graft: GraftLike; // Nested graft to apply
};

Usage in config:

const spec = compileSpec({
  id: 'user_id',
  address: {
    from: 'address_data',    // Source path
    graft: addressGraft      // Any object with GRAFT_BRAND
  }
});

Path Utilities (v0.2)

parsePath(path)

Parse a dot-notation path into segments.

parsePath("profile.address.city");  // ["profile", "address", "city"]
parsePath("id");                    // ["id"]

isNestedPath(path)

Check if a path contains nested segments.

isNestedPath("profile.address.city");  // true
isNestedPath("id");                    // false

getNestedValue(obj, path) / setNestedValue(obj, path, value)

Safely traverse nested objects. Throws GraftSecurityError if any path segment is dangerous.

const obj = { profile: { address: { city: "NYC" } } };

getNestedValue(obj, "profile.address.city");  // "NYC"
getNestedValue(obj, "profile.missing");       // undefined

setNestedValue(obj, "profile.address.zip", "10001");
// Creates intermediate objects as needed

DeepKeys<T, Depth>

Generate a union type of all valid paths into an object type.

import type { DeepKeys, DeepValue } from '@graftjs/core';

type User = {
  id: string;
  profile: {
    name: string;
    address: { city: string };
  };
};

type UserPaths = DeepKeys<User>;
// "id" | "profile" | "profile.name" | "profile.address" | "profile.address.city"

type CityType = DeepValue<User, "profile.address.city">;  // string

Error Classes

All errors extend GraftError for easy catching:

try {
  const spec = compileSpec(config);
} catch (error) {
  if (error instanceof GraftError) {
    // Handle graft-specific errors
  }
}

GraftCollisionError

Multiple sources mapping to same target.

// Properties
error.targetKey: string;      // The conflicting target key
error.sourceKeys: string[];   // Conflicting source keys

GraftSecurityError

Dangerous key detected (prototype pollution attempt).

// Properties
error.dangerousKey: string;   // The dangerous key that was blocked

GraftConfigError

Invalid mapping configuration.

GraftMappingError

Runtime mapping failure.

// Properties
error.direction: 'forward' | 'backward';  // Which direction failed

GraftNestedError

Nested graft execution failure.

// Properties
error.mapping: { from: string; to: string };  // The mapping that failed
error.direction: 'forward' | 'backward';      // Which direction failed
error.cause: Error;                           // The underlying error

Example handling:

try {
  const result = executeForward(input, spec);
} catch (error) {
  if (error instanceof GraftNestedError) {
    console.error(`Nested graft failed: ${error.mapping.from} → ${error.mapping.to}`);
    console.error(`Direction: ${error.direction}`);
    console.error(`Cause: ${error.cause.message}`);
  }
}

Advanced Usage

Building a Custom Adapter

Here's how to build an adapter for another schema library:

import { compileSpec, executeForward, executeBackward } from '@graftjs/core';
import type { MappingConfig, GraftOptions } from '@graftjs/core';

// Example: Valibot adapter
function createGraft<Left, Right>(
  leftSchema: ValibotSchema,
  rightSchema: ValibotSchema,
  config: MappingConfig<Left, Right>,
  options?: GraftOptions
) {
  // 1. Introspect schemas to generate auto-mappings
  const autoMappings = generateAutoMappings(leftSchema, rightSchema);

  // 2. Merge with manual config
  const mergedConfig = { ...autoMappings, ...config };

  // 3. Compile the spec
  const spec = compileSpec<Left, Right>(mergedConfig, options);

  // 4. Return methods that wrap with validation
  return {
    toRight(input: unknown): Right {
      const parsed = parse(leftSchema, input);
      const mapped = executeForward<Right>(parsed, spec);
      return parse(rightSchema, mapped);
    },
    toLeft(input: unknown): Left {
      const parsed = parse(rightSchema, input);
      const mapped = executeBackward<Left>(parsed, spec);
      return parse(leftSchema, mapped);
    },
    mapRight(input: Left): Right {
      const mapped = executeForward<Right>(input, spec);
      return parse(rightSchema, mapped);
    },
    mapLeft(input: Right): Left {
      const mapped = executeBackward<Left>(input, spec);
      return parse(leftSchema, mapped);
    },
    spec
  };
}

Performance Optimization

Do:

  • ✅ Compile specs once at app startup
  • ✅ Reuse specs across many executions
  • ✅ Use executeForward/executeBackward directly for hot paths

Don't:

  • ❌ Recompile specs for every execution
  • ❌ Create new configs in loops
  • ❌ Use spread operators in hot paths
// ✅ Good - compile once
const spec = compileSpec(config);
for (const item of thousands) {
  const mapped = executeForward(item, spec);
}

// ❌ Bad - recompiling on every iteration
for (const item of thousands) {
  const spec = compileSpec(config); // Wasteful!
  const mapped = executeForward(item, spec);
}

Type-Level Features

Path Type Generation

@graftjs/core provides type utilities for generating valid paths into objects:

import type { TopLevelKeys, RequiredKeys, OptionalKeys } from '@graftjs/core';

type User = {
  id: string;
  name?: string;
  age: number | undefined;
};

type Top = TopLevelKeys<User>;     // "id" | "name" | "age"
type Req = RequiredKeys<User>;     // "id"
type Opt = OptionalKeys<User>;     // "name" | "age"

Compatibility Checking

The type system ensures source and target types are compatible:

import type { CompatibleSourceKey } from '@graftjs/core';

type From = { id: string; age: number; name: string };
type To = { userId: string; userAge: number };

// "id" is compatible with "userId" (both string)
type CanMapId = CompatibleSourceKey<From, To, "userId">;  // "id"

// "age" is NOT compatible with "userId" (number vs string)
type CannotMap = CompatibleSourceKey<From, To, "userId">; // never

Current Capabilities (v0.5)

Supported features:

  • ✅ Deep path mapping with dot notation
  • ✅ Transforms with forward/backward functions
  • ✅ Nested graft composition via GRAFT_BRAND
  • ✅ Error handling with GraftNestedError

Planned features:

  • Array element mapping
  • Union type support
  • Deep auto-mapping

See the roadmap for upcoming features.

TypeScript Configuration

For best results, enable strict mode in your tsconfig.json:

{
  "compilerOptions": {
    "strict": true,
    "noUncheckedIndexedAccess": true
  }
}

License

MIT

See Also