Skip to content

Framework Reference

This page documents the current JavaScript framework surface from stdlib/sloppy/app.js and bootstrap tests.

Imports

Root runtime exports come from sloppy (for example Sloppy, Router, Results, ProblemDetails, RequestId, RequestLogging, Testing, experimental TestServices, schema, data, and sql).

Provider descriptor registration currently has one runtime module:

ts
import { sqlite } from "sloppy/providers/sqlite";

Compiler metadata markers such as Route<T>, Query<T>, Body<T>, Header<...>, RequestContext, Service<T>, Config<...>, Sqlite<...>, Postgres<...>, and SqlServer<...> are compile-time extraction shapes used by sloppyc.

sloppyc supports the static subset that the Plan and generated artifact can represent. Literal route registration, groups, controllers, middleware, CORS, health checks, ProblemDetails, request IDs, request logging, services, config, and typed handler bindings are compiler surfaces where documented. Dynamic shapes are rejected at build time with SLOPPYC_E_* diagnostics. Testing and experimental TestServices remain test helpers and are not compiler input.

Sloppy Object

APIBehavior
Sloppy.create()Returns a built app with default builder state.
Sloppy.createBuilder()Returns a mutable builder (config, logging, capabilities, services, addModule, build).
Sloppy.module(name)Creates a module descriptor with capability/service/route phases.

App Object

APIBehavior
config, log, services, capabilitiesBuilt providers.
use(providerOrWorker)Accepts worker resources and Sloppy provider descriptors. Current provider kind accepted by app validation: sqlite.
useCors(policy)Registers an app-host CORS policy for subsequently registered routes and generated preflight handlers.
useErrors(options?)Enables the app-host error policy for problem responses, typed mappings, status mappings, and safe error logs.
mapError(type, mapper)Registers a typed exception mapper.
useModule(moduleOrFactory)Accepts route-only Sloppy.module(...) or named synchronous function modules.
mapGet/mapPost/mapPut/mapPatch/mapDeleteRoute registration methods.
get/post/put/patch/deleteAliases for map*.
mapHealthChecks(options?)Registers bootstrap health, liveness, and readiness routes.
mapGroup / groupRoute grouping helpers with group-local middleware support.
mapController / controllerController mapper APIs.
freeze() / isFrozen()Freeze app mutation state.
__getRoutes(), __debug(), __getModuleGraph(), __getPlanContributions()Tested introspection helpers used by tooling/tests.

Builder Behavior

  • build() runs module phases in dependency order.
  • Capability phases run first, then service phases, then route phases.
  • build() freezes builder mutation.
  • Duplicate module names are rejected.
  • Missing module dependencies and dependency cycles are rejected.
  • Async module phase callbacks are rejected.

Module Rules

  • Module names must be lowercase.
  • dependsOn(...) creates dependency edges.
  • Duplicate module registration is rejected.
  • Route-only module descriptors can be used directly with app.useModule(...).
  • Function modules must be named and synchronous.

CORS Policies

Bootstrap app-host CORS uses this shape:

ts
app.useCors({
  origins: ["https://app.example"],
  headers: ["content-type", "authorization"],
  exposedHeaders: ["x-total-count"],
  credentials: true,
  maxAgeSeconds: 600,
});

Current behavior:

  • app.useCors(policy) applies to routes registered after the policy call.
  • origins is required and accepts an origin string, an origin array, or "*".
  • credentials: true requires explicit origins.
  • methods can override the preflight Access-Control-Allow-Methods value; otherwise the app-host derives methods from registered routes with the same pattern.
  • headers lists request headers allowed during preflight.
  • exposedHeaders writes Access-Control-Expose-Headers on actual responses.
  • maxAgeSeconds writes Access-Control-Max-Age on successful preflight responses.
  • Actual route responses receive CORS headers only when the request has an allowed Origin header.
  • The bootstrap app-host creates OPTIONS preflight routes for CORS-enabled patterns.

For source-input builds, sloppyc extracts literal app.useCors(...) policy objects into generated response wrapping, Plan metadata, and generated OPTIONS preflight routes. Dynamic policies fail with SLOPPYC_E_UNSUPPORTED_CORS.

Provider Descriptors In Framework Registration

sqlite(name, options?) returns a frozen descriptor with:

  • __sloppyProvider: true
  • kind: "sqlite"
  • name
  • token: data.<name> unless name already contains a dot
  • options

Provider descriptor name validation:

  • non-empty string
  • no leading/trailing whitespace
  • pattern: letters, digits, dot, underscore, hyphen

Merged provider options at app.use(...) must satisfy current sqlite checks, including non-empty database.

