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

@bou-co/parsing

v2.0.1

Published

Bou Parsing is your ultimate sidekick for taming unruly data! Whether you're wrangling data from APIs, generating TypeScript types on the fly, or splitting complex queries into bite-sized pieces, Bou Parsing has got you covered. With its powerful yet easy

Readme

Bou Parsing

Bou Parsing is your ultimate sidekick for taming unruly data! Whether you're wrangling data from APIs, generating TypeScript types on the fly, or splitting complex queries into bite-sized pieces, Bou Parsing has got you covered. With its powerful yet easy-to-use functions, you can effortlessly manipulate, validate, and transform your data into exactly what you need.

While Bou Parsing is fully isomorphic and works perfectly in the browser, it truly shines on the server-side (e.g., in Next.js App Router, Astro, NestJS, or Express). It allows you to fetch massive API responses or do complex mappings and calculations, parse them into exact, type-safe structures, and cache the computations to drastically reduce server, database and network load before sending data to the frontend.

NPM | GitHub

Get Started

1 - Install the package

Install the Bou Parsing package from NPM. It supports all frameworks.

npm i @bou-co/parsing

2 - Initialize the parser

In the root level of your code, run the initializeParser function to export your tailored createParser function. This allows you to set up global configurations like caching and variables once.

// parser-config.ts
import { initializeParser } from '@bou-co/parsing';

export const { createParser } = initializeParser(/** Global configurations comes here **/);

3 - Start using the parser

Use your customized createParser anywhere in your app's data flow to safely pick, validate, and type your data.

import { createParser } from '../path-to/parser-config';

const rawDataFromApi = {
  _id: 'abc-123',
  title: 'Hello World',
  description: 'Lorem ipsum',
  priority: 1,
};

const myParser = createParser({
  title: 'string',
  description: 'string',
  priority: 'number',
});

const result = await myParser(rawDataFromApi);

/* Result:
{
  "title": "Hello World",
  "description": "Lorem ipsum",
  "priority": 1
}
*/

Table of Contents


Basic Usage

Defining the data you want

When querying data with an API that returns more than you need, you can use the parser to pick only the exact fields you want, omitting the rest.

import { createParser } from '../path-to/parser-config';

const rawDataFromApi = {
  _id: 'abc-123',
  title: 'Test',
  description: 'Lorem ipsum',
  priority: 1,
};

const myParser = createParser({
  title: 'string',
  description: 'string',
  priority: 'number',
});

const result = await myParser(rawDataFromApi);

/* Result:
{
  "title": "Test",
  "description": "Lorem ipsum",
  "priority": 1
}
*/

Adding and modifying values

You can append static values, compute synchronous/asynchronous values, or derive new properties from the raw input data.

import { createParser } from '../path-to/parser-config';

const rawDataFromApi = {
  title: 'Test',
  priority: 1,
};

const myParser = createParser({
  title: 'string',

  // 1. Static value added as is
  postType: 'blogPost',

  // 2. Function return value
  randomNumber: () => 42,

  // 3. Promises supported
  asyncText: async () => {
    return await Promise.resolve('Fetched later');
  },

  // 4. Custom override based on existing data
  priority: (context) => {
    if (!context.data.priority) return 100;
    return context.data.priority * 10;
  },

  // 5. Variation of raw value
  metaTitle: (context) => `${context.data.title} - Our blog`,
});

const result = await myParser(rawDataFromApi);

/* Result:
{
  "title": "Test",
  "postType": "blogPost",
  "randomNumber": 42,
  "asyncText": "Fetched later",
  "priority": 10,
  "metaTitle": "Test - Our blog"
}
*/

Nested data structures

Parsers seamlessly handle nested objects, arrays, and even other parsers as property definitions.

import { createParser } from '../path-to/parser-config';

const rawDataFromApi = {
  title: 'Nested Test',
  details: { desc: 'Inner description', level: 5 },
  tags: [{ name: 'ts' }, { name: 'js' }],
};

const tagParser = createParser({
  name: 'string',
  isAwesome: () => true,
});

