katax-core 1.5.4
Typed validation with 20+ schemas, coercion, preprocess, async validators, file, base64, email and error utilities.
3 published packages · npm + GitHub + DeepWiki
Three TypeScript packages to build, validate, and deploy Node.js APIs on VPS infrastructure with PM2.
Typed validation with 20+ schemas, coercion, preprocess, async validators, file, base64, email and error utilities.
Runtime container: config, logger, DB (PostgreSQL/MySQL/MongoDB/Redis), cache, cron, WebSocket, transports, registry and health.
CLI for API scaffolding, CRUD, endpoints, repositories, OpenAPI docs and PM2 deploy on VPS.
npm install katax-core
npm install katax-service-manager
npm install -g katax-cli
# optional peer deps for katax-service-manager
npm install pg mysql2 mongodb redis socket.io dotenv node-cron pino-pretty katax-core is the validation layer of the ecosystem. The main entry point is k. Supports 20+ schema types, coercion, preprocess, async validators, advanced composition and error utilities.
import { k, type kataxInfer } from 'katax-core';
const UserSchema = k.object({
email: k.email(),
name: k.string().minLength(2).maxLength(100),
age: k.number().min(18).optional(),
});
type User = kataxInfer<typeof UserSchema>;
const result = UserSchema.safeParse(req.body);
if (!result.success) return res.status(400).json({ errors: result.issues }); All schemas implement: parse(), safeParse(), validate(), parseAsync(), safeParseAsync(), isValidAsync(), hasAsyncValidation(), kataxInfer.
SafeParseResult<T> — { success: true; data: T } | { success: false; issues: Issue[] }Issue — { path: (string | number)[]; message: string }KataxError — Custom error with issues: Issue[]AsyncSafeParseResult<T>, AsyncValidationResultk.string() k.number() k.boolean() k.date() k.twoDates()
k.object() k.array() k.tuple() k.record()
k.union() k.intersection() k.lazy()
k.email() k.file() k.base64()
k.custom() k.literal() k.enum()
k.any() k.unknown() k.never()
k.coerce k.preprocess() k.string()| Method | Description |
|---|---|
minLength(n, msg?) | Minimum length |
maxLength(n, msg?) | Maximum length |
length(n, msg?) | Exact length |
email(msg?) | Valid email format |
url(msg?) | Valid URL (URL constructor) |
regex(pattern, msg?) | Regex match |
uuid(msg?) | Valid UUID (RFC 4122) |
ip(msg?) | Valid IPv4 |
startsWith(prefix, msg?) | Must start with string |
endsWith(suffix, msg?) | Must end with string |
includes(substr, msg?) | Must include substring |
oneOf(options[], msg?) | Must be one of the options |
notOneOf(options[], msg?) | Must not be any of the options |
lowercase(msg?) | Lowercase only |
uppercase(msg?) | Uppercase only |
alpha(msg?) | Letters only [A-Za-z] |
alphanumeric(msg?) | Letters and numbers only |
ascii(msg?) | ASCII characters only |
noWhitespace(msg?) | No spaces |
nonempty(msg?) | Cannot be empty |
trim(msg?) | No leading/trailing spaces |
k.number()| Method | Description |
|---|---|
min(value, msg?) | Minimum value (>=) |
max(value, msg?) | Maximum value (<=) |
length(exact, msg?) | Exact value |
positive(msg?) | Must be > 0 |
negative(msg?) | Must be < 0 |
integer(msg?) | Must be integer |
finite(msg?) | Must be finite |
multipleOf(factor, msg?) | Must be a multiple of |
between(min, max, msg?) | Inclusive range |
greaterThan(n, msg?) | Strictly greater than |
lessThan(n, msg?) | Strictly less than |
notEqual(n, msg?) | Not equal to value |
oneOf(options[], msg?) | Must be one option |
notOneOf(options[], msg?) | Must not be any option |
nonempty(msg?) | Alias for min(1) |
k.boolean()| Method | Description |
|---|---|
isTrue(msg?) | Must be true |
isFalse(msg?) | Must be false |
equals(expected, msg?) | Must equal the value |
k.object(shape)| Method | Description |
|---|---|
extend(extension) | Add/override fields |
merge(other) | Merge two object schemas |
pick([keys]) | Select fields |
omit([keys]) | Exclude fields |
partial() | All fields optional |
strict() | No extra keys allowed (throws) |
passthrough() | Allow extra keys in output |
strip() | Remove extra keys (default) |
getShape() | Get raw shape |
caseInsensitive() | Case-insensitive lookup |
const schema = k.object({
name: k.string().minLength(2),
age: k.number().min(18).optional(),
}).strict(); k.array(elementSchema?)| Method | Description |
|---|---|
minLength(n, msg?) | Minimum length |
maxLength(n, msg?) | Maximum length |
length(n, msg?) | Exact length |
notEmpty(msg?) | Cannot be empty |
unique(msg?) | Unique elements (deep equality) |
contains(element, msg?) | Must contain element |
k.email()| Method | Description |
|---|---|
domain(domain, msg?) | Restrict to specific domain |
domains([domains], msg?) | Multiple allowed domains |
domainPattern(pattern, msg?) | Domain regex (*.domain.com) |
notDomains([blacklist], msg?) | Domain blacklist |
localMinLength(n, msg?) | Min local part length |
localMaxLength(n, msg?) | Max local part length |
localPattern(regex, msg?) | Local part pattern |
corporate(msg?) | Block free providers (Gmail, Yahoo...) |
noPlus(msg?) | Forbid '+' addressing |
noDots(msg?) | Forbid '.' in local part |
k.email().corporate().noPlus() // Corporate emails only, no '+' k.date()Input: ISO 8601. Output: Date object. Uses date-fns.
| Method | Description |
|---|---|
min(dateStr, msg?) | On or after the date |
max(dateStr, msg?) | On or before the date |
between(start, end, msg?) | Inclusive range |
isFuture(msg?) | Must be in the future |
isPast(msg?) | Must be in the past |
format(formatStr, msg?) | Must match format |
isDateOnly(msg?) | Date only (YYYY-MM-DD) |
hasTime(msg?) | Must include time |
formatOutput(format) | Transforms output to formatted string |
k.twoDates(separator?)Input: Two ISO dates separated by separator (default '|'). Output: [Date, Date].
| Method | Description |
|---|---|
maxDifference(days, msg?) | Max days apart |
minDifference(days, msg?) | Min days apart |
maxDifferenceHours(hours, msg?) | Max hours apart |
minDifferenceHours(hours, msg?) | Min hours apart |
order(ascending, msg?) | Chronological order |
k.file()Compatible with Browser File API and Node.js Multer objects.
| Method | Description |
|---|---|
maxSize(bytes, msg?) | Maximum size |
minSize(bytes, msg?) | Minimum size |
type(mimeType, msg?) | Exact MIME type |
types([mimeTypes], msg?) | Multiple MIME types |
typePattern(pattern, msg?) | MIME pattern (image/*) |
extension(ext, msg?) | File extension |
extensions([exts], msg?) | Multiple extensions |
namePattern(regex, msg?) | Filename regex |
image(msg?) | Shortcut: image/* |
video(msg?) | Shortcut: video/* |
audio(msg?) | Shortcut: audio/* |
document(msg?) | Shortcut: PDF, Word, Excel, plaintext |
k.file().image().maxSize(5 * 1024 * 1024) // image max 5MB k.base64()Input: Base64 string (optionally with data URL). Compatible Browser + Node.js.
| Method | Description |
|---|---|
minDecodedSize(bytes, msg?) | Min decoded size |
maxDecodedSize(bytes, msg?) | Max decoded size |
mimeType(type, msg?) | Exact data URL MIME type |
mimeTypePattern(pattern, msg?) | MIME pattern |
dataUrl(msg?) | Must be data URL (data:...) |
json(msg?) | Decoded must be valid JSON |
image(msg?) | Shortcut: image/* |
pdf(msg?) | Shortcut: application/pdf |
// Union — at least one schema must match (short-circuit)
k.union([k.string(), k.number()])
// Intersection — ALL schemas must match (merge objects)
k.intersection([
k.object({ id: k.number() }),
k.object({ name: k.string() })
])
// Lazy — deferred resolution for recursive types
const categorySchema = k.lazy(() =>
k.object({
name: k.string(),
subcategories: k.array(categorySchema)
})
); k.literal('active') // Exact value (===)
k.enum(['active', 'inactive']) // String union
k.tuple([k.number(), k.string()]) // Fixed-length typed array
k.record(k.number()) // Record<string, number>
k.any() // Accepts everything (bypasses safety)
k.unknown() // Accepts everything (forces narrowing)
k.never() // Never matches k.custom<T>(validator)const positiveEven = k.custom<number>((value, path) => {
if (typeof value !== 'number') return [{ path, message: 'Must be number' }];
if (value <= 0 || value % 2 !== 0) return [{ path, message: 'Must be positive even' }];
return value;
});
// Add refinement
positiveEven.refine((v) => v < 1000, 'Must be less than 1000'); schema.optional() // T | undefined
schema.nullable() // T | null
schema.default(42) // Returns default if undefined
schema.transform((v) => ...) // Transforms output type k.coerceAutomatically converts before validating:
k.coerce.number() // "42" → 42, true → 1
k.coerce.boolean() // "true"/"1"/"yes"/"on" → true
k.coerce.string() // 42 → "42", null → ""
k.coerce.date() // ISO string → Date, timestamp → Date k.preprocess(fn, schema)k.preprocess(
(val) => typeof val === 'string' ? val.trim().toLowerCase() : val,
k.string().email()
) import { createIssue, issues, mergeIssues, isIssueArray } from 'katax-core';
createIssue(['field'], 'error message');
mergeIssues(arr1, arr2);
isIssueArray(value); // type guard const usernameSchema = k.string().minLength(3).asyncRefine(async (value) => {
const exists = await usersRepo.existsByUsername(value);
return exists ? [{ path: ['username'], message: 'Already taken' }] : [];
});
const result = await usernameSchema.safeParseAsync(input); Singleton container that manages the full lifecycle: config, logger, databases, WebSocket, cron, cache, registry, health and shutdown.
Katax, kataxConfigService, LoggerServiceDatabaseService, WebSocketServiceCronService, CacheServiceRegistryService, RedisStreamBridgeServiceKataxServiceError, KataxConfigErrorKataxNotInitializedErrorKataxDatabaseError, KataxRedisErrorKataxWebSocketError, KataxRegistryErrorRedisTransport, CallbackTransport, TelegramTransportregisterVersionToRedis, startHeartbeat, registerProjectInRedisimport { katax } from 'katax-service-manager';
await katax.init({
loadEnv: true,
appName: 'my-api',
logger: { level: 'info', prettyPrint: true, enableBroadcast: false },
hooks: {
beforeInit: () => {}, afterInit: () => {},
beforeShutdown: () => {}, afterShutdown: () => {},
onError: (context, error) => {},
},
registry: { url: 'https://dashboard.example.com/api/services' },
}); // ✅ CORRECT — dotenv before import
import dotenv from 'dotenv';
dotenv.config();
import { katax } from 'katax-service-manager';
// ✅ CORRECT — built-in loadEnv (v0.5+)
import { katax } from 'katax-service-manager';
await katax.init({ loadEnv: true });
// ❌ WRONG — katax reads env on import, before dotenv
import { katax } from 'katax-service-manager';
import dotenv from 'dotenv';
dotenv.config(); katax.env('PORT', '3000'); // string with default
katax.env('PORT', 3000); // number (auto-cast)
katax.env('DEBUG', false); // boolean (auto-cast)
katax.envRequired('JWT_SECRET'); // throws if missing
katax.isDev; katax.isProd; katax.isTest;
katax.nodeEnv; katax.appName; katax.version; interface KataxLifecycleHooks {
beforeInit?: () => void | Promise<void>;
afterInit?: () => void | Promise<void>;
beforeShutdown?: () => void | Promise<void>;
afterShutdown?: () => void | Promise<void>;
onError?: (context: string, error: unknown) => void | Promise<void>;
} katax.onShutdown(async () => {
await cleanupCustomResource();
});
await katax.shutdown(); // Closes all services in order
// SIGTERM and SIGINT are handled automatically import { Katax } from 'katax-service-manager';
beforeEach(() => Katax.reset());
katax.overrideService('db:main', mockDb);
katax.overrideService('logger', mockLogger);
katax.clearOverride('db:main'); // Remove specific
katax.clearOverride(); // Remove all Supports PostgreSQL, MySQL, MongoDB and Redis with typed connections and configurable pool.
interface DatabaseConfig {
name: string;
type: 'postgresql' | 'mysql' | 'mongodb' | 'redis';
required?: boolean; // Default: true
connection: string | ConnectionOptions;
pool?: { max?: 10, min?: 2, idleTimeoutMillis?: 30000, connectionTimeoutMillis?: 30000 };
}
// Connection options per type:
// PostgreSQL: { host, port?: 5432, database, user, password, ssl? }
// MySQL: { host, port?: 3306, database, user, password, ssl? }
// MongoDB: { host, port?: 27017, database, user?, password?, authSource? }
// Redis: { host, port?: 6379, password?, db?, tls? } // Create
await katax.database({
name: 'main', type: 'postgresql',
connection: {
host: katax.envRequired('DB_HOST'),
port: katax.env('DB_PORT', 5432),
database: katax.envRequired('DB_NAME'),
user: katax.envRequired('DB_USER'),
password: katax.envRequired('DB_PASSWORD'),
},
pool: { max: 10, min: 2 },
});
// Optional (does not crash on failure)
await katax.database({ name: 'analytics', type: 'postgresql', required: false, connection: { ... } });
// Redis
await katax.database({
name: 'cache', type: 'redis',
connection: { host: '127.0.0.1', port: 6379 },
});
// Retrieve
const db = katax.db('main');
db.asSql(); // ISqlDatabase (throws if not SQL)
db.asMongo(); // IMongoDatabase
db.asRedis(); // IRedisDatabase interface ISqlDatabase {
query<T>(sql: string, params?: unknown[]): Promise<T>;
getClient(): Promise<unknown>;
close(): Promise<void>;
}
interface IMongoDatabase {
getClient(): Promise<unknown>; // MongoClient
close(): Promise<void>;
}
interface IRedisDatabase {
redis(...args: (string | number | Buffer)[]): Promise<unknown>;
close(): Promise<void>;
} Redis reconnect is built-in since v0.5+. No manual configuration required.
High-level API over Redis with automatic JSON serialization.
const cache = katax.cache('cache'); // redis db name | Method | Description |
|---|---|
get<T>(key) | Get value (auto-deserializes JSON) |
set(key, value, ttl?) | Store with optional TTL (seconds) |
del(key) | Delete key |
delMany(keys[]) | Delete multiple keys |
exists(key) | Check existence |
ttl(key) | Remaining TTL (-1: no expiry, -2: missing) |
expire(key, seconds) | Set expiration |
incr(key) | Increment by 1 |
incrBy(key, n) | Increment by n |
decr(key) | Decrement by 1 |
mget<T>(keys[]) | Get multiple values |
mset(entries[]) | Store multiple [key, value] |
clear(pattern?) | Delete by pattern (disabled in prod for '*') |
stats() | Redis statistics |
await cache.set('user:123', userData, 3600);
const user = await cache.get<User>('user:123');
await cache.del('user:123');
await cache.incr('page:views');
await cache.mset([['k1', v1], ['k2', v2]]); Structured logger based on Pino with WebSocket broadcasting and transport system.
interface LoggerConfig {
level?: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';
prettyPrint?: boolean;
enableBroadcast?: boolean;
destination?: string;
} // Strings or objects
katax.logger.info('Server started');
katax.logger.info({ message: 'Server started', port: 3000 });
katax.logger.error({ message: 'Query failed', error: err });
// Broadcast to WebSocket
katax.logger.info({ message: 'Trade executed', broadcast: true, room: 'admins' });
// Persist to Redis transport
katax.logger.info({ message: 'Critical event', persist: true });
// Avoid feedback loops
katax.logger.warn({ message: 'Transport failed', skipTransport: true });
katax.logger.warn({ message: 'Telegram issue', skipTelegram: true });
// Child logger with context
const log = katax.logger.child({ service: 'payments', userId: 123 });
log.info({ message: 'Payment processed' }); interface LogMessageObject {
message: string; broadcast?: boolean; room?: string;
persist?: boolean; skipTransport?: boolean;
skipTelegram?: boolean; skipRedis?: boolean;
error?: Error; stack?: string; code?: string | number;
userId?: string | number; requestId?: string;
duration?: number; statusCode?: number;
method?: string; path?: string; ip?: string;
[key: string]: unknown;
} import { RedisTransport, TelegramTransport, CallbackTransport } from 'katax-service-manager';
// Redis — persist logs to Redis Stream
const redis = new RedisTransport(katax.db('cache'), { streamKey: 'katax:logs' });
redis.filter = (log) => log.level === 'error' || log.persist === true;
katax.logger.addTransport(redis);
// Telegram — critical alerts
const telegram = new TelegramTransport({
botToken: katax.envRequired('TELEGRAM_BOT_TOKEN'),
chatId: katax.envRequired('TELEGRAM_ALERTS_CHAT_ID'),
levels: ['error', 'fatal'],
includePersist: true,
parseMode: 'Markdown',
name: 'telegram-errors',
});
katax.logger.addTransport(telegram);
// Callback — custom handler
const callback = new CallbackTransport({
name: 'custom',
send: async (log) => { /* custom logic */ },
filter: (log) => log.level === 'error',
});
katax.logger.addTransport(callback);
// Management
katax.logger.removeTransport('telegram-errors');
await katax.logger.closeTransports();
katax.logger.getPinoLogger(); // Underlying Pino instance // Shared with Express (preferred)
const httpServer = createServer(app);
await katax.socket({
name: 'main',
httpServer,
cors: { origin: '*' },
});
// Standalone with auth
await katax.socket({
name: 'events',
port: 3001,
enableAuth: true,
authValidator: async (token) => token === katax.envRequired('WS_SECRET'),
});
const ws = katax.ws('main'); | Method | Description |
|---|---|
emit(event, data, room?) | Emit event (optionally to room) |
emitToRoom(room, event, data) | Emit to specific room |
on(event, handler) | Listen for events |
onConnection(handler) | Handle new connections |
hasRoomListeners(room) | Has listeners in room? |
getRoomClientsCount(room) | Clients in room |
hasConnectedClients() | Any clients connected? |
getConnectedClientsCount() | Total connected clients |
getServer() | Underlying Socket.IO server |
close() | Close WebSocket server |
ws.onConnection((socket) => {
socket.on('message', (data) => { ... });
socket.emit('welcome', { status: 'connected' });
socket.join('room-123');
socket.leave('room-123');
}); katax.cron({
name: 'process-assets',
schedule: '*/10 6-15 * * 1-5', // every 10 min, 6am-3pm, Mon-Fri
task: processAssets,
runOnInit: katax.isProd,
timezone: 'America/Mexico_City',
enabled: true,
});
// Management
katax.cronService.getJobs();
katax.cronService.startJob('process-assets');
katax.cronService.stopJob('process-assets');
katax.cronService.removeJob('process-assets');
katax.cronService.stopAll(); Register your service with a dashboard or custom handler. Auto-heartbeat with retry.
await katax.init({
registry: {
url: 'https://dashboard.example.com/api/services',
apiKey: katax.env('REGISTRY_KEY'),
heartbeatInterval: 30000, // ms
requestTimeoutMs: 5000,
retryAttempts: 2,
retryBaseDelayMs: 300,
metadata: { env: katax.nodeEnv, region: 'us-east' },
},
});
// Or custom handler
registry: {
handler: {
register: async (info) => { ... },
heartbeat: async (info) => { ... },
unregister: async (payload) => { ... },
}
}
katax.isRegistered; // boolean
katax.getServiceInfo(); // ServiceInfo | null const health = await katax.healthCheck();
// { status: 'healthy' | 'degraded' | 'unhealthy',
// services: { databases: {}, sockets: {}, cron: boolean },
// timestamp: number }
app.get('/api/health', async (req, res) => {
const health = await katax.healthCheck();
const code = health.status === 'healthy' ? 200 : health.status === 'degraded' ? 503 : 500;
res.status(code).json(health);
}); Connects Redis Stream logs to WebSocket for real-time dashboards.
const bridge = katax.bridge('cache', 'main', {
appName: 'my-api',
streamKey: 'katax:logs',
group: 'katax-bridge-my-api',
batchSize: 10,
blockTimeout: 2000,
});
await bridge.start();
bridge.isRunning();
bridge.stop();
// Client events:
// subscribe-project(appName) → receives 'project-history' + 'log'
// unsubscribe-project(appName) // Managed (auto-cleanup on shutdown)
katax.heartbeat(
{ app: katax.appName, port: 3000, version: katax.version, intervalMs: 10000 },
'cache', 'main',
);
// Manual helpers
import { registerProjectInRedis, startHeartbeat } from 'katax-service-manager';
await registerProjectInRedis(redisDb, { app: katax.appName, version: katax.version, port: PORT });
const hb = startHeartbeat(redisDb, { app: katax.appName, port: PORT, intervalMs: 10000 }, ws);
hb?.stop(); import dotenv from 'dotenv';
dotenv.config();
import { katax } from 'katax-service-manager';
import { createServer } from 'http';
import app from './app.js';
async function bootstrap(): Promise<void> {
try {
await katax.init({
loadEnv: true,
logger: {
level: katax.env('LOG_LEVEL', 'info') as any,
prettyPrint: katax.isDev,
enableBroadcast: true,
},
});
await katax.database({
name: 'main', type: 'postgresql',
connection: {
host: katax.envRequired('DB_HOST'),
port: katax.env('DB_PORT', 5432),
database: katax.envRequired('DB_NAME'),
user: katax.envRequired('DB_USER'),
password: katax.envRequired('DB_PASSWORD'),
},
pool: { max: 10, min: 2 },
});
await katax.database({
name: 'cache', type: 'redis',
connection: { host: katax.env('REDIS_HOST', '127.0.0.1'), port: 6379 },
});
const PORT = katax.env('PORT', '3000');
const httpServer = createServer(app);
await katax.socket({ name: 'main', httpServer, cors: { origin: '*' } });
katax.cron({
name: 'cleanup', schedule: '0 3 * * *',
task: async () => { /* nightly cleanup */ },
});
httpServer.listen(PORT, () => {
katax.logger.info({ message: `Server running on http://localhost:${PORT}` });
});
} catch (err) {
console.error('Bootstrap failed:', err);
process.exit(1);
}
}
void bootstrap(); CLI for scaffolding Katax-based APIs and managing VPS deployments with PM2.
katax init [project-name]Scaffolds a complete Express + TypeScript + katax-core project.
| Flag | Description |
|---|---|
-f, --force | Overwrite existing directory |
--pm <npm|pnpm> | Package manager (default: pnpm) |
--ignore-scripts | Disable lifecycle scripts |
--write-npmrc | Write .npmrc for reproducible installs |
Interactive prompts: name/description, database (PostgreSQL/MySQL/MongoDB/None), auth (JWT/None), validation (katax-core/None), Swagger/OpenAPI, katax-service-manager mode (singleton/instance), registry, lifecycle hooks, Redis cache, WebSocket/Socket.IO, port, git init.
katax add endpoint <name>Scaffolds an endpoint with 4 files: validator, controller, handler, routes.
| Flag | Description |
|---|---|
-m, --method <method> | HTTP method (GET, POST, PUT, PATCH, DELETE) |
-p, --path <path> | Custom route path |
Supports interactive field definition (name, type, required, rules). Auto-updates main router and regenerates OpenAPI docs. Supports nested resources: admin/audit/logs.
katax generate crud <resource-name>Generates complete CRUD (5 endpoints): list, get by ID, create, update, delete.
| Flag | Description |
|---|---|
--no-auth | Without auth middleware |
katax generate repository <name>Generates data access layer. Detects DB type from package.json. Typed methods: findAll(), findById(), create(), update(), delete(). Uses ISqlDatabase / IMongoDatabase.
katax generate docs| Flag | Description |
|---|---|
-f, --force | Force regeneration |
-o, --output <path> | Custom output path |
-p, --port <port> | Server port |
-u, --url <url> | Production URL |
Scans src/api/, generates OpenAPI 3.0 spec, creates Swagger UI at /docs and /api-docs.
katax infoShows project structure, dependencies and routes. Aliases: status, ls.
katax deploy init # Initial PM2 setup (repo, branch, path, cluster, memory, env)
katax deploy update # Pull, rebuild, restart
-b, --branch <branch>
--hard # Hard reset
-a, --app-name <name>
katax deploy rollback # Revert to previous commit(s)
-c, --commits <n> # Default: 1
-a, --app-name <name>
katax deploy logs # View PM2 logs
-l, --lines <n>
-f, --follow
-a, --app-name <name>
katax deploy status # Show PM2 apps katax fix docs # Patches build script to copy openapi.json
--skip-install
katax fix all # Apply all fixes
katax fix list # List available patches --no-color # No colors
--verbose # Detailed logging
-v, --version # Show version katax init my-api --pm pnpm --ignore-scripts --write-npmrc
katax add endpoint admin/audit/logs -m POST
katax generate crud admin/users --no-auth
katax generate repository products
katax generate docs -u https://api.example.com
katax deploy update -a my-api-prod Generates k.object() schemas with per-field validation, async validators and TypeScript inference via kataxInfer.
Generates business logic with ControllerResult<T>, createSuccessResult() and createErrorResult().
Generates Express middleware chaining validator + controller with sendResponse().
Generates Express Router with method calls and JSDoc documentation.
AST-based update of the main routes.ts. Prevents duplicates.
Scans validators and routes, generates OpenAPI 3.0 spec with schemas and tags.
hashPassword() (bcrypt)hashPasswordArgon2()initSSE() — init connectionsendSSEEvent() — send eventsendSSEComment() — keep-alivecloseSSE() — cleanupSSEStream class with auto keep-alivesendSuccess<T>()sendError()sendValidationError()sendResult<T,E>()sendResponse()line(), import(), export(), openBlock(), build()renderTemplate(), toPascalCase(), toCamelCase()success(), error(), warning(), info()The Copilot skill for the Katax ecosystem. Knows versions, exports, patterns, CLI commands and troubleshooting.
# Local skill path
.github/skills/katax-ecosystem/SKILL.md
# Usage examples
@katax-ecosystem create a schema with coerce and asyncRefine for query params
@katax-ecosystem bootstrap PostgreSQL + Redis + graceful shutdown
@katax-ecosystem generate a nested CRUD admin/users with typed repository