The core package responsible for type definitions, sheet data parsing, variable substitution, and validation of the error content JSON DSL. Has zero external dependencies.
Installation
pnpm add @sanghyuk-2i/huh-corenpm install @sanghyuk-2i/huh-coreyarn add @sanghyuk-2i/huh-coreCDN
<!-- unpkg -->
<script src="https://unpkg.com/@sanghyuk-2i/huh-core"></script>
<!-- jsDelivr -->
<script src="https://cdn.jsdelivr.net/npm/@sanghyuk-2i/huh-core"></script>
<!-- Pin version -->
<script src="https://unpkg.com/@sanghyuk-2i/huh-core@0.1.0"></script>When loaded via CDN, all APIs are available through the window.HuhCore global variable:
const { resolveError, parseSheetData, validateConfig, renderTemplate } = HuhCore;Types
ERROR_TYPES (constant)
Uppercase constants for built-in error types.
const ERROR_TYPES = {
TOAST: 'TOAST',
MODAL: 'MODAL',
PAGE: 'PAGE',
} as const;ACTION_TYPES (constant)
Uppercase constants for built-in action types.
const ACTION_TYPES = {
REDIRECT: 'REDIRECT',
RETRY: 'RETRY',
BACK: 'BACK',
DISMISS: 'DISMISS',
} as const;SEVERITY_LEVELS (constant)
Uppercase constants for built-in severity levels.
const SEVERITY_LEVELS = {
INFO: 'INFO',
WARNING: 'WARNING',
ERROR: 'ERROR',
CRITICAL: 'CRITICAL',
} as const;ErrorType
An extensible string type that allows built-in types + custom types. Values are managed in uppercase.
type BuiltInErrorType = 'TOAST' | 'MODAL' | 'PAGE';
type ErrorType = BuiltInErrorType | (string & {}); // Freely extensible: 'BANNER', 'SNACKBAR', etc.ActionType
Allows built-in action types + custom action types.
type BuiltInActionType = 'REDIRECT' | 'RETRY' | 'BACK' | 'DISMISS';
type ActionType = BuiltInActionType | (string & {}); // Freely extensible: 'OPEN_CHAT', 'SHARE', etc.Severity
Allows built-in severity levels + custom severity levels.
type BuiltInSeverity = 'INFO' | 'WARNING' | 'ERROR' | 'CRITICAL';
type Severity = BuiltInSeverity | (string & {}); // Freely extensible custom severityErrorAction
interface ErrorAction {
label: string; // Action button text
type: ActionType; // Action kind
target?: string; // URL to navigate to for redirect
}ErrorEntry
Represents a single error entry.
interface ErrorEntry {
type: ErrorType; // Error display type
message: string; // Error message (may contain template variables)
title?: string; // Title (for modal, page)
image?: string; // Image URL (for page)
severity?: Severity; // Severity level
action?: ErrorAction; // User action
}ErrorConfig
The complete error configuration. A map of ErrorEntry keyed by trackId.
type ErrorConfig = Record<string, ErrorEntry>;{
"ERR_LOGIN_FAILED": {
"type": "TOAST",
"message": "Login failed"
},
"ERR_NOT_FOUND": {
"type": "PAGE",
"message": "Page not found",
"title": "404"
},
"ERR_MAINTENANCE": {
"type": "BANNER",
"message": "Server is under maintenance"
}
}ResolvedError
The return type of the resolveError function. An ErrorEntry with trackId added.
type ResolvedError = ErrorEntry & {
trackId: string;
}LocalizedErrorConfig
Multi-language error configuration. A map of ErrorConfig keyed by locale code.
type LocalizedErrorConfig = Record<string, ErrorConfig>;const locales: LocalizedErrorConfig = {
ko: {
ERR_AUTH: { type: 'MODAL', message: 'Session expired', title: 'Auth Error' },
},
en: {
ERR_AUTH: { type: 'MODAL', message: 'Session expired', title: 'Auth Error' },
},
};CrossLocaleValidationResult
The return type of the validateLocales function.
interface CrossLocaleValidationResult {
valid: boolean; // true if no errors
errors: CrossLocaleValidationError[]; // Error list (missing trackId, etc.)
warnings: CrossLocaleValidationError[]; // Warning list (type/actionType mismatch, etc.)
}
interface CrossLocaleValidationError {
trackId: string;
field?: string;
locales: string[]; // List of locales with the issue
message: string;
}ValidationResult
interface ValidationResult {
valid: boolean; // true if no errors
errors: ValidationError[]; // Error list (missing type, invalid values, etc.)
warnings: ValidationError[]; // Warning list (title used on toast, etc.)
}
interface ValidationError {
trackId?: string;
field?: string;
message: string;
}HuhPlugin
Defines a plugin that hooks into the error and action lifecycle.
interface HuhPlugin {
name: string;
onError?: (error: ResolvedError, context: HuhErrorContext) => void;
onAction?: (error: ResolvedError, action: ErrorAction) => void;
}name— Plugin identifier (used in warning messages)onError— Called whenhuhresolves an erroronAction— Called when a user triggers an action
HuhErrorContext
Context passed to the onError plugin hook.
interface HuhErrorContext {
trackId: string;
variables?: Record<string, string>;
locale?: string;
severity?: Severity;
}Functions
parseSheetData(rows: string[][]): ErrorConfig
Converts raw data from a data source (header + data row array) into an ErrorConfig. All sources (Google Sheets, Airtable, Notion, CSV, XLSX) use the same 2D string array format.
rows string[][] (required)
2D string array where the first row is the header.
Throws
- Error if fewer than 2 rows
- Error if
trackId,type,messageheaders are missing - Error if
typeis empty
TIP
type and actionType values are automatically converted to uppercase. Whether you enter toast, Toast, or TOAST in the data source, it will be processed as TOAST. severity values are also automatically converted to uppercase. Custom types (BANNER, SNACKBAR, etc.) can be freely used in addition to built-in types.
Example
import { parseSheetData } from '@sanghyuk-2i/huh-core';
const rows = [
[
'trackId',
'type',
'message',
'title',
'image',
'severity',
'actionLabel',
'actionType',
'actionTarget',
],
['ERR_001', 'toast', 'An error occurred', '', '', '', '', ''],
['ERR_002', 'modal', 'Session expired', 'Notice', '', 'OK', 'dismiss', ''],
['ERR_003', 'banner', 'Server under maintenance', '', '', 'Contact Us', 'open_chat', ''],
];
const config = parseSheetData(rows);
// {
// ERR_001: { type: 'TOAST', message: 'An error occurred' },
// ERR_002: { type: 'MODAL', message: 'Session expired', title: 'Notice', action: { label: 'OK', type: 'DISMISS' } },
// ERR_003: { type: 'BANNER', message: 'Server under maintenance', action: { label: 'Contact Us', type: 'OPEN_CHAT' } }
// }resolveError(config, trackId, variables?): ResolvedError
Looks up an error by trackId and substitutes template variables in the message/title/action.
config ErrorConfig (required)
Error configuration object.
trackId string (required)
Error ID to look up.
variables Record<string, string>
Template variables to substitute (optional).
Throws
Unknown trackIderror iftrackIdis not found in config
Example
import { resolveError } from '@sanghyuk-2i/huh-core';
const config = {
ERR_SESSION: {
type: 'MODAL',
message: "{{userName}}'s session has expired",
title: '{{userName}}',
action: { label: 'Login Again', type: 'REDIRECT', target: '/login' },
},
};
const resolved = resolveError(config, 'ERR_SESSION', { userName: 'Jane' });
// resolved.message -> "Jane's session has expired"
// resolved.title -> "Jane"
// resolved.trackId -> "ERR_SESSION"renderTemplate(template, variables): string
Substitutes placeholders in a string. Unmatched variables are left as-is.
template string (required)
Template string containing placeholders.
variables Record<string, string> (required)
Variable map to substitute.
Example
import { renderTemplate } from '@sanghyuk-2i/huh-core';
renderTemplate('Hello {{name}}!', { name: 'Jane' });
// -> "Hello Jane!"
renderTemplate('{{a}} and {{b}}', { a: 'Hello' });
// -> "Hello and {{b}}" (unmatched variables are preserved)validateConfig(config): ValidationResult
Validates an ErrorConfig.
Validation Rules (errors)
| Condition | Message |
|---|---|
type is empty | Missing required field: type |
message is empty | Missing required field: message |
action.label is empty | Action is missing required field: label |
REDIRECT type without target | Action type "REDIRECT" requires a target URL |
TIP
Custom types (BANNER, SNACKBAR, etc.) pass without errors. The validator does not check the type value itself -- it only raises an error when the value is empty.
Warning Rules (warnings)
Warnings are only generated for built-in types. Custom types do not trigger warnings.
| Condition | Message |
|---|---|
| Config is empty | Config is empty |
TOAST has title | Toast errors typically do not display a title |
TOAST has image | Toast errors typically do not display an image |
PAGE has no action | Page errors should provide an action |
Unrecognized severity value | Unrecognized severity "X". Built-in levels: INFO, WARNING, ERROR, CRITICAL |
Example
import { validateConfig } from '@sanghyuk-2i/huh-core';
const result = validateConfig({
ERR_001: { type: 'TOAST', message: '' },
ERR_002: { type: 'MODAL', message: 'OK', action: { label: 'Go', type: 'REDIRECT' } },
ERR_003: { type: 'BANNER', message: 'Custom type works' }, // Custom type — passes without errors
});
// result.valid -> false
// result.errors -> [
// { trackId: 'ERR_001', field: 'message', message: 'Missing required field: message' },
// { trackId: 'ERR_002', field: 'action.target', message: 'Action type "REDIRECT" requires a target URL' },
// ]validateLocales(locales): CrossLocaleValidationResult
Validates cross-locale consistency of multi-language error configurations.
locales LocalizedErrorConfig (required)
Map of error configurations keyed by locale code.
Validation Rules (errors)
| Condition | Message |
|---|---|
| A trackId in one locale is missing in another | trackId "XXX" is missing in locale(s): ... |
Warning Rules (warnings)
| Condition | Message |
|---|---|
| Same trackId has different types | trackId "XXX" has different "type" across locales |
| Same trackId has different action types | trackId "XXX" has different "action.type" across locales |
TIP
If there is only one locale, validation is skipped and returns valid: true.
Example
import { validateLocales } from '@sanghyuk-2i/huh-core';
const result = validateLocales({
ko: {
ERR_AUTH: { type: 'MODAL', message: 'Session expired' },
ERR_NETWORK: { type: 'TOAST', message: 'Network error' },
},
en: {
ERR_AUTH: { type: 'MODAL', message: 'Session expired' },
// ERR_NETWORK missing -> error
},
});
// result.valid -> false
// result.errors -> [{ trackId: 'ERR_NETWORK', locales: ['en'], message: '...' }]runPluginHook(plugins, hook, ...args): void
Executes a specific hook on all plugins, catching and warning on any errors thrown by individual plugins.
plugins HuhPlugin[] (required)
Array of plugins to execute.
hook 'onError' | 'onAction' (required)
The hook name to execute.
args Parameters (required)
Arguments to pass to the hook function.
Example
import { runPluginHook } from '@sanghyuk-2i/huh-core';
import type { HuhPlugin } from '@sanghyuk-2i/huh-core';
const plugins: HuhPlugin[] = [myPlugin(), anotherPlugin()];
// Called internally by framework providers — typically not called directly
runPluginHook(plugins, 'onError', resolvedError, { trackId: 'ERR_001' });
runPluginHook(plugins, 'onAction', resolvedError, action);