@setu-ts/common

IRuntimeServices.reportError? is the optional last-resort JSON error sink used by the kernel and errorHandler when no logger is registered. Implementations contain serialization and write failures. IErrorResponder.statusFor? purely predicts the owner’s served status for a thrown value; request logging consumes it without reporting a clamp twice. A custom response hook can serve a different status, which the predictor does not reflect.

Shared types, interfaces, and capability tokens for the Setu-TS framework.

This package is the framework’s contract layer: every other package depends on it, and it depends on nothing. It contains only interfaces, type aliases, constants, and pure zero-dependency type utilities — no runtime behavior beyond those.

Installation

# npm (pnpm add / yarn add / bun add work the same way)
npm install @setu-ts/common

# Deno
deno add npm:@setu-ts/common

What’s Inside

OptionalDependencyUnavailableError(dependency, { cause?, requiredBy? }) preserves dependency and the original cause. Since 0.11.0, loaders pass their npm package name as requiredBy to make the message actionable; it is used only in the message, with no new instance field: Optional dependency 'ioredis' could not be loaded by '@setu-ts/cache-plugin'. Install it where '@setu-ts/cache-plugin' is installed, in the version range that package declares for it — in an npm, Yarn or Bun workspace that is usually the workspace root, not the member — or choose an option that does not need it on this runtime. Without requiredBy, the message says the package that loads it generically.

AreaExports
Capability tokensCAPABILITIES, createCapabilityToken(), CapabilityToken, StandardCapability
Plugin contractIPlugin, IPluginContext, IApplication, context APIs (IRouterApi, IMiddlewareApi, ILifecycleApi, …)
Service registryIServiceRegistry, RegisterOptions, ServiceFactory
HTTP abstractionsIRequest, IResponse, IRequestContext, IMiddleware, MiddlewareFunction, RouteHandler, RouteDefinition, ResponseSnapshot, ResponseSnapshotInit
Runtime abstractionIRuntimeServices, IFileSystem, IHttpAdapter
Optional DIIContainer, Provider, ServiceScope, createToken, InjectionToken, ContainerToken, InjectEntry
Domain contractsILogger, IConfig, IValidationService, IHealthIndicator, IMetric, IJwtService, IOrmAdapter, ICacheStore, IEventBus, IMessageBroker, IQueue, ISecretManager, IAuditLogger, ICircuitBreaker, IStorage, IMailer, INotifier, IFeatureFlags, ITenantResolver
Shared typesHttpMethod, RuntimePlatform, LogLevel, LifecyclePhase, HealthStatus, MetricType, PLUGIN_PRIORITY
Utility typesResult<T, E> (ok, err, isOk, isErr, unwrap), Option<T> (some, none, isSome, isNone, fromNullable), PathPattern (createPathMatcher)

Usage

Resolve capabilities by token, typed by the interfaces defined here:

import { CAPABILITIES, type ILogger, type IPlugin } from '@setu-ts/common';

export function MyPlugin(): IPlugin {
  return {
    name: 'my-plugin',
    version: '1.0.0',
    dependencies: [CAPABILITIES.LOGGER],
    register(ctx) {
      const logger = ctx.services.get<ILogger>(CAPABILITIES.LOGGER);
      logger.info('my-plugin registered');
    },
  };
}

Handle fallible operations without throwing:

import { err, ok, type Result } from '@setu-ts/common';

function parsePort(raw: string): Result<number, RangeError> {
  const port = Number(raw);
  return Number.isInteger(port) && port > 0 && port < 65536
    ? ok(port)
    : err(new RangeError(`Invalid port: ${raw}`));
}

Response snapshots

IResponse.snapshot().headers is the documented live Headers view for middleware and application code. ResponseSnapshot.responseInit is an @internal kernel-to-runtime optimization hint: adapters may use its snapshot-local header input while constructing a native response, including adding derived headers such as Content-Length. Do not consume it from application or middleware code.

Rules

  • Zero dependencies, always.
  • No runtime-specific APIs — ever.
  • Every export is public API: documented in the repository’s PUBLIC_API.md, JSDoc’d, and covered by the backward-compatibility policy.

Ingress behaviours

IngressKind, IngressContext, IIngressBehavior, BehaviorLike, and composeBehaviorChain provide one transport-neutral, void-result behaviour chain for queue jobs, scheduler fires, broker deliveries, and WebSocket frames. An IngressContext contains only kind, name, payload, and the optional attempt, headers and consumer fields. Behaviours run in declared order; returning without next() short-circuits the native handler.

consumer (M109a) is a dispatch identity, not a capability or state bag: the subscription’s SubscribeOptions.queue when given, otherwise subscription:<instance>:<n> on 'messaging', and the job name on 'queue'; absent on 'scheduler' and 'websocket'. It exists so per-consumer state (an idempotency record) is keyed per subscriber rather than per topic.

WebSocketUpgradeGuard and WebSocketGuardDecision are the separate, route-scoped handshake guard types. They run before an accepted upgrade; they are not frame behaviours.

Idempotency (M109a)

CAPABILITIES.IDEMPOTENCY ('idempotency') names the provider of the idempotency service. The common package holds only the contracts — the store port IIdempotencyStore and its IdempotencyClaimRequest, IdempotencyClaimResult and IdempotencySettleResult types, the service contract IIdempotencyService, the route option type IdempotentRouteOptions with IdempotencyKeySource and IdempotencyFingerprintSource, and the ingress option types IdempotentIngressOptions (with its shared half IdempotentIngressCommonOptions), IngressIdempotencyKeySource and IngressIdempotencyFingerprintSource. The mechanism — IdempotencyPlugin, idempotent(), idempotentIngress() and @Idempotent — lives in @setu-ts/idempotency-plugin. The guarantee is no duplicate processing within the limits of the store — it is not a single-execution guarantee.

Error diagnostics

serializeError() preserves ordinary error fields plus a deliberately small classifier allowlist: code, errno, syscall, severity, constraint, codeName, and statusCode. Only strings, finite numbers, and booleans are kept; objects such as driver query payloads and $metadata are omitted. Classifier strings are capped at 512 Unicode code points, aggregate errors keep at most eight direct members, and the complete serialized tree is bounded to 64 nodes. An aggregate that is truncated reports omittedErrorCount.

Redaction

Classification and redaction live in @setu-ts/common so every egress plugin shares one policy shape. A RedactionPolicy maps dot-path field patterns to a classification, and classifications to redactors:

MemberMeaning
fieldsDot-path patterns, each mapped to a classification or to a FieldRedaction
redactorsRedactor selected by the classification a matched pattern carries
defaultRedactorFallback used when redactors has no entry for that classification

A fields value is either a classification string ('pii') or a FieldRedaction ({ classification, redactor? }). The object form lets one classification carry two treatments — mask the email, erase the name — without inventing a second classification string. redactValue and redactRecord select a redactor in this order: the matched field’s own redactor, then redactors[classification], then defaultRedactor, then eraseRedactor.

The framework knows four classifications — DATA_CLASSIFICATIONS.PII ('pii'), PHI ('phi'), PCI ('pci') and SECRET ('secret') — and accepts any other string, which reaches redactors[<string>].

import { createMaskRedactor, createRedactionService, DATA_CLASSIFICATIONS } from '@setu-ts/common';

const service = createRedactionService({
  fields: {
    'user.email': {
      classification: DATA_CLASSIFICATIONS.PII,
      redactor: createMaskRedactor({ keep: 4 }),
    },
    'user.name': DATA_CLASSIFICATIONS.PII,
    'auth.token': DATA_CLASSIFICATIONS.SECRET,
  },
});

service.redactRecord({ user: { email: 'jane@example.com', name: 'Jane' } });

The precedence above is executable:

ExpressionValue
(await import('@setu-ts/common')).createRedactionService({ fields: { email: { classification: 'pii', redactor: () => 'field' } }, redactors: { pii: () => 'class' } }).redactValue('email', 'x')"field"
(await import('@setu-ts/common')).createRedactionService({ fields: { name: { classification: 'pii' } }, redactors: { pii: () => 'class' } }).redactValue('name', 'x')"class"
(await import('@setu-ts/common')).createRedactionService({ fields: { name: { classification: 'pii' } }, defaultRedactor: () => 'default' }).redactValue('name', 'x')"default"
(await import('@setu-ts/common')).createRedactionService({ fields: { token: { classification: 'secret' } } }).redactValue('token', 'x')"[Redacted]"
(await import('@setu-ts/common')).createRedactionService({ fields: { 'user.email': { classification: 'pii', redactor: (await import('@setu-ts/common')).createMaskRedactor({ keep: 4 }) } } }).redactValue('user.email', 'jane@example.com')"************.com"

See the repository’s PUBLIC_API.md for the FieldRedaction type, the classification table, and the full contract, and ARCHITECTURE.md for how this package fits the plugin architecture.

Exports