const myParser = createParser({
  title: 'string',

  // Nested Object
  nestedDataObject: {
    desc: 'string',
    level: 'number',
  },

  // Nested Array
  nestedDataArray: {
    '@array': true,
    name: 'string',
    indexLabel: ({ index }) => `Item ${index}`, // Arrays expose 'index' in context
  },

  // Nested Parser
  parsedTags: tagParser.asArray,
});

// Notice we map 'details' to 'nestedDataObject' and 'tags' to 'nestedDataArray'/'parsedTags'
// Since input keys don't match exactly, we'd normally alias them or pass data directly.
// Let's execute assuming the raw data matches the parser schema structure for simplicity:
const structuredData = {
  title: rawDataFromApi.title,
  nestedDataObject: rawDataFromApi.details,
  nestedDataArray: rawDataFromApi.tags,
  parsedTags: rawDataFromApi.tags,
};

const result = await myParser(structuredData);

/* Result:
{
  "title": "Nested Test",
  "nestedDataObject": { "desc": "Inner description", "level": 5 },
  "nestedDataArray": [
    { "name": "ts", "indexLabel": "Item 0" },
    { "name": "js", "indexLabel": "Item 1" }
  ],
  "parsedTags": [
    { "name": "ts", "isAwesome": true },
    { "name": "js", "isAwesome": true }
  ]
}
*/

Conditional data

Support for fully conditional data picking and addition using @if.

import { createParser } from '../path-to/parser-config';

const rawDataFromApi = {
  title: 'Test',
  priority: 2,
};

const myParser = createParser({
  title: 'string',
  priority: 'number',
  '@if': [
    {
      // Adds 'highPriority' if priority is above 1
      when: (context) => context.data.priority > 1,
      then: { highPriority: true },
    },
    {
      // Modifies 'title' if priority is below 10
      when: (context) => context.data.priority < 10,
      then: { title: (context) => `${context.data.title} (Draft)` },
    },
  ],
});

const result = await myParser(rawDataFromApi);

/* Result:
{
  "title": "Test (Draft)",
  "priority": 2,
  "highPriority": true
}
*/

Advanced Usage

Merging data

Use @combine to fetch or compute large external datasets and merge them directly into the current parser projection.

import { createParser } from '../path-to/parser-config';

const rawDataFromApi = { _id: '123', title: 'Test' };

const additionalDataParser = createParser({
  readCount: 'number',
});

const myParser = createParser({
  title: 'string',
  '@combine': async (context) => {
    // Imagine an API call here based on context.data._id
    const externalData = { readCount: 42 };
    return await additionalDataParser(externalData);
  },
});

const result = await myParser(rawDataFromApi);

/* Result:
{
  "title": "Test",
  "readCount": 42
}
*/

Variables

Variables provide advanced template logic for string values coming from raw data. They allow content editors (e.g., in a CMS) to use dynamic data without requiring coders to build an entire EJS or templating engine.

Variables support:

  • Functions: Resolve dynamic data (e.g., currentYear: () => new Date().getFullYear()).
  • Async Execution: Fetch variable values from a DB or CMS dynamically.
  • Deep object resolution: Access nested properties using dot notation (e.g., {{user.address.city}}).
  • Fallbacks: Chain variable checks (e.g., {{user.name || "Guest"}} or {{score || 0}}).
  • Pipes: Transform output values inline (e.g., {{date | toDateString}} or {{title | uppercase}}).
// 1. Global Setup (in parser-config.ts)
import { initializeParser } from '@bou-co/parsing';

export const { createParser } = initializeParser(() => ({
  variables: {
    currentYear: () => new Date().getFullYear(),
    uppercase: ({ data }) => String(data).toUpperCase(),
  },
}));

// 2. Usage
import { createParser } from '../path-to/parser-config';

// Imagine this string comes directly from database or CMS
const rawDataFromApi = {
  title: 'Copyright {{currentYear}}',
  user: 'Hello {{user.firstName || "Guest" | uppercase}}!',
};

const myParser = createParser({
  title: 'string',
  user: 'string',
});

// Provide instance variables overriding or supplementing global ones
const instanceData = {
  variables: {
    user: { firstName: 'john' },
  },
};