Only SQLite descriptors are currently accepted by app.use(...). PostgreSQL and SQL Server appear through typed injection and runtime data APIs, not through app.use(postgres(...)) or app.use(sqlserver(...)) descriptor modules.

Health Checks

app.mapHealthChecks(options?) registers the current bootstrap health route set:

  • aggregate health: GET /health
  • liveness: GET /health/live
  • readiness: GET /health/ready

Paths can be customized:

ts
app.mapHealthChecks({
  path: "/status",
  livenessPath: "/status/live",
  readinessPath: "/status/ready",
});

Health checks are functions or named check objects:

ts
app.mapHealthChecks({
  checks: [
    function cache() {
      return true;
    },
    {
      name: "worker-loop",
      liveness: true,
      readiness: false,
      async check() {
        return { ok: true };
      },
    },
    {
      name: "database",
      check() {
        return false;
      },
    },
  ],
});

Check functions receive the same handler context shape as regular bootstrap handlers, including services, capabilities, config, log, and route. Function checks are readiness checks by default. Object checks can opt into liveness with liveness: true and can opt out of readiness with readiness: false. Liveness has no dependency checks by default, so the liveness endpoint reports the process route table as reachable unless a check explicitly targets liveness.

Healthy responses use status 200; unhealthy responses use status 503. Response bodies contain only the health status and per-check names/statuses:

json
{
  "status": "unhealthy",
  "checks": [
    { "name": "database", "status": "unhealthy" }
  ]
}

Check return details and thrown error messages are intentionally omitted from the health response. Detailed failure logging belongs in the logging and diagnostics surfaces.

For source-input builds, sloppyc extracts literal app.mapHealthChecks(...) calls. The compiler emits three generated GET handlers and adds route-level Plan metadata with the health endpoint kind and selected check names. Health paths and check metadata must be static literals in compiler input. Health check functions that capture module-level locals are rejected.

Request IDs

RequestId.defaults(options?) returns app-host middleware that assigns one request ID to each request:

ts
import { RequestId } from "sloppy";

app.use(RequestId.defaults());

Default behavior:

  • generated IDs use a stable req-<number> format;
  • incoming request IDs are ignored;
  • ctx.requestId is available to later middleware and handlers;
  • the response receives an x-request-id header.

Options:

OptionBehavior
headerHeader name to read/write. Default: x-request-id. Must be a safe unmanaged HTTP header name.
responseHeaderWrites the response header when true. Default: true.
trustIncomingUses a safe incoming header value when true. Default: false.
generatorFunction used to generate IDs. Useful for deterministic tests.

Trusted incoming values must be non-empty HTTP header values without control characters. Invalid incoming values are ignored and a generated ID is used instead.

For source-input builds, sloppyc extracts static RequestId.defaults(...) middleware. Dynamic generator callbacks remain app-host test-only.

Request Logging

RequestLogging.defaults(options?) returns app-host middleware that writes one structured log entry when the request completes:

ts
import { RequestId, RequestLogging } from "sloppy";

app.use(RequestId.defaults());
app.use(RequestLogging.defaults());

The entry message is request completed. Fields include the request method, path or target, status, route pattern when known, request ID when present, and duration in milliseconds when enabled.

Options:

OptionBehavior
includeRouteIncludes the route pattern when the app-host knows it. Default: true.
includeDurationIncludes durationMs from the host clock. Default: true.
includeRequestIdIncludes requestId when RequestId ran earlier in the pipeline. Default: true.

Request logging records metadata only. It does not log request bodies or request headers. Authorization, cookie, API key, and proxy authorization header values stay out of the default log entry.

For source-input builds, sloppyc extracts static RequestLogging.defaults(...) middleware. Dynamic option values are rejected at build time with a diagnostic.

Logging

The app logger and request logger support:

APIBehavior
trace/debug/info/warn/error(message, fields?)Writes a structured event when the level is enabled.
isEnabled(level)Checks the current minimum level.
forCategory(name)Returns a category logger that keeps the same request metadata.

Field objects are shallow and bounded. copyLogFields() accepts at most eight fields per event; passing more than eight fields throws. Supported field values are null, booleans, finite numbers, and strings. Unsupported values are rejected rather than recursively stringified.

Builder logging APIs:

APIBehavior
setMinimumLevel(level)Sets the bootstrap app-host minimum level.
setQueueCapacity(count)Records bounded queue capacity intent for logging descriptors.
addRedactionKey(key)Adds an app-specific sensitive field key.
addMemorySink(options?)Adds a deterministic in-memory sink for tests.
writeTo.console(options?)Records a console sink descriptor with pretty or jsonl format.
writeTo.file(options)Records a JSONL file sink descriptor.