ExportKind
assertPrimaryKeyUnchangedfunction
assertRealPathContainedfunction
attachConnectionErrorReporterfunction
authorizationFailureInitfunction
brandErrorResponderfunction
causeMessagefunction
checkWritePreconditionfunction
compileRealtimeDiagnosticsAliasfunction
composeBehaviorChainfunction
contentTypeForfunction
contextToTraceparentfunction
createCachedProbefunction
createCapabilityTokenfunction
createConnectionErrorReporterfunction
createMaskRedactorfunction
createPathMatcherfunction
createRealtimeHandlerErrorReporterfunction
createRealtimeObservationCollectorfunction
createRedactionServicefunction
createTokenfunction
deadlineRangeErrorfunction
decodeCursorfunction
decodeFrameDatafunction
describeErrorfunction
dispatchRealtimeFramefunction
encodeCursorfunction
encodeFrameDatafunction
eraseRedactorfunction
errfunction
errorResponderOffunction
extractContextFromHeadersfunction
formEncodingOffunction
fromNodeRedisfunction
fromNullablefunction
fullJitterDelayfunction
hasForbiddenAliasCharacterfunction
httpStatusHintOffunction
installRequestIdentityGuardfunction
isConnectionFaultfunction
isConnectionFaultCodefunction
isErrfunction
isLexicallyContainedfunction
isNonefunction
isOkfunction
isPromiseLikefunction
isScopeTypefunction
isSomefunction
isValidPublishIdfunction
isWebSocketUpgradeRequestfunction
isWorkerReadySignalfunction
isWorkerTaskReplyfunction
isWorkerTaskRequestfunction
keysetPredicatefunction
mintNextCursorfunction
nonefunction
okfunction
parseCookiefunction
parseEnvfunction
parseFormBodyfunction
parseJsonBodyfunction
parsePublishOptionsfunction
parseTraceparentToContextfunction
publicRequestUrlfunction
publishHeaderNameProblemfunction
publishHeaderValueProblemfunction
publishIdProblemfunction
replaceLocalefunction
replacePrincipalfunction
replaceTenantfunction
requestOriginOffunction
resolveKeysetSortfunction
resolveProbeTimingfunction
resolveRegistryEntryfunction
resolveResponseStatusfunction
respondWithAuthorizationFailurefunction
respondWithErrorfunction
responseMetadataOffunction
scopedPermissionAbilityfunction
scopedRoleAbilityfunction
scopeFromParamfunction
scopeFromTenantfunction
sealRequestIdentityfunction
securityMetadataOffunction
serializeCookiefunction
serializeErrorfunction
setUpgradeIntentfunction
somefunction
sortFingerprintfunction
splitWorkerEnvfunction
tenantBindingMismatchfunction
unwrapfunction
upgradeIntentOffunction
validatedStateKeyfunction
validationMetadataOffunction
withDeadlinefunction
withHttpStatusHintfunction
withResponseMetadatafunction
withSecurityMetadatafunction
withValidationMetadatafunction
DuplicateKeyErrorclass
InvalidCursorErrorclass
InvalidFieldValueErrorclass
MalformedRequestBodyErrorclass
OptionalDependencyUnavailableErrorclass
PrimaryKeyChangeErrorclass
ReconnectSupervisorclass
UnknownPayloadFieldErrorclass
UnsupportedFormEncodingErrorclass
CAPABILITIESconst
CLIENT_IP_STATE_KEYconst
CREDENTIAL_SOURCE_STATE_KEYconst
DATA_CLASSIFICATIONSconst
DEDUPLICATION_ID_HEADERconst
DEFAULT_SECRET_FIELD_PATTERNSconst
ERROR_RESPONDER_BRANDconst
ERROR_RESPONDER_STATE_KEYconst
HTTP_STATUS_HINTconst
IDEMPOTENCY_RECORD_KINDconst
INBOX_RECORD_KINDconst
MAX_PUBLISH_HEADER_NAME_BYTESconst
MAX_PUBLISH_HEADER_VALUE_BYTESconst
MAX_PUBLISH_HEADERSconst
MAX_PUBLISH_ID_BYTESconst
MAX_SCOPE_ID_LENGTHconst
ORDERING_KEY_HEADERconst
OUTBOX_RECORD_KINDconst
PLUGIN_PRIORITYconst
REQUEST_ORIGIN_STATE_KEYconst
RESERVED_HEADER_NAMESconst
RESERVED_HEADER_PREFIXESconst
RESPONSE_METADATAconst
SCOPED_RBAC_POLICYconst
SECURITY_METADATAconst
SESSION_STATE_KEYconst
SESSION_TENANT_BINDING_KEYconst
SESSION_TENANT_BINDING_STATE_KEYconst
TELEMETRY_CONTEXT_OPAQUEconst
TRACEPARENT_HEADERconst
TRACESTATE_HEADERconst
UPGRADE_INTENTconst
VALIDATION_METADATAconst
AddJobOptionsinterface
AuditEntryinterface
AuthorizationDecisionObservationinterface
AuthorizationDecisionStepinterface
AuthorizationDiagnosticsBatchinterface
BehaviorLikeinterface
BulkheadPolicyinterface
CacheDiagnosticsRecordinterface
CacheDiagnosticsResponseinterface
CacheDiagnosticsSnapshotinterface
CachedProbeOptionsinterface
CircuitBreakerPolicyinterface
ClassProviderinterface
ConfigDiagnosticsSnapshotinterface
ConfigProvenanceEntryinterface
ConnectionErrorReporterinterface
ConnectionErrorReporterOptionsinterface
CookieAttributesinterface
CqrsCommandinterface
CqrsQueryinterface
CqrsRequestinterface
CursorPayloadinterface
DeadlineOptionsinterface
DiagnosticsBatchinterface
DiagnosticsEdgeinterface
DiagnosticsEventinterface
DiagnosticsNodeinterface
DiagnosticsSnapshotinterface
EncodedPayloadinterface
EnvVarSpecinterface
Errinterface
ErrorResponderTargetinterface
ErrorResponseInitinterface
EventDiagnosticsRecordinterface
EventDiagnosticsResponseinterface
EventDiagnosticsSnapshotinterface
FactoryProviderinterface
FieldRedactioninterface
FlagContextinterface
FormBodyinterface
FormFileinterface
GraphqlConnectionInfointerface
GraphqlExecutionOutcomeinterface
GraphqlExecutionResultinterface
GraphqlFormattedErrorinterface
GraphqlOperationContextinterface
GraphqlRequestParamsinterface
GrpcServiceDefinitioninterface
HandlerResultinterface
HealthCheckResultinterface
HealthDiagnosticsObservationinterface
HealthDiagnosticsSnapshotinterface
HealthIndicatorOptionsinterface
HealthReportinterface
HttpStatusHintinterface
IAdapterTransactioninterface
IApplicationinterface
IAuditLoggerinterface
IAuthorizationDiagnosticsSourceinterface
IAuthorizationPolicyServiceinterface
IAuthorizationServiceinterface
IAuthServiceinterface
IAuthSessionServiceinterface
IAuthStrategyinterface
ICacheDiagnosticsSourceinterface
ICacheStoreinterface
ICircuitBreakerinterface
ICliApiinterface
ICommandBusinterface
ICommandHandlerinterface
IConfiginterface
IConfigDiagnosticsSourceinterface
IContainerinterface
ICounterinterface
ICqrsFacadeinterface
IDatabaseAdapterinterface
IDataSourceinterface
IDecoratorApiinterface
IdempotencyClaimRequestinterface
IdempotentIngressCommonOptionsinterface
IdempotentRouteOptionsinterface
IdempotentWithinOptionsinterface
IdempotentWithinResultinterface
IDiagnosticsSourceinterface
IDnsResolverinterface
IDomainEventinterface
IEnvironmentApiinterface
IErrorResponderinterface
IEventBusinterface
IEventDiagnosticsSourceinterface
IFeatureFlagsinterface
IFileSysteminterface
IGaugeinterface
IGrantSourceinterface
IGraphqlServiceinterface
IGrpcServiceinterface
IHealthApiinterface
IHealthDiagnosticsSourceinterface
IHealthIndicatorinterface
IHealthServiceinterface
IHistograminterface
IHttpAdapterinterface
IIdempotencyServiceinterface
IIdempotencyStoreinterface
IInboxStoreinterface
IIngressBehaviorinterface
IJobinterface
IJwtServiceinterface
ILifecycleApiinterface
ILocalDiagnosticsListenerinterface
ILocalDiagnosticsListenerFactoryinterface
ILocalizerinterface
ILoggerinterface
IMailerinterface
IMessageBrokerinterface
IMetadataStoreinterface
IMetricinterface
IMetricsApiinterface
IMetricsServiceinterface
IMiddlewareinterface
IMiddlewareApiinterface
IMultiTenancyServiceinterface
InboxFailureUpdateinterface
InboxIdsinterface
InboxRecordinterface
InboxStoreStatsinterface
IngressContextinterface
InjectionTokeninterface
INotifierinterface
IOpenApiApiinterface
IOrmAdapterinterface
IOutboundHttpDiagnosticsSourceinterface
IOutboxStoreinterface
IOutboxWriteScopeinterface
IPipelineBehaviorinterface
IPlugininterface
IPluginContextinterface
IPrincipalinterface
IQueryBusinterface
IQueryHandlerinterface
IQueueinterface
IQueueDiagnosticsSourceinterface
IRealtimeBackplaneinterface
IRealtimeDiagnosticsSourceinterface
IRealtimeHandlerErrorReporterinterface
IRealtimeObservationCollectorinterface
IRedactionServiceinterface
IRequestinterface
IRequestContextinterface
IResilienceServiceinterface
IResponseinterface
IRouterApiinterface
IRuntimeServicesinterface
ISchedulerinterface
ISchedulerDiagnosticsSourceinterface
IScopedRoleSourceinterface
ISecretManagerinterface
IServiceDiscoveryinterface
IServiceRegistryinterface
ISessioninterface
ISessionServiceinterface
ISessionStoreinterface
ISpaninterface
ISseConnectioninterface
ISseServiceinterface
ISsrServiceinterface
IStorageinterface
IStorageDiagnosticsSourceinterface
ISubscriptioninterface
ISummaryinterface
ITelemetryServiceinterface
ITenantinterface
ITenantDataStoreinterface
ITenantRepositoryinterface
ITenantResolverinterface
ITraceDiagnosticsSourceinterface
ITransactioninterface
ITransactionalIdempotencyStoreinterface
ITransactionIsolationSupportinterface
IValidationServiceinterface
IViewEngineinterface
IWebSocketConnectioninterface
IWebSocketServiceinterface
IWebSocketTransportinterface
IWorkerHandleinterface
IWorkerHostinterface
IWorkerPoolinterface
JwtSignOptionsinterface
LocalDiagnosticsListenerOptionsinterface
MailMessageinterface
MaskOptionsinterface
MessageMetadatainterface
MetricConfiginterface
MetricOptionsinterface
MiddlewareOptionsinterface
Noneinterface
NormalizedQueryinterface
NotificationMessageinterface
Okinterface
OutboundHttpDiagnosticsRecordinterface
OutboundHttpDiagnosticsResponseinterface
OutboundHttpDiagnosticsSnapshotinterface
OutboxKeyinterface
OutboxRecordinterface
OutboxStoreStatsinterface
PageResultinterface
ParsedPublishOptionsinterface
PendingSignIninterface
PickOptionsinterface
PluralFormsinterface
PolicyAbilityInfointerface
PolicyDefinitioninterface
ProbeTiminginterface
ProcessOptionsinterface
ProviderOptionsinterface
PublishOptionsinterface
PutObjectOptionsinterface
QueueAttemptObservationinterface
QueueDepthObservationinterface
QueueDiagnosticsBatchinterface
QueueDiagnosticsSourceBatchinterface
QueueDiagnosticsSourceStatusinterface
QueueSourceAttemptObservationinterface
QueueSourceDepthObservationinterface
RbacConfiginterface
RealtimeDiagnosticsGaugesinterface
RealtimeDiagnosticsOptionsinterface
RealtimeDiagnosticsRecordinterface
RealtimeDiagnosticsResponseinterface
RealtimeDiagnosticsSnapshotinterface
RealtimeFrameinterface
RealtimeGaugeReadinginterface
RealtimeHandlerErrorReporterOptionsinterface
RealtimeObservationCollectorInitinterface
ReconnectSupervisorOptionsinterface
RecurringOptionsinterface
RedactionContextinterface
RedactionPolicyinterface
RegisterOptionsinterface
RequestOptionsinterface
RetryOptionsinterface
RetryPolicyinterface
RoleDefinitioninterface
RoomBroadcastOptionsinterface
RouteDefinitioninterface
RouteInfointerface
RouteResponseMetadatainterface
RouteSchemainterface
RouteSecurityMetadatainterface
RouteValidationMetadatainterface
ScheduledJobinterface
ScheduleOptionsinterface
SchedulerDiagnosticsRecordinterface
SchedulerDiagnosticsResponseinterface
SchedulerDiagnosticsSnapshotinterface
ScopedGrantinterface
ScopedRbacTargetinterface
ScopedRoleDefinitioninterface
ScopeRefinterface
SerializedErrorinterface
ServiceInstanceinterface
SignedUrlOptionsinterface
SignInOptionsinterface
Someinterface
SpanContextinterface
SpanOptionsinterface
SplitWorkerEnvinterface
SrvRecordinterface
SseChannelinterface
SseMessageinterface
StartOptionsinterface
StatResultinterface
StorageDiagnosticsRecordinterface
StorageDiagnosticsResponseinterface
StorageDiagnosticsSnapshotinterface
SubscribeOptionsinterface
TaskPoolStatsinterface
TelemetryContextinterface
TraceDiagnosticsBatchinterface
TraceLinkRelationshipinterface
TraceObservationinterface
TraceparentSourceinterface
TraceSamplerDescriptioninterface
TransactionalIdempotencyClaiminterface
TransactionalIdempotencyRecordinterface
TransactionOptionsinterface
ValidationIssueinterface
ValidationMiddlewareOptionsinterface
ValueProviderinterface
WebSocketCloseEventinterface
WebSocketConnectionContextinterface
WebSocketEventSinkinterface
WebSocketHandlersinterface
WebSocketRoominterface
WebSocketRouteOptionsinterface
WebSocketUpgradeIntentinterface
WorkerErrorShapeinterface
WorkerReadySignalinterface
WorkerRunOptionsinterface
WorkerTaskReplyinterface
WorkerTaskRequestinterface
WrapOptionsinterface
AnonymousPolicyChecktype
AuthMethodtype
AuthorizationCoveragetype
AuthorizationDecisionOperationtype
AuthorizationDecisionReasontype
AuthorizationFailuretype
AuthorizationSourceStatetype
BackoffStrategytype
CacheDiagnosticsOperationtype
CapabilityTokentype
ChannelSendResulttype
CircuitStatetype
CliCommandHandlertype
Componenttype
ConfigProvenanceOrigintype
ConfigSchemaEffecttype
ConnectionErrorLoggertype
Constructortype
ContainerTokentype
CursorValuetype
DataClassificationtype
DecoratorHandlertype
DiagnosticsEdgeKindtype
DiagnosticsEventKindtype
DiagnosticsEventOutcometype
DiagnosticsEventStagetype
DiagnosticsFailureCodetype
DiagnosticsInspectorStatetype
DiagnosticsNodeKindtype
DiagnosticsSnapshotStatetype
EntityKeytype
EventDiagnosticsCoveragetype
EventDiagnosticsStatetype
EventHandlertype
EventObservationOperationtype
FilterComparisontype
FilterExpressiontype
FilterOperatortype
FormEncodingtype
FormValuetype
GrantQuerytype
GraphqlSubscriptionOutcometype
GrpcServingStatustype
HardenedCalltype
HealthIndicatorFntype
HealthObservationStatetype
HealthStatustype
HttpMethodtype
IdempotencyClaimResulttype
IdempotencyFingerprintSourcetype
IdempotencyKeySourcetype
IdempotencySettleResulttype
IdempotentIngressOptionstype
InboxReleaseOutcometype
InboxStatustype
IngressIdempotencyFingerprintSourcetype
IngressIdempotencyKeySourcetype
IngressKindtype
InjectEntrytype
ITenantIsolationStrategytype
JobProcessortype
JsonValuetype
LifecyclePhasetype
LoadBalanceStrategytype
LocalizationMessagetype
LogLeveltype
LogMetadatatype
MessageCataloguetype
MessageHandlertype
MetricTypetype
MiddlewareFunctiontype
NextFunctiontype
NodeRedisAdaptertype
NodeRedisAdapterOptionstype
NodeRedisClientFactorytype
NodeRedisClientLiketype
Optiontype
OrderDirectiontype
OutboundHttpStatusClasstype
OutboxStatustype
OutboxTransitiontype
PathPatterntype
PluginPrioritytype
PolicyAbilitytype
PolicyChecktype
PolicyReftype
PolicyTargettype
Providertype
QueueDepthCoveragetype
QueueDepthCycleCoveragetype
QueueDepthScopetype
QueueDiagnosticsFailuretype
QueueProcessorOutcometype
QueueSettlementStatetype
QueueSourceFailuretype
QueueSourceStatetype
RealtimeFrameHandlertype
RealtimeFrameKindtype
RealtimeGaugeStatetype
RealtimeObservationOperationtype
RealtimeSourceKindtype
ReconnectModetype
Redactortype
RegistryFactorytype
RequestHandlertype
RequestOrigintype
ResilientCalltype
ResponseSnapshottype
ResponseSnapshotInittype
Resulttype
RouteHandlertype
RpcFetchHandlertype
RuntimePlatformtype
RuntimeSignaltype
SchedulerBackofftype
SchedulerDiagnosticsOperationtype
SchedulerJobHandlertype
ScopeSourcetype
SecurityRequirementtype
ServerHandletype
ServiceFactorytype
ServiceOutcometype
ServiceScopetype
SessionDatatype
SessionViewtype
SignInOutcometype
SpanAttributeValuetype
SpanKindtype
SpanStatustype
StandardCapabilitytype
StorageDiagnosticsOperationtype
TimerHandletype
TraceCoveragetype
TraceInstrumentationKindtype
TraceOutcometype
TraceParentVisibilitytype
TraceSourceStatetype
TransactionIsolationLeveltype
Unsubscribetype
ValidationTargettype
WebSocketGuardDecisiontype
WebSocketReadyStatetype
WebSocketUpgradeDecisiontype
WebSocketUpgradeGuardtype
WebSocketUpgradeRoutertype
WritePreconditiontype
WritePreconditionChecktype

Generated from the package barrel by pnpm run docs:exports; pnpm run check:docs fails when it drifts.

Readiness and dependency outages (M113b)

isConnectionFault(error: unknown): boolean walks up to five cause objects, stops at cycles, and recognises the eight node/net connection-fault codes plus measured ioredis 5 and node-redis 6 outage rejections (closed, offline, socket closed, connect timeout, socket timeout). It never mutates errors. Command errors such as WRONGTYPE remain unclassified.