const result = await myParser(rawDataFromApi, instanceData);

/* Result:
{
  "title": "Copyright 2026",
  "user": "Hello JOHN!"
}
*/

Dynamic Variable Resolvers

Instead of defining every possible variable upfront, variableResolver allows you to dynamically intercept and resolve variables by their exact name when they are encountered. This is incredibly powerful for catching wildcards, fetching data on-demand from a database, or handling dynamic keys.

import { initializeParser } from '@bou-co/parsing';

export const { createParser } = initializeParser(() => ({
  variableResolver: async (variableName, context) => {
    // Dynamically catch variables named 'userName'
    if (variableName === 'userName') {
      const { userId } = context.data; //

      // Simulated DB fetch (e.g., await db.getUser(userId))
      const userName = await Promise.resolve('Alice');
      return userName;
    }

    // Return undefined to let standard fallbacks or other variables take over
    return undefined;
  },
}));

const dynamicParser = createParser({ message: 'string' });

const result = await dynamicParser({ message: 'Welcome back, {{userName}}!', userId: 123 });

/* Result:
{
  "message": "Welcome back, Alice!"
}
*/

Dynamic projections

Pass a function instead of a static object to return a different projection structure based on the input data dynamically.

import { createParser } from '../path-to/parser-config';

const dynamicParser = createParser(({ data }) => {
  if (data.type === 'detailed') {
    return { value: 'number', metadata: 'string' };
  }
  return { value: 'number' };
});

const result = await dynamicParser({ type: 'detailed', value: 10, metadata: 'extra info' });

/* Result:
{
  "value": 10,
  "metadata": "extra info"
}
*/

Extending parsers

Merge a new projection onto an existing parser securely without mutating the original definition.

import { createParser } from '../path-to/parser-config';

const original = createParser({ value: 'number' });
const extended = original.extend({ additional: 'string' });

const result = await extended({ value: 456, additional: 'test' });

/* Result:
{
  "value": 456,
  "additional": "test"
}
*/

Context overriding

Inject new context properties (like variables) into a pre-existing parser by calling .withContext().

import { createParser } from '../path-to/parser-config';

const parser = createParser({ value: 'string' }, { variables: { first: 1 } });
const overriddenParser = parser.withContext({ variables: { second: 2 } });

// overriddenParser now has both { first: 1, second: 2 } available in variables context.

Lifecycle hooks

Register before and after hooks. before hooks inject shared context values prior to parsing, which trickle down to nested/extended parsers.

import { createParser } from '../path-to/parser-config';

const productParser = createParser(
  {
    finalPrice: ({ data, basePrice }) => data.price + basePrice,
  },
  {
    before: (context) => {
      context.basePrice = 10;
      return context;
    },
  },
);

const result = await productParser({ price: 25 });

/* Result:
{
  "finalPrice": 35
}
*/

Transformers

Transformers run conditionally globally against properties. Helpful for automatic data morphing based on context.

// 1. Setup in parser-config.ts
import { initializeParser } from '@bou-co/parsing';

const localize = {
  // If the object looks like a translation map (e.g. { en: 'Hello', fi: 'Hei' })
  when: ({ data, locales = ['en', 'fi'] }) => typeof data === 'object' && Object.keys(data).every((k) => locales.includes(k)),
  // Resolve the string of the current locale
  then: ({ data, currentLocale = 'en' }) => data[currentLocale],
};

export const { createParser } = initializeParser({ transformers: { localize } });

// 2. Usage
import { createParser } from '../path-to/parser-config';

const myParser = createParser({ greeting: 'string' });

const rawData = { greeting: { en: 'Hello', fi: 'Hei' } };
const result = await myParser(rawData);

/* Result: { "greeting": "Hello" } */

Chaining parsers (Reparsing)

The data output by one parser can be safely passed into another parser for multi-pass executions.

import { createParser } from '../path-to/parser-config';

const stepOne = createParser({ value: 'number' });
const stepTwo = createParser({ value: ({ data }) => data.value * 2 });

const initialData = await stepOne({ value: 123 });
const finalData = await stepTwo(initialData);

/* Result: { "value": 246 } */

Examples & Use Cases

