@arkyn/shared

v3.0.2

Published

Comprehensive collection of reusable utilities for data formatting, validation, generation, and manipulation, featuring Brazilian document validators and financial tools.

Readme

@arkyn/shared

Comprehensive collection of reusable utilities for data formatting, validation, generation, and manipulation, featuring Brazilian document validators and financial tools.

npm version License TypeScript

🎯 What it solves

@arkyn/shared is a dependency-light, framework-agnostic toolkit for formatting, validating, generating, and parsing common data types in JavaScript/TypeScript applications. It has strong support for Brazilian documents (CPF, CNPJ, CEP) and locale-aware financial formatting, alongside general-purpose helpers for dates, JSON, strings, IDs, and sensitive-data masking. Because it has no framework assumptions, the same functions run identically on the client and the server β€” it's the shared foundation used by both @arkyn/components and @arkyn/server.

✨ Features

  • πŸ“… Date formatting & parsing β€” configurable input/output formats with timezone shifting
  • πŸ’° Currency & financial formatting β€” locale-aware currency strings and card installment math
  • πŸ“ Text formatting β€” capitalization, ellipsis truncation, digit masking
  • πŸ‡§πŸ‡· Brazilian document formatting β€” CPF, CNPJ, CEP
  • πŸ“ž Phone formatting β€” country-mask-aware phone number formatting via libphonenumber-js
  • πŸ”§ Generators β€” UUID (v4/v7) IDs, URL-friendly slugs, deterministic colors from strings
  • 🧩 JSON parsers β€” pretty-printing, large-field truncation, sensitive-key masking
  • πŸ›‘οΈ Sensitive-data masking β€” hide digits/fields in strings and JSON payloads
  • 🌐 HTML utilities β€” detect and strip HTML markup
  • 🧹 String/number utilities β€” strip non-numeric characters, strip currency symbols, ensure quoting

πŸ“‹ Prerequisites

  • Node.js >=18.0.0
  • Bun >=1.0.0
  • libphonenumber-js >=1.13.7 β€” optional peer dependency, required only if you use formatToPhone or findCountryMask

πŸ“¦ Installation

ESM only. This package ships as native ES modules with no CommonJS build β€” use import, not require().

npm install @arkyn/shared

If you plan to use formatToPhone or findCountryMask, also install the peer dependency:

npm install libphonenumber-js

πŸš€ Quick Start

import {
  formatToCpf,
  formatToCurrency,
  generateId,
  generateSlug,
} from "@arkyn/shared";

// Format a raw CPF string (throws if it doesn't have 11 digits)
const cpf = formatToCpf("12345678909");
// "123.456.789-09"

// Format currency using a currency code from @arkyn/templates
const price = formatToCurrency(1234.56, "BRL");
// "R$ 1.234,56"

// Generate a UUID v4
const id = generateId("text", "v4");
// "550e8400-e29b-41d4-a716-446655440000"

// Generate a URL-friendly slug
const slug = generateSlug("Hello, World! This is a Test.");
// "hello-world-this-is-a-test"

πŸ“– API Reference

Formats

formatDate

Formats a date (and optional time) string into a custom output format. Calculations are done in UTC+0; use the timezone parameter to shift the result. Throws Error if the resulting date is invalid or inputFormat is not recognized.

import { formatDate } from "@arkyn/shared";

formatDate(["25/12/2023", "15:30:00"], "brazilianDate", "YYYY-MM-DD hh:mm");
// "2023-12-25 15:30"

formatDate(["2023-12-25", "15:30:00"], "timestamp", "DD/MM/YYYY hh:mm", -3);
// "2023-12-25 12:30"

formatJsonObject

Formats a JSON-compatible value (object, array, string, or primitive) into a human-readable, indented string. Strings that parse as JSON are recursively formatted.

import { formatJsonObject } from "@arkyn/shared";

const obj = { name: "John", age: 30, hobbies: ["reading", "gaming"] };
formatJsonObject(obj, 0);
// {
//   "name": "John",
//   "age": 30,
//   "hobbies": [
//     "reading",
//     "gaming"
//   ]
// }

formatJsonString

Parses a JSON string and returns a pretty-printed representation. Throws Error if the input is not valid JSON.

import { formatJsonString } from "@arkyn/shared";