isConnectionFaultCode(code: unknown): boolean returns true only for a string in the module-private set: ECONNREFUSED, ECONNRESET, ETIMEDOUT, EHOSTUNREACH, ENETUNREACH, ENOTFOUND, EAI_AGAIN, EPIPE. Use the predicate instead of the removed, unreleased CONNECTION_FAULT_CODES export; callers cannot mutate the classification vocabulary.

Using node-redis (0.11.0)

Every Redis-backed plugin uses ioredis by default and takes an injected client. To use node-redis (redis, v5 or v6) instead, wrap a client you build in fromNodeRedis and pass it to that option:

import { fromNodeRedis } from '@setu-ts/common';
import { createClient } from 'redis';

const url = 'redis://127.0.0.1:6379';
const cacheClient = fromNodeRedis(createClient({ url }));
const queueClient = fromNodeRedis(createClient({ url }), { commandTimeoutMs: 5_000 });

No Setu-TS package depends on redis: install it in your application. fromNodeRedis types its argument structurally, and the one adapter fits every plugin’s client option — cache, queue, Redis Streams messaging, the realtime backplane (client and subscriber), the scheduler lock, idempotency and the auth rate-limit store.

  • One client per plugin. Most plugins close their client when the application stops, so a client shared between plugins would be closed under the others. The backplane takes two: client: fromNodeRedis(base) and subscriber: fromNodeRedis(base.duplicate()).
  • Lazy connect. The adapter connects on its first command (or when a plugin calls connect()), as ioredis does by default, so building it opens no socket. A client you already connected is used as it is.
  • A bound on every command. node-redis does not bound a command once it is written: against a paused or stopped server it waits indefinitely. The adapter rejects any command — and the lazy connect — after commandTimeoutMs (default 15 000; 0 disables; otherwise a finite number up to 2 147 483 647, else RangeError) with Error('Command timed out'), the error ioredis raises, so the plugins report an outage. The command is not cancelled; a late reply is discarded. A plugin’s own commandTimeoutMs option applies only to a client the plugin builds.
  • An 'error' listener is always attached, because node-redis rethrows an unlistened 'error' event and would crash the process. onError(error) receives each event; without it the listener stays silent and failures surface through the rejected commands and health.
  • Traces: pass a factory. OpenTelemetry’s redis instrumentation patches node-redis as Node loads it, and TelemetryPlugin registers it when the application starts — after a configuration that imports redis has already loaded it, so those commands record no spans. Pass a factory instead of a client and redis loads on the first connect: fromNodeRedis(async () => (await import('redis')).createClient({ url })). The factory runs once (a failure is retried on the next connect), and on/off listeners registered before it wait for the client. A setu new --redis-client node-redis project does this in src/node-redis.ts.
  • RESP3. node-redis 6 speaks RESP3 by default, where some replies are maps. The adapter turns map replies back into the RESP2 shapes the plugins read, so either protocol works.
  • Closing. quit() closes the client (falling back to destroy() when that exceeds the bound) and is idempotent; status reports 'wait', 'connecting', 'ready' or 'end'.

Cluster and sentinel clients (createCluster, createSentinel) are not supported.

Portable database refusals (0.11.0)

UnknownPayloadFieldError(entity, field, options?) and InvalidFieldValueError(entity, options?) carry fixed 400 HTTP details without caller field names or values. The former is also re-exported by @setu-ts/database-plugin. sortFingerprint(orderBy, { entity, keyColumns }) binds a cursor to its entity, key columns and sort; omitting scope preserves the legacy string. The fingerprint checks correctness; authorization still comes from the query’s tenant predicates.

API reference

Kernel diagnostics (@setu-ts/kernel + @setu-ts/common)

An optional, read-only view of application composition and kernel execution. Enable it explicitly per application; an omitted diagnostics option creates no collector, metadata mirror, ring, or timer anywhere in the kernel, and IApplication.diagnostics is absent.

import { createApplication } from '@setu-ts/kernel';

const app = createApplication({
  plugins: [RuntimePlugin()],
  // Explicit activation. `labels` is the disclosure decision: a name leaves
  // the process ONLY when it exactly matches an allowlist entry.
  diagnostics: {
    labels: { plugins: ['catalog'], routes: ['/items'] },
  },
});
await app.start();

const snapshot = app.diagnostics!.snapshot(); // DiagnosticsSnapshot
const batch = app.diagnostics!.read(0, 128); // DiagnosticsBatch

The reader is pull-only. snapshot() and read() are the whole surface: neither registers a callback, resolves a service, invokes application code, or mutates state. Returned data is deeply frozen; a slow or stopped reader cannot backpressure requests. DTOs (DiagnosticsSnapshot, DiagnosticsNode, DiagnosticsEdge, DiagnosticsEvent, DiagnosticsBatch) and IDiagnosticsSource are exported from @setu-ts/common; KernelDiagnosticsOptions and the ApplicationOptions.diagnostics field from @setu-ts/kernel.

Capture minimization. No dynamic request path, query string, header, body, cookie, token, session value, database value, or raw error text is ever recorded. labels is an exact allowlist (≤256 entries per list, ≤160 UTF-8 bytes per entry, no control characters); un-allowlisted names are projected as opaque sequential ids (p1, c1, r1, m1), never truncated into new names. Approving a label is a disclosure decision, not a secret-detection guarantee.

One field is not allowlist-gated. A plugin node’s version is emitted whenever it passes a bounded major.minor.patch semver grammar, independently of labels.plugins — so an application that enables diagnostics with no labels at all still discloses the version of every registered plugin, including the framework’s own through RuntimePlugin. A version that fails the grammar (a git sha, a sentence, any string over 64 characters) is omitted rather than projected.

Bounds. Snapshots are capped at 1,024 nodes, 4,096 edges, and 256 KiB of compact JSON (truncated reports omission). Events are capped at 1,024 retained records and 1,024 bytes each; eviction is reported as lost sequence numbers, and drops as droppedEvents. Startup failure and final shutdown clear retained metadata while preserving the original application error — a reader then sees only the coarse failed/closed state, the failure code, and counters. A retried start() — possible only after a failure before any plugin registered — continues the event numbering, so a later read reports the discarded range as lost. Timing is monotonic from runtime registration; atMs/durationMs are null before that, never fabricated.

The runnable consumer is scripts/inspect-kernel.ts; the paired throughput/latency harness is scripts/benchmark-kernel-diagnostics.ts --mode=disabled|enabled. Network authentication and transport for an external devtool belong to M98b, which consumes this same contract.

Shared reconnect supervisor (@setu-ts/common)

Since 0.10.0, common exports ReconnectSupervisor, ReconnectSupervisorOptions, ReconnectMode and fullJitterDelay(attempt, initialMs, maxMs). Messaging’s RabbitMQ, NATS and Kafka brokers and the RabbitMQ queue adapter share this implementation. Messaging does not re-export these symbols.

ReconnectMode is 'drive' | 'observe'. The options require runtime, mode and attachFaultListener(onFault), which returns a disposer. Drive mode additionally requires reconnect and replay callbacks; reassert is optional. Observe mode can supply attachRecoveryListener(onRecovered). initialMs defaults to 500 and maxMs to 30000. fullJitterDelay returns an integer uniformly distributed from zero through the exponential window’s capped ceiling. Attempts are unbounded.

Call start() to attach listeners, fault() to mark an outage and start drive-mode retries, recovered() to clear a fault, and stop() to cancel scheduled attempts and dispose listeners. faulted reports the current fault window. A stopped supervisor is terminal; construct a new instance to start another lifecycle.

Dotenv parser (@setu-ts/common)

Since 0.11.0, common exports parseEnv(content: string): Readonly<Record<string, string>>, the strict dotenv parser ConfigPlugin loads .env files with. It moved from config-plugin, where it was internal, so that setu workspace deploy builds a member’s Kubernetes Secret from exactly the values the application would read from the same file.

Blank lines and # comment lines are ignored. An entry may start with export . A value may be unquoted (a trailing # comment is dropped), double-quoted (escape sequences decoded) or single-quoted (taken literally). An empty value is the empty string, and a duplicate key keeps its last value. A line without =, an invalid key, or an unterminated quote throws an Error naming the line number.

import { parseEnv } from '@setu-ts/common';

parseEnv('export A="x y"\nB=plain # note\nEMPTY=\n');
// { A: 'x y', B: 'plain', EMPTY: '' }

API Reference: @setu-ts/common

Runtime/error contract memberConsumerBehavior
IRuntimeServices.reportError?(record: Readonly<Record<string, unknown>>): voidKernel unhandled errors and errorHandler 5xx, only without a loggerLast-resort JSON line to stderr (Workers platform log channel); serialization and write failures never throw
IErrorResponder.statusFor?(error: unknown): numberRequest loggerPure prediction of the owner’s served status rule; no clamp reporting; custom response-hook status overrides are not reflected

isConnectionFault(error: unknown): boolean walks up to five cause objects, stops at cycles, and recognises the eight node/net connection-fault codes plus measured ioredis 5 outage rejections and, since M117, node-redis 6’s: messages The client is closed, The client is offline, Socket closed unexpectedly, Connection timeout, and the pattern Socket timeout timeout. Expecting data, but didn't receive any in <n>ms. (all name: 'Error'). It never mutates errors. Command errors such as WRONGTYPE remain unclassified.

node-redis (M117, 0.11.0). fromNodeRedis(client: NodeRedisClientLike | NodeRedisClientFactory, options?: NodeRedisAdapterOptions): NodeRedisAdapter wraps a node-redis client (redis v5/v6) in one ioredis-shaped adapter that every Redis-backed plugin’s client option accepts (IRedisClient, IRedisQueueClient, IRedisStreamsClient, IRedisBackplaneClient, IRedisLockClient, IRedisIdempotencyClient, IRateLimitRedisClient). NodeRedisClientLike is structural — isOpen, isReady, connect(), sendCommand(args), close(), destroy(), on/off, subscribe(channel, listener), unsubscribe(channel) — and no package imports redis. Commands go through sendCommand with ioredis’s argument rule (undefined dropped, arrays spread one level, numbers as strings). NodeRedisAdapterOptions.commandTimeoutMs (default 15000; 0 disables; a finite number up to 2147483647, otherwise RangeError) bounds every command and the lazy connect, rejecting with Error('Command timed out'); the node-redis command is not cancelled. onError(error) receives the client’s 'error' events through a listener the adapter always attaches. RESP3 map replies become RESP2 shapes (XREADGROUP/XREAD → [key, value] pairs; other maps → flat arrays). connect() resolves at once for an open client and connects once otherwise; quit() is idempotent, closes with close() and falls back to destroy() past the bound; disconnect() destroys; status is 'wait', 'connecting', 'ready' or 'end'. Pub/sub: subscribe(channel) delivers (channel, message) to on('message', …) listeners. Cluster and sentinel clients are not supported. NodeRedisClientFactory (() => NodeRedisClientLike | Promise<NodeRedisClientLike>) may be passed instead of a client: it is called once, on the first connect (a rejection is retried on the next), and listeners registered with on/off before then are attached when the client exists. A factory that imports redis inside it is how a client built at module load is traced: OpenTelemetry patches @redis/client as Node loads it, after TelemetryPlugin has registered. A quit() before or during the first connect opens no socket, and one issued while a connect is in flight closes the socket that connect opens.

isConnectionFaultCode(code: unknown): boolean returns true only for a string in the module-private set: ECONNREFUSED, ECONNRESET, ETIMEDOUT, EHOSTUNREACH, ENETUNREACH, ENOTFOUND, EAI_AGAIN, EPIPE. Use this predicate instead of the removed, unreleased CONNECTION_FAULT_CODES export; callers cannot mutate the classification vocabulary.

The contract layer every other package builds on. Implemented in Milestone 1; this section is the authoritative export list (AI_GUIDELINES §10.5). All exports carry full JSDoc.

Realtime subscriber dispatch (0.11.0)

dispatchRealtimeFrame(handlers: Iterable<RealtimeFrameHandler>, frame: RealtimeFrame, reporter: IRealtimeHandlerErrorReporter): boolean snapshots subscribers, isolates synchronous throws and returned thenable rejections, and reports each completion. It returns false only for synchronous throws; asynchronous outcomes are unknown at return, preserving the M98l receive count.

IRealtimeHandlerErrorReporter exposes failed(handler, error, frame): void and succeeded(handler): void; implementations must not throw. createRealtimeHandlerErrorReporter accepts RealtimeHandlerErrorReporterOptions (source, call-time logger, optional onHandlerError(error, frame)) and returns that interface. A WeakMap tracks failures per handler: first failure warns, subsequent failures debug with repeats starting at 1, the next success logs subscriber recovered after N failures at info and rearms the warning. Diagnostics use describeError and metadata { source, error, frameKind, frameName }, never frame data. The hook replaces all default logging and receives every failure. Logger and hook failures are swallowed; no clock or timers are involved. Both backplane provider packages consume these exports.

Diagnostics alias character policy

hasForbiddenAliasCharacter(value: string): boolean returns true for any Unicode control (Cc), format (Cf), line separator (Zl, U+2028) or paragraph separator (Zp, U+2029) character, including bidi overrides and zero-width format characters. Each refusal reads “contains a control, format or line-separator character”. Diagnostics sources, kernel labels and the connector wire validation share this pure predicate; each keeps its byte limit and fixed error message. Printable Unicode such as é remains accepted. The browser SDK maintains the same policy locally to preserve its type-only common imports.

Redaction