Next.js App Router & Server Components

Why: Bou Parsing is natively asynchronous, making it an ideal companion for React Server Components in the Next.js App Router. Instead of manually typing incoming API props or component structures, the parser automatically infers the final shape of the data based on your schema.

Note: You do not need the useParserValue hook on the server. Just await the parser function directly! For Client Components, refer to the Client-Side React Integration section.

Example 1: The "CMS-Driven" Approach (Dynamic Input)

In this approach, the component receives a loosely typed object (e.g., a dynamic block from a headless CMS) and the parser validates and shapes the data, outputting strictly typed props for the JSX. It's a best practice to co-locate the parser and the component.

// components/hero-block/parser.ts
import { createParser } from '../../path-to/parser-config';

export const heroBlockParser = createParser({
  title: 'string',
  description: 'string',
  imageUrl: ({ data }) => `https://example.com/images/${data.imageId}`,
});
// components/hero-block/hero-block.tsx
import React from 'react';
import { heroBlockParser } from './parser';

export const HeroBlock = async (initialProps: object) => {
  const props = await heroBlockParser(initialProps);
  // `props` is automatically typed as: { title: string, description: string, imageUrl: string }

  return (
    <section>
      <h1>{props.title}</h1>
      <p>{props.description}</p>
      <img src={props.imageUrl} alt={props.title} />
    </section>
  );
};
// app/[...slug]/page.tsx
import { HeroBlock } from '../../components/hero-block/hero-block';

// Map CMS block types to React Components
const ComponentMap: Record<string, any> = {
  hero: HeroBlock,
};

// Catch-all route to handle dynamic nested paths (e.g. /about/our-team)
export default async function Page({ params }: { params: { slug?: string[] } }) {
  // Resolve the path, defaulting to 'home' if at the root
  const path = params.slug ? params.slug.join('/') : 'home';

  // Fake data fetching based on the dynamic route path
  const res = await fetch(`https://api.example.com/pages/${path}`);
  const data = await res.json();

  return (
    <main>
      {/* Dynamically resolve and pass raw, loosely typed data to the components */}
      {data.blocks?.map((block: any, index: number) => {
        const Component = ComponentMap[block.type];

        // Skip unknown block types safely
        if (!Component) return null;

        // The component's inner parser will handle typing and validation natively
        return <Component key={index} {...block} />;
      })}
    </main>
  );
}

Example 2: The "Traditional Component" Approach (Strictly Typed Input)

When you need excellent developer experience for hardcoding components manually, you can strictly type the initialProps. The parser takes these strict props, validates them, and can execute side-effects like fetching additional data.

// components/user-card/parser.ts
import { createParser } from '../../path-to/parser-config';

// Define the strict input interface
export interface UserCardInitialProps {
  userId: string;
  theme?: 'light' | 'dark';
}

export const userCardParser = createParser({
  theme: ({ data }) => data.theme || 'light', // Fallback
  userProfile: async ({ data }) => {
    // Fetch user details dynamically based on the strict userId prop
    const res = await fetch(`https://api.example.com/users/${data.userId}`);
    return await res.json();
  },
});
// components/user-card/user-card.tsx
import React from 'react';
import { userCardParser, UserCardInitialProps } from './parser';

export const UserCard = async (initialProps: UserCardInitialProps) => {
  // `props` infers both the fallback theme and the resolved userProfile
  const props = await userCardParser(initialProps);

  return (
    <div className={`theme-${props.theme}`}>
      <h2>{props.userProfile.name}</h2>
    </div>
  );
};
// app/page.tsx
import { UserCard } from '../components/user-card/user-card';

export default function Page() {
  return (
    <main>
      <h1>Our Team</h1>
      {/* Strongly typed props with excellent DX */}
      <UserCard userId="u_123" theme="dark" />
      <UserCard userId="u_456" /> {/* theme defaults to 'light' */}
    </main>
  );
}

Example 3: The "Hybrid" Approach (Nested Parsers & Reusable Sub-components)

In complex pages, you often have a large block of data coming from a CMS containing nested structures (like an article with an author). You can nest parsers to validate the entire tree at once.

