@hwy-fm/std
v0.1.2
Published
A framework built on [`@hwy-fm/kernel`](https://www.npmjs.com/package/@hwy-fm/kernel).
Maintainers
Readme
@hwy-fm/std
A framework built on @hwy-fm/kernel.
Kernel is a statically compiled computation kernel that provides five primitives and leaves all architectural decisions to you. std makes those decisions — 8 Slots, a default protocol, one-liner decorators — so you can write business logic directly. Everything std provides is built through Kernel's public API. You can customize any Slot, add new ones, or replace the entire architecture.
Quick Start
npm install -g @hwy-fm/cli
hwy create my-app
cd my-app && npm install && hwy startPre-configured Slots
std registers 8 Slots across all three pipeline stages:
INGRESS (request processing)
| Slot | Composer | Order | Typical Use |
|------|----------|-------|-------------|
| Catch | onion | 1st | Error boundary — wraps all subsequent Slots |
| Receive | linear | 2nd | Parse / deserialize incoming data |
| Identify | linear | 3rd | Authentication — resolve caller identity |
| Guard | linear | 4th | Authorization — check permissions |
| Check | linear | 5th | Validation — verify input constraints |
PROCESS (business logic)
| Slot | Composer | Use |
|------|----------|-----|
| Process | linear | Business logic entry point (Seed) |
EGRESS (response / cleanup)
| Slot | Composer | Order | Use |
|------|----------|-------|-----|
| Deliver | egress | 1st | Serialize and send response |
| Trace | egress | 2nd | Logging, metrics, tracing |
EGRESS Slots always execute (finally semantics), even when the pipeline fails.
Slot Builder API
Each Slot is a StdSlotBuilder with typed decorators:
// Class decorator — @Injectable() is auto-applied
@Kernel.Guard.instruction('**')
class AuthGuard {
execute(ctx) { /* ... */ }
}
// Method decorator — host class needs @Injectable()
@Injectable()
class MyService {
@Kernel.Process.seed('users')
getUsers(ctx) { ctx.result = []; }
@Kernel.Process.seed('orders')
getOrders(ctx) { ctx.result = []; }
}| Method | Description |
|--------|-------------|
| .instruction() | Register an Instruction (no filter — runs for all Seeds) |
| .instruction('pattern') | Register an Instruction matching a pattern |
| .instruction(entry) | Register with full entry options |
| .seed('pattern') | Register a Seed with a match pattern |
| .seed(entry) | Register a Seed with full entry options |
| .exclude(token) | Exclude a specific Instruction from this Slot |
| .reset() | Reset all Instructions in this Slot |
Composer-Typed Signatures
Decorators validate method signatures at compile time based on the Slot's composer:
| Composer | Method Signature | Example |
|----------|-----------------|---------|
| linear | (ctx) => any | Guard, Check, Process |
| onion | (ctx, next) => any | Catch |
| egress | (ctx, error?) => any | Deliver, Trace |
// ✅ Correct — onion composer expects (ctx, next)
@Kernel.Catch.instruction()
class ErrorBoundary {
async execute(ctx, next) {
try { await next(); }
catch (e) { ctx.result = { error: e.message }; }
}
}
// ✅ Correct — egress receives optional error
@Kernel.Deliver.instruction()
class ResponseWriter {
execute(ctx, error?) {
if (error) ctx.result = { error: error.message };
}
}Custom Slots
Declare additional Slots beyond the 8 pre-configured ones:
import { Kernel } from '@hwy-fm/kernel';
// Declare a custom INGRESS slot
const Throttle = Kernel.ingress('throttle', {
composer: 'linear',
anchors: { after: ['identify'], before: ['guard'] },
});
@Throttle.instruction('**')
class RateLimiter {
execute(ctx) { /* rate limiting logic */ }
}| Method | Description |
|--------|-------------|
| Kernel.ingress(name, options?) | Declare an INGRESS Slot |
| Kernel.process(name, options?) | Declare a PROCESS Slot |
| Kernel.egress(name, options?) | Declare an EGRESS Slot |
Custom Slot Methods
Extend all StdSlotBuilder instances with reusable decorator logic via defineSlotMethod.
The callback returns a SlotMethodDescriptor:
| Field | Description |
|-------|-------------|
| entry | Registration metadata (match, order, protocol, etc.). Defaults to {} |
| kind | 'instruction' (default) or 'seed' — controls which registry the decorator writes to |
| decorate | Optional transform hook — receives (target, propertyKey?, descriptor?) before registration |
// A timing decorator — wraps execute to add performance logging
Kernel.defineSlotMethod('timed', (label: string) => ({
decorate(target, propertyKey, descriptor) {
const original = propertyKey ? descriptor!.value : target.prototype.execute;
const wrapped = async (...args) => {
const start = performance.now();
const result = await original(...args);
console.log(`[${label}] ${performance.now() - start}ms`);
return result;
};
if (propertyKey) descriptor!.value = wrapped;
else target.prototype.execute = wrapped;
},
}));
// Now available on every Slot:
@Kernel.Process.timed('build')
class BuildHandler {
execute(ctx) { /* ... */ }
}
// Register as Seed instead of Instruction
Kernel.defineSlotMethod('handler', (match: string) => ({
entry: { match },
kind: 'seed',
}));
@Kernel.Process.handler('build')
class BuildSeed {
execute(ctx) { ctx.result = { ok: true }; }
}Type-Safe Custom Methods
Use StdSlotMethodRegistry declaration merging to get full TypeScript support for custom methods:
declare module '@hwy-fm/std' {
interface StdSlotMethodRegistry {
timed(label: string): StdDecorator;
handler(match: string): StdDecorator;
}
}Once declared, Kernel.Process.timed('build') returns a StdTypedDecorator that validates execute() signatures at compile time — exactly like the built-in instruction() and seed().
Gateway
Cross-kernel dispatch — typed base class that wraps createContext() + forward():
import { Gateway } from '@hwy-fm/std';
const ViewKernel = KernelModel();
@Kernel.Deliver.instruction()
class ViewBridge extends Gateway(ViewKernel) {
prepare(ctx) {
return {
metadata: { path: ctx.metadata.view },
input: { data: ctx.result },
};
}
}| Method | Description |
|--------|-------------|
| prepare(ctx) | Return { metadata, input, protocol?, injector? } or null to skip |
| resolve(result, ctx) | Handle the target Kernel's result (default: ctx.result = result) |
| execute(ctx) | Auto-called — runs prepare() → forward() → resolve() |
Gateway automatically:
- Creates a context for the target Kernel
- Uses
forward()(lightweight, no duplicate telemetry) - Propagates
signalfor timeout / cancellation - Shares the debug trace across Kernels
STD_PROTOCOL
STD_PROTOCOL is a built-in protocol identifier. All std decorators register under this protocol by default.
To override the protocol at bootstrap time (e.g. for platform-specific adapters), provide a custom value:
@Kernel.bootstrap({}, [
{ provide: STD_PROTOCOL, useValue: MY_CUSTOM_PROTOCOL },
])
class App { /* ... */ }The built-in StdAdmission automatically remaps STD_PROTOCOL to the provided value at compile time.
Application Metadata
@Kernel.bootstrap() accepts a metadata argument — either a plain object or a DI token pointing to a class that implements MetadataInfo. The resolved data is injected as APPLICATION_METADATA and can be consumed via the @Input decorator.
Plain Object
@Kernel.bootstrap({ api: '/v1', title: 'MyApp', db: { host: 'localhost' } })
class App {
main() {
// Called automatically after bootstrap completes
}
}Async Loader (MetadataInfo)
When metadata needs to be loaded asynchronously (remote config, environment files, etc.), provide a class that implements MetadataInfo:
@Injectable()
class RemoteConfig implements MetadataInfo {
async load() {
return { api: '/v1', title: 'MyApp' };
}
}
@Kernel.bootstrap(RemoteConfig)
class App { }Consuming with @Input
Use @Input('path') on constructor parameters or properties to read values from the resolved metadata. Supports nested paths with dot notation:
@Kernel.Process.seed('home')
class HomeHandler {
@Input('api') api!: string; // '/v1'
@Input('title') title!: string; // 'MyApp'
@Input('db.host') dbHost!: string; // 'localhost'
execute(ctx) {
ctx.result = { api: this.api, title: this.title };
}
}@Input supports properties and constructor parameters. It does not support method-level parameter injection.
SlotName Constants
import { SlotName } from '@hwy-fm/std';
SlotName.Catch // 'catch'
SlotName.Receive // 'receive'
SlotName.Identify // 'identify'
SlotName.Guard // 'guard'
SlotName.Check // 'check'
SlotName.Process // 'process'
SlotName.Deliver // 'deliver'
SlotName.Trace // 'trace'Related Packages
| Package | Description |
|---------|-------------|
| @hwy-fm/kernel | The pipeline engine — raw API, multi-kernel isolation, full extensibility |
| @hwy-fm/di | Dependency injection — Scoping, Hooks, Async Governance |
| @hwy-fm/cli | Build tools + CLI scaffolding |
License
MIT © hwyn