createRedactionService(policy) compiles a synchronous RedactionPolicy once and returns an IRedactionService. Policies classify dot paths (* matches one segment and ** matches any number) and map classifications to a Redactor. Matching is case-insensitive by default; createRedactionService(policy, { caseSensitive: true }) enables case-sensitive matching. When patterns overlap, the most specific match wins; equally specific patterns retain declaration order. eraseRedactor replaces values with '[Redacted]', while createMaskRedactor({ keep: 4 }) preserves a trailing suffix of strings. redactRecord() never mutates caller metadata and descends only into plain objects and arrays. Since 0.11.0 it returns a snapshot: each plain object and array it walks is read once and copied, unclassified subtrees included, so what a logger serializes is exactly what redaction checked (a getter cannot change it afterwards). Earlier versions returned unclassified subtrees by identity. The logger, telemetry, and audit plugins accept either a policy or an IRedactionService through their redaction option; this is an option-passed pure utility, not a capability token.

RedactionPolicy has three members:

MemberTypePurpose
fieldsReadonly<Record<string, DataClassification | FieldRedaction>>Dot-path patterns and the classification or FieldRedaction each carries
redactorsReadonly<Record<string, Redactor>>Redactor selected by the matched classification
defaultRedactorRedactorFallback when redactors has no entry for that classification

FieldRedaction is { readonly classification: DataClassification; readonly redactor?: Redactor } — the value arm that lets one classification carry two treatments. For every matched field, redactValue/redactRecord select a redactor in this order: the matched field’s own redactor (when it has one), then redactors[classification], then defaultRedactor, then eraseRedactor. RedactionContext.classification always reports the matched classification. A field’s redactor and classification are read as own properties only: an inherited redactor is ignored, and an entry without its own string classification is erased.

DataClassification is one of four framework constants or any application-defined string (which reaches redactors[<string>]):

ConstantValue
DATA_CLASSIFICATIONS.PII'pii'
DATA_CLASSIFICATIONS.PHI'phi'
DATA_CLASSIFICATIONS.PCI'pci'
DATA_CLASSIFICATIONS.SECRET'secret'

Values (runtime exports)

ExportKindPurpose
CAPABILITIESconstStandard capability tokens — the single source of truth. Includes SSE: 'sse' (SSE hub), SSR: 'ssr' (SSR framework), WORKER_POOL: 'worker-pool' (worker thread pool), REALTIME_BACKPLANE: 'realtime-backplane' (cross-replica fan-out), SESSION: 'session' (cookie sessions), AUTH_SESSION: 'auth-session' (signed-in principal), VIEW: 'view' (view rendering), LOCALIZATION: 'localization' (message catalogues and locale resolution, M103), OUTBOX: 'outbox' (transactional outbox, outbox.<name> for a named messaging instance, M107), INBOX: 'inbox' (consumer inbox, inbox.<name> for a named messaging instance, M108)
createCapabilityToken(name)functionValidates and creates a custom (optionally dot-namespaced) token; throws TypeError on invalid names
isConnectionFault(error)functionRecognises connection outages through a bounded, cycle-safe cause walk, including measured ioredis rejection shapes.
fromNodeRedis(client, options?)functionM117: wraps a node-redis client in the ioredis-shaped adapter every Redis-backed plugin’s client option accepts
NodeRedisClientFactorytypeM117: creates the node-redis client on the adapter’s first connect, so redis can load after TelemetryPlugin registers its instrumentation
NodeRedisClientLiketypeM117: the structural node-redis client fromNodeRedis takes
NodeRedisAdapterOptionstypeM117: commandTimeoutMs, onError
NodeRedisAdaptertypeM117: the adapter fromNodeRedis returns
isConnectionFaultCode(code)functionReturns true only for a string in the private eight-code node/net connection-fault set.
encodeFrameData(data)functionEncodes a WebSocket payload for a realtime backplane; binary becomes base64
decodeFrameData(payload)functionDecodes a backplane payload back to string or Uint8Array
createCachedProbe(options)functionBuilds a cached, coalesced, time-bounded reachability probe from { probe, hrtime, ttlMs?, timeoutMs?, setTimer?, clearTimer? }. hrtime and the timer seam come from IRuntimeServices so a custom runtime’s clock and timers are honoured; the timers fall back to the ambient ones. Every plugin’s isHealthy() is built through it so a /health scrape cannot become load against the backend; a probe that rejects or exceeds timeoutMs resolves false
withDeadline(run, options)functionSince M101a. Runs one backend call under a bound whose expiry is a recorded failure: run receives an AbortSignal to forward; the call is raced against the deadline (an injected seam may ignore the signal), and on expiry the signal is aborted and the promise REJECTS with options.onTimeout(). The call’s own rejection is never swallowed. timeoutMs: 0 arms no timer. The request-path counterpart of createCachedProbe, which never rejects. Prefer a client’s native per-command timeout where one exists
deadlineRangeError(name, timeoutMs)functionSince M101a. Returns the RangeError naming name for a deadline outside 0–2147483647 (including NaN), or null when valid — so an option holder can refuse a bad value at construction with the same message withDeadline uses
DeadlineOptionstypeSince M101a. { timeoutMs, onTimeout, timing? } for withDeadline; timing is the setTimer/clearTimer surface, typically resolveProbeTiming(ctx.runtime)
resolveProbeTiming(runtime)functionResolves a probe’s clock-and-timer surface — { hrtime, setTimer, clearTimer } bound to the injected IRuntimeServices (e.g. ctx.runtime), ready to spread into createCachedProbe’s options. NO ambient performance.now()/Date.now() fallback: all time access outside packages/runtime goes through IRuntimeServices (AI_GUIDELINES §4), so a caller with no runtime to inject has no clock the probe may lawfully read
createConnectionErrorReporter(options)functionBuilds a de-duplicating sink for one connection’s error events from { source, logger }, where logger is a thunk read at CALL time. The first error of a run, or one whose rendering differs from the previous, logs at warn; a repeat of the previous rendering logs at debug with a repeats count; recovered() after at least one error logs once at info and resets, so the next outage warns again. The error is rendered by describeError — name, message, safe classifiers, aggregate members and the cause chain — and never its stack. Neither method throws — a failing logger or an unstringifiable value is swallowed, since the caller is an event-emitter listener. An accessor returning undefined drops the event (no log sink is registered; the owning package’s isHealthy probe still reports the outage)
attachConnectionErrorReporter(client, reporter?)functionAttaches a reporter to a client’s 'error' (→ report) and 'ready' (→ recovered) events, returning false when the value exposes no on method. With no reporter the listeners still attach and drop every event, so a built client never writes to the console. For clients a package BUILT: an injected client belongs to the caller, and adding an 'error' listener to it would silence the caller’s own handling, since ioredis falls back to console.error only when no listener exists
parseCookie(header)functionParses a Cookie header into a name→value record; percent-decodes, strips RFC 6265 quoting, first occurrence wins. Here because the session plugin and the decorator plugin’s Cookie() source both need it and no plugin may import another
serializeCookie(n, v, a?)functionSerializes a Set-Cookie value; percent-encodes so a payload cannot inject attributes, and forces Secure alongside SameSite=None. Throws TypeError on an invalid name or a non-integer maxAge
isWorkerReadySignal(m)functionGuard: narrows a worker message to a WorkerReadySignal
isWorkerTaskRequest(m)functionGuard: narrows a worker message to a WorkerTaskRequest
isWorkerTaskReply(m)functionGuard: narrows a worker message to a WorkerTaskReply
PLUGIN_PRIORITYconstWell-known plugin priority bands (HIGHEST…LOWEST)
createToken<T>(name)functionCreates a typed DI token (M125): a frozen { name } object that IContainer.register, resolve and has accept and match by identity, and from which resolve infers T with no type argument. Pure — it keeps no counter or other state, so two calls with one name are two distinct tokens. A DI token, not a capability token: it is never checked against the capability-token grammar.
ok(value) / err(error)functionResult constructors
isOk(r) / isErr(r)functionResult type guards
unwrap(r)functionReturns the Ok value or throws the Err error
some(value) / none()functionOption constructors (none() returns a frozen singleton)
isSome(o) / isNone(o)functionOption type guards
fromNullable(v)functionConverts T | null | undefined to Option<T>
serializeError(value)functionSerializes any thrown value into a plain SerializedError ({ name, message, stack?, cause?, classifiers?, errors?, omittedErrorCount? }) with the cause chain followed to a bounded depth. classifiers only carries strings, finite numbers, and booleans from code, errno, syscall, severity, constraint, codeName, and statusCode; payload objects such as query data and $metadata are omitted. errors is present only for an AggregateError. Classifier strings cap at 512 Unicode code points, AggregateError.errors caps at eight direct members, and one call produces at most 64 error nodes; a truncated aggregate reports omittedErrorCount. A non-Error value yields { name: 'Error', message: String(value) }. Pure — no runtime-specific APIs. Here so the logger plugin (metadata normalization), the kernel (fallback-500 logging), exceptions, grpc-plugin, and notification-plugin can all serialize without importing one another.
describeError(value)functionRenders any thrown value as ONE bounded diagnostic line for a string log sink: Name: message (code=…, …) [inner; inner] <- cause, built from the same serializeError record, so a non-Error value yields Error: <string> and an AggregateError lists its members. Whitespace and control or format characters collapse to a single space (one value cannot inject a line break), and the line caps at 8192 Unicode code points with … [truncated]. Pure and total — it never throws, whatever getters a value carries. Shared by createConnectionErrorReporter, the idempotency plugin’s claim/release/settle lines, and the messaging plugin’s broker error lines, which re-export it (M119g).
causeMessage(value)functionExtracts a caller-facing message from any thrown value: its message when it is an Error, String(value) otherwise. The one-line counterpart of serializeError — that shapes a record for a log sink, this supplies the sentence that goes inside another error’s message. Pure. Here because resolveRegistryEntry and @setu-ts/scheduler-plugin’s declared-job refusal must produce the SAME sentence for the two arms of one option, and §2.2 forbids one reading the other’s copy.
parseJsonBody(text)functionParses one request-body text as JSON — the ONE parse all three IRequest.json() implementations (@setu-ts/runtime’s served HTTP path, the kernel’s inject(), @setu-ts/testing’s MockRequest) share, so a malformed body answers 400 at every producer rather than a masked 500 on the served path while the test path answered 400 (X37-1). Throws MalformedRequestBodyError, carrying the platform SyntaxError as cause (M90f)
DuplicateKeyErrorclassA write that would duplicate a primary key or a unique index, branded 409 Conflict with the fixed detail “A record with the same unique key already exists. The conflicting write was rejected.” Carries the targeted entity when known and the driver error as cause, never served. Not retryable. In common because @setu-ts/database-plugin and @setu-ts/cloudflare-plugin raise it and any store recording “already processed” with a unique insert must recognise it
assertPrimaryKeyUnchangedfunctionSince M113g. The one shared primary-key check every update/updateWhere of the eight IDataSource implementations (the seven database-plugin adapters and cloudflare-plugin’s D1 data source) runs before any I/O; throws PrimaryKeyChangeError when the payload carries an own key column whose value is not undefined and does not equal the addressed key’s value for that column. An equal value is accepted, compared as a string for string, number, bigint and ObjectId-like values and by time for Date values. In common because cloudflare-plugin raises it too and §2.2 forbids it importing database-plugin (the DuplicateKeyError precedent). Ships in 0.10.0
PrimaryKeyChangeErrorclassSince M113g. An update/updateWhere payload that changes a row’s primary key, branded 400 Bad Request at construction with the fixed detail “An update cannot change a row’s primary key.” Thrown by assertPrimaryKeyUnchanged before any I/O; the message names the entity and the column, never a key value; errorHandler reads its hint. In common for the DuplicateKeyError reason — every database-plugin adapter and cloudflare-plugin’s D1 data source raise it, and an application must be able to instanceof it without importing either. Ships in 0.10.0
InvalidCursorErrorclassSince M113g. A cursor the backend cannot use — a malformed token, a sort-fingerprint mismatch, or a decoded key-value list whose length or kinds do not fit the target — branded 400 Bad Request at construction with the fixed detail “The pagination cursor is not valid for this query.” Thrown (rejected) by the eight findPage implementations before any backend call; the message names the entity and which check failed, never the token or its values; errorHandler reads its hint. In common for the DuplicateKeyError reason. Ships in 0.10.0
OptionalDependencyUnavailableErrorclassThrown when an optional, lazily-loaded dependency could not be imported (M113d). Constructor: (dependency: string, options?: { cause?: unknown; requiredBy?: string }). Carries the bare package name in dependency and the original rejection as cause. Since 0.11.0, optional requiredBy names the loading npm package in the message only (no instance field); omitted names use generic wording. The message directs installation where that package is installed, within the version range that package declares for it, usually the workspace root in npm/Yarn/Bun workspaces, or choosing an option that does not need it. A startup/configuration fault, so no HTTP status hint. In common because every converted loader catch (14 published packages and cli) throws it, and the named-error wrappers in gRPC, SAML and GraphQL carry it as cause; §2.2 forbids a plugin importing another plugin. Ships in 0.10.0
MalformedRequestBodyErrorclassThe malformed request body rejection, branded 400 Bad Request at construction with the fixed detail “The request body could not be parsed as JSON.” The cause is the platform SyntaxError — kept for the log, never served (M90f)
parseFormBody(body, contentType)functionParses one request body as a form — the ONE parse all three IRequest.formData?() implementations (@setu-ts/runtime’s served HTTP path, the kernel’s inject(), @setu-ts/testing’s MockRequest) share, and the function every accessor-less fallback path calls (M94b). Adopts the web FormData semantics — ordered getAll for repeated names, entries() in wire order, file-versus-text discriminated on filename !== undefined (filename="" is still a file) — over a framework-owned value whose bytes stay synchronous. Throws UnsupportedFormEncodingError (415) for a non-form content-type (JSON, a missing content-type, a multipart type with no boundary=); an UNPARSEABLE multipart body yields an EMPTY form, the promoted parser’s released behaviour, documented as the accessor’s limit (M94b)
formEncodingOf(contentType)functionClassifies a content-type as 'urlencoded' or 'multipart' — case-insensitively and parameter-tolerantly — or undefined when the type is not a parseable form, a multipart type with no boundary= included. The ONE classifier the upload middleware’s multipart guard and the CSRF verifier’s form guard both read, replacing two private includes() checks that had drifted on case folding (M94b)
FormBody / FormFile / FormValue / FormEncodingtypesThe form read IRequest.formData?() resolves: a read-only get/getAll/entries view over string | FormFile values, where FormFile carries the parser’s own SYNCHRONOUS Uint8Array — the web File’s async, copying byte access is deliberately not adopted, because it would add a second full copy per file part on the upload path this abstraction exists to make cheaper. Named FormBody, never FormData, so it cannot shadow the global. The write side is not adopted: a request body is a fact about the request, not a collection a handler edits (M94b)
UnsupportedFormEncodingErrorclassThe not-a-form rejection behind parseFormBody and IRequest.formData?(), branded 415 Unsupported Media Type at construction — the MalformedRequestBodyError precedent: answered in the application’s configured format rather than a masked 500, and exported so a handler catching its own formData() rejection can use instanceof (M94b)
resolveResponseStatus(status, target)functionReturns a status the web Response constructor accepts, clamping anything outside [200, 599], any non-integer, and the null-body statuses 204/205/304 (in range, but the Fetch standard forbids the body every path here writes — unclamped, the runtime mapper would drop that body and serve a bodiless 204, discarding the error) to 500 and reporting it through the logger capability when the target carries one. Applied by respondWithError before it delegates, and by createErrorResponder’s responder before it builds the error, so the formatted body’s status member and the written status cannot disagree. Exists because three shipped call sites take a status the APPLICATION authors (FlagGuardOptions.statusCode, the multi-tenancy rejectionStatus, a WebSocketGuardDecision.status), so a typo such as 4004 for 404 otherwise threw RangeError out of the error path itself.
respondWithError(ctx, init)functionWrites an error response through the request’s published IErrorResponder (the application’s configured format), falling back to { error, detail? } when errorHandler has not published one. The seam that lets a package that produces error responses but may not import @setu-ts/exceptions answer in the configured format (M70f).
respondWithAuthorizationFailure(ctx, failure)functionWrites the standard 401, 403, or 501 authorization refusal through respondWithError, so independent authorization entry points retain identical configured formatting. The four AuthorizationFailure arms: authentication-required (401 “Unauthorized”), not-configured (501 “Not Implemented”), insufficient-privileges (403 “Forbidden”, no role or permission named), and second-factor-required (403 “Forbidden”, “Second factor required” — names only the required action, never the factor or the policy, M100d).
authorizationFailureInit(failure)functionSince M110a. The ONE owner of each standard refusal’s { status, title, detail } (a frozen HttpStatusHint): respondWithAuthorizationFailure writes it and AuthPlugin’s AuthorizationDeniedError brands itself with it, so a guard’s refusal and a thrown denial answer the same body under the configured error format
ERROR_RESPONDER_STATE_KEYconstexceptions:error-responder, the ctx.state key under which errorHandler publishes its IErrorResponder
brandErrorResponder(fn, r)functionAttaches errorHandler’s resolved IErrorResponder to its middleware function under ERROR_RESPONDER_BRAND, so the kernel — which runs the drain 503, the malformed-request 400, and the request hooks BEFORE the pipeline — can read the same responder at startup (M70f re-review)
errorResponderOf(fn)functionReads the brand off a middleware function, returning the attached IErrorResponder (or undefined). The kernel’s only route to the resolved formatter for the pre-pipeline sites
ERROR_RESPONDER_BRANDconstA Symbol.for brand pairing with the two functions above; Symbol.for (not Symbol()) so two copies of the package in one process resolve the same key
withHttpStatusHint(error, hint)functionBrands an Error with the status, title and caller-safe detail it should be answered with, and returns the same reference. status must be an integer in 400–599; throws TypeError if the error is not extensible. The channel by which a package that may not import @setu-ts/exceptions (§2.2) states how its own error maps to a response — @setu-ts/database-plugin brands its three query-shape refusals 501 — where respondWithError cannot help because the thrower holds no IRequestContext (M89b)
httpStatusHintOf(error)functionReads the hint back from a thrown value, or undefined when it carries none. Accepts unknown, because a catch binds one. errorHandler calls it before wrapping a non-HttpError into a 500; a foreign or malformed value under the same global symbol reads as absent rather than being trusted — including a status that is not an integer in 400–599
HTTP_STATUS_HINTconstSymbol.for('setu.http.status-hint'), the key the two functions above use. Symbol.for (not Symbol()) so two copies of the package in one process resolve the same key
validatedStateKey(target)functionReturns `validation-plugin:validated-${target}` — the ctx.state key under which validation-plugin’s middleware writes a validated value and decorator-plugin’s Body()/Query()/Param() sources read it back. Exported so two packages agree on the wire format byte-for-byte instead of each hardcoding the literal (the M47 frame-codec precedent)
REQUEST_ORIGIN_STATE_KEYconsthttp-security-plugin:request-origin; the trusted { protocol, host } IP security publishes for the common accessor (M113n)
RequestOrigintype{ readonly protocol: 'http' | 'https'; readonly host: string; readonly origin: string } returned by requestOriginOf (M113n)
requestOriginOf(ctx)functionReturns the published public origin or one derived from ctx.request.url; makes no proxy trust decision (M113n)
publicRequestUrl(ctx)functionSubstitutes the published origin while keeping path and query; returns the original URL byte for byte without publication (M113n)
CLIENT_IP_STATE_KEYconsthttp-security-plugin:client-ip, the cross-package key ipSecurityMiddleware writes and rateLimitMiddleware reads
SESSION_STATE_KEYconstsession-plugin:session, the ctx.state key under which session-plugin’s middleware parks the live ISession and multi-tenancy-plugin’s tenant middleware reads it back for the tenant-binding compare (M101c, V8-7)
CREDENTIAL_SOURCE_STATE_KEYconst auth-plugin:credential-source, the ctx.state key under which auth-plugin’s authentication middleware records the credential of the strategy that authenticated the request (‘header’ or ‘cookie’), unset when nothing authenticated it; session-plugin’s form CSRF reads it and exempts only a request a header-credential strategy authenticated, so a junk bearer header beside a session cookie stays verified (M113e)
SESSION_TENANT_BINDING_KEYconst__setu_tenant, the reserved session-payload key under which the sealed tenant binding is stored and read by both compare sites (M101c, V8-7)
SESSION_TENANT_BINDING_STATE_KEYconstsession-plugin:tenant-binding, set to true by session-plugin’s middleware when tenantBinding is on; multi-tenancy-plugin’s tenant-side compare runs only when it reads true, so tenantBinding: false disables both compare sites in every middleware order (M101c, V8-7)
tenantBindingMismatch(session, tenantId)functionThe pure tenant-binding compare shared by the two compare sites (M101c, V8-7): true only when the session is bound to a DIFFERENT tenant than the one resolved for the request; inert for an unbound session or a tenant-less request
createPathMatcher(patterns)functionBuilds a path-exclusion predicate from readonly PathPattern[]; strings match exactly and regular expressions are tested against the path.
installRequestIdentityGuard(prototype)functionInstalls the identity guard once on a request class’s prototype, so its instances need no per-request seal; called by the runtime’s served request, the kernel’s inject() request and @setu-ts/testing’s mock (0.11.0)
sealRequestIdentity(request)functionInstalls the one-implicit-write request identity guard for user, tenant and locale
replacePrincipal(request, principal)functionDeliberately replaces request.user after it has been guarded
replaceTenant(request, tenant)functionDeliberately replaces request.tenant after it has been guarded
replaceLocale(request, locale)functionDeliberately replaces request.locale after it has been guarded — the localization middleware’s write, and the application’s way to apply a signed-in user’s saved preference once authentication has run (M103)
isPromiseLike(value)functionDuck-typed thenable test (M87) — see the note below the Types table