The native sloppy run path creates its runtime logger from Plan/config metadata. It supports minimum level, queue capacity, console sink settings, and JSONL file sink settings from appsettings.json / environment-derived config.

Static Provider Handles

The current static provider handle syntax is:

ts
const db = app.provider("sqlite:main");

This path is generated for SQLite provider handles. Non-SQLite static provider handles are rejected by the compiler-generated provider bridge path with SLOPPYC_E_UNSUPPORTED_PROVIDER_BRIDGE rather than silently becoming live PostgreSQL or SQL Server execution.

Typed Provider Injection

Typed handlers can ask for provider parameters:

ts
app.get("/users", async (db: Sqlite<"main">, ctx: RequestContext) => {
  return Results.json(await db.query("select id, name from users"));
});

Sqlite<"...">, Postgres<"...">, and SqlServer<"..."> are compile-time metadata markers. Runtime execution depends on the active V8 bridge, provider configuration, and live-provider dependencies for PostgreSQL and SQL Server.

Mutation And Lifecycle Errors

Common enforced errors:

  • duplicate route registration (method + pattern)
  • duplicate service registration
  • app/builder frozen mutation
  • circular service dependencies
  • singleton resolving scoped dependency
  • root service resolution for non-singleton services

The source-input compiler emits literal app.services.* and builder.services.* registrations into generated artifacts when the factory is an inline non-capturing function.

ProblemDetails

ProblemDetails.defaults(options?) returns an app-level error handling descriptor:

ts
import { Sloppy, ProblemDetails } from "sloppy";

const app = Sloppy.create();
app.use(ProblemDetails.defaults());

When installed, synchronous thrown handler errors and async rejected handler errors are converted to 500 problem responses with this safe body:

json
{"status":500,"title":"Internal Server Error","code":"SLOPPY_E_HANDLER_ERROR"}

Options:

OptionBehavior
detail: "never"Default. Do not include exception details.
detail: "development"Include details only in the stdlib app path when Sloppy:Environment or Environment is Development.
detail: "always"Include details in the stdlib app path. Use only for local diagnostics.

Error Policy

app.useErrors(options?) is the current app-host error policy surface. It maps unhandled handler exceptions to 500 application/problem+json, request validation errors to 400, auth failures to 401 or 403, and provider-shaped errors to redacted 500 provider problems. In Testing.createHost(app), the policy also maps missing routes to 404, oversized bodies to 413, and unsupported media types to 415.

app.mapError(ErrorCtor, mapper) registers a typed mapping. The mapper receives (error, ctx) and returns either a Results.* descriptor or a plain problem object.

The policy logs one safe request failed event when a logger is available. It does not log exception messages, request bodies, headers, provider messages, tokens, cookies, or connection strings.

Limits

  • app.use(...) provider validation is currently sqlite-only.
  • app.provider("sqlite:main") is the current static provider handle path; non-SQLite static handles are diagnostic-only.
  • Typed PostgreSQL and SQL Server injection still needs live database setup for execution.
  • Health checks currently register bootstrap route-table entries. Deployment health policy, authentication, and load-balancer integration are separate framework/deployment areas.
  • Request ID and request logging helpers run in the bootstrap app-host middleware path. Native request contexts also expose runtime-generated ctx.requestId for sloppy run.
  • Double-underscore methods are usable and tested, but remain internal-oriented surfaces.
  • Handler execution through sloppy run is included in supported npm platform packages. Source builds need the handler execution runtime enabled.
  • app.useErrors(...) is app-host behavior in this slice. Compiler/OpenAPI/audit support beyond the compatibility ProblemDetails.defaults(...) wrapper is planned separately.

App Test Host

Testing.createHost(app) creates a first-party in-memory host for JavaScript app-host tests. It freezes the app, snapshots routes, and dispatches HTTP-like requests through route matching, middleware, results, ProblemDetails, CORS preflight routes, health checks, and scoped services.

Use it for fast framework and handler tests:

ts
import { Sloppy, Results, Testing } from "sloppy";

const app = Sloppy.create();
app.get("/hello/{name}", (ctx) => Results.json({ hello: ctx.route.name }));

const host = Testing.createHost(app);
const response = await host.get("/hello/Ada");

Use sloppy run --once for compiled artifacts, Plan validation, native dispatch, V8 handler execution, generated typed bindings, provider bridges, and package/runtime layout checks.

Do not import Testing or TestServices from compiler input. sloppyc rejects those imports with SLOPPYC_E_UNSUPPORTED_TESTING_IMPORT; use them from JavaScript tests around the app-host or dependency-backed test surfaces.

Public alpha. APIs and artifact formats may still change between alpha revisions.