| name | blong-validation |
| description | Define input/output validation for Blong handlers using TypeScript types or TypeBox schemas. Automatic validation generates OpenAPI documentation and runtime checks. Make sure to use this skill whenever a handler needs typed parameters, you're generating API docs, or the user asks about schemas, validation rules, or TypeBox types in Blong — even if they don't mention 'validation' by name. |
Implementing Validation
Overview
Validation definitions are used for API documentation, parameter validation, and response
construction. They can be automatically derived from TypeScript types or manually specified using
TypeBox.
Purpose
- API Documentation: Generate OpenAPI specs automatically
- Parameter Validation: Ensure inputs meet requirements
- Response Validation: Verify outputs match contracts
- Type Safety: Enforce contracts at runtime
- Developer Experience: Auto-completion and type checking
Validation Approaches
1. Automatic Validation (Recommended)
- Define TypeScript
Handler type
- Framework auto-generates validation schema
- Updates automatically when types change
2. Manual Validation
- Define validation using TypeBox
- Place in
gateway layer
- Full control over validation rules
Automatic Validation
Step 1: Define Handler Type
import {IMeta, handler} from '@feasibleone/blong';
type Handler = ({
/** @description "Parameter 1 description" */
param1: string;
/** @description "Optional parameter description" */
param2?: number;
/** @description "Enum parameter" */
status: 'active' | 'inactive';
}) => Promise<{
resultId: number;
message: string;
}>;
export default handler(() =>
async function realmEntityAction(
params: Parameters<Handler>[0],
$meta: IMeta
): ReturnType<Handler> {
return {
resultId: 123,
message: 'Success'
};
}
);
Step 2: Create ~.schema.ts
Place ~.schema.ts file in the handler folder:
import {validationHandlers} from '@feasibleone/blong';
import {Type, type Static} from 'typebox';
type realmEntityAction = Static<typeof realmEntityAction>;
const realmEntityAction = Type.Function(
[
Type.Object({
param1: Type.String({description: 'Parameter 1 description'}),
param2: Type.Optional(Type.Number({description: 'Optional parameter description'})),
status: Type.Union([Type.Literal('active'), Type.Literal('inactive')], {
description: 'Enum parameter',
}),
}),
],
Type.Promise(
Type.Object({
resultId: Type.Number({description: 'Result property description'}),
message: Type.String({description: 'Result message'}),
}),
),
{description: 'Description for API documentation'},
);
export default validationHandlers({
realmEntityAction,
});
Note: This file is auto-generated/updated by the framework when:
- File is older than handler files
- Handler types have changed
- Framework detects Handler type definitions
Step 3: Configure Validation
export default orchestrator(blong => ({
extends: 'orchestrator.dispatch',
activation: {
default: {
namespace: ['entity'],
imports: ['realmname.entity'],
validations: ['realmname.entity.validation']
validations: [/^realmname\.\w+\.validation$/]
}
}
}));
Regex Validation Patterns:
The validations property can accept regex patterns to match multiple validation groups
automatically:
validations: [/^realmname\.\w+\.validation$/];
validations: [/^realmname\.(entity1|entity2)\.validation$/];
validations: ['realmname.entity1.validation', /^realmname\.entity2\./];
Manual Validation
Define in Gateway Layer
import {validation} from '@feasibleone/blong';
export default validation(
async ({lib: {type}}) =>
function realmEntityAction() {
return {
params: type.Object({
param1: type.String({
description: 'Parameter 1',
minLength: 3,
maxLength: 50,
}),
param2: type.Optional(
type.Number({
description: 'Optional number',
minimum: 0,
maximum: 100,
}),
),
email: type.String({
format: 'email',
description: 'Email address',
}),
status: type.Union([type.Literal('active'), type.Literal('inactive')]),
}),
result: type.Object({
resultId: type.Number({description: 'ID'}),
message: type.String({description: 'Message'}),
}),
description: 'Perform entity action',
method: 'POST',
path: '/entity/action',
auth: true,
tags: ['entity'],
};
},
);
Configure Manual Validation
config: {
default: {
orchestratorDispatch: {
namespace: ['entity'],
imports: ['realmname.entity'],
validations: ['realmname.gateway.entity']
}
}
}
Complete Examples
User Creation Handler
import {IMeta, handler} from '@feasibleone/blong';
type Handler = ({
/** @description "Username (3-20 characters)" */
username: string;
/** @description "Email address" */
email: string;
/** @description "User role" */
role: 'admin' | 'user' | 'guest';
/** @description "User profile" */
profile?: {
firstName: string;
lastName: string;
age?: number;
};
}) => Promise<{
userId: number;
username: string;
createdAt: string;
}>;
export default handler(({handler: {sqlUserAdd}}) =>
async function userUserAdd(
params: Parameters<Handler>[0],
$meta: IMeta
): ReturnType<Handler> {
const result = await sqlUserAdd(params, $meta);
return {
userId: result.userId,
username: result.username,
createdAt: new Date().toISOString()
};
}
);
Manual Validation with Custom Rules
import {validation} from '@feasibleone/blong';
export default validation(
async ({lib: {type}}) =>
function paymentTransferCreate() {
return {
params: type.Object({
fromAccount: type.String({
description: 'Source account number',
pattern: '^ACC[0-9]{6}$',
}),
toAccount: type.String({
description: 'Destination account number',
pattern: '^ACC[0-9]{6}$',
}),
amount: type.Number({
description: 'Transfer amount',
minimum: 0.01,
maximum: 1000000,
multipleOf: 0.01,
}),
currency: type.String({
description: 'Currency code',
pattern: '^[A-Z]{3}$',
default: 'USD',
}),
reference: type.Optional(
type.String({
description: 'Payment reference',
maxLength: 100,
}),
),
metadata: type.Optional(type.Record(type.String(), type.Any())),
}),
result: type.Object({
transferId: type.String({
description: 'Transfer ID',
format: 'uuid',
}),
status: type.Literal('pending'),
createdAt: type.String({format: 'date-time'}),
}),
description: 'Create a new transfer',
method: 'POST',
path: '/transfer',
tags: ['payment'],
};
},
);
Array Validation
type Handler = ({
/** @description "Array of numbers" */
numbers: number[];
}) => Promise<{
average: number;
}>;
Generates:
const handler = Type.Function(
[
Type.Object({
numbers: Type.Array(Type.Number(), {
description: 'Array of numbers',
}),
}),
],
Type.Promise(
Type.Object({
average: Type.Number({description: 'Average value'}),
}),
),
);
Validation Configuration
Overriding Defaults
export default validation(
async ({lib: {type}}) =>
function handlerName() {
return {
params: type.Object({
}),
result: type.Object({
}),
auth: false,
method: 'GET',
path: '/custom/path',
description: 'Custom description',
summary: 'Brief summary',
tags: ['tag1', 'tag2'],
deprecated: false,
responses: {
'200': {
description: 'Success',
content: type.Object({
}),
},
'400': {
description: 'Bad request',
},
'404': {
description: 'Not found',
},
},
};
},
);
OpenAPI Generation
Validations automatically generate OpenAPI documentation:
Endpoint Path Convention
Automatic (from semantic triple):
- Handler:
userUserAdd
- Path:
/rpc/user/user/add
Manual override:
path: '/users';
HTTP Method
Default: POST for all handlers
Override:
method: 'GET';
method: 'PUT';
method: 'DELETE';
OpenAPI Tags
Group endpoints in documentation:
tags: ['user', 'authentication'];
Best Practices
- Use Automatic Validation: Prefer automatic over manual when possible
- Descriptive JSDoc: Include descriptions for all properties
- Validate Formats: Use
format for emails, URIs, dates, UUIDs
- Set Constraints: Define min/max, minLength/maxLength, patterns
- Optional vs Required: Make optional what should be optional
- Enums for Fixed Values: Use unions/literals for fixed value sets
- Keep ~.schema.ts Updated: Let framework regenerate it
- Document Errors: Document possible error responses
- Consistent Types: Use same types for same concepts across API
- Test Validation: Verify validation catches invalid inputs
Validation in Tests
Test that validation works:
async function testValidation(assert, {$meta}) {
await assert.rejects(
userUserAdd(
{
username: 'test',
email: 'invalid-email',
role: 'user',
},
$meta,
),
'Validation should reject invalid email',
);
const result = await userUserAdd(
{
username: 'test',
email: 'test@example.com',
role: 'user',
},
$meta,
);
assert.ok(result.userId);
}
Examples from Codebase
- Auto-validation:
core/test/demo/orchestrator/subject/~.schema.ts
- Handler with types:
core/test/demo/orchestrator/subject/subjectAge.ts
- Manual validation: Check gateway folders in realms