Summary
This proposal describes a pure FP kernel direction where the kernel module contains the ApplicationManifest, the application module owns runtime adapters, and other framework components contribute immutable declarations/configuration to the manifest.
This is design guidance only. It does not describe the current implementation and does not request an immediate implementation.
Kernel Principles
Responsibility
The kernel should contain the ApplicationManifest: the pure application declaration shared by application adapters and framework tools.
The kernel should own only immutable manifest data. It should not own runtime behavior.
Manifest Boundary
The ApplicationManifest should describe the application composition through framework components.
Framework components can contribute immutable configuration or declaration data to the manifest. Their implementation and domain behavior stay in their own modules.
Adapter Boundary
The application module should own application adapters. Each adapter should interpret the ApplicationManifest into a runtime-specific application, such as an HTTP application or a console application.
Adapters own runtime-specific configuration and behavior. The kernel should not own:
- HTTP requests, responses, routes, middleware, or Koa state
- console command input or output
- environment-variable loading
- file loading
- dependency injection
- event dispatching
Component Boundary
Other framework components should own their own declarations and behavior.
Examples:
- routing owns route declaration and routing behavior
- validator owns validation declaration and validation behavior
- serializer owns serialization declaration and serialization behavior
- security owns security declaration and security behavior
The kernel stores component declarations in the manifest. It does not execute the components.
Kernel Design
Core Model
The kernel should expose an immutable application manifest:
export type ApplicationManifest = Readonly<{
components: readonly ApplicationComponent[];
}>;
Each component entry should be immutable declaration/configuration data:
export type ApplicationComponent<TKind extends string = string, TConfig = unknown> = Readonly<{
kind: TKind;
config: Readonly<TConfig>;
}>;
Manifest creation should be pure:
export type DefineApplication = (manifest: ApplicationManifest) => ApplicationManifest;
The kernel should not define generic parameters until there is a concrete cross-adapter use case.
Application Adapters
The application module should contain application adapters. Adapters should be functions that turn an ApplicationManifest and adapter config into an application value:
export type ApplicationAdapter<TConfig, TApplication> = (
manifest: ApplicationManifest,
config: TConfig,
) => TApplication;
If adapter creation must perform IO, the effect should be explicit:
export type EffectfulApplicationAdapter<TConfig, TApplication> = (
manifest: ApplicationManifest,
config: TConfig,
) => Promise<TApplication>;
HTTP Application
An HTTP application should be immutable application data created from the manifest:
export type HttpApplication = Readonly<{
kind: 'http';
handler: HttpHandler;
}>;
export type CreateHttpApplication = (
manifest: ApplicationManifest,
config: HttpAdapterConfig,
) => HttpApplication;
Starting a server is an effect and should be separate from application creation:
export type Listen = (
application: HttpApplication,
options: ListenOptions,
) => Promise<RunningHttpApplication>;
export type RunningHttpApplication = Readonly<{
close: () => Promise<void>;
}>;
Koa may remain an implementation detail for an HTTP adapter, but the destination architecture should not define the public application type as Koa.
Console Application
A console application should follow the same data-plus-functions shape:
export type ConsoleApplication = Readonly<{
kind: 'console';
commands: readonly CommandDefinition[];
}>;
export type CreateConsoleApplication = (
manifest: ApplicationManifest,
config: ConsoleAdapterConfig,
) => ConsoleApplication;
export type RunConsoleApplication = (
application: ConsoleApplication,
input: ConsoleInput,
) => Promise<ConsoleResult>;
Creating the console application can be pure. Running a command is effectful.
Boundaries
The kernel technical design should not include:
- HTTP requests, responses, routes, middleware, or Koa state
- console command input or output
- generic parameters without a concrete cross-adapter use case
- environment-variable loading
- file loading
- dependency injection
- event dispatching
Component Contributions
Other framework components should expose pure functions that create ApplicationComponent values.
export type CreateApplicationComponent<TKind extends string, TConfig> = (
config: Readonly<TConfig>,
) => ApplicationComponent<TKind, TConfig>;
Example shape:
const manifest = defineApplication({
components: [
routingComponent({ routes }),
validationComponent({ constraints }),
serializationComponent({ normalizers }),
],
});
The kernel stores those declarations. Application adapters and framework tools interpret only the components they understand.
Summary
This proposal describes a pure FP kernel direction where the
kernelmodule contains theApplicationManifest, theapplicationmodule owns runtime adapters, and other framework components contribute immutable declarations/configuration to the manifest.This is design guidance only. It does not describe the current implementation and does not request an immediate implementation.
Kernel Principles
Responsibility
The kernel should contain the
ApplicationManifest: the pure application declaration shared by application adapters and framework tools.The kernel should own only immutable manifest data. It should not own runtime behavior.
Manifest Boundary
The
ApplicationManifestshould describe the application composition through framework components.Framework components can contribute immutable configuration or declaration data to the manifest. Their implementation and domain behavior stay in their own modules.
Adapter Boundary
The
applicationmodule should own application adapters. Each adapter should interpret theApplicationManifestinto a runtime-specific application, such as an HTTP application or a console application.Adapters own runtime-specific configuration and behavior. The kernel should not own:
Component Boundary
Other framework components should own their own declarations and behavior.
Examples:
The kernel stores component declarations in the manifest. It does not execute the components.
Kernel Design
Core Model
The kernel should expose an immutable application manifest:
Each component entry should be immutable declaration/configuration data:
Manifest creation should be pure:
The kernel should not define generic parameters until there is a concrete cross-adapter use case.
Application Adapters
The
applicationmodule should contain application adapters. Adapters should be functions that turn anApplicationManifestand adapter config into an application value:If adapter creation must perform IO, the effect should be explicit:
HTTP Application
An HTTP application should be immutable application data created from the manifest:
Starting a server is an effect and should be separate from application creation:
Koa may remain an implementation detail for an HTTP adapter, but the destination architecture should not define the public application type as Koa.
Console Application
A console application should follow the same data-plus-functions shape:
Creating the console application can be pure. Running a command is effectful.
Boundaries
The kernel technical design should not include:
Component Contributions
Other framework components should expose pure functions that create
ApplicationComponentvalues.Example shape:
The kernel stores those declarations. Application adapters and framework tools interpret only the components they understand.