formatJsonString('{"name":"John","hobbies":["reading","gaming"]}');
// {
//   "name": "John",
//   "hobbies": [
//     "reading",
//     "gaming"
//   ]
// }

formatToCapitalizeFirstWordLetter

Capitalizes the first letter of each space-separated word and lowercases the rest.

import { formatToCapitalizeFirstWordLetter } from "@arkyn/shared";

formatToCapitalizeFirstWordLetter("HELLO WORLD"); // "Hello World"

formatToCep

Formats a string into the Brazilian postal code (CEP) pattern XXXXX-XXX. Throws Error if the cleaned input doesn't contain exactly 8 numeric digits.

import { formatToCep } from "@arkyn/shared";

formatToCep("12345678"); // "12345-678"

formatToCnpj

Formats a string into the CNPJ pattern XX.XXX.XXX/XXXX-XX. Throws Error if the cleaned input doesn't contain exactly 14 numeric digits.

import { formatToCnpj } from "@arkyn/shared";

formatToCnpj("12345678000195"); // "12.345.678/0001-95"

formatToCpf

Formats a string into the CPF pattern XXX.XXX.XXX-XX. Throws Error if the cleaned input doesn't contain exactly 11 numeric digits.

import { formatToCpf } from "@arkyn/shared";

formatToCpf("12345678909"); // "123.456.789-09"

formatToCurrency

Formats a number into a locale-aware currency string using Intl.NumberFormat, based on a currency code from @arkyn/templates. Set config.showPrefix to false to omit the currency symbol. Throws Error for unsupported currency codes.

import { formatToCurrency } from "@arkyn/shared";

formatToCurrency(1234.56, "BRL"); // "R$ 1.234,56"
formatToCurrency(1234.56, "USD", { showPrefix: false }); // "1,234.56"

formatToEllipsis

Truncates a string to maxLength, avoiding breaking mid-word, and appends "..." if truncation occurred.

import { formatToEllipsis } from "@arkyn/shared";

formatToEllipsis("Hello, world!", 5); // "Hello..."

formatToHiddenDigits

Replaces specific digit positions in a string with a masking character, leaving non-digit characters unchanged. options.range can be a positive number (first N digits), a negative number (last N digits), or a [start, end] tuple (1-indexed, inclusive). Defaults to hiding the first 3 digits with "*".

import { formatToHiddenDigits } from "@arkyn/shared";

formatToHiddenDigits("123-456-7890", { range: 3 }); // "***-456-7890"
formatToHiddenDigits("123-456-7890", { range: [4, 6], hider: "#" });
// "123-###-7890"

formatToPhone

Formats a phone number according to the country mask defined in @arkyn/templates, parsing it with libphonenumber-js. Throws Error if the number is invalid or no country mask is found. Requires the libphonenumber-js peer dependency.

import { formatToPhone } from "@arkyn/shared";

formatToPhone("+5534920524282"); // "(34) 92052-4282"
formatToPhone("+12125550199"); // "(212) 555-0199"

Generators

generateColorByString

Generates a deterministic hexadecimal color code from a hash of the input string β€” the same input always produces the same color.

import { generateColorByString } from "@arkyn/shared";

generateColorByString("example"); // "#5e8f9a" (consistent for the same input)

generateId

Generates a UUID in the requested version ("v4" random or "v7" time-ordered) and representation ("text" string or "binary" Uint8Array). Throws Error for an invalid type/format combination.

import { generateId } from "@arkyn/shared";

generateId("text", "v4"); // "550e8400-e29b-41d4-a716-446655440000"
generateId("binary", "v7"); // Uint8Array([...])

generateSlug

Converts a string into a URL-friendly slug: strips accents, removes non-alphanumeric characters (except spaces/hyphens), lowercases, replaces spaces with hyphens, and trims/collapses hyphens.

import { generateSlug } from "@arkyn/shared";

generateSlug("Hello, World! This is a Test.");
// "hello-world-this-is-a-test"

Parsers

parseLargeFields

Parses a JSON string and truncates string fields exceeding maxLength (default 1000), replacing them with a message indicating the original length. Traverses nested objects/arrays. Throws Error if the input is not valid JSON.

import { parseLargeFields } from "@arkyn/shared";

const json = JSON.stringify({
  name: "John",
  description: "A very long description that exceeds the maximum length...",
});

