@empyria/common
v0.3.18
Published
Shared backend goodies for Empyria
Readme
@empyria/common
Shared backend services for higher-level components in Empyria, a nanoservice framework built primarily on Bun: environment variable schemas, error definitions, Codogram (SMS/TOTP-style challenge codes), and Ata (JSON schema building blocks and validation).
Requirements
- Bun
>=1.4.0or Node.js>=26 - Plain ESM, no build step, no TypeScript
Both requirements come from @empyria/classification,
which this package depends on and which uses the native Temporal global for all
date/time handling.
Install
bun add @empyria/commonUsage
import { createEnv, BaseErrors, generateCodogram, validate, string } from '@empyria/common'
const envSchema = createEnv({ API_KEY: string() })
throw new BaseErrors.EntityInvalid({ type: 'User', id: 42 })
const pin = generateCodogram('PIN')Everything is re-exported from the package root via index.js. Individual
modules under lib/ can also be imported directly if you only need one:
import { defineSchema } from '@empyria/common/lib/Ata.js'Modules
| Module | Purpose |
| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| index.js | Package entry point; re-exports every module below plus Moleculer meta helpers (moleculerMeta, moleculerEmpyriaMeta) and workflow ID helpers (workflowName, workflowId, identifyWorkflow). |
| lib/Errors.js | EmpyriaError, the ErrorCreator factory, and BaseErrors — Empyria's catalog of pre-built, templated errors. |
| lib/Env.js | createEnv — builds a service's environment-variable JSON schema from Empyria-wide defaults; pass { closed: true } to reject undeclared vars and bind { coerceTypes, removeAdditional } to the schema. |
| lib/Ata.js | JSON schema building blocks (string, number, integer, bool, enumType, constant, nullable, array, discriminated, email, isoDateTime, ...) and validation — throwing (defineSchema, createValidator, validate), transform-intent aliases (parse, createParser), or non-throwing/structured (createChecker, checkSchema, now returning the parsed data) — built on ata-validator. See Schema builders and Validation options below. |
| lib/Codogram.js | generateCodogram — one-time challenge codes (PIN, alphanumeric, alphabetic) for SMS/TOTP-style verification flows. |
| lib/Defaulter.js | defaultsFromSchema — builds a fully-populated default object for a JSON schema. |
Every exported function is documented with JSDoc directly in its source file — hovering
a function in VSCode or Zed shows its parameters and return type without any extra
tooling, since both editors read JSDoc from plain .js files automatically.
Tests live under test/, one file per module, separate from the lib/ sources.
Validation options
createValidator / validate / parse / createChecker / checkSchema forward
ata-validator's options to the underlying
Validator. Options are resolved in three layers, last wins:
- library defaults —
setAtaDefaults({ ... }), called once at startup for a deployment-wide policy;getAtaDefaults()reads it back,setAtaDefaults()clears it. - schema-bound —
defineSchema(map, { validation: { ... } })stores options on a non-enumerable key, so a schema carries its own parsing policy (schemaOptions(schema)reads it). The schema still serialises and compares as plain JSON Schema. - per-call — the
optionsargument to any of the functions above.
formats is merged one level deep, with EMPYRIA_FORMATS always underneath.
| Option | What it does (Empyria relevance) |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
| coerceTypes | "4040" → 4040 before validation. The reason env.js files work — every process.env value is a string. |
| useDefaults | Fill missing props from their schema default. On by default; every string()/number()/bool()/enumType() emits one. |
| removeAdditional | Strip keys not covered by the schema. Only visible with additionalProperties: false (i.e. defineSchema({ strict: true })). |
| assertFormat | Whether a format mismatch is a hard error. On by default. |
| abortEarly | Stop at the first error, skip enrichment. For hot verdict-only paths (pair with createChecker, not the throwing path). |
| richErrors | Keep ATA#### codes / doc URLs / hints that renderPretty & co. consume. On by default; leave it on. |
| verbose | Add parentSchema to each error (ajv's verbose). |
| formats | Custom format checkers. EMPYRIA_FORMATS (empyria-email, empyria-password, empyria-ipv4, empyria-ipv6) is folded in automatically. |
Return value / mutation. createValidator & validate return ata-validator's
result.data — the same object as the input unless coerceTypes / removeAdditional /
useDefaults applied. Those passes also mutate the input object in place, so hand them a
copy when the caller must keep its input pristine (as the env.js files do with
{ ...process.env }). parse / createParser are behavioural aliases for call sites whose
intent is to transform rather than merely assert. createChecker / checkSchema now also
return data (the parsed value on success, undefined on failure).
Schema builders
Each returns a plain JSON Schema fragment; compose them in defineSchema(map, opts).
| Builder | Produces |
| --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| bool(default?) | { type: 'boolean' } |
| string(default?, minLength \| { minLength, maxLength, pattern, format, description }) | { type: 'string', … } |
| number(default?, { min, max, exclusiveMin, exclusiveMax, multipleOf, description }) | { type: 'number', … } |
| integer(default?, …) | { type: 'integer', … } — use for ports/counts; rejects 3.5 |
| enumType(list, default?, { type, description }) | { enum, type? } |
| constant(value, { description }) | { const: value } |
| nullable(schema) | type: [t, 'null'], or { anyOf: [schema, { type: 'null' }] } |
| array(items?, { minItems, maxItems, uniqueItems, default, description }) | { type: 'array', … } |
| email() / password() / ip4() / ip6() | { type: 'string', format: 'empyria-*' } (see EMPYRIA_FORMATS) |
| isoDateTime() / isoDate() / isoTime() / duration() / uri() / uuid() | { type: 'string', format: … } (ata built-in formats) |
| logLevel() / nodeEnv() / tmpFile() / logFile() | domain presets |
| discriminated(prop, { value: schema, … }, { description }) | JSON Schema v1 propertyDependencies — an object whose shape depends on prop's value |
Dialect
defineSchema(map, { dialect: 'v1' \| '2020-12' \| 'draft-07' \| <uri> }) stamps $schema
(ata-validator reads it to pick the dialect). setDefaultDialect('v1') / getDefaultDialect()
set a process-wide default a per-call dialect overrides. v1 differs from 2020-12 only in
$dynamicRef scoping and unlocks propertyDependencies; discriminated() works on any
dialect (ata supports the keyword as an extension) but dialect: 'v1' makes it spec-legitimate.
defineSchema(map, { strict: 'unevaluated' }) sets unevaluatedProperties: false while
leaving additionalProperties open — the composable form of strict, for schemas meant to be
allOf-combined.
Scripts
bun run format # check formatting (oxfmt)
bun run format:fix # apply formatting
bun run lint # lint (oxlint)
bun run lint:fix # lint and fix
bun run test # run tests with coverageLicense
MIT © Imre Fazekas