Then, you can use ParserReturnValue to extract the inferred TypeScript type from the child parser, allowing you to pass the pre-parsed, strictly-typed data into a static, "dumb" React component that doesn't need to run any parsing itself.

// components/author-badge/parser.ts
import { createParser, ParserReturnValue } from '../../path-to/parser-config';

// 1. Define the child parser in its own generic folder
export const authorBadgeParser = createParser({
  name: 'string',
  role: 'string',
});

// 2. Export its inferred type for use in static components
export type AuthorBadgeProps = ParserReturnValue<typeof authorBadgeParser>;
// components/author-badge/author-badge.tsx
import React from 'react';
import type { AuthorBadgeProps } from './parser';

// This is a "dumb" static component. It expects strictly typed, pre-parsed data.
export const AuthorBadge = (props: AuthorBadgeProps) => {
  return (
    <div className="author-badge">
      <strong>{props.name}</strong>
      <span>{props.role}</span>
    </div>
  );
};
// components/article-block/parser.ts
import { createParser } from '../../path-to/parser-config';
import { authorBadgeParser } from '../author-badge/parser';

// 3. Nest the generic author parser inside the parent parser
export const articleBlockParser = createParser({
  title: 'string',
  content: 'string',
  author: authorBadgeParser, // Nests the parser directly
});
// components/article-block/article-block.tsx
import React from 'react';
import { articleBlockParser } from './parser';
import { AuthorBadge } from '../author-badge/author-badge';

// This is the parent component handling the raw, dynamic input
export const ArticleBlock = async (initialProps: object) => {
  // `props` is automatically typed and includes the parsed `author` object!
  const { title, content, authorBadge } = await articleBlockParser(initialProps);

  return (
    <article>
      <h1>{title}</h1>
      {/* Pass the fully parsed and typed `author` object to the child component */}
      <AuthorBadge {...authorBadge} />
      <p>{content}</p>
    </article>
  );
};
// app/article/[slug]/page.tsx
import { ArticleBlock } from '../../../components/article-block/article-block';

export default async function Page({ params }: { params: { slug: string } }) {
  // Fake data fetching
  const res = await fetch(`https://api.example.com/articles/${params.slug}`);
  const articleData = await res.json();

  return (
    <main>
      <ArticleBlock {...articleData} />
    </main>
  );
}

Server-Side Data Fetching & Caching

Why: Server-side environments (like Next.js App Router or Express) are perfect for parsing heavy API responses. By configuring the storage options, createParser can cache expensive computations (like DB calls or formatted strings) natively.

Features Used: initializeParser (storage), createParser (cache options), Async parsing.

// 1. Setup caching in parser-config.ts
import { initializeParser, toHash } from '@bou-co/parsing';
import { redis } from '../redis';

// Advanced typing: extend the context interface
declare module '@bou-co/parsing' {
  interface ParserCachingOptions {
    name?: string;
    ttl?: number;
  }
}

export const { createParser } = initializeParser({
  storage: {
    generateKey: (context) => {
      if (!context.cache.name) throw new Error('Caching options must include a name');
      return `${context.cache.name}:${toHash(context.data)}`;
    },
    add: async (key, value, context) => {
      await redis.set(key, JSON.stringify(value), { ex: context.cache.ttl });
    },
    match: async (key) => await redis.get(key),
  },
});

// 2. Create the parser with caching enabled
import { createParser } from '../path-to/parser-config';

const expensiveParser = createParser(
  {
    summary: async ({ data }) => {
      // Expensive DB Query or AI generation based on data.id
      await new Promise((r) => setTimeout(r, 1000));
      return `Processed: ${data.id}`;
    },
  },
  {
    cache: { enabled: true, ttl: 3600, name: 'summary-cache' },
  },
);

// 3. Execution (e.g., inside an Express route or Next.js Server Action)
const rawData = { id: 'user_123' };
const result = await expensiveParser(rawData); // Takes 1s first time, almost instant on subsequent calls!

CMS Content Templating with Variables

Why: Instead of building complex string-replacement utilities or integrating heavy templating engines like EJS, Bou Parsing allows content editors in a CMS to use double curly braces ({{variable}}) for dynamic injection. Coders define the variable resolvers (which can even be async DB lookups), and the parser handles replacing them safely.