parseLargeFields(json, 50);
// '{"name":"John","description":"To large information: field as 57 characters"}'

parseSensitiveData

Parses a JSON string and replaces the values of the given keys with "****", recursively, including JSON-encoded string values. Defaults to masking password, confirmPassword, and creditCard. Returns the original string unchanged if it is not valid JSON (does not throw).

import { parseSensitiveData } from "@arkyn/shared";

const jsonString = JSON.stringify({
  username: "user123",
  password: "secret",
  profile: { creditCard: "1234-5678-9012-3456" },
});

parseSensitiveData(jsonString, ["password", "creditCard"]);
// '{"username":"user123","password":"****","profile":{"creditCard":"****"}}'

parseToDate

Parses a date (and optional time) string into a Date object. Calculations are done in UTC+0; use timezone to shift the result. Throws Error if the resulting date is invalid or inputFormat is not recognized.

import { parseToDate } from "@arkyn/shared";

parseToDate(["25/12/2023", "15:30:00"], "brazilianDate", -3);
// Date: 2023-12-25T12:30:00.000Z

parseToDate(["2023-12-25"], "timestamp");
// Date: 2023-12-25T00:00:00.000Z

Services

ValidateDateService

Class used internally by formatDate and parseToDate to validate date components and input-format strings β€” enforces 4-digit years, month/day ranges, month-specific day counts, and leap-year rules. validateDateParts and validateInputFormat both throw Error on invalid input.

import { ValidateDateService } from "@arkyn/shared";

const service = new ValidateDateService();
service.validateDateParts(2024, 2, 29); // OK β€” leap year
service.validateDateParts(2023, 2, 29); // throws β€” not a leap year
service.validateInputFormat("brazilianDate"); // OK
service.validateInputFormat("custom"); // throws

Utilities

calculateCardInstallment

Calculates the total price and per-installment price for a card payment plan with compound interest. No interest is applied when fees is 0 or numberInstallments is 1. Throws Error if numberInstallments <= 0 or fees < 0.

import { calculateCardInstallment } from "@arkyn/shared";

calculateCardInstallment({ cashPrice: 1000, numberInstallments: 12, fees: 0.02 });
// { totalPrice: 1124.62, installmentPrice: 93.72 }

ensureQuotes

Wraps a string in double quotes unless it is already enclosed in single or double quotes.

import { ensureQuotes } from "@arkyn/shared";

ensureQuotes("example"); // '"example"'
ensureQuotes('"already quoted"'); // '"already quoted"'

findCountryMask

Resolves the matching phone mask (using "_" as digit placeholders) and country metadata for an E.164 phone number, using libphonenumber-js and @arkyn/templates. For countries with multiple mask lengths (e.g. Brazil with/without the ninth digit), the mask matching the number's digit count is returned. Throws Error if the number is invalid or no mask is found. Requires the libphonenumber-js peer dependency.

import { findCountryMask } from "@arkyn/shared";

const [mask, country] = findCountryMask("+5511999999999");
// mask: "(__) _____-____"
// country.name: "Brazil"

isHtml

Checks whether a string contains HTML markup (opening or closing tags), case-insensitively.

import { isHtml } from "@arkyn/shared";

isHtml("<p>Hello world</p>"); // true
isHtml("Plain text"); // false

removeCurrencySymbols

Removes currency symbols (R$, $, €, Β₯, Β£, and other Unicode currency symbols) from a string and trims whitespace.

import { removeCurrencySymbols } from "@arkyn/shared";

removeCurrencySymbols("R$13,45"); // "13,45"
removeCurrencySymbols("€99.99"); // "99.99"

removeNonNumeric

Strips all non-numeric characters from a string.

import { removeNonNumeric } from "@arkyn/shared";

removeNonNumeric("abc123def456"); // "123456"

stripHtmlTags

Removes HTML tags from a string, including <script> and <style> blocks and HTML comments.

import { stripHtmlTags } from "@arkyn/shared";

stripHtmlTags("<p>Hello <strong>World</strong></p>"); // "Hello World"

πŸ“š Documentation

Full documentation: https://docs.arkyn.dev/docs/shared/introduction

πŸ“„ License

This project is licensed under the Apache 2.0 License - see the LICENSE file for details.