stratal

Stratal Core Framework

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "stratal" with this command: npx skills add strataljs/stratal/strataljs-stratal-stratal

Stratal Core Framework

Stratal is a modular Cloudflare Workers framework with dependency injection (tsyringe), Hono-based routing with OpenAPI generation, queue consumers, cron jobs, i18n, caching, storage, and email. Full documentation at stratal.dev.

Key Constraints

  • ESM-only ("type": "module" )

  • Build with tsc only — never esbuild/tsup (tsyringe requires emitDecoratorMetadata )

  • experimentalDecorators and emitDecoratorMetadata must be enabled in tsconfig

  • Always import Zod from stratal/validation , never from zod directly

  • Service classes can be used directly as DI tokens (@inject(MyService) ). Only create Symbol tokens when the service needs to be replaceable or is part of a reusable library

  • Use constructor injection with @inject() decorators

Project Setup

Docs: Installation · Your First Worker

// src/index.ts — Worker entry point import { Stratal } from 'stratal'; import { AppModule } from './app.module';

const app = new Stratal({ module: AppModule });

export default app;

Modules

Docs: Modules · Lifecycle Hooks

@Module({ imports: [OtherModule], providers: [MyService], controllers: [MyController], consumers: [MyConsumer], jobs: [MyCronJob], }) export class AppModule implements OnInitialize { onInitialize(ctx: ModuleContext) { /* setup logic */ } }

Dynamic modules use forRoot() (sync) or forRootAsync() (async factory). Lifecycle hooks: OnInitialize , OnShutdown .

Controllers and Routing

Docs: Controllers & Routing · OpenAPI

@Controller('/api/v1/users', { tags: ['Users'] }) export class UsersController implements IController { constructor(@inject(UsersService) private usersService: UsersService) {}

@Route({ body: CreateUserSchema, response: UserSchema }) async create(ctx: RouterContext) { const data = await ctx.body<CreateUserInput>(); return ctx.json(await this.usersService.create(data)); } }

Method → HTTP mapping (convention-based): index → GET, show → GET /:id, create → POST (201), update → PUT /:id, patch → PATCH /:id, destroy → DELETE /:id.

Use await ctx.body<T>() to get validated body — not ctx.req.valid('json') .

HTTP Method Decorators

As an alternative to convention-based @Route() , use explicit HTTP method decorators for full control over method and path:

import { Controller, Get, Post, All } from 'stratal/router';

@Controller('/api/v1/users', { tags: ['Users'] }) export class UsersController implements IController { constructor(@inject(UsersService) private usersService: UsersService) {}

@Get('/', { response: UsersListSchema }) async list(ctx: RouterContext) { return ctx.json(await this.usersService.findAll()); }

@Post('/', { body: CreateUserSchema, response: UserSchema, statusCode: 201 }) async create(ctx: RouterContext) { const data = await ctx.body<CreateUserInput>(); return ctx.json(await this.usersService.create(data), 201); }

@Get('/:id', { params: z.object({ id: z.string().uuid() }), response: UserSchema }) async show(ctx: RouterContext) { const { id } = ctx.params<{ id: string }>(); return ctx.json(await this.usersService.findById(id)); }

@All('/:path{.+}', { response: z.object({ message: z.string() }) }) async catchAll(ctx: RouterContext) { return ctx.json({ message: 'Not found' }, 404); } }

Available decorators: @Get(path, config?) , @Post(path, config?) , @Put(path, config?) , @Patch(path, config?) , @Delete(path, config?) , @All(path, config?) .

RouteConfig options: body , params , query , response (required), tags , security , description , summary , statusCode , hideFromDocs .

