@ppwcode/api-contracts
v1.1.0
Published
Collection of common reusable schemas for OpenAPI specs and TypeScript types
Keywords
Readme
@ppwcode/api-contracts
Reusable Zod 4 schemas and inferred TypeScript types for canonical API values.
npm install @ppwcode/api-contractsZod 4 is a peer dependency. Install zod@^4 explicitly if your package manager
does not install peers. Import contracts from their category; the package has no
root entrypoint. Output supports browser-safe ES2022, ESM and CommonJS.
import { z } from "zod";
import { TrimmedStringSchema } from "@ppwcode/api-contracts/value/string";
import { DateOnlySchema } from "@ppwcode/api-contracts/value/time";
import {
BelgianEnterpriseNumberSchema,
type BelgianEnterpriseNumber,
} from "@ppwcode/api-contracts/identity/be";
const CompanySchema = z.object({
name: TrimmedStringSchema,
foundedOn: DateOnlySchema.optional(),
enterpriseNumber: BelgianEnterpriseNumberSchema,
});
const company = CompanySchema.parse({
name: "Example company",
enterpriseNumber: "0123456749",
});
const number: BelgianEnterpriseNumber = company.enterpriseNumber;
const result = BelgianEnterpriseNumberSchema.safeParse("0123.456.749");
// result.success === false: formatting is never silently removed.Contracts
| Category | Types | Scope |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| /value/string | CanonicalURI; CanonicalURIWithKnowledgeTime; CleanedString; EmailAddress; Language; RelativeURI; TelephoneNumber; TrimmedString | Canonical strings, URIs, email addresses, language codes, and telephone numbers |
| /value/time | DateOnly; DateOnlyPeriod; DateTime; ISODateToSecond; Month; Quarter; Year | Calendar values and ISO timestamps |
| /value/number | Decimal | Exact integer-based decimal representation |
| /value/money | CurrencyCode; MonetaryValue; MonetaryValue2; MonetaryValueEUR2; ZeroMonetaryValueEUR2; NonNegativeMonetaryValue; NonNegativeMonetaryValueEUR2; NonPositiveMonetaryValue; NegativeMonetaryValue; NegativeMonetaryValueEUR2; PositiveMonetaryValue; PositiveMonetaryValueEUR2; PositiveMonetaryValueEUR4 | Currency codes and signed monetary values |
| /value/location | Address; Country | Postal addresses and country codes |
| /identity/generic | AccountId; Mode; UUID | Generic identifiers and execution modes |
| /identity/person | Gender | Person attributes |
| /identity/banking | Iban | Uppercase IBAN with country structure and MOD 97-10 checksum |
| /identity/be | BelgianEnterpriseNumber; KboNumber; CbeNumber; CrnNumber; BelgianIban; BelgianSocialSecurityNumber; Niss; Insz; Inss; BelgianVatNumber; SigedisId; SigedisRegistrantId; SigedisRegistrantRegulationIdentification | Belgian identity and organization identifiers |
| /resource/health | Health; Status | Service health and status |
| /resource/lifecycle | Auditable; AuditablePersistent; Audited; CreatedInError; InsertAuditable; InsertAuditablePersistent; Persistent; Timestamped | Resource lifecycle, persistence, and audit metadata |
| /resource/versioning | StructureVersion; StructureVersioned; HistoryVersion; History; HREFHistory; HREFHistorySearchable | Resource structure versions and history |
| /resource/search | SearchTerm; SearchDocument; SearchDocument2; SearchDocumentBase; SearchDocumentContentBase2; SearchResultBase; SearchResultBase2; SearchResults; HREF; MixedSearchResults; Results | Search documents, results, and links |
| /resource/relations | ToOneFromChi | Relative links to associated resources |
| /http/parameters | Accept; ForceError; Page; PerPage; PersistenceId | HTTP request parameter values |
| /http/headers | CommonResponseHeaders; Location | HTTP response headers |
| /http/caching | CacheControlNoCache; CacheControlPrivateImmutable | HTTP cache directives |
| /protocol/oauth2 | BearerAccessToken; WWWAuthenticate | OAuth2 resource-server values |
IbanSchema is available from @ppwcode/api-contracts/identity/banking. Its country
lengths and BBAN patterns follow the supplied PPWCode.Util.Validation.IV.IBAN
class (69 countries); this is a fixed compatibility table, not a live IBAN
registry. The PPWCode.vNext documentation
describes its country and MOD 97-10 checks. Unlike the C# identification class,
this contract accepts only the canonical electronic form and does not normalize
paper formatting. Country structures are included in generated JSON Schema;
the checksum requires runtime validation. Iban and BelgianIban are distinct
brands; parse a value with the desired schema to obtain that type.
import { IbanSchema } from "@ppwcode/api-contracts/identity/banking";
IbanSchema.parse("GB82WEST12345698765432");No schema trims, changes case, removes separators, or otherwise transforms input. Canonical primitive domain values are branded. Parse untrusted values with the corresponding schema to obtain the branded type; this prevents accidental interchange of values such as dates, currencies, URLs, modes, and identifiers. Structural object schemas are intentionally not branded, so they remain easy to compose and extend. Branding is a TypeScript-only distinction and has no effect on the wire representation, JSON Schema, or OpenAPI output.
NissSchema/Niss and InszSchema/Insz alias the social security number.
NISS means numéro d’identification de la sécurité sociale; INSZ means
identificatienummer van de sociale zekerheid. BIS numbers are included, so
neither birth date nor gender is inferred or validated. The non-standard spelling
INSS is not exported. KboNumberSchema/KboNumber and
CbeNumberSchema/CbeNumber alias the enterprise number: KBO is Kruispuntbank
van Ondernemingen and CBE is Crossroads Bank for Enterprises. All aliases
reference their canonical schema instance.
A valid checksum does not prove registry membership, VAT registration, identity, or bank-account existence. Treat NISS/INSZ as sensitive personal data: avoid raw values in logs, telemetry, URLs and validation-error reports; use synthetic test fixtures and restrict storage and access to what your application requires.
JSON Schema and OpenAPI
npm run build also generates a complete OpenAPI 3.1 example at
dist/openapi-example.yaml. Open it in an OpenAPI viewer to explore a
GET /example response composed from the canonical contracts, with reusable
component schemas and their metadata. The spec is self-contained and included in
the published package at @ppwcode/api-contracts/openapi-example.yaml; resolve it
with import.meta.resolve() or require.resolve() to read the YAML file.
Its generator is scripts/generate-openapi-example.mjs.
npm run build generates standalone YAML files in dist/schemas/, included in
the published package. Each file contains JSON Schema Draft 2020-12 and is named
after its canonical component ID: TrimmedString, TelephoneNumber, DateOnly,
BelgianSocialSecurityNumber, BelgianEnterpriseNumber, BelgianVatNumber,
BelgianIban, and Iban. Aliases share their canonical file.
Consumers can resolve a file with
import.meta.resolve("@ppwcode/api-contracts/schemas/DateOnly.yaml") or
require.resolve("@ppwcode/api-contracts/schemas/DateOnly.yaml") and read it with
a YAML parser. OpenAPI 3.1/3.2 documents can reference the installed files directly
(adjust the relative path to the location of your API document):
components:
schemas:
DateOnly:
$ref: ./node_modules/@ppwcode/api-contracts/dist/schemas/DateOnly.yamlThe package also publishes the reusable OpenAPI component fragments migrated
from the deprecated @ppwcode/openapi package under
@ppwcode/api-contracts/components/.... These include shared parameters,
headers, responses, request bodies, and OAuth2/JWT/JWK fragments. Root API
documents and endpoint/path compositions are intentionally not part of this
library. Component fragments are package artifacts; the Zod schemas and their
generated /schemas/*.yaml files remain the canonical data contracts.
No consumer-side schema generation is needed. You can also generate JSON Schema programmatically:
import { z } from "zod";
import { BelgianEnterpriseNumberSchema } from "@ppwcode/api-contracts/identity/be";
const jsonSchema = z.toJSONSchema(BelgianEnterpriseNumberSchema, {
target: "draft-2020-12",
});Metadata includes stable component IDs, English titles, descriptions, examples,
patterns and lengths, plus the standard date format where applicable. Checksum
refinements require runtime Zod validation: JSON Schema and OpenAPI cannot express
these calculations. Metadata is attached last; attach your own metadata last too
when deriving a schema. Avoid assigning an existing component ID to a new schema.
Choose your own generator. For example, with zod-openapi installed separately:
import { createDocument } from "zod-openapi";
import { BelgianEnterpriseNumberSchema } from "@ppwcode/api-contracts/identity/be";
const document = createDocument({
openapi: "3.2.0", // '3.1.0' is also tested.
info: { title: "Company API", version: "1.0.0" },
paths: {
"/enterprise-number": {
get: {
responses: {
"200": {
description: "Enterprise number",
content: {
"application/json": { schema: BelgianEnterpriseNumberSchema },
},
},
},
},
},
},
});There is no runtime dependency on an OpenAPI generator. Native JSON Schema and representative OpenAPI 3.1/3.2 documents are snapshot tested.
Development
Use Node 24 LTS and npm. npm ci installs the locked toolchain.
The GitHub workflows read .nvmrc and select the latest available Node 24 release.
| Command | Purpose |
| -------------------------- | ---------------------------------------------------------------- |
| npm test | Watch unit and metadata tests |
| npm run test:unit | Unit, property and metadata tests |
| npm run test:types | Identifier and alias type assertions |
| npm run test:coverage | Unit tests, coverage and JUnit report |
| npm run build | ESM/CommonJS, declarations and standalone schema YAML files |
| npm run test:integration | OpenAPI tests against dist (build first) |
| npm run test:ci | All tests (build first) |
| npm run verify:quality | Lint, strict types, unit coverage and type tests |
| npm run verify:package | Build, integration, publint, Are the Types Wrong, dry-run pack |
| npm run license:check | REUSE lint (install REUSE 6.2.0 first) |
| npm run license:docker | License check using fsfe/reuse:6.2.0 |
| npm run verify | All quality, package and license gates |
| npm run release:pack | Verify matching RELEASE_TAG, all gates, then create an archive |
npm run ci:check-production-vulnerabilities audits production dependencies using
the locked better-npm-audit tool. It also runs as part of npm run verify.
GitHub Actions runs independent quality, package, license and Dependency Audit jobs
for PRs, main, releases and manual CI workflow runs. The
Release workflow handles new v* tags and creates
GitHub releases with the built-in workflow token, using the matching changelog
entry as the description. It then calls the reusable
Publish workflow, which validates the release tag,
calls the reusable CI workflow, and hands the verified
package build directly to the production deployment. Publication requires
environment approval and uses npm trusted publishing. No repository release token
or npm token is stored. A failed publication can be retried by running the Publish
workflow manually and selecting the published release tag under Use workflow
from. No branch build or manual CI verification run publishes.
Category paths, exported names, component IDs, brands and accepted wire values
are public API. The package is organized beneath five top-level namespaces:
value/*, identity/*, resource/*, http/*, and protocol/*.
Contributing
Follow the implementation workflow when adding or changing contracts, entrypoints, or package infrastructure.
Keep unreleased changes under ## Unreleased in CHANGELOG.md,
then use npm version [major|minor|patch] to bump the version when preparing a
release, choosing one of major, minor, or patch (for example,
npm version patch). The version lifecycle hook automatically promotes
## Unreleased to the new ## X.Y.Z heading and validates that it contains a
change description.
The command runs verify:quality before the bump, updates package.json and
package-lock.json, updates and checks the changelog for the new version, and
creates the version commit and tag. When preparing the version update for a PR, use
npm version [major|minor|patch] --no-git-tag-version and include the updated
version files in the PR.
After the version update and changelog are reviewed and merged, push the matching
vX.Y.Z tag on the merged commit to trigger the release workflow. Publishing
requires successful checks and production approval. Follow
release setup for the complete release procedure.
Licensed under Apache License 2.0; see NOTICE.
