This guide shows how to use SafeShape as packages inside another TypeScript project.
Upgrading an existing 1.x consumer? Follow the 1.x to 2.0 migration guide before replacing reviewed contract baselines.
- Node.js
>=20.10 - ESM-compatible project setup
- TypeScript with strict checking recommended
Recommended TypeScript compiler settings:
{
"compilerOptions": {
"strict": true,
"module": "NodeNext",
"moduleResolution": "NodeNext"
}
}Install safe-shape when you want the full runtime and tooling set available
in one project:
npm install safe-shapeThen import only the packages needed by each module:
import { object, string, validateSchema } from "safe-shape";The safe-shape package re-exports the public runtime and tooling APIs and
also installs the safe-shape CLI binary.
If you prefer narrower installs, choose only the packages needed by the project surface:
For runtime validation:
npm install @safe-shape/coreFor JSON-friendly validation reports:
npm install @safe-shape/core @safe-shape/validationFor HTTP boundary helpers:
npm install @safe-shape/core @safe-shape/httpFor contract snapshots and compatibility analysis:
npm install --save-dev @safe-shape/core @safe-shape/compatFor build-time tooling:
npm install --save-dev @safe-shape/cli @safe-shape/json-schema @safe-shape/typescriptKeep SafeShape package versions aligned. Upgrade installed safe-shape and
@safe-shape/* packages together.
Create a schema module that can be imported by application code and by the CLI:
// src/contracts/user.ts
import { integer, literal, object, string, union, type Infer } from "@safe-shape/core";
export const userSchema = object({
id: string({ minLength: 1, maxLength: 100 }).annotate({
title: "User id",
description: "Stable public user identifier.",
examples: ["user_1"],
}),
role: union([literal("admin"), literal("member")]),
age: integer({ minimum: 0, maximum: 150 }).optional(),
}).annotate({
title: "User",
description: "User resource.",
});
export type User = Infer<typeof userSchema>;SafeShape validates runtime inputs without hidden coercion. If input should be
changed, use an explicit transform().
For a single cross-field error, attach a relative path through
refine(predicate, { id, path, message }). When one synchronous rule can emit
several errors, use refineWithIssues(collector, { id }); collected paths are
relative, ordered, immutable, and preserved by Standard Schema, validation
reports, CLI validation, and HTTP helpers. Custom rules remain opaque to
artifact tooling and are rejected during JSON Schema export.
Use parse() when invalid data should throw:
import { userSchema } from "./contracts/user.js";
const user = userSchema.parse(input);Use safeParse() when invalid data is part of normal control flow:
import { userSchema } from "./contracts/user.js";
const result = userSchema.safeParse(input);
if (!result.success) {
return {
status: 400,
issues: result.error.issues,
};
}
return {
status: 200,
data: result.data,
};Use @safe-shape/validation when the caller needs a JSON-friendly report:
import { validateSchema } from "@safe-shape/validation";
import { userSchema } from "./contracts/user.js";
const report = validateSchema(userSchema, input);SafeShape schemas can be passed directly to libraries accepting Standard Schema V1. No adapter or additional runtime dependency is required:
import type { StandardSchemaV1 } from "@safe-shape/core";
function acceptsStandardSchema(schema: StandardSchemaV1, input: unknown) {
return schema["~standard"].validate(input);
}
acceptsStandardSchema(userSchema, input);SafeShape validation is synchronous. Standard failures expose message and path
while retaining the richer native SafeShape issue fields at runtime. Import
type StandardSchemaV1 from @safe-shape/core when local Standard Schema types
are useful; structural compatibility also works with
@standard-schema/spec.
For consumers requiring Standard JSON Schema V1, explicitly add the exporter capability without changing the original schema:
import { createStandardJsonSchema } from "@safe-shape/json-schema";
const standardArtifact = createStandardJsonSchema(userSchema);
const inputJsonSchema = standardArtifact["~standard"].jsonSchema.input({
target: "draft-2020-12",
libraryOptions: { id: "https://example.com/contracts/user-input" },
});The adapter also retains Standard Schema validation and type inference. Input
and output conversion are independent. Draft 2020-12 and Draft 7 are supported;
other targets, refinements, and opaque outputs throw JsonSchemaExportError
with machine-readable issues.
Build tools can avoid exceptions and inspect all detected issues with
safeToJsonSchema(schema, options). A failed result contains no partial schema,
so it is safe to use as a CI gate.
Use @safe-shape/http to keep request and response validation at the framework
boundary:
import { object, string } from "@safe-shape/core";
import { httpContract, safeParseHttpRequest } from "@safe-shape/http";
import { userSchema } from "./contracts/user.js";
const getUserContract = httpContract({
params: object({
id: string(),
}),
response: userSchema,
});
const result = safeParseHttpRequest(getUserContract, {
params: request.params,
});
if (!result.success) {
return {
status: 400,
body: { issues: result.error.issues },
};
}The HTTP package is framework-neutral. Map your framework request object into the contract sections you want to validate.
For deployed response drift, keep validation strict while changing only the application failure policy. The production response recovery guide shows how to report immutable issues, revalidate stale cache data through the same contract, and render a local unavailable state instead of returning invalid network data as a typed value.
Expose contract tooling from your project scripts:
{
"scripts": {
"contracts:doctor": "safe-shape --json doctor",
"contracts:schema": "safe-shape --json schema export --module ./dist/contracts/user.js --export userSchema --schema https://json-schema.org/draft/2020-12/schema --id https://example.com/contracts/user --out ./dist/contracts/user.schema.json",
"contracts:types": "safe-shape --json schema types --module ./dist/contracts/user.js --export userSchema --name User --out ./dist/contracts/user.d.ts",
"contracts:validate": "safe-shape --json schema validate --module ./dist/contracts/user.js --export userSchema --input ./fixtures/user.json",
"contracts:snapshot": "safe-shape contract snapshot --module ./dist/contracts/user.js --export userSchema --id user --format v2 --out ./.safe-shape/user.contract.json",
"contracts:check": "safe-shape --json contract check --module ./dist/contracts/user.js --export userSchema --against ./.safe-shape/user.contract.json --side input --compatibility backward"
}
}The CLI loads JavaScript ESM modules by file path. Compile TypeScript contract modules before running CLI commands against them. Snapshot v1 remains the default; v2 is explicit and supports recursive contracts plus independent input and output checks. JSON compatibility results include migration diagnostics.
Use these checks in projects that depend on SafeShape:
npm run build
npm run contracts:doctor
npm run contracts:schema
npm run contracts:types
npm run contracts:validate
npm run contracts:checkKeep reviewed snapshots in version control and never regenerate them inside the check job. See Contract Checks in CI for portable shell, GitHub Actions, and GitLab CI examples, exit codes, artifacts, and baseline policy.
For package maintainers in this repository, npm run release:check already
runs build, typecheck, tests, runnable examples, benchmarks, consumer install
checks, npm audit, and package dry-run checks.
Install only the packages needed by the target project:
@safe-shape/corefor schemas, parsing, diagnostics, and type inference.@safe-shape/compatfor snapshots, fingerprints, and compatibility reports.@safe-shape/validationfor JSON-friendly reports.@safe-shape/httpfor framework-neutral HTTP request/response boundaries.@safe-shape/json-schemafor JSON Schema export tooling.@safe-shape/typescriptfor TypeScript declaration generation.@safe-shape/clifor command-line workflows.
Do not rely on private implementation fields. Use public schemas,
describeSchema(), exporter packages, and CLI commands.