Features Used: variables (Global & Instance), Async resolvers, Fallbacks (||), Pipes (|), Deep object resolution.

// 1. Global Setup in parser-config.ts
import { initializeParser } from '@bou-co/parsing';
import { db } from '../database';

export const { createParser } = initializeParser(() => ({
  variables: {
    // Basic function resolver
    currentYear: () => new Date().getFullYear(),

    // Async DB fetch: only called if the variable is actually used in the text!
    latestRelease: async () => {
      const release = await db.query('SELECT version FROM releases ORDER BY date DESC LIMIT 1');
      return release.version;
    },

    // Pipe for transformation
    capitalize: ({ data }) => String(data).charAt(0).toUpperCase() + String(data).slice(1),
  },
}));

// 2. Parser definition
import { createParser } from '../path-to/parser-config';

const cmsBlockParser = createParser({
  heading: 'string',
  body: 'string',
});

// 3. Execution (e.g., inside an API route fetching CMS data)
// This raw data represents what a content editor typed into the CMS:
const rawDataFromCMS = {
  heading: 'Release {{latestRelease || "v1.0.0"}} is out!',
  body: 'Copyright {{currentYear}}. Welcome back, {{user.name || "friend" | capitalize}}.',
};

// We pass the current logged-in user dynamically via instance context
const instanceContext = {
  variables: {
    user: { name: 'alice' },
  },
};

const result = await cmsBlockParser(rawDataFromCMS, instanceContext);

/* Result:
{
  "heading": "Release v2.4.1 is out!",
  "body": "Copyright 2026. Welcome back, Alice."
}
*/

CMS Dynamic Variables with On-Demand Fetching & Caching

Why: Often in CMS systems, content editors want to embed reusable snippets or documents directly into their text (e.g., {{snippets/summer-sale.title}}). Instead of pre-fetching all possible snippets upfront—which can be slow and resource-heavy—you can use variableResolver to fetch only the exact snippets used in the text on-demand.

Features Used: variableResolver, Deep object resolution.

// 1. Global Setup in parser-config.ts
import { initializeParser } from '@bou-co/parsing';

export const { createParser } = initializeParser(() => ({
  variableResolver: async (variableName, context) => {
    // Intercept any variable starting with 'snippets/'
    if (variableName.startsWith('snippets/')) {
      const slug = variableName.split('/')[1];

      // Fetch the snippet from the CMS
      const dataFromCMS = {
        'current-sale-title': '50% Off Summer Sale',
        'current-sale-description': 'Get the best deals of the season.',
      };
      const snippet = await Promise.resolve(slug.toUpperCase());

      // Cache the result globally so subsequent usages of this exact
      // variableName don't trigger another CMS fetch
      return snippet;
    }

    // Return undefined to let standard fallbacks or other variables take over
    return undefined;
  },
}));

// 2. Parser definition
import { createParser } from '../path-to/parser-config';

const pageParser = createParser({
  content: 'string',
});

// 3. Execution
// The raw data from the CMS contains a reference to a snippet
const rawDataFromCMS = {
  content: 'Check out our latest promo: {{snippets/current-sale-title}}! {{snippets/current-sale-description}}',
};

const result = await pageParser(rawDataFromCMS);

/* Result:
{
  "content": "Check out our latest promo: 50% Off Summer Sale! Get the best deals of the season."
}
*/

Advanced TypeScript Generation & Utilities

Why: Hand-writing types for CMS or 3rd-party API responses is brittle. Bou Parsing allows you to infer exact TypeScript interfaces directly from your parser definitions.

Features Used: ParserReturnValue, typed<T>, optional<T>, Module Declaration Overrides.

import { ParserReturnValue, typed, optional } from '@bou-co/parsing';
import { createParser } from '../path-to/parser-config';

// 1. Extend global context for strict type safety inside functions
declare module '@bou-co/parsing' {
  interface FunctionalContext {
    userRole: 'admin' | 'guest';
  }
}

// 2. Define custom interfaces
interface Author {
  name: string;
  title?: string;
}