Types

GroupExports
TokensCapabilityToken, StandardCapability
Shared typesHttpMethod, RuntimePlatform, LogLevel, LifecyclePhase, HealthStatus, MetricType, PluginPriority, JsonValue — the recursive JSON-safe value type (M74); SseMessage.data is typed with it, and its object arm admits undefined because JSON.stringify drops such a key
UtilitiesResult<T, E>, Ok<T>, Err<E>, Option<T>, Some<T>, None, PathPattern — one path-exclusion entry (string | RegExp); a string is an exact match, never a prefix
Plugin contractIPlugin, IPluginContext, IApplication, StartOptions
Plugin context APIsIMiddlewareApi, MiddlewareOptions, IRouterApi, IEnvironmentApi, EnvVarSpec, IHealthApi, IMetricsApi, IOpenApiApi, IDecoratorApi, DecoratorHandler, ICliApi, CliCommandHandler, ILifecycleApi, IMetadataStore
Service registryIServiceRegistry, RegisterOptions, ServiceFactory<T>, RegistryFactory<T>, resolveRegistryEntry
HTTPIRequest, IResponse, IRequestContext, IMiddleware, MiddlewareFunction, NextFunction, RouteHandler, RouteDefinition, RouteSchema, SecurityRequirement, SECURITY_METADATA, RouteSecurityMetadata, withSecurityMetadata, securityMetadataOf, VALIDATION_METADATA, RouteValidationMetadata, withValidationMetadata, validationMetadataOf, RESPONSE_METADATA, RouteResponseMetadata, withResponseMetadata, responseMetadataOf, HandlerResult, ResponseSnapshot, ResponseSnapshotInit, UPGRADE_INTENT, WebSocketUpgradeIntent, setUpgradeIntent, upgradeIntentOf, isWebSocketUpgradeRequest, HTTP_STATUS_HINT, HttpStatusHint, withHttpStatusHint, httpStatusHintOf
RuntimeIRuntimeServices, IFileSystem, IHttpAdapter, IWorkerHost, IWorkerHandle, TimerHandle, ServerHandle, StatResult
DI (optional)IContainer, Constructor<T>, ServiceScope, Provider<T>, ClassProvider<T>, FactoryProvider<T>, ValueProvider<T>, ProviderOptions<T>, InjectionToken<T>, ContainerToken<T> — string | InjectionToken<T> | Constructor<T>, the token any IContainer method accepts — and InjectEntry, one inject element: a ContainerToken or { token, optional: true }. See “DI container contract (M125)” below
LoggingILogger, LogMetadata
ConfigIConfig
ValidationIValidationService, ValidationTarget, ValidationIssue
HealthIHealthIndicator, HealthIndicatorFn, HealthIndicatorOptions, HealthCheckResult, IHealthService, HealthReport, HealthStatus, CachedProbeOptions, ProbeTiming, ConnectionErrorReporter, ConnectionErrorReporterOptions, ConnectionErrorLogger
MetricsIMetric, MetricConfig, IMetricsService, ICounter, IGauge, IHistogram, ISummary, MetricOptions
AuthIPrincipal, IJwtService, JwtSignOptions — IPrincipal is { id: string; roles?: readonly string[]; permissions?: readonly string[]; claims?: Readonly<Record<string, unknown>> }
DatabaseIOrmAdapter, ITransaction, IDatabaseAdapter, IAdapterTransaction, IDataSource, NormalizedQuery, OrderDirection, TransactionOptions, TransactionIsolationLevel, ITransactionIsolationSupport — the data-access port, promoted from database-plugin in M52c so a backend can live in another package (cloudflare-plugin’s D1Adapter is the first); the last three carry portable transaction isolation (M90g)
CacheICacheStore
EventsIEventBus, IDomainEvent<T>, EventHandler<T>, Unsubscribe
MessagingIMessageBroker, PublishOptions, ISubscription, MessageHandler<T>, MessageMetadata, SubscribeOptions, RequestOptions, RequestHandler<TReq, TRes>, ORDERING_KEY_HEADER, DEDUPLICATION_ID_HEADER, MAX_PUBLISH_ID_BYTES, parsePublishOptions, ParsedPublishOptions, publishIdProblem, isValidPublishId, MAX_PUBLISH_HEADERS, MAX_PUBLISH_HEADER_NAME_BYTES, MAX_PUBLISH_HEADER_VALUE_BYTES, RESERVED_HEADER_NAMES, RESERVED_HEADER_PREFIXES, publishHeaderNameProblem, publishHeaderValueProblem
IngressIngressKind, IngressContext<TPayload>, BehaviorLike<TWork, TResult>, IIngressBehavior, composeBehaviorChain
QueueIQueue, IJob<T>, JobProcessor<T>, AddJobOptions, ProcessOptions, RecurringOptions
SchedulerIScheduler, ScheduledJob<T>, SchedulerJobHandler<T>, ScheduleOptions<T>, RetryOptions, SchedulerBackoff
SecretsISecretManager
AuditIAuditLogger, AuditEntry
ResilienceICircuitBreaker, CircuitState, IResilienceService, WrapOptions, CircuitBreakerPolicy, RetryPolicy, BulkheadPolicy, BackoffStrategy, ResilientCall, HardenedCall
StorageIStorage, SignedUrlOptions
MailIMailer, MailMessage
NotificationsINotifier (with optional sendSettled?), NotificationMessage, ChannelSendResult
ErrorsIErrorResponder, ErrorResponseInit, ErrorResponderTarget, SerializedError, AuthorizationFailure — the request-scoped error responder seam, standard authorization refusals, and the pure error serializer (M70f)
Feature flagsIFeatureFlags, FlagContext
Multi-tenancyIMultiTenancyService, ITenantRepository, ITenantResolver, ITenant
SSRISsrService
SSEISseService, ISseConnection, SseChannel, SseMessage
Realtime backplaneIRealtimeBackplane, RealtimeFrame, RealtimeFrameHandler, RealtimeFrameKind, EncodedPayload
WebSocketIWebSocketService, IWebSocketConnection, IWebSocketTransport, WebSocketRoom, RoomBroadcastOptions, WebSocketHandlers, WebSocketRouteOptions, WebSocketConnectionContext, WebSocketCloseEvent, WebSocketReadyState, WebSocketEventSink, WebSocketUpgradeDecision, WebSocketUpgradeRouter, WebSocketUpgradeGuard, WebSocketGuardDecision
Worker poolIWorkerPool, WorkerRunOptions, TaskPoolStats, WorkerReadySignal, WorkerTaskRequest, WorkerTaskReply, WorkerErrorShape
SessionISessionService, ISession, ISessionStore, SessionData, SessionView, CookieAttributes
Auth sessionIAuthSessionService, SignInOptions, SignInOutcome, AuthMethod
Authorization policiesIAuthorizationPolicyService, PolicyDefinition, PolicyAbility, PolicyCheck, AnonymousPolicyCheck, PolicyRef, PolicyAbilityInfo, PolicyTarget — the asynchronous, target-aware check (M110a) served under CAPABILITIES.AUTHORIZATION_POLICIES (can / authorize / describe / define). The bare PolicyDefinition (A = string, T = never, before a METHOD signature) is the type-erased form a registry stores, which is what makes every typed policy assignable to it under strictFunctionTypes
Scoped RBACSCOPED_RBAC_POLICY, scopedPermissionAbility, scopedRoleAbility, isScopeType, MAX_SCOPE_ID_LENGTH, ScopeRef, ScopedGrant, GrantQuery, IGrantSource, IScopedRoleSource, ScopedRoleDefinition, ScopedRbacTarget, ScopeSource, scopeFromTenant, scopeFromParam — the M110b contracts AuthPlugin’s built-in scoped-rbac policy evaluates and DecoratorPlugin/DatabasePlugin name without importing AuthPlugin. A scope type is lowercase kebab-case (isScopeType); an id is non-empty and at most MAX_SCOPE_ID_LENGTH (256) UTF-16 code units, refused rather than truncated. GrantQuery is { kind: 'chain', scopes } (the check’s scope and those it inherits from, plus global grants) or { kind: 'all' } (sign-in timing only). scopeFromParam and scopeFromTenant resolve undefined for an absent, empty or over-long id, which leaves the scope unresolved and the check DENIES (never a global check)
Service discoveryIServiceDiscovery, ServiceInstance, PickOptions, LoadBalanceStrategy, ServiceOutcome
DNSIDnsResolver, SrvRecord
gRPCIGrpcService, GrpcServiceDefinition, GrpcServingStatus, RpcFetchHandler
CloudflaresplitWorkerEnv, SplitWorkerEnv
View renderingIViewEngine, Component — the view port (render(component, props): string | Promise<string>) and the structural component type it renders, named by @Render and renderView (M92)
LocalizationILocalizer, LocalizationMessage, PluralForms, MessageCatalogue — the localization port (t(key, values?), locale, locales, forLocale(tag)) served under CAPABILITIES.LOCALIZATION, and the catalogue shape: a string with {name} placeholders or a CLDR plural record with other required (M103)
OutboxIOutboxStore, IOutboxWriteScope, OutboxRecord, OutboxStatus, OutboxKey, OutboxTransition, OutboxStoreStats, OUTBOX_RECORD_KIND — the transactional-outbox store port (append, scanPending, failedKeys, claim, markInvalid, markSent, markFailure, release, stats, purge, verify; every method rejects, never throws), the row shape (every field a JSON scalar, times as epoch ms; claimVersion starts at zero and is claimable in [0, Number.MAX_SAFE_INTEGER - 1], leaseUntil starts at zero; PostgreSQL bigint, SQLite/D1 64-bit INTEGER, JSON numbers elsewhere), the conditional-transition outcome (applied / missing / not-pending / not-failed / claim-lost), and the 'setu-outbox' discriminator every row carries and every read requires (M107)
InboxIInboxStore, InboxRecord, InboxStatus, InboxIds, InboxFailureUpdate, InboxReleaseOutcome, InboxStoreStats, INBOX_RECORD_KIND — the consumer-inbox store port (find, run, recordFailure, park, parked, release, stats, purge, verify; every method rejects, never throws; run creates the marker first in one transaction), the row shape (every field a JSON scalar, times as epoch ms), the release outcome (applied / missing / not-parked), and the 'setu-inbox' discriminator every row carries and every read requires (M108)
IdempotencyIIdempotencyStore, IdempotencyClaimRequest, IdempotencyClaimResult, IdempotencySettleResult, IIdempotencyService, IdempotentRouteOptions, IdempotencyKeySource, IdempotencyFingerprintSource, IdempotentIngressOptions, IdempotentIngressCommonOptions, IngressIdempotencyKeySource, IngressIdempotencyFingerprintSource — the store port, the service contract served under CAPABILITIES.IDEMPOTENCY, and the route/ingress option types (M109a). The record is an opaque string so every store holds identical bytes; the mechanism lives in @setu-ts/idempotency-plugin. IngressContext.consumer (below) is the dispatch identity the ingress key is scoped by.