Key rules:

  • HTTP method decorators and @Route() cannot be mixed in the same controller — use one pattern or the other

  • Default status code is 200 for all methods; use statusCode: 201 explicitly for POST create endpoints

  • @All routes are automatically hidden from OpenAPI docs (OpenAPI doesn't support catch-all HTTP methods)

Dependency Injection

Docs: DI · Providers

// Simple: use class directly as token @Transient() export class UsersService { /* ... */ } // inject with: @inject(UsersService)

// Symbol tokens — only for replaceable abstractions const USER_REPO = Symbol.for('UserRepository'); @Module({ providers: [ { provide: USER_REPO, useClass: PgUserRepository, scope: Scope.Request }, ], })

Scope Behavior

Scope.Transient

New instance per resolution (default)

Scope.Singleton

Single instance globally

Scope.Request

New instance per HTTP request

Provider types: useClass , useValue , useFactory (with inject array), useExisting .

StratalEnv Augmentation

Docs: Environment Typing

// 1. Generate wrangler types: npx wrangler types // 2. Extend StratalEnv with Cloudflare.Env: export {};

declare module 'stratal' { interface StratalEnv extends Cloudflare.Env {} }

Run npx wrangler types to generate Cloudflare.Env from your wrangler.jsonc bindings.

Guards and Middleware

Docs: Guards · Middleware

// Guard — implements CanActivate @Transient() export class ApiKeyGuard implements CanActivate { constructor(@inject(DI_TOKENS.CloudflareEnv) private env: StratalEnv) {} canActivate(ctx: RouterContext): boolean { return ctx.header('x-api-key') === this.env.API_KEY; } } // Apply with @UseGuards(ApiKeyGuard) on controller or method

// Middleware class — implements Middleware @Transient() export class LoggingMiddleware implements Middleware { async handle(ctx: RouterContext, next: () => Promise<void>) { console.log(--> ${ctx.c.req.method} ${ctx.c.req.path}); await next(); } }

// Middleware registration — module implements MiddlewareConfigurable export class AppModule implements MiddlewareConfigurable { configure(consumer: MiddlewareConsumer) { consumer.apply(LoggingMiddleware).forRoutes('*'); consumer.apply(CorsMiddleware).exclude('/health').forRoutes(ApiController); } }

Configuration

Docs: Configuration

const databaseConfig = registerAs('database', (env: StratalEnv) => ({ url: env.DATABASE_URL, poolSize: 10, }));

@Module({ providers: [databaseConfig.asProvider()] }) export class AppModule {} // Inject with: @inject(databaseConfig.KEY) config: { url: string; poolSize: number }

Events

Docs: Events

@Listener() export class UserCreatedListener { @On('after.User.create') async sendWelcomeEmail(context: EventContext<'after.User.create'>) { // handle event } }

Augment CustomEventRegistry for type-safe custom events. Options: priority (number), blocking (boolean).

Error Handling

Docs: Error Handling

import { ApplicationError, ERROR_CODES } from 'stratal/errors';

export class UserNotFoundError extends ApplicationError { constructor(userId: string) { super('errors.userNotFound', ERROR_CODES.RESOURCE.NOT_FOUND, { userId }); } }

ApplicationError provides structured JSON responses with i18n message keys, numeric error codes (from ERROR_CODES ), and metadata.

Queue Consumers

Docs: Queues

@Transient() export class EmailConsumer implements IQueueConsumer<EmailPayload> { readonly messageTypes = ['email.send'];

async handle(message: QueueMessage<EmailPayload>) { // process message.payload } }

Register in module consumers array. Messages have id , type , payload , and optional metadata .

Cron Jobs

Docs: Cron Jobs

@Transient() export class CleanupJob implements CronJob { readonly schedule = '0 2 * * *';

async execute(controller: ScheduledController) { // runs daily at 2 AM UTC } }

Register in module jobs array. Schedule must match a trigger in wrangler.jsonc .

Workers

Docs: Durable Objects · Service Bindings · Workflows

Stratal provides base classes for Cloudflare Workers primitives (Durable Objects, Service Bindings/RPC, Workflows) with built-in DI support. Each class exposes a runInScope() method that creates a request-scoped DI container from the static Stratal singleton.

// src/index.ts — MUST export Stratal as default + worker classes as named exports export { Counter } from './counter'; export { AuthRpc } from './auth-rpc'; export { MyWorkflow } from './my-workflow'; export default new Stratal({ module: AppModule });

StratalDurableObject

Extends DurableObject . runInScope auto-registers DI_TOKENS.DurableObjectState and DI_TOKENS.DurableObjectId .

export class Counter extends StratalDurableObject { async increment() { return this.runInScope(async (container) => { const counterService = container.resolve<CounterService>(CounterService)

  counterService.increment();
});

} }

StratalWorkerEntrypoint

Extends WorkerEntrypoint for RPC / Service Bindings.

export class AuthRpc extends StratalWorkerEntrypoint { async verifyToken(token: string) { return this.runInScope(async (container) => { const auth = container.resolve(AuthService); return auth.verify(token); }); } }

StratalWorkflow

Extends WorkflowEntrypoint with generic Env and Params types.

export class MyWorkflow extends StratalWorkflow<Env, { orderId: string }> { async run(event: WorkflowEvent<{ orderId: string }>, step: WorkflowStep) { await step.do('process', () => this.runInScope(async (container) => { const svc = container.resolve(OrderService); return svc.validate(event.payload.orderId); }) ); } }

Built-in Modules Quick Reference

Module Import Docs

CacheModule

stratal/cache

Caching

EmailModule

stratal/email

Email

StorageModule

stratal/storage

Storage

I18nModule

stratal/i18n

i18n

OpenAPIModule

stratal/openapi

OpenAPI

LoggerService

stratal/logger

Logging

ConfigModule

stratal/config

Configuration

QueueModule

stratal/queue

Queues

Sub-path Imports

Path Key Exports

stratal

Stratal , Application , @Module , StratalEnv

stratal/di

Container , DI_TOKENS , Scope , inject , Transient

stratal/router

@Controller , @Route , @Get , @Post , @Put , @Patch , @Delete , @All , RouteConfig , RouterContext , UseGuards , IController

stratal/validation

z (Zod), ZodType , validation utilities

stratal/errors

ApplicationError , ERROR_CODES , built-in error classes

stratal/events

@Listener , @On , EventRegistry

stratal/i18n

I18nModule , I18nService

stratal/cache

CacheModule , CacheService

stratal/email

EmailModule , EmailService

stratal/storage

StorageModule , StorageService

stratal/queue

QueueModule , QueueService , IQueueConsumer

stratal/logger

LoggerService , LOGGER_TOKENS

stratal/config

ConfigModule , registerAs

stratal/openapi

OpenAPIModule

stratal/workers

StratalDurableObject , StratalWorkerEntrypoint , StratalWorkflow , runInScope

Do's and Don'ts

  • Do use class references as DI tokens for simple services (@inject(MyService) )

  • Do use Symbol tokens only for replaceable abstractions or reusable libraries

  • Do use await ctx.body<T>() for validated request bodies

  • Do import Zod from stratal/validation

  • Do use constructor injection with @inject()

  • Do add @Transient() to consumers, jobs, guards, middleware, and listeners

  • Do register consumers in consumers and jobs in jobs arrays

  • Don't use esbuild or tsup — only tsc

  • Don't use ctx.req.valid('json') — use await ctx.body<T>()

  • Don't import Zod from zod directly

  • Do export the Stratal instance as the default export (required for the static singleton used by worker classes)

  • Do use runInScope for each method/workflow step that needs DI — each call gets a fresh request-scoped container

  • Don't cache container references across runInScope calls — the container is only valid within the callback

  • Don't disable emitDecoratorMetadata in tsconfig

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

stratal-framework

No summary provided by upstream source.

Repository SourceNeeds Review
General

stratal-seeders

No summary provided by upstream source.

Repository SourceNeeds Review
General

stratal-incremental-adoption

No summary provided by upstream source.

Repository SourceNeeds Review
General

stratal-testing

No summary provided by upstream source.

Repository SourceNeeds Review