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

@frankfmy/validation

v1.1.2

Published

ArkType schemas for common identity, account, pagination, money and validation contracts.

Readme

@frankfmy/validation

ArkType runtime schemas and inferred TypeScript types for common identity, account, money, business, and pagination contracts.

npm · Source · Issues

Release status and installation

This README documents the 1.1.2 API contract. Check npm for available published versions and use a matching package version; unversioned installation follows the registry's current dist-tag:

bun add @frankfmy/validation

The package is ESM with TypeScript declarations and declares ArkType 2 and @frankfmy/core as runtime dependencies. It re-exports ArkType's type (also as t) and scope, so the examples do not need a separate direct ArkType install. If your application imports arktype itself, declare it as a direct dependency.

Runtime values versus types

Call names ending in Schema for runtime validation. Short names such as Email, INN, PasswordStrong, and UserCreate are type-only exports; importing them as functions will not work. Capitalization is UrlSchema and UuidSchema, not URL or UUID.

import { EmailSchema, type, type Email } from '@frankfmy/validation';

const result = EmailSchema('[email protected]');
if (result instanceof type.errors) {
  console.error(result.summary);
} else {
  const email: Email = result;
  console.log(email);
}

Calling a schema returns validated data or an ArkType type.errors collection. Do not test instanceof Error for this result. For applications that prefer a discriminated union or exceptions, use the exported wrappers:

import { EmailSchema, validate, validateOrThrow } from '@frankfmy/validation';

const result = validate(EmailSchema, 'not-an-email');
if (!result.success) console.error(result.errors); // string[]
else console.log(result.data);

const email = validateOrThrow(EmailSchema, '[email protected]');
// Throws an ordinary Error with a validation summary on invalid input.

Do not log entire validation inputs or password-related error summaries in production; error text may contain submitted values. Responses for domain-specific refinements currently contain Russian messages, even though this documentation is English.

Basic schemas and string factories

import {
  UrlSchema, UuidSchema, NonEmptyStringSchema,
  minLengthString, maxLengthString, rangeLengthString
} from '@frankfmy/validation';

UrlSchema('https://example.com');
UuidSchema('550e8400-e29b-41d4-a716-446655440000');
NonEmptyStringSchema('Hello');
minLengthString(3)('abc');
maxLengthString(5)('hello');
rangeLengthString(3, 10)('example');

EmailSchema validates an email string, UrlSchema a URL string, and UuidSchema specifically a UUID v4. String length constraints are inclusive. NonEmptyStringSchema requires length at least one, not non-whitespace text: it does not trim. Pass valid numeric bounds to the factories; they build ArkType constraints rather than sanitize configuration.

Russian identifiers

import {
  PhoneRUSchema, INNSchema, OGRNSchema, SNILSSchema, PassportRUSchema
} from '@frankfmy/validation';

PhoneRUSchema('+79161234567');
INNSchema('7707083893');       // Organization: 10 digits
INNSchema('500100732259');     // Individual: 12 digits
OGRNSchema('1027700132195');
SNILSSchema('11223344595');
PassportRUSchema('4510 123456'); // A string, not an object of passport fields

| Runtime schema | Check | | --- | --- | | PhoneRUSchema | Russian mobile-number pattern; landlines are rejected | | INN10Schema, INN12Schema, INNSchema | Organization, individual, or either INN length plus checksum | | OGRN13Schema, OGRN15Schema, OGRNSchema | Organization OGRN, entrepreneur OGRNIP, or either length plus checksum | | KPPSchema | Nine-digit KPP structure | | SNILSSchema | SNILS structure/checksum | | BIKSchema | Nine digits beginning with 04 | | PassportRUSchema | Ten digits after removing nondigit characters; no issuance or ownership check |

These refinements remove nondigit characters for checking only, then return the original string. They do not normalize stored values or query official registries. Normalize separately if your storage contract requires it. There are matching type-only exports (PhoneRU, INN10, INN12, INN, OGRN13, OGRN15, OGRN, KPP, SNILS, BIK, PassportRU).

Passwords and usernames

import { PasswordStrongSchema, PasswordSimpleSchema, UsernameSchema } from '@frankfmy/validation';

PasswordStrongSchema('Example-Passphrase-2026!');
PasswordSimpleSchema('atleast6');
UsernameSchema('example_user');
  • PasswordStrongSchema: at least eight characters, an ASCII uppercase letter, ASCII lowercase letter, digit, and one of the special characters recognized by its punctuation regex. It does not impose a maximum length or check breached passwords.
  • PasswordSimpleSchema: at least six characters only. It is not equivalent to the strong policy.
  • UsernameSchema: 2–50 characters from ASCII letters, digits, and underscore.

Validation is not hashing. Apply an application-specific maximum password/input size before expensive processing, and use a password KDF such as core's hashPassword for storage. Never log passwords.

Money and numbers

import {
  MoneySchema, PositiveMoneySchema, PercentSchema,
  TenderBudgetSchema, RatingSchema, PositiveIntSchema, NonNegativeIntSchema
} from '@frankfmy/validation';

