Skip to content

ApplicationManifest-based kernel design proposal #194

@imdhemy

Description

@imdhemy

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions