api-contract-check
v0.0.1-alpha.2
Published
Creates validators for API contracts
Readme
api-contract-check
Status: in active development, not published yet.
Generate small runtime schemas from TypeScript API contracts and validate request / response payloads at runtime.
This library is meant for frontend API pipelines where contracts already exist as TypeScript files.
api-contract-check does not parse OpenAPI directly. It converts a narrow TypeScript subset into a compact schema format and provides a small runtime checker for that schema.
What It Solves
- full regeneration from a list of API entry files
- partial regeneration from cached inverse dependencies
- file-based pipelines
- virtual memory pipelines
- mixed pipelines where some files come from disk and some from memory
Entry Contract
Each entry API file must declare both TypeRequest and TypeResponse.
import type { TypeUser } from '../models/TypeUser';
type TypeRequest = {
id: string;
};
type TypeResponse = TypeUser;Install
pnpm add api-contract-checkAPI
createValidators(input: string[], {
tsconfigPath: string,
readFile?: (filePath: string) => string | undefined,
})Returns:
{
result: Array<{
filePath: string;
content: string;
}>;
deps: Record<string, string[]>;
}content is generated source code:
export default {
request: ...,
response: ...,
} as const;At runtime:
check({
schema,
type: 'request' | 'response',
value,
getExtraneous?: (paths: string[]) => void,
})Returns null on success or string[] with validation errors.
Full Regeneration
import { createValidators } from 'api-contract-check';
const { result, deps } = createValidators(
['src/api/getUser.ts', 'src/api/getUserExtended.ts'],
{
tsconfigPath: 'tsconfig.json',
}
);
for (const entry of result) {
console.log(entry.filePath);
console.log(entry.content);
}
console.log(deps);Partial Regeneration
deps maps every dependency file to affected entry files.
const changedFile = 'src/models/TypeUser.ts';
const affectedEntries = cachedDeps[changedFile] ?? [];
const { result, deps } = createValidators(affectedEntries, {
tsconfigPath: 'tsconfig.json',
});The dependency model is intentionally coarse-grained: it tracks the full transitive import / export ... from / export * graph for each generated entry.
Memory And Mixed Pipelines
If your generator keeps files in memory, pass readFile.
import { createValidators } from 'api-contract-check';
const memory: Record<string, string> = {
'/virtual/api/getUser.ts': `
import type { TypeUser } from '@models/TypeUser';
type TypeRequest = {};
type TypeResponse = TypeUser;
`,
};
const { result } = createValidators(['/virtual/api/getUser.ts'], {
tsconfigPath: 'tsconfig.json',
readFile: (filePath) => memory[filePath],
});You can also mix memory and disk:
const { result } = createValidators(['/virtual/api/getUser.ts'], {
tsconfigPath: 'tsconfig.json',
readFile: (filePath) => memory[filePath], // falls back to disk when undefined
});Runtime Validation
Generated schema modules are plain export default objects. Import them and call check(...).
import { check } from 'api-contract-check';
import schema from './generated/getUser.validator';
const payload = {
id: 'user-1',
extraField: true,
};
const extraneous: string[] = [];
const errors = check({
schema,
type: 'request',
value: payload,
getExtraneous: (paths) => extraneous.push(...paths),
});
console.log(errors); // null or ['request.id is not a string', ...]
console.log(payload); // extraneous fields are removed in place
console.log(extraneous); // ['request.extraField']Supported TypeScript Subset
Supported:
- primitive keywords:
any,number,object,boolean,string,null - interfaces with property signatures
- type aliases
- references to supported declarations
- literal types
- arrays
- fixed tuples
- unions
- intersections of object-like types, including compatible duplicate members
- nested inline object literals
- enums and enum member references
import,import type,export ... from,export *- parenthesized types
Intentionally unsupported:
extendsPromise<T>Record<K, V>- mapped types
- conditional types
- rest types
- standalone optional type nodes
- function types as schema
- broad generic support
Unsupported nested constructs degrade to 'any'.
Design
- source-to-source, not
ts.Program-based type analysis - builder returns generated code and inverse deps, but does not write files
- checker is a small runtime schema interpreter
- optimized for generated frontend API contracts, not arbitrary TypeScript
Notes
- unknown object keys are removed during validation by default
getExtraneousreceives normalizedrequest.*/response.*paths- union errors are intentionally top-level and compact
- missing or unreadable files are hard errors
- unresolved imports are hard errors
Development
pnpm test
pnpm analyze
pnpm benchFor more detailed project constraints and behavior, see purpose.md.