MoneySchema(1234.56);
PositiveMoneySchema(1);
PercentSchema(75.5);
TenderBudgetSchema(1_000_000);
RatingSchema(4.5);
PositiveIntSchema(1);
NonNegativeIntSchema(0);

| Schema | Accepted numeric range | | --- | --- | | MoneySchema | At least 0 | | PositiveMoneySchema | Greater than 0 | | PercentSchema | 0–100, inclusive | | TenderBudgetSchema | At least 10,000 | | RatingSchema | 1–5, inclusive; fractions are allowed | | PositiveIntSchema | Integer greater than 0 | | NonNegativeIntSchema | Integer at least 0 |

These schemas do not coerce numeric strings or enforce currency precision. Add explicit finite-number and business upper bounds when required by your application. Matching short names are inferred types, not validators.

Account and form schemas

import { UserCreateSchema, validate, type UserCreate } from '@frankfmy/validation';

const result = validate(UserCreateSchema, {
  email: '[email protected]',
  name: 'Иван Иванов',
  phone: '+79161234567', // Optional
  password: 'Example-Passphrase-2026!'
});

if (result.success) {
  const user: UserCreate = result.data;
  // Pass user to application-owned registration logic; do not log its password.
}

UserCreateSchema requires name, not firstName, lastName, or middleName. It does not create a user, check uniqueness, or hash a password.

| Schema | Fields | | --- | --- | | UserCreateSchema | Required email, name (2–100 characters), strong password; optional phone | | UserLoginSchema | Required email and nonempty password | | UserUpdateSchema | Optional name (2–100), phone, URL avatar; an empty object is valid | | ChangePasswordSchema | Nonempty currentPassword, strong newPassword, and matching confirmPassword | | ResetPasswordSchema | Nonempty token, strong newPassword, and matching confirmPassword |

Password-change schemas compare confirmation values only. They do not verify the current credential, validate a reset token against a store, or enforce token expiry or single use. Unknown object properties are not rejected by an explicit strict-object policy here; do not assume this is a field-stripping boundary.

Business objects

| Schema | Fields | | --- | --- | | LegalEntitySchema | Required name (1–500), inn (INN10), ogrn (OGRN13); optional kpp, address (1–500) | | IndividualEntrepreneurSchema | Required name (1–500), inn (INN12), ogrnip (OGRN15); optional address (1–500) | | BankDetailsSchema | Required nonempty bankName, valid bik, string accountNumber, string corrAccount |

BankDetailsSchema does not enforce a 20-digit account length or validate the account/BIK checksum. Add those checks yourself, for example with validateAccount from @frankfmy/core. The object schemas have matching inferred type exports without Schema.

Pagination and search

import { PaginationSchema, SearchParamsSchema } from '@frankfmy/validation';

PaginationSchema({ page: 1, limit: 20, sortBy: 'createdAt', sortOrder: 'desc' });
SearchParamsSchema({ query: 'example', page: 1, limit: 20 });

All fields are optional. page and limit must be integers at least one; there are no inserted defaults or upper limits. sortOrder is 'asc' | 'desc'. sortBy is an arbitrary string, not an approved SQL identifier: map it through an application-owned allowlist before building queries. URL query strings must be converted and bounded explicitly rather than passed as numeric strings.

Composition and scope

import { type, INNSchema, PhoneRUSchema, v } from '@frankfmy/validation';

const CompanyData = type({
  name: 'string >= 3',
  inn: INNSchema,
  email: 'string.email',
  phone: PhoneRUSchema
});
const result = CompanyData({
  name: 'Example company', inn: '7707083893',
  email: '[email protected]', phone: '+79161234567'
});

v.email('[email protected]');
v.money(123.45);

Use ArkType 2's .narrow((value, ctx) => condition || ctx.mustBe('...')) for custom refinements, not an unsupported validator options object. The exported validationScope and its runtime export v contain email, url, uuid, money, positiveInt, nonEmptyString, and passwordSimple; they are not a scope containing every schema in this package.

Explicit validation in Elysia

This example uses Elysia's basic body schema first, then ArkType for the domain contract. It returns only a placeholder acceptance response, not an account or authentication implementation:

import { Elysia, t } from 'elysia';
import { UserCreateSchema, validate } from '@frankfmy/validation';

const app = new Elysia().post('/register', ({ body, set }) => {
  const result = validate(UserCreateSchema, body);
  if (!result.success) {
    set.status = 400;
    return { accepted: false }; // Avoid echoing password-containing validation details.
  }
  return { accepted: true, name: result.data.name };
}, {
  body: t.Object({
    email: t.String(), name: t.String(), password: t.String(),
    phone: t.Optional(t.String())
  })
});

Install elysia separately if using this integration. The validation package itself has no Elysia dependency.

Author and license

By Artem Prianishnikov (FrankFMY).

Email · Proton Mail · Telegram · LinkedIn · X · Website · GitHub · npm

Current author-owned source is Apache-2.0; see LICENSE and NOTICE. Published versions 1.1.1 and earlier remain under their original PolyForm Noncommercial license. This does not retroactively relicense those artifacts or third-party dependencies.