DI container contract (M125). The container is optional (no plugin may require it), and its contract changed in 0.11.0:

  • Tokens. Every IContainer method takes a ContainerToken<T>: a string, an InjectionToken<T> from createToken<T>(name), or a class (Constructor<T>). Object tokens are matched by identity. resolve on an InjectionToken<T> or a class returns T without a type argument; resolve<T>(token: string) is unchanged.
  • Visible dependencies. ClassProvider.inject and the new FactoryProvider.inject are readonly InjectEntry[]. FactoryProvider.useFactory is (...deps: never[]) => T and is called with the resolved dependencies in inject order; an absent optional dependency arrives as undefined. A factory without inject is opaque to boot validation.
  • ProviderOptions<T> (generic, default unknown) adds dispose?: (instance: T) => void | Promise<void>, called by dispose() for each instance the container created.
  • forRequest(ctx: IRequestContext): IContainer returns the request’s scope, created on first call and disposed through ctx.defer when the request finishes. Without ctx.defer it throws.
  • dispose(): void | Promise<void> disposes what the container created — on a scope its scoped instances and the transients it built, on the root its singletons — in reverse creation order, each even if an earlier one failed — an asynchronous one bounded at 5000 ms, recorded as a named timeout when it expires — then throws (or rejects with) one AggregateError. It returns a promise only when a dispose function did. Repeat calls are no-ops; a disposed scope refuses further resolves. Instances from the service-registry fallback are never disposed.
  • ServiceScope. scoped means one instance per scope; a request reaches its scope through forRequest. Resolving a scoped token on the root container is refused, and so is a transient there whose graph reaches a scoped provider.
  • Breaking for implementors: forRequest and dispose are required members (0.11.0).

IRequestContext.defer?(cleanup) (M125) — registers per-request cleanup. The kernel runs cleanups in reverse registration order once the request finishes: before a buffered response is handed back (awaiting any that return a promise), and when a streaming body ends, errors or is cancelled; on the failure path, after the 500 is written. Each asynchronous cleanup is bounded at 5000 ms: on expiry the kernel reports it and moves on, and the cleanup’s later outcome is ignored. A cleanup’s throw or rejection is reported at warn through the logger capability when one is registered, and never changes the response; a defer after completion runs at once. The member is optional, so no implementor breaks: the kernel’s context implements it, createTestContext does not. A request that never calls it stays on the synchronous path.

IRequestContext.slot?<T>(key, create) (M125) — owner-keyed per-request storage. It returns the value this request holds under key, compared by object identity, and on the first call for that key stores and returns create(); a throwing create stores nothing, so a later call retries. Entries cannot be enumerated or deleted and live as long as the context, so a key the owner keeps private gives it storage no other code can read or replace through slot. create must not call slot with its own key. The kernel’s context keeps the first entry without allocating; the member is optional, and createTestContext does not implement it. forRequest keeps each request’s scope here.

isPromiseLike(value) (M87) — reports whether a value is thenable, by the duck-typed test (typeof value.then === 'function') rather than instanceof Promise. @setu-ts/kernel and @setu-ts/runtime both use it on the request path to decide whether a handler result needed awaiting. instanceof answers false for a promise from another realm and for userland promise libraries — all of which satisfy RouteHandler’s declared Promise<HandlerResult> structurally — and treating one as already-settled sends the response while the handler is still running. Pair it with Promise.resolve, which returns a native promise unchanged and adopts a foreign thenable.

ResponseSnapshot.responseInit (M88, @internal) — an optional snapshot-local ResponseSnapshotInit header input used only by the kernel-to-runtime response conversion. The runtime consumes it before it reads snapshot.headers, allowing normal terminal responses to avoid materializing the framework’s native Headers object; native servers may derive headers such as Content-Length from that input. Middleware and application code must continue to use the documented live snapshot.headers view; snapshots with explicitly mutated or repeated headers use that existing path unchanged.

Contract notes:

  • IPluginContext.runtime is non-optional: a runtime provider is mandatory and registers first, so every plugin may rely on it (ARCHITECTURE.md §7).

  • ILifecycleApi.onStopping(fn) runs at the very start of stop(), before the application begins refusing new requests with a 503 and before the socket closes — the only hook that fires while the application is still serving normally. It exists for “tell the outside world to stop routing here” work, most obviously deregistering from service discovery; doing that in onShutdown leaves callers routed at a closed port for up to one health-check interval. Hooks run LIFO and are awaited, so a slow hook delays shutdown. A rejecting hook surfaces from stop(), but only after the drain, the socket close, and the onShutdown/onClose phases have run: a failing hook must not be able to prevent the application from stopping. With no hook registered the phase is skipped and stop() behaves exactly as before.

  • Schema positions (RouteSchema, IValidationService, IOpenApiApi) are typed unknown so common carries no validator dependency; the validation plugin narrows them (Zod by default).

  • RouteSchema.operationId?: string and RouteSchema.description?: string (since 0.10.0) declare operation metadata. OpenAPI uses the id verbatim, derives it only when absent, and emits the description. Included declared ids must be nonblank and unique; OperationIdError from @setu-ts/openapi-plugin refuses invalid declarations at bootstrap and document generation.

  • RouteSchema.security is documentation only — it describes an operation for the OpenAPI document and for client generation, and enforces nothing. Authentication is enforced by middleware and guards. An empty array declares the operation public and is not the same as omitting the field, which leaves it inheriting the document-level requirement.

  • Declaring is not the only way a document learns about authentication. withSecurityMetadata brands a MiddlewareFunction with a RouteSecurityMetadata ({ authenticated: boolean }), and securityMetadataOf reads it back — the channel by which @setu-ts/openapi-plugin derives an operation’s requirement from the guards @setu-ts/auth-plugin produced, without either package importing the other. SECURITY_METADATA is created with Symbol.for, so two copies of common in one process resolve the same key. The brand is symbol-keyed and non-enumerable: the middleware’s identity and behaviour are unchanged. It carries authentication PRESENCE only — a role is not a security scheme, so requireRole('admin') brands { authenticated: true } and nothing more.

  • withHttpStatusHint brands an Error rather than a middleware function, and the reason it exists is that respondWithError cannot reach every site: a data source throwing from deep inside an adapter holds no IRequestContext, so its error arrives at errorHandler as a plain Error, is normalized to a 500, and is masked. That is right for a driver fault and wrong for a refusal the caller caused. HttpStatusHint is an ErrorResponseInit whose detail is required, and errorHandler builds the response from the hint’s own status/title/detail — never from the error’s message, which is the operator-facing diagnostic and stays log-only. Both entry points construct the response through one implementation, so a hinted throw and a respondWithError call carrying the same values answer byte-identically under every configured format. A hinted error is exempt from maskInternalErrors, and the exemption is narrow by construction rather than by trust: what it serves is a fixed sentence the brand site wrote, so there is no driver diagnostic in the body for masking to remove. A brand on a deliberately thrown HttpError is ignored — that error already states its own status.

    status must be an integer in 400–599, and a hint outside that range is treated as ABSENT so the error takes the ordinary masked-500 path. Two reasons, and the second is why the floor is 400 rather than 200: the web Response constructor throws RangeError outside [200, 599], so an unserveable status would make the error handler itself the fault — a throw escaping the one catch that exists to contain throws — and a hint says how an ERROR is answered, which is never a success or a redirect. This is deliberately stricter than the ErrorResponseInit it extends, whose status is a plain number that an application authors (FlagGuardOptions.statusCode is the shipped example) and so may legitimately be any serveable status, not only an error one. ErrorResponseInit.status is guarded separately, by resolveResponseStatus, against the serveability bound [200, 599] rather than this error-ness one.

  • withValidationMetadata brands a MiddlewareFunction with a RouteValidationMetadata ({ target: ValidationTarget; schema: unknown }), and validationMetadataOf reads it back — the same mechanism, for the same reason, applied to request shape rather than authentication. Every helper @setu-ts/validation-plugin ships carries it, as does IValidationService.middleware(schema, target), and @setu-ts/openapi-plugin reads it to fill an operation’s requestBody and parameters. VALIDATION_METADATA is created with Symbol.for for the same cross-copy reason, the brand is symbol-keyed and non-enumerable, and the schema travels by REFERENCE — a reader transforms it with whatever schema support it has, and identity is what the OpenAPI generator’s deduplication keys on. A foreign value under the same global symbol reads as absent rather than being trusted.

  • withResponseMetadata brands a RouteHandler with a RouteResponseMetadata ({ status: number }), and responseMetadataOf reads it back — the same mechanism as the two brands above, applied to the status a handler answers with on success. Every route @setu-ts/decorator-plugin’s @HttpCode or @Redirect produces carries it, and @setu-ts/openapi-plugin reads it (deriveResponseStatus, on by default) to document the operation under that status instead of the assumed 200. RESPONSE_METADATA is created with Symbol.for for the same cross-copy reason, and the brand is symbol-keyed and non-enumerable.

    The carrier is the HANDLER rather than a middleware function, and that is a design decision rather than a detail: a guard and a validator ARE middleware, while a success status is a property of the handler, and a decorated route may carry no middleware at all — branding a synthetic middleware purely to hold it would add a pipeline entry that does nothing. status must be an integer in 200–599, the range the web Response constructor accepts; a brand outside it reads as ABSENT rather than being trusted, because a status no runtime could serve is not a status a document should claim. It carries the status ONLY: a response header is not represented, since an OpenAPI response-header entry needs a schema and a description a @ResponseHeader('X-Total', '42') declaration does not carry.

  • IRequest.json() rejects with MalformedRequestBodyError — branded 400 — when the body is not valid JSON (X37-1). The read is lazy (M87) and happens inside the handler, so the rejection reaches errorHandler through the ordinary throw path and is answered 400 in the configured format; a handler that CATCHES its own json() rejection keeps full control of what is served. The same rejection comes from all three producers, so an in-process inject() observes exactly what a served request observes. Added in Milestone 90f.

  • IRequest.formData?() resolves a FormBody for both form encodings through the shared parseFormBody, memoized exactly like json() — the in-flight promise is cached, a rejection with it. It throws UnsupportedFormEncodingError, branded 415, when the content-type is neither form encoding, and yields an EMPTY form for a multipart body it cannot parse: the promoted parser’s released behaviour, stated as the accessor’s limit rather than hidden behind a status. The same parse runs at all three producers, so an in-process inject() observes exactly what a served request observes; a request WITHOUT the optional accessor (an out-of-repo implementor) falls back to the same parseFormBody(await request.bytes(), contentType) — the fallback is the same function, not a second implementation. Optional on the signal?/fs?/ raw? precedent. Added in Milestone 94b.

  • IRequest.raw?: Request and IRequestContext.raw?: Request carry the undisturbed web Request the HTTP adapter received, alongside the mapped framework request whose body has already been buffered. The kernel terminal handler reads it to decide a WebSocket upgrade (which needs the native request) and to reconstruct a gRPC request. Optional, on the M42 signal? / M44 fs? precedent: a custom adapter may omit it, and the terminal handler then treats the request as neither an upgrade nor RPC and falls through to the 404 — it never throws. inject() populates it, so an injected request can exercise both paths. Added in Milestone 70a.

  • setUpgradeIntent(request, intent) brands an IRequest with a WebSocketUpgradeIntent ({ sink, protocol? }) and upgradeIntentOf(request) reads it back, under the UPGRADE_INTENT symbol. This is how the kernel tells the HTTP adapter “the pipeline ran, nothing short-circuited, perform the handshake”. The IRequest is the channel rather than IRequestContext.state because the adapter holds the former and never sees the context — the kernel builds and discards it internally, so nothing written to ctx.state could reach the adapter. UPGRADE_INTENT uses Symbol.for, so two copies of common in one process agree. Added in Milestone 70a.

  • isWebSocketUpgradeRequest(headers) is the shared RFC 6455 §4.2.1 predicate: Upgrade must equal websocket case-insensitively and Connection must contain the upgrade token, matched after splitting on commas — a substring test would also claim Connection: no-upgrade. It lives in common because the kernel decides whether a request is an upgrade and does not depend on @setu-ts/runtime, which re-exports this same function rather than keeping a copy. Added in Milestone 70a.

  • HandlerResult is an opaque brand only the kernel constructs; handlers obtain it from IResponse terminal methods (json, text, send, redirect, stream).

  • IResponse has two header setters with distinct semantics: header(name, value) replaces any existing value for name (Headers.set), while appendHeader(name, value) adds a value without removing existing ones (Headers.append). appendHeader is the correct way to emit multiple headers of the same name — most notably several Set-Cookie headers (e.g. access + refresh cookies). Both chain (return this).

  • IResponse.setHeaders(headers: Readonly<Record<string, string>>): IResponse (0.11.0) — sets several headers at once, exactly as header(name, value) called for each own enumerable string-keyed entry in order, with the same validation and errors. Passing the SAME frozen object on every request lets the kernel validate it once and copy it into each response in one step; any other object is applied entry by entry. HttpSecurityPlugin’s security headers use it. Fastify’s reply.headers(obj), Express’s res.set(obj) and Koa’s ctx.set(obj) are the precedent for the batch call; the template cache is Setu’s. Breaking for IResponse implementors, including test fakes typed as IResponse: add the member as a loop over header(), or use @setu-ts/testing’s createTestContext.

  • IResponse.html(body: string): HandlerResult — sends an HTML response with content-type: text/html; charset=utf-8. The charset is not optional: a bare text/html lets a browser sniff the encoding. Added because every CSRF-protected form example was setting the header by hand. Breaking for out-of-repo IResponse implementors (both in-repo implementors — the kernel’s ResponseBuilder and testing’s MockResponse — implement it).

  • IResponse.stream(body: ReadableStream<Uint8Array>): HandlerResult — sends a streaming response body. The runtime maps this to new Response(streamBody, { status, headers }); streaming is free on every platform (Node via Hono, Deno, Bun, Cloudflare Workers) with no buffer-then-send. Added in Milestone 42.

  • IResponse.snapshot() returns a discriminated union keyed on streaming: when false, body is Uint8Array | string | null (buffered); when true, body is ReadableStream<Uint8Array> (live stream). This allows middleware to safely inspect the response without draining a live stream — middleware that reads the body must check streaming first. Widened from the flat shape added in Milestone 11 to a discriminated union in Milestone 42.

  • A body written at a null-body status (204, 205, 304) is dropped when the response is mapped to a web Response, and a stream body is cancel()led so its source is released. The Response constructor rejects a body at these statuses, and the mapping runs in the HTTP adapter after the pipeline, so passing one through raised a TypeError no middleware or errorHandler could catch. Dropping matches Express and Fastify and follows RFC 9110 §15.3.5; headers are preserved, which RFC 9110 permits. IResponse.snapshot() is unchanged and still reports whatever body was written — the drop happens at the mapping boundary, not in the response model.

  • IRequest.ip?: string — the socket peer on Node, Deno and Bun; behind a proxy read CLIENT_IP_STATE_KEY for the resolved client. Workers and plain fetch calls have no peer.

  • IRequest.signal?: AbortSignal — an abort signal that fires when the underlying HTTP connection is severed (client disconnect, timeout). Populated by the HTTP adapter from the native Request.signal; optional because injected / test requests may not carry one. Added in Milestone 42.

  • IRequest.user, IRequest.tenant and IRequest.locale each allow one implicit assignment per request; a later assignment throws. replacePrincipal, replaceTenant and replaceLocale are the explicit escapes for an intentional replacement. This catches late accidental overwrites, not authorization bypasses: a write before authentication is still the permitted first write. Every request the framework creates — a served request, an inject() request and a createTestContext request — carries this guard on its class prototype (installRequestIdentityGuard, 0.11.0), so on those the three fields are inherited accessors: Object.keys(request) and JSON.stringify(request) do not list them. A request an application builds itself is guarded per instance by the kernel, as before, and its fields stay own and enumerable.

  • IRequest.locale?: string (M103) — the request’s resolved BCP 47 tag, written by the localization plugin’s middleware at priority 45 and absent without that plugin or on a path it excludes. It is a flagged, optional, source-compatible widening on the tenant precedent. Three things to keep in mind: a reader running BELOW priority 45 sees undefined (the priority table orders it, exactly as for tenant at 40); a response cache keys on the value present when the cache runs, so an override must happen before the lookup to be reflected there; and it is a preference, never an authorization input — any client can select any supported locale.

  • The application service registry seals after runBootstrap(). Its register, registerFactory, and unregister methods then throw; request-scoped child registries remain mutable. Startup-time override: true mutations log at info, and successful unregisters log at warn through the logger capability.

  • IRequestContext.signal: AbortSignal — required abort signal (always present). Populated by createRequestContext from the native Request.signal; falls back to a non-aborting sentinel for injected/test contexts so handlers always have a live signal to listen on. Added in Milestone 42.

  • CAPABILITIES.SSE ('sse') — the capability token under which the SsePlugin registers the ISseService. The service provides real-time, one-way server-to-client messaging over an SSE stream built on IResponse.stream(). Added in Milestone 43.

  • CAPABILITIES.SSR ('ssr') — the capability token under which the React Router plugin registers the ISsrService. The service provides server-side rendering by delegating to React Router’s request handler and writing back the result via IResponse. Added in Milestone 44.

  • CAPABILITIES.REALTIME_BACKPLANE ('realtime-backplane') — the capability token under which RealtimeBackplanePlugin registers an IRealtimeBackplane. Consumed optionally by the WebSocket and SSE plugins: present, their rooms and channels fan out across replicas; absent, they broadcast in-process exactly as before. Added in Milestone 47.

  • CAPABILITIES.WEBSOCKET ('websocket') — the capability token under which the WebSocket plugin registers the IWebSocketService. The service provides full-duplex, bidirectional real-time messaging: exact-path routes with lifecycle handlers, named broadcast rooms, and an application-level heartbeat. Distinct from SSE, which is one-way. Added in Milestone 46.

  • CAPABILITIES.SESSION ('session') — the capability token under which SessionPlugin registers the ISessionService. Provides cookie-backed sessions for server-rendered applications: an encrypted self-contained cookie by default, or an opaque id over an ISessionStore. Distinct from AUTHENTICATION, which establishes who a caller is — a session carries per-visitor state and exists for anonymous visitors too. Added in Milestone 48.

  • CAPABILITIES.GRPC ('grpc') — the capability token under which GrpcPlugin registers the IGrpcService. The service provides gRPC/Connect co-serving on the same port as ordinary Hono routes through the kernel terminal handler. The deprecated optional IHttpAdapter.setRpcHandler? seam is no longer consulted. Added in Milestone 49.

  • CAPABILITIES.CLOUDFLARE ('cloudflare') — the capability token under which CloudflarePlugin registers ICloudflareBindings: typed access to a Worker’s KV, R2, D1, Queues, service and Durable Object bindings, its string variables, and waitUntil. Added in Milestone 52.

  • CAPABILITIES.GRAPHQL ('graphql') — the capability token under which GraphqlPlugin registers the IGraphqlService. The service provides schema-first and code-first GraphQL execution over ordinary kernel routes with media-type negotiation. Added in Milestone 51.

  • Contribution-token pattern: HTTP_ADAPTER and the five contribution tokens (HEALTH_INDICATOR, METRIC_REGISTRATION, OPENAPI_SCHEMA, CLI_COMMAND, DECORATOR_HANDLER) are multi-provider capabilities. The kernel collects plugin contributions registered under these tokens via services.getAll(); the corresponding first-party plugins aggregate and expose them. HTTP_ADAPTER is single-provider — the runtime plugin registers its IHttpAdapter there.

  • METADATA_STORE ('metadata-store') is the single-provider capability backing IPluginContext.metadata; the DecoratorPlugin registers its IMetadataStore there. It is distinct from OPENAPI so an OpenAPI plugin registering under OPENAPI does not populate ctx.metadata.

Kernel diagnostics contracts

The read-only DTOs and reader interface the kernel’s optional IApplication.diagnostics serves (Milestone 98a): IDiagnosticsSource (snapshot() / read(after, limit?)), DiagnosticsSnapshot, DiagnosticsNode, DiagnosticsEdge, DiagnosticsEvent, DiagnosticsBatch, and the closed vocabularies DiagnosticsNodeKind, DiagnosticsEdgeKind, DiagnosticsSnapshotState, DiagnosticsFailureCode, DiagnosticsEventKind, DiagnosticsEventOutcome and DiagnosticsEventStage. They are type-only exports: @setu-ts/common ships no diagnostics runtime value and no capability token, because the reader is reached through the application, never resolved from the registry. Activation, label allowlists, and the projection bounds are the kernel’s — see Kernel diagnostics.

Inspector contracts (0.8.0)

The inspector-side contracts the optional addon packages register and the Diagnostics Connector consumes: IHealthDiagnosticsSource under CAPABILITIES.HEALTH_DIAGNOSTICS (M98d, served by the health plugin — see Health) and IConfigDiagnosticsSource under CAPABILITIES.CONFIG_DIAGNOSTICS (M98e, served by the config plugin — see ConfigPlugin). The shared vocabulary DiagnosticsInspectorState distinguishes unsupported (a connector-side answer: the operation is implemented but no source is registered) from disabled (the owning plugin’s answer: present but not opted in). The M98e DTOs — ConfigProvenanceOrigin (environment/file/unknown), ConfigSchemaEffect (not-configured/validated/introduced/removed/unknown), ConfigProvenanceEntry, and ConfigDiagnosticsSnapshot — are type-only exports carrying application-approved aliases and evidence only; the privacy rules are the config plugin’s and the wire shape is docs/diagnostics-protocol.md’s.

Cache observation contracts (M98i). CAPABILITIES.CACHE_DIAGNOSTICS ('cache-diagnostics') is a MULTI-provider token: every CachePlugin instance registers one ICacheDiagnosticsSource (snapshot(): CacheDiagnosticsSnapshot — synchronous, performs no cache operation). The DTOs are CacheDiagnosticsSnapshot (state, alias | null, coverage: 'owned-instance', records, dropped), CacheDiagnosticsRecord (alias, CacheDiagnosticsOperation, count, lastDurationMs, ageMs, and the non-negative saturating counters succeeded, failed, hits, misses, present, absent, removed, notRemoved) and the connector’s CacheDiagnosticsResponse. See the diagnostics-connector section for the wire operation.

Scheduler observation contracts (M98k). CAPABILITIES.SCHEDULER_DIAGNOSTICS ('scheduler-diagnostics') is a MULTI-provider token: every SchedulerPlugin instance registers one ISchedulerDiagnosticsSource (snapshot(): SchedulerDiagnosticsSnapshot — synchronous, never acquires a lock, invokes a handler, or reads the job registry). The DTOs are SchedulerDiagnosticsSnapshot (state, alias | null, coverage: 'owned-instance', records, dropped), SchedulerDiagnosticsRecord (the JOB alias, SchedulerDiagnosticsOperation ('fire' | 'attempt'), count, lastDurationMs | null, ageMs, and the non-negative saturating counters started, succeeded, failed, contended, lockFailed, retryAttempts, lastLatenessMs) and the connector’s SchedulerDiagnosticsResponse. Lateness is max(0, actualStart - intendedFire); a skipped local fire is never a globally missed execution and no missed counter exists. See the diagnostics-connector section for the wire operation. Storage observation contracts (M98m). CAPABILITIES.STORAGE_DIAGNOSTICS ('storage-diagnostics') is a MULTI-provider token: every StoragePlugin instance registers one IStorageDiagnosticsSource (snapshot(): StorageDiagnosticsSnapshot — synchronous, performs no storage operation). The DTOs are StorageDiagnosticsSnapshot (state, alias | null, coverage: 'owned-instance', records, dropped), StorageDiagnosticsRecord (alias, StorageDiagnosticsOperation, count, lastDurationMs, ageMs, succeeded, failed, lastBytes) and the connector’s StorageDiagnosticsResponse. The operation vocabulary is the six public calls put, get, delete, exists, getSignedUrl, getStream; lastDurationMs and lastBytes are null for getSignedUrl, and lastBytes is null for the non-buffered operations. Unlike the M98l realtime contracts, the collector is NOT shared in common — a single plugin owns it — so the option and collector types live in @setu-ts/storage-plugin. See the diagnostics-connector section for the wire operation.

Event observation contracts (M98j). CAPABILITIES.EVENTS_DIAGNOSTICS ('event-diagnostics') is a MULTI-provider token: every EventsPlugin instance registers one IEventDiagnosticsSource (snapshot(): EventDiagnosticsSnapshot — synchronous, never publishes, invokes a handler or enumerates subscriptions). The DTOs are EventDiagnosticsSnapshot (state: EventDiagnosticsState, alias | null, coverage: EventDiagnosticsCoverage, records, dropped), EventDiagnosticsRecord (alias, EventObservationOperation, count, started, succeeded, failed, noSubscribers, lastDurationMs, ageMs) and the connector’s EventDiagnosticsResponse. See the diagnostics-connector section for the wire operation.

Realtime observation contracts (M98l). CAPABILITIES.REALTIME_DIAGNOSTICS ('realtime-diagnostics') is a MULTI-provider token: the WebSocket, SSE and realtime-backplane plugins each register one IRealtimeDiagnosticsSource (snapshot(): RealtimeDiagnosticsSnapshot — synchronous; it never sends, closes, publishes, subscribes, or creates or enumerates a room or channel). The DTOs are RealtimeDiagnosticsSnapshot (state, alias | null, sourceKind: RealtimeSourceKind | 'unknown', coverage: 'owned-instance', gauges: RealtimeDiagnosticsGauges, records, dropped), RealtimeDiagnosticsGauges (state: RealtimeGaugeState, openConnections | null, groups | null), RealtimeDiagnosticsRecord (alias, RealtimeObservationOperation, count, lastDurationMs, ageMs, succeeded, failed, backpressureCloses) and the connector’s RealtimeDiagnosticsResponse. Because the three owning plugins need the identical collector and may not import one another, common also ships it once: compileRealtimeDiagnosticsAlias(options) validates a RealtimeDiagnosticsOptions ({ enabled: true, alias }, alias 1–64 UTF-8 bytes, no control character, no other key; fixed messages that never echo a value) and createRealtimeObservationCollector(init: RealtimeObservationCollectorInit) returns an IRealtimeObservationCollector (enabled, observe, observePublish, snapshot, close, and the frozen snapshot-only source facade a plugin registers). A null alias gives an inert collector that reads neither the clock nor the gauges. RealtimeGaugeReading is the gauge reader’s return shape. The collector is pure and bounded — no I/O, no timer, no registry access — and a failing clock or gauge reader latches collection-failed without changing any application result. See the diagnostics-connector section for the wire operation.

Outbound HTTP observation contracts (M98n). CAPABILITIES.OUTBOUND_HTTP_DIAGNOSTICS ('outbound-http-diagnostics') is a MULTI-provider token whose producer is the SDK’s createObservedFetch registration plugin. IOutboundHttpDiagnosticsSource.snapshot(): OutboundHttpDiagnosticsSnapshot is synchronous and never performs a request. The DTOs are OutboundHttpDiagnosticsSnapshot (state, alias | null, coverage: 'owned-instance', at most one record), OutboundHttpDiagnosticsRecord (alias, operation: 'attempt', started, count, responses, failures, lastStatusClass: OutboundHttpStatusClass | null, lastDurationMs, ageMs), OutboundHttpStatusClass and the connector’s OutboundHttpDiagnosticsResponse. See the diagnostics-connector section for the wire operation.

Trace observation contracts (M98g). CAPABILITIES.TRACE_DIAGNOSTICS ('trace-diagnostics') is a SINGLE-provider token: the TelemetryPlugin always registers one ITraceDiagnosticsSource (read(instanceId, after, limit?): TraceDiagnosticsBatch — synchronous, requires a non-empty instance id the batch must match, M98a cursor contract over a 1,024-record ring, limit 1–128, throws the fixed value-free RangeError for a bad instance, a bad cursor, or a cursor beyond the source’s sequence). The DTOs are TraceDiagnosticsBatch and TraceObservation; the closed vocabularies are TraceSourceState, TraceCoverage, TraceInstrumentationKind (http, fetch, ioredis, redis since M117, amqplib, kafkajs), TraceOutcome, TraceParentVisibility and the TraceSamplerDescription shape. Minimization precedes retention: only exact approved operation aliases, validated W3C identifiers, at most eight validated link pairs, kind, outcome, duration and monotonic age are retained; span names, attributes, events, resources, tracestate, baggage, exceptions and status messages never reach collector state (canary-asserted). See the diagnostics-connector section for the wire operation.

Queue observation contracts (M98f). CAPABILITIES.QUEUE_DIAGNOSTICS ('queue-diagnostics') is a MULTI-provider token: every QueuePlugin instance registers one IQueueDiagnosticsSource (read(after, limit?): QueueDiagnosticsSourceBatch — synchronous, M98a cursor contract, limit 1–128) under it with { multi: true }, never in provides. The source-side DTOs are QueueDiagnosticsSourceBatch, QueueSourceAttemptObservation and QueueSourceDepthObservation; the connector’s merged DTOs are QueueDiagnosticsBatch, QueueDiagnosticsSourceStatus, QueueAttemptObservation and QueueDepthObservation; the closed vocabularies are QueueProcessorOutcome, QueueSettlementState, QueueDepthScope, QueueDepthCycleCoverage, QueueDepthCoverage, QueueSourceFailure, QueueDiagnosticsFailure and QueueSourceState. Every member is a bounded primitive or an approved alias: no payload, header, raw job id, claim token, credential or error text is expressible. See Queue observations and docs/diagnostics-protocol.md.

Auth-session contract (M100c). CAPABILITIES.AUTH_SESSION ('auth-session') resolves the IAuthSessionService that AuthPlugin registers when its signIn option is configured — the one owner of “this session is signed in as this principal”. signIn(ctx, principal, options: SignInOptions): Promise<SignInOutcome> records { principal, methods, at } in the request’s session and regenerates the session id (session fixation); SignInOptions.methods is a list of AuthMethod ('pwd' | 'otp' | 'pop' | 'fed', RFC 8176 values). SignInOutcome is { status: 'signed-in' } | { status: 'second-factor-required' } (M100d): when signIn.mfa.required(principal, methods) returns true the record is NOT written, the principal is instead held in a PendingSignIn, and the second arm is returned — so a caller must branch on the status rather than assume success. current(ctx) reads the recorded principal without running the strategy chain, pending(ctx) reads the held-back PendingSignIn ({ principal, methods, at }, or null when none is stored or it has expired), and signOut(ctx) destroys the session. All three require the session middleware to have run (signIn rejects, the others throw). What signOut revokes depends on the session strategy: the store strategy deletes the entry, while the default encrypted-cookie strategy leaves a copied cookie valid until its maxAge. Promotion out of the pending state is package-internal (promotePending) and is NOT part of IAuthSessionService: an exported promotion would let a caller assert it verified a factor, and a caller-supplied method is not evidence. Only the package’s own verifiers reach it — a guarantee about the framework’s surface, not a sandbox: application code can still call signIn with methods: ['pwd', 'otp'] and is trusted to record only methods it verified — and signIn.mfa.pendingTtlMs (default 300 000 ms) is the single owner of how long a pending record survives — TotpService has no TTL option of its own.

Authorization observation contracts (M98h). CAPABILITIES.AUTHORIZATION_DIAGNOSTICS ('authorization-diagnostics') is a SINGLE-provider token: the AuthPlugin always registers one IAuthorizationDiagnosticsSource (read(instanceId, after, limit?): AuthorizationDiagnosticsBatch — synchronous, requires a non-empty instance id the batch must match, M98a cursor contract over a 1,024-decision ring, limit 1–128, throws the fixed value-free RangeError for a bad instance, a bad cursor, or a cursor beyond the source’s sequence). The DTOs are AuthorizationDiagnosticsBatch, AuthorizationDecisionObservation and AuthorizationDecisionStep; the closed vocabularies are AuthorizationSourceState, AuthorizationCoverage (rbac-not-configured / provider-identity-unavailable / custom-provider / unknown), AuthorizationDecisionOperation and AuthorizationDecisionReason. Every member is a bounded primitive or an application-approved alias: no principal identifier, role or permission value, request path, raw rule, credential or error text is expressible, and a step count is a count only — it never carries a rule name that was not approved. See the diagnostics-connector section and docs/diagnostics-protocol.md.

Registry current-provider identity (M98h). IServiceRegistry gains one OPTIONAL method, isCurrent?(token, instance): boolean — a non-resolving identity predicate that reports whether instance is still the registration get would select for token, without resolving a lazy factory, enumerating services, or returning the current value. ServiceRegistry implements it as an identity comparison (request-scoped children delegate to their parent). It is optional on purpose: third-party registry-shaped implementations keep compiling, and a consumer that needs a current-provider check must treat an absent isCurrent as “cannot verify” — never as a fallback to a resolving get, which could instantiate a custom lazy service. The M98h authorization source is the first consumer: its absence latches provider-identity-unavailable and a false result latches custom-provider, both terminal.

Ingress behaviours

IngressKind is 'queue' | 'scheduler' | 'messaging' | 'websocket'. IngressContext<TPayload> is the immutable work envelope supplied to an ingress behaviour: { kind, name, payload, attempt?, headers?, consumer? }. Queue and scheduler populate the 1-based attempt; messaging supplies transport headers when available and WebSocket frames supply neither optional field.

consumer (M109a) is a DISPATCH identity, not a capability and not a state bag: the subscription’s SubscribeOptions.queue when given, otherwise subscription:<instance>:<n> (unique per subscription per process) on 'messaging', and the job name on 'queue'; it is absent on 'scheduler' and 'websocket'. It exists so per-consumer state — an idempotency record — is keyed per subscriber rather than per topic. The envelope’s “no state, no services” rule stands: a behaviour still reaches a capability through its RegistryFactory arm.

IIngressBehavior.handle(context, next) is the void-result contract for non-HTTP work. BehaviorLike<TWork, TResult> is the structural shape shared with CQRS, and composeBehaviorChain runs behaviours in declared order. A behaviour that does not call next() short-circuits the terminal handler; a thrown error follows that ingress’s existing error path. The common composer is also consumed internally by CQRS; it adds no CQRS surface.

Idempotency

CAPABILITIES.IDEMPOTENCY ('idempotency') names the service an idempotency provider registers; the port (IIdempotencyStore), the service contract (IIdempotencyService) and the route/ingress option types live in @setu-ts/common so a store adapter (memory, Redis, a Cloudflare Durable Object) and the decorator plugin can consume them without importing one another (AI_GUIDELINES §2.2). The mechanism — IdempotencyPlugin, idempotent(), idempotentIngress(), @Idempotent — lives in @setu-ts/idempotency-plugin; see that package’s section. The guarantee is no duplicate processing within the limits of the store — it is not a single-execution guarantee.

Tier C adds ITransactionalIdempotencyStore, with find(id), run(claim, work), purge(before, limit) and verify(). run owns the transaction and passes its scope to work, which returns { result: string, value: R }. find returns a TransactionalIdempotencyRecord or undefined when either row is missing or foreign. IDEMPOTENCY_RECORD_KIND is the exported value 'setu-idempotency'.

TransactionalIdempotencyClaim carries id, fingerprint, createdAt and expiresAt. TransactionalIdempotencyRecord adds the encoded result. The timestamps are epoch milliseconds; expiresAt makes a record eligible for purge, and it remains authoritative until purged.

IdempotentWithinOptions requires key, namespace and scope; it accepts optional fingerprint: unknown and ttlMs: number. Keys are 1–255 printable non-space ASCII characters, excluding "; namespace is 1–256 printable ASCII characters, and scope is 0–512. Scope must come from authenticated identity, typically JSON.stringify([tenantId, principalId]); '' deliberately makes the key global. Do not join parts with a separator that can appear inside them: distinct identities can collide. The default TTL is 86,400,000 ms, bounded to an integer from 60,000 to 2,592,000,000 ms. IdempotentWithinResult<R> carries value: R and replayed: boolean; value is the JSON round trip on both paths, so dates become strings and JSON-omitted members are absent.

The new IIdempotencyService contract includes two required tier-C members:

within<R, S = unknown>(
  options: IdempotentWithinOptions,
  fn: (scope: S) => Promise<R>,
): Promise<IdempotentWithinResult<R>>;
purgeTransactional(): Promise<number>;

S is the caller’s unchecked annotation of the store scope, normally IUnitOfWork. Without a configured transactional store, both members reject with IdempotencyConfigurationError('transactional', …). A replacement provider must implement both.

WebSocketUpgradeGuard is a route guard that receives a WebSocketConnectionContext and returns either true or a { status } refusal (WebSocketGuardDecision). WebSocketRouteOptions.guards is an optional readonly array of those guards; the matched route runs them in declared order before its handshake and the first refusal wins.


Agent card · TypeDoc API reference