@orkestrel/validator
v0.0.1
Published
Runtime validation toolkit for TypeScript
Maintainers
Readme
@orkestrel/validator
Runtime validation toolkit for TypeScript — zero dependencies, fully typed.
@orkestrel/validator gives you two independent tools that work together:
- String validation — a declarative rule engine for validating user input. Define which rules apply, call
validateInput, and get back a structured result with every failing rule and its message. - Type guards — a composable system for narrowing
unknownvalues to precise TypeScript types at runtime. Over 60 built-inis*guards plus a full set of builder functions for constructing guards over objects, arrays, tuples, enums, and more.
Both axes are ESM-first, tree-shakeable, and produce no side effects.
Installation
npm install @orkestrel/validatorAll exports are available from the package root:
import { validateInput, isString, objectOf } from '@orkestrel/validator'String Validation
Use the rule engine when you need to validate free-form user input — form fields, query parameters, CLI arguments — and want structured error feedback.
Defining rules
Describe constraints as a plain object:
import type { ValidationRules } from '@orkestrel/validator'
const usernameRules: ValidationRules = {
required: true,
minimum: 3,
maximum: 20,
alphanumeric: true,
}
const emailRules: ValidationRules = {
required: true,
email: true,
}
const commentRules: ValidationRules = {
required: true,
minimum: 1,
maximum: 500,
}Validating input
import { validateInput } from '@orkestrel/validator'
const result = validateInput('Ada123', usernameRules)
if (result.valid) {
// result.errors is []
} else {
for (const error of result.errors) {
console.log(error.rule, error.message)
}
}validateInput evaluates every active rule and collects all failures — it does not stop at the first one. The returned ValidationResult always has a valid boolean and an errors array.
Quick pass/fail check
When you only need a boolean and don't need the error list:
import { testInput } from '@orkestrel/validator'
if (testInput('Ada123', usernameRules)) {
// input is valid
}Built-in rules
| Rule | Value | Fails when |
| -------------- | ------------------- | -------------------------------------------------------- |
| required | true | The trimmed input is empty |
| minimum | number ≥ 0 | input.length < minimum |
| maximum | number ≥ 0 | input.length > maximum |
| pattern | regex string | Input does not match the compiled pattern |
| email | true | Input does not look like [email protected] |
| url | true | Input does not begin with http:// or https:// |
| numeric | true | Input is not an integer or decimal (optionally negative) |
| integer | true | Input is not a whole integer (optionally negative) |
| alphanumeric | true | Input contains anything other than letters and digits |
| custom | ValidatorFunction | The function returns false or an error string |
Rules set to false or omitted are skipped. Rules are always evaluated in this order:
required → minimum → maximum → pattern → email → url → numeric → integer → alphanumeric → custom
Default error messages
Each built-in rule produces a default message when it fails:
| Rule | Default message |
| -------------- | ---------------------------------------- |
| required | 'This field is required' |
| minimum | 'Must be at least N characters' |
| maximum | 'Must be at most N characters' |
| pattern | 'Must match pattern: <pattern>' |
| email | 'Must be a valid email address' |
| url | 'Must be a valid URL' |
| numeric | 'Must be a numeric value' |
| integer | 'Must be an integer' |
| alphanumeric | 'Must contain only letters and digits' |
Custom validators
Every rule slot accepts a ValidatorFunction instead of its canonical value type. The function replaces the built-in check entirely for that slot.
type ValidatorFunction = (input: string) => boolean | stringReturn true when the input passes. Return false or a message string when it fails.
const rules: ValidationRules = {
// Replace the built-in minimum check with custom logic
minimum: (input) => input.startsWith('z') || 'Must start with z',
// The custom slot has no built-in behaviour — always use a function
custom: (input) => input !== 'forbidden' || 'That value is not allowed',
}Using a function on a rule slot still uses that slot's position in the evaluation order — a minimum function runs in the minimum position.
Collecting multiple errors
const result = validateInput('', {
required: true,
minimum: 3,
email: true,
})
// {
// valid: false,
// errors: [
// { rule: 'required', message: 'This field is required' },
// { rule: 'minimum', message: 'Must be at least 3 characters' },
// { rule: 'email', message: 'Must be a valid email address' },
// ]
// }Validating rule objects at runtime
If rules arrive from an untrusted source (config file, API), assert them before use:
import { assertValidationRules } from '@orkestrel/validator'
assertValidationRules(untrustedRules) // throws ValidatorError when invalid
// now untrustedRules is narrowed to ValidationRulesassertValidationRules checks:
- All keys are supported rule names
- All values are the correct kind for their rule (
booleanfor flag rules,number ≥ 0forminimum/maximum, string forpattern) minimum ≤ maximumwhen both are provided as numberspatterncompiles as a valid regular expression
ValidatorError
Invalid rule configuration throws a ValidatorError — a subclass of Error with two extra fields:
import { ValidatorError, isValidatorError } from '@orkestrel/validator'
try {
assertValidationRules({ minimum: 10, maximum: 3 })
} catch (error) {
if (isValidatorError(error)) {
console.log(error.code) // 'INVALID_RULE_VALUE'
console.log(error.message) // 'Minimum cannot be greater than maximum'
console.log(error.context.rule) // 'minimum'
console.log(error.context.value) // 10
}
}The three error codes:
| Code | When thrown |
| -------------------- | -------------------------------------------------------------------- |
| INVALID_RULES | The rule object has unsupported keys or wrong value kinds |
| INVALID_RULE_VALUE | A numeric rule is out of range (minimum > maximum, negative value) |
| INVALID_PATTERN | The pattern string is not a valid regular expression |
Use isValidatorError in catch blocks to distinguish package errors from unexpected throws.
Type Guards
Use type guards when you need to narrow unknown values — incoming API responses, JSON.parse output, event payloads, dynamic config — to specific TypeScript types.
All guards follow the same contract:
// Returns true and narrows the type — never throws
isString(value) // value is string
isNumber(value) // value is numberPrimitive guards
import {
isNull,
isUndefined,
isDefined,
isString,
isNumber,
isBoolean,
isBigInt,
isSymbol,
isFunction,
isDate,
isRegExp,
isError,
isPromise,
isPromiseLike,
isIterable,
isAsyncIterator,
isArrayBuffer,
isSharedArrayBuffer,
} from '@orkestrel/validator'| Guard | Narrows to | Notes |
| ------------------------ | ------------------------ | ----------------------------------------- |
| isNull(v) | null | Strict === null |
| isUndefined(v) | undefined | Strict === undefined |
| isDefined(v) | T | Excludes both null and undefined |
| isString(v) | string | typeof === 'string' |
| isNumber(v) | number | Includes NaN and Infinity |
| isBoolean(v) | boolean | |
| isBigInt(v) | bigint | |
| isSymbol(v) | symbol | |
| isFunction(v) | AnyFunction | typeof === 'function' |
| isDate(v) | Date | instanceof Date |
| isRegExp(v) | RegExp | instanceof RegExp |
| isError(v) | Error | instanceof Error |
| isPromise(v) | Promise<T> | Native instanceof Promise only |
| isPromiseLike(v) | Promise<T> or thenable | Checks then, catch, finally methods |
| isIterable(v) | Iterable<T> | Strings are iterable |
| isAsyncIterator(v) | AsyncIterable<T> | Checks Symbol.asyncIterator |
| isArrayBuffer(v) | ArrayBuffer | |
| isSharedArrayBuffer(v) | SharedArrayBuffer | Feature-checks availability first |
const raw: unknown = JSON.parse(text)
if (isString(raw)) {
// raw: string
}
// isDefined removes both null and undefined
function process<T>(value: T | null | undefined) {
if (isDefined(value)) {
// value: T
}
}Object and collection guards
import { isObject, isRecord, isMap, isSet, isWeakMap, isWeakSet } from '@orkestrel/validator'| Guard | Narrows to | Notes |
| -------------- | -------------------------- | ------------------------------------- |
| isObject(v) | object | Any non-null object, including arrays |
| isRecord(v) | Record<string, unknown> | Non-null, non-array object |
| isMap(v) | ReadonlyMap<K, V> | instanceof Map |
| isSet(v) | ReadonlySet<T> | instanceof Set |
| isWeakMap(v) | WeakMap<object, unknown> | |
| isWeakSet(v) | WeakSet<object> | |
isRecord is the right choice for plain objects — it excludes arrays:
isObject([]) // true — arrays are objects
isRecord([]) // false — arrays excluded
isRecord({}) // true
isRecord(null) // falseArray and typed array guards
import {
isArray,
isDataView,
isArrayBufferView,
isUint8Array,
isInt32Array,
isFloat64Array,
// ... all 11 typed-array guards
} from '@orkestrel/validator'isArray wraps Array.isArray. For element-level checking use arrayOf from the guard builders section.
isArrayBufferView accepts any typed array or DataView via ArrayBuffer.isView.
All 11 typed-array guards: isInt8Array, isUint8Array, isUint8ClampedArray, isInt16Array, isUint16Array, isInt32Array, isUint32Array, isFloat32Array, isFloat64Array, isBigInt64Array, isBigUint64Array.
Emptiness guards
import {
isEmptyString,
isEmptyArray,
isEmptyObject,
isEmptyMap,
isEmptySet,
isNonEmptyString,
isNonEmptyArray,
isNonEmptyObject,
isNonEmptyMap,
isNonEmptySet,
} from '@orkestrel/validator'Empty:
| Guard | Narrows to |
| ------------------ | --------------------------------- |
| isEmptyString(v) | '' |
| isEmptyArray(v) | readonly [] |
| isEmptyObject(v) | Record<string \| symbol, never> |
| isEmptyMap(v) | ReadonlyMap<never, never> |
| isEmptySet(v) | ReadonlySet<never> |
Non-empty:
| Guard | Narrows to |
| --------------------- | ----------------------------------- |
| isNonEmptyString(v) | string (length > 0) |
| isNonEmptyArray(v) | readonly [T, ...T[]] |
| isNonEmptyObject(v) | Record<string \| symbol, unknown> |
| isNonEmptyMap(v) | ReadonlyMap<K, V> |
| isNonEmptySet(v) | ReadonlySet<T> |
isEmptyObject counts both string keys and enumerable symbol keys — an object with only a symbol property is not considered empty:
isEmptyObject({}) // true
isEmptyObject({ [Symbol('x')]: true }) // falseFunction guards
import {
isAsyncFunction,
isGeneratorFunction,
isAsyncGeneratorFunction,
isZeroArg,
isZeroArgAsync,
isZeroArgGenerator,
isZeroArgAsyncGenerator,
} from '@orkestrel/validator'| Guard | Passes when |
| ------------------------------ | ---------------------------------- |
| isZeroArg(fn) | fn.length === 0 |
| isAsyncFunction(fn) | Native async function |
| isGeneratorFunction(fn) | function* |
| isAsyncGeneratorFunction(fn) | async function* |
| isZeroArgAsync(fn) | Async and zero arguments |
| isZeroArgGenerator(fn) | Generator and zero arguments |
| isZeroArgAsyncGenerator(fn) | Async generator and zero arguments |
isAsyncFunction(async () => true) // true
isAsyncFunction(() => Promise.resolve(true)) // false — sync, returns a Promise
isGeneratorFunction(function* () {
yield 1
}) // trueNote: Detection uses
constructor.name. Minifiers that rename function constructors will break these guards.
Guard Builders
Guard builders compose primitive guards into guards for complex shapes. They are the primary API for narrowing structured data like API responses.
Building object schemas — objectOf
import { objectOf, isString, isNumber } from '@orkestrel/validator'
const isUser = objectOf({ id: isString, age: isNumber })
const raw: unknown = await fetchUser()
if (isUser(raw)) {
// raw: Readonly<{ id: string; age: number }>
console.log(raw.id, raw.age)
}objectOf is exact by default: it rejects any value with extra string keys not in the shape. Symbol keys on the value are silently ignored.
Optional keys
Pass an array of key names to make those keys optional:
const isUser = objectOf(
{ id: isString, role: isString, note: isString },
['note'], // note may be absent
)
isUser({ id: 'u1', role: 'admin' }) // true
isUser({ id: 'u1', role: 'admin', note: 'hi' }) // true
isUser({ id: 'u1', role: 'admin', note: 1 }) // false — note fails its guardPass true to make every key optional:
const isPartialUser = objectOf({ id: isString, age: isNumber }, true)
isPartialUser({}) // true
isPartialUser({ id: 'u1' }) // truerecordOf
Identical signature to objectOf. Use recordOf when the value is a dictionary-style record rather than a structured entity — purely a naming convention for readability.
Arrays — arrayOf
import { arrayOf, isString } from '@orkestrel/validator'
const isStrings = arrayOf(isString)
isStrings(['a', 'b']) // true
isStrings(['a', 1]) // falseTuples — tupleOf
Validates a fixed-length array with per-index guards:
import { tupleOf, isString, isNumber } from '@orkestrel/validator'
const isEntry = tupleOf(isString, isNumber)
isEntry(['id', 42]) // true
isEntry(['id', 'x']) // false — second element fails
isEntry(['id']) // false — length mismatchLiterals and enums — literalOf, enumOf
import { literalOf, enumOf } from '@orkestrel/validator'
// Narrow to a fixed set of literal values
const isRole = literalOf('user', 'admin', 'guest')
isRole('admin') // true
isRole('owner') // false
// Narrow to a TypeScript enum
enum Status {
Idle,
Busy,
}
const isStatus = enumOf(Status)
isStatus(Status.Idle) // true
isStatus(0) // true — numeric enum value
isStatus('Idle') // false — reverse-mapped names are not values
// String enum
const isFlag = enumOf({ active: 'ACTIVE', paused: 'PAUSED' })
isFlag('ACTIVE') // true
isFlag('active') // false — keys are not valuesSets and Maps — setOf, mapOf
import { setOf, mapOf, isString, isNumber } from '@orkestrel/validator'
const isTagSet = setOf(isString)
const isScores = mapOf(isString, isNumber)
isTagSet(new Set(['a', 'b'])) // true
isTagSet(new Set(['a', 1])) // false
isScores(new Map([['alice', 98]])) // true
isScores(new Map([['alice', '98']])) // falseIterables — iterableOf
Accepts any value with Symbol.iterator — arrays, sets, generators — and validates every yielded element:
import { iterableOf, isNumber } from '@orkestrel/validator'
const isNumbers = iterableOf(isNumber)
isNumbers([1, 2, 3]) // true
isNumbers(new Set([1, 2])) // true
isNumbers([1, '2']) // falseKeys and instance checks — keyOf, instanceOf
import { keyOf, instanceOf } from '@orkestrel/validator'
// Accept only keys present on a specific object
const COLORS = { red: '#f00', blue: '#00f', green: '#0f0' } as const
const isColorKey = keyOf(COLORS)
isColorKey('red') // true
isColorKey('yellow') // false
// Accept instances of a class
class ApiError extends Error {
constructor(
readonly status: number,
message: string,
) {
super(message)
}
}
const isApiError = instanceOf(ApiError)
isApiError(new ApiError(404, 'Not found')) // true
isApiError(new Error('generic')) // falseShape transforms — pickOf, omitOf
Build a new guard shape by selecting or removing keys from an existing one. The result is a GuardsShape — pass it to objectOf or recordOf.
import { pickOf, omitOf, objectOf, isString, isNumber } from '@orkestrel/validator'
const userShape = { id: isString, age: isNumber, name: isString, role: isString }
// Keep only the keys you need
const isPublicUser = objectOf(pickOf(userShape, ['id', 'name']))
isPublicUser({ id: 'u1', name: 'Ada' }) // true
isPublicUser({ id: 'u1', name: 'Ada', age: 30 }) // false — exact shape, extra key rejected
// Remove keys you don't want
const isUserWithoutAge = objectOf(omitOf(userShape, ['age']))
isUserWithoutAge({ id: 'u1', name: 'Ada', role: 'admin' }) // trueLogical Combinators
Refinement — whereOf
The most common combinator. Refines a base guard with an additional predicate. The predicate receives a typed value (the base guard already narrowed it), so TypeScript provides full autocomplete.
import { whereOf, isString, testInput } from '@orkestrel/validator'
// Simple predicate
const isNonEmptyString = whereOf(isString, (value) => value.length > 0)
// Embed a rule check inside a guard
const usernameRules = { required: true, minimum: 3, maximum: 20, alphanumeric: true }
const isUsername = whereOf(isString, (value) => testInput(value, usernameRules))AND — andOf
import { andOf, isString } from '@orkestrel/validator'
const isNonEmptyString = andOf(isString, (value: string) => value.length > 0)OR — orOf
import { orOf, isString, isNumber } from '@orkestrel/validator'
const isStringOrNumber = orOf(isString, isNumber)NOT — notOf
import { notOf, isString } from '@orkestrel/validator'
const isNotString = notOf(isString)Union — unionOf
Variadic OR across any number of guards:
import { unionOf, literalOf } from '@orkestrel/validator'
const isDirection = unionOf(
literalOf('north'),
literalOf('south'),
literalOf('east'),
literalOf('west'),
)Intersection — intersectionOf and composedOf
Both require every guard to pass. Use intersectionOf for true type intersections; use composedOf when layering constraints on the same base type:
import { intersectionOf, composedOf, isString } from '@orkestrel/validator'
const isShortString = intersectionOf(
isString,
(value: unknown): value is string => isString(value) && value.length < 10,
)
// composedOf reads more clearly when all guards share the same type
const isAlphaCode = composedOf(
(value: unknown): value is string => isString(value) && /^[A-Za-z]+$/.test(value),
(value: unknown): value is string => isString(value) && value.length === 2,
)
isAlphaCode('en') // true
isAlphaCode('en1') // falseComplement — complementOf
Subtracts a narrower type from a broader one — passes when the value satisfies the base guard but not the excluded guard:
import { complementOf, unionOf, objectOf, literalOf, isNumber } from '@orkestrel/validator'
const isCircle = objectOf({ kind: literalOf('circle'), radius: isNumber })
const isRectangle = objectOf({ kind: literalOf('rectangle'), width: isNumber, height: isNumber })
const isShape = unionOf(isCircle, isRectangle)
const isNotCircle = complementOf(isShape, isCircle)
isNotCircle({ kind: 'rectangle', width: 2, height: 3 }) // true
isNotCircle({ kind: 'circle', radius: 3 }) // falseNullable — nullableOf
Extends any guard to also accept null:
import { nullableOf, isString } from '@orkestrel/validator'
const isMaybeString = nullableOf(isString)
isMaybeString(null) // true
isMaybeString('value') // true
isMaybeString(undefined) // falseTransform — transformOf
Validates the original input by projecting it and checking the projected value. The original (not the projection) is what gets narrowed:
import { transformOf, isString, isNumber } from '@orkestrel/validator'
// Accept strings with a positive character count
const isNonEmptyString = transformOf(
isString,
(value) => value.length,
(n): n is number => typeof n === 'number' && n > 0,
)
isNonEmptyString('abc') // true
isNonEmptyString('') // falseLazy — lazyOf
Defers guard creation until first call. Required when building guards for recursive types, because the guard variable isn't defined yet at the point of the objectOf call:
import type { Guard } from '@orkestrel/validator'
import { lazyOf, objectOf, arrayOf, isNumber } from '@orkestrel/validator'
interface Tree {
readonly value: number
readonly children?: readonly Tree[]
}
const isTree: Guard<Tree> = lazyOf(() =>
objectOf(
{
value: isNumber,
children: arrayOf(isTree), // safe — isTree is resolved at call time
},
['children'],
),
)
isTree({ value: 1, children: [{ value: 2 }] }) // true
isTree({ value: 'x' }) // falseCombining Guards and Validation
The two axes compose naturally. Use whereOf with testInput to embed field-level validation inside a structural guard:
import {
objectOf,
arrayOf,
literalOf,
isString,
whereOf,
testInput,
validateInput,
} from '@orkestrel/validator'
const usernameRules = { required: true, minimum: 3, maximum: 20, alphanumeric: true }
const emailRules = { required: true, email: true }
const isSignupForm = objectOf(
{
username: whereOf(isString, (v) => testInput(v, usernameRules)),
email: whereOf(isString, (v) => testInput(v, emailRules)),
role: literalOf('user', 'admin'),
tags: arrayOf(whereOf(isString, (v) => v.length > 0)),
},
['tags'], // tags is optional
)
const payload: unknown = JSON.parse(request.body)
if (isSignupForm(payload)) {
// payload is fully typed:
// {
// username: string
// email: string
// role: 'user' | 'admin'
// tags?: readonly string[]
// }
submit(payload)
} else {
// Get per-field error details with validateInput
const usernameResult = validateInput(isString(payload) ? payload : '', usernameRules)
return { errors: usernameResult.errors }
}TypeScript Types
These types are exported for use in your own function signatures.
Guard types
import type { Guard, GuardType, GuardsShape } from '@orkestrel/validator'
// Guard<T> — the type predicate signature
const isRole: Guard<'user' | 'admin'> = literalOf('user', 'admin')
// GuardType — extract T from a Guard<T>
type Role = GuardType<typeof isRole> // 'user' | 'admin'
// GuardsShape — a map of string keys to guards, input to objectOf / recordOf
const userShape: GuardsShape = { id: isString, age: isNumber }Output types from shapes
import type { FromGuards, OptionalFromGuards, AllOptionalFromGuards } from '@orkestrel/validator'
const userShape = { id: isString, age: isNumber, note: isString }
// All keys required
type User = FromGuards<typeof userShape>
// Readonly<{ id: string; age: number; note: string }>
// Selected keys optional
type FlexUser = OptionalFromGuards<typeof userShape, ['note']>
// Readonly<{ id: string; age: number; note?: string }>
// All keys optional
type PartialUser = AllOptionalFromGuards<typeof userShape>
// Readonly<Partial<{ id: string; age: number; note: string }>>Validation types
import type {
ValidationRules,
ValidationResult,
ValidationError,
ValidationRuleName,
ValidatorFunction,
ValidatorErrorCode,
ValidatorErrorContext,
} from '@orkestrel/validator'Function type aliases
import type {
AnyFunction, // (...args: unknown[]) => unknown
AnyAsyncFunction, // (...args: unknown[]) => Promise<unknown>
ZeroArgFunction, // () => unknown
ZeroArgAsyncFunction, // () => Promise<unknown>
AnyConstructor, // new (...args: never[]) => T
} from '@orkestrel/validator'Low-level APIs
These are exported for advanced use cases but most consumers won't need them directly.
evaluateRule
Evaluate a single rule against a string without constructing a full rule set:
import { evaluateRule } from '@orkestrel/validator'
evaluateRule('required', true, '') // 'This field is required'
evaluateRule('required', true, 'x') // undefined (passed)
evaluateRule('minimum', 3, 'ab') // 'Must be at least 3 characters'cloneValidationRules
Shallow-clone a rule object (validates it first):
import { cloneValidationRules } from '@orkestrel/validator'
const copy = cloneValidationRules({ required: true, minimum: 3 })createEnumValues
Extract the runtime values from a TypeScript enum. Numeric enum reverse-mapping is handled automatically:
import { createEnumValues } from '@orkestrel/validator'
createEnumValues({ idle: 'IDLE', busy: 'BUSY' }) // ['IDLE', 'BUSY']
enum Status {
Idle,
Busy,
}
createEnumValues(Status) // [0, 1] — not ['Idle', 'Busy']createShapeGuard
The factory behind objectOf and recordOf. Prefer those functions in application code:
import { createShapeGuard } from '@orkestrel/validator'
const isUser = createShapeGuard({ id: isString, age: isNumber })
const isFlexUser = createShapeGuard({ id: isString, note: isString }, ['note'])
const isPartialUser = createShapeGuard({ id: isString, age: isNumber }, true)Domain-level type guards
Guards for the validation domain types themselves — useful when processing rule objects or results from unknown sources:
import {
isValidationRuleName, // 'required' | 'minimum' | ...
isValidationRules, // ValidationRules
isValidationError, // ValidationError
isValidationResult, // ValidationResult
isValidatorFunction, // ValidatorFunction
} from '@orkestrel/validator'
isValidationRuleName('required') // true
isValidationRuleName('unknown') // false
isValidationRules({ required: true, minimum: 3 }) // true
isValidationRules({ required: 'yes' }) // false
isValidationError({ rule: 'required', message: 'This field...' }) // true
isValidationResult({ valid: true, errors: [] }) // truerecordValue
Read a property from an object by any key type (including symbols):
import { recordValue } from '@orkestrel/validator'
const sym = Symbol('id')
const obj = { name: 'Ada', [sym]: 42 }
recordValue(obj, 'name') // 'Ada'
recordValue(obj, sym) // 42
recordValue(obj, 'missing') // undefinedBuilt-in regex constants
import {
EMAIL_PATTERN, // /^[^\s@]+@[^\s@]+\.[^\s@]+$/
URL_PATTERN, // /^https?:\/\/.+/
NUMERIC_PATTERN, // /^-?\d+(\.\d+)?$/
INTEGER_PATTERN, // /^-?\d+$/
ALPHANUMERIC_PATTERN, // /^[a-zA-Z0-9]+$/
VALIDATION_RULE_NAMES, // ordered list of all rule names
} from '@orkestrel/validator'Import these when building custom validators that should match the built-in rule semantics exactly.