// 3. Create the Parser
const articleParser = createParser({
  title: 'string',
  category: typed<'blog' | 'news'>, // Forces union type instead of generic 'string'
  author: optional<Author>, // Custom complex interface, explicitly optional
  canEdit: ({ userRole }) => userRole === 'admin', // userRole is typed!
});

// 4. Extract the exact TypeScript Type
export type Article = ParserReturnValue<typeof articleParser>;

/*
Article type equals:
interface Article {
  title?: string;
  category?: 'blog' | 'news';
  author?: Author | undefined;
  canEdit?: boolean;
}
*/

const rawData = { title: 'Hello', category: 'blog', author: { name: 'Jane' } };
const result = await articleParser(rawData, { userRole: 'admin' });

/* Result:
{ "title": "Hello", "category": "blog", "author": { "name": "Jane" }, "canEdit": true }
*/

Global Localization via Transformers

Why: Content models often return localized data as objects (e.g. { en: 'Text', es: 'Texto' }). Rather than parsing this manually in every component, transformers intercept and resolve the correct locale automatically across your entire dataset.

Features Used: transformers, Context variables.

// 1. Configure the transformer
import { initializeParser } from '@bou-co/parsing';

const localize = {
  when: ({ data }) => typeof data === 'object' && ('en' in data || 'es' in data),
  then: ({ data, locale = 'en' }) => data[locale] || data['en'], // Fallback to en
};

export const { createParser } = initializeParser({
  transformers: { localize },
});

// 2. Create the Parser
import { createParser } from '../path-to/parser-config';

const pageParser = createParser({
  heading: 'string',
  body: 'string',
});

// 3. Execution
const rawDataFromCMS = {
  heading: { en: 'Welcome', es: 'Bienvenido' },
  body: { en: 'Content', es: 'Contenido' },
};

const resultEn = await pageParser(rawDataFromCMS, { locale: 'en' });

/* Result: { "heading": "Welcome", "body": "Content" } */

const resultEs = await pageParser(rawDataFromCMS, { locale: 'es' });

/* Result: { "heading": "Bienvenido", "body": "Contenido" } */

Client-Side React Integration

Why: When running the parser directly inside a React component, handling asynchronous resolution and states can be tedious. The useParserValue hook abstracts this safely.

Features Used: useParserValue

import React from 'react';
import { useParserValue } from '@bou-co/parsing/react';
import { createParser } from '../path-to/parser-config';

const userParser = createParser({
  name: 'string',
  profileUrl: async ({ data }) => `https://img.com/${data.id}`,
});

export const UserProfile = ({ rawData }) => {
  // Hook handles async resolution natively
  const { data: user, loading } = useParserValue(rawData, userParser);

  if (loading) return <div>Loading profile...</div>;

  return (
    <div>
      <h1>{user?.name}</h1>
      <img src={user?.profileUrl} alt="Profile" />
    </div>
  );
};

API Reference

Core Functions

initializeParser(config?)

Initializes the parsing engine with global settings (transformers, storage caching, variables, lifecycle hooks).

  • Returns: { createParser, resolveVariables, getVariableValue }

createParser(projection, options?)

Creates an executable parser function based on the provided schema projection.

  • Returns: An asynchronous parsing function that takes (rawData, contextOverride?).
  • Methods: .extend(newProjection), .withContext(newContext)

Utility Functions

typed<T>(value?)

Forces TypeScript to infer a specific custom type instead of basic primitives. Used inside projection definitions.

optional<T>(value?)

Similar to typed<T>, but explicitly marks the inferred TypeScript type as possibly undefined.

condition(when, then)

Helper to create conditional projection logic structurally, typically used inside @if.

get(path, from?)

Utility to easily pick nested string properties (e.g. get('user.address.street')) when writing custom value resolver functions.

toHash(data)

Deterministically hashes an object or primitive into a stable string. Highly useful for generating deterministic Cache/Storage keys in initializeParser.

useParserValue(data, parser)

React hook exported from @bou-co/parsing/react. Safely resolves async parsers inside React components, returning { data, loading, error }.


Developed by Bou