Writing a Custom Contract Kind
When your domain has unique architectural constraints (e.g. pricing formulas, workflow graphs, hardware interface registers), you can implement a custom ContractKind.
The ContractKind<TData> Interface
import { type ContractKind, type Finding, type Rule } from '@cdal/core';
export interface SlaContractData {
slas: Array<{ id: string; targetMinutes: number; severity: string }>;
}
export function slaKind(): ContractKind<SlaContractData> {
return {
id: 'sla',
files: ['sla.contract.mjs'],
load(mod: Record<string, unknown>): SlaContractData {
const data = (mod.default ?? mod) as SlaContractData;
if (!Array.isArray(data?.slas)) {
throw new Error('SLA contract must export an array under "slas".');
}
return data;
},
rules: [
{
id: 'SLA-POSITIVE-TARGET',
severity: 'error',
check(data): Finding[] {
const findings: Finding[] = [];
for (const s of data.slas) {
if (s.targetMinutes <= 0) {
findings.push({
ruleId: 'SLA-POSITIVE-TARGET',
severity: 'error',
file: 'sla.contract.mjs',
message: `SLA "${s.id}" must have targetMinutes > 0 (found: ${s.targetMinutes}).`,
});
}
}
return findings;
},
},
],
mutations: [
{
rule: 'SLA-POSITIVE-TARGET',
file: 'sla.contract.mjs',
find: /targetMinutes:\s*\d+/,
replace: 'targetMinutes: 0',
},
],
emit(data) {
const code = `export const SLAS = ${JSON.stringify(data.slas, null, 2)} as const;\n`;
return [{ path: 'sla.ts', content: code }];
},
};
}
Registering in cdal.config.mjs
import { defineConfig } from '@cdal/core';
import { slaKind } from './tools/custom-kinds/sla.mjs';
export default defineConfig({
kinds: [slaKind()],
});
Running cdal selftest will automatically verify your custom rule's mutation probe!