@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.
| Area | Exports |
|---|---|
| Capability tokens | CAPABILITIES, createCapabilityToken(), CapabilityToken, StandardCapability |
| Plugin contract | IPlugin, IPluginContext, IApplication, context APIs (IRouterApi, IMiddlewareApi, ILifecycleApi, …) |
| Service registry | IServiceRegistry, RegisterOptions, ServiceFactory |
| HTTP abstractions | IRequest, IResponse, IRequestContext, IMiddleware, MiddlewareFunction, RouteHandler, RouteDefinition, ResponseSnapshot, ResponseSnapshotInit |
| Runtime abstraction | IRuntimeServices, IFileSystem, IHttpAdapter |
| Optional DI | IContainer, Provider, ServiceScope, createToken, InjectionToken, ContainerToken, InjectEntry |
| Domain contracts | ILogger, IConfig, IValidationService, IHealthIndicator, IMetric, IJwtService, IOrmAdapter, ICacheStore, IEventBus, IMessageBroker, IQueue, ISecretManager, IAuditLogger, ICircuitBreaker, IStorage, IMailer, INotifier, IFeatureFlags, ITenantResolver |
| Shared types | HttpMethod, RuntimePlatform, LogLevel, LifecyclePhase, HealthStatus, MetricType, PLUGIN_PRIORITY |
| Utility types | Result<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:
| Member | Meaning |
|---|---|
fields | Dot-path patterns, each mapped to a classification or to a FieldRedaction |
redactors | Redactor selected by the classification a matched pattern carries |
defaultRedactor | Fallback 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:
| Expression | Value |
|---|---|
(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
| Export | Kind |
|---|---|
assertPrimaryKeyUnchanged | function |
assertRealPathContained | function |
attachConnectionErrorReporter | function |
authorizationFailureInit | function |
brandErrorResponder | function |
causeMessage | function |
checkWritePrecondition | function |
compileRealtimeDiagnosticsAlias | function |
composeBehaviorChain | function |
contentTypeFor | function |
contextToTraceparent | function |
createCachedProbe | function |
createCapabilityToken | function |
createConnectionErrorReporter | function |
createMaskRedactor | function |
createPathMatcher | function |
createRealtimeHandlerErrorReporter | function |
createRealtimeObservationCollector | function |
createRedactionService | function |
createToken | function |
deadlineRangeError | function |
decodeCursor | function |
decodeFrameData | function |
describeError | function |
dispatchRealtimeFrame | function |
encodeCursor | function |
encodeFrameData | function |
eraseRedactor | function |
err | function |
errorResponderOf | function |
extractContextFromHeaders | function |
formEncodingOf | function |
fromNodeRedis | function |
fromNullable | function |
fullJitterDelay | function |
hasForbiddenAliasCharacter | function |
httpStatusHintOf | function |
installRequestIdentityGuard | function |
isConnectionFault | function |
isConnectionFaultCode | function |
isErr | function |
isLexicallyContained | function |
isNone | function |
isOk | function |
isPromiseLike | function |
isScopeType | function |
isSome | function |
isValidPublishId | function |
isWebSocketUpgradeRequest | function |
isWorkerReadySignal | function |
isWorkerTaskReply | function |
isWorkerTaskRequest | function |
keysetPredicate | function |
mintNextCursor | function |
none | function |
ok | function |
parseCookie | function |
parseEnv | function |
parseFormBody | function |
parseJsonBody | function |
parsePublishOptions | function |
parseTraceparentToContext | function |
publicRequestUrl | function |
publishHeaderNameProblem | function |
publishHeaderValueProblem | function |
publishIdProblem | function |
replaceLocale | function |
replacePrincipal | function |
replaceTenant | function |
requestOriginOf | function |
resolveKeysetSort | function |
resolveProbeTiming | function |
resolveRegistryEntry | function |
resolveResponseStatus | function |
respondWithAuthorizationFailure | function |
respondWithError | function |
responseMetadataOf | function |
scopedPermissionAbility | function |
scopedRoleAbility | function |
scopeFromParam | function |
scopeFromTenant | function |
sealRequestIdentity | function |
securityMetadataOf | function |
serializeCookie | function |
serializeError | function |
setUpgradeIntent | function |
some | function |
sortFingerprint | function |
splitWorkerEnv | function |
tenantBindingMismatch | function |
unwrap | function |
upgradeIntentOf | function |
validatedStateKey | function |
validationMetadataOf | function |
withDeadline | function |
withHttpStatusHint | function |
withResponseMetadata | function |
withSecurityMetadata | function |
withValidationMetadata | function |
DuplicateKeyError | class |
InvalidCursorError | class |
InvalidFieldValueError | class |
MalformedRequestBodyError | class |
OptionalDependencyUnavailableError | class |
PrimaryKeyChangeError | class |
ReconnectSupervisor | class |
UnknownPayloadFieldError | class |
UnsupportedFormEncodingError | class |
CAPABILITIES | const |
CLIENT_IP_STATE_KEY | const |
CREDENTIAL_SOURCE_STATE_KEY | const |
DATA_CLASSIFICATIONS | const |
DEDUPLICATION_ID_HEADER | const |
DEFAULT_SECRET_FIELD_PATTERNS | const |
ERROR_RESPONDER_BRAND | const |
ERROR_RESPONDER_STATE_KEY | const |
HTTP_STATUS_HINT | const |
IDEMPOTENCY_RECORD_KIND | const |
INBOX_RECORD_KIND | const |
MAX_PUBLISH_HEADER_NAME_BYTES | const |
MAX_PUBLISH_HEADER_VALUE_BYTES | const |
MAX_PUBLISH_HEADERS | const |
MAX_PUBLISH_ID_BYTES | const |
MAX_SCOPE_ID_LENGTH | const |
ORDERING_KEY_HEADER | const |
OUTBOX_RECORD_KIND | const |
PLUGIN_PRIORITY | const |
REQUEST_ORIGIN_STATE_KEY | const |
RESERVED_HEADER_NAMES | const |
RESERVED_HEADER_PREFIXES | const |
RESPONSE_METADATA | const |
SCOPED_RBAC_POLICY | const |
SECURITY_METADATA | const |
SESSION_STATE_KEY | const |
SESSION_TENANT_BINDING_KEY | const |
SESSION_TENANT_BINDING_STATE_KEY | const |
TELEMETRY_CONTEXT_OPAQUE | const |
TRACEPARENT_HEADER | const |
TRACESTATE_HEADER | const |
UPGRADE_INTENT | const |
VALIDATION_METADATA | const |
AddJobOptions | interface |
AuditEntry | interface |
AuthorizationDecisionObservation | interface |
AuthorizationDecisionStep | interface |
AuthorizationDiagnosticsBatch | interface |
BehaviorLike | interface |
BulkheadPolicy | interface |
CacheDiagnosticsRecord | interface |
CacheDiagnosticsResponse | interface |
CacheDiagnosticsSnapshot | interface |
CachedProbeOptions | interface |
CircuitBreakerPolicy | interface |
ClassProvider | interface |
ConfigDiagnosticsSnapshot | interface |
ConfigProvenanceEntry | interface |
ConnectionErrorReporter | interface |
ConnectionErrorReporterOptions | interface |
CookieAttributes | interface |
CqrsCommand | interface |
CqrsQuery | interface |
CqrsRequest | interface |
CursorPayload | interface |
DeadlineOptions | interface |
DiagnosticsBatch | interface |
DiagnosticsEdge | interface |
DiagnosticsEvent | interface |
DiagnosticsNode | interface |
DiagnosticsSnapshot | interface |
EncodedPayload | interface |
EnvVarSpec | interface |
Err | interface |
ErrorResponderTarget | interface |
ErrorResponseInit | interface |
EventDiagnosticsRecord | interface |
EventDiagnosticsResponse | interface |
EventDiagnosticsSnapshot | interface |
FactoryProvider | interface |
FieldRedaction | interface |
FlagContext | interface |
FormBody | interface |
FormFile | interface |
GraphqlConnectionInfo | interface |
GraphqlExecutionOutcome | interface |
GraphqlExecutionResult | interface |
GraphqlFormattedError | interface |
GraphqlOperationContext | interface |
GraphqlRequestParams | interface |
GrpcServiceDefinition | interface |
HandlerResult | interface |
HealthCheckResult | interface |
HealthDiagnosticsObservation | interface |
HealthDiagnosticsSnapshot | interface |
HealthIndicatorOptions | interface |
HealthReport | interface |
HttpStatusHint | interface |
IAdapterTransaction | interface |
IApplication | interface |
IAuditLogger | interface |
IAuthorizationDiagnosticsSource | interface |
IAuthorizationPolicyService | interface |
IAuthorizationService | interface |
IAuthService | interface |
IAuthSessionService | interface |
IAuthStrategy | interface |
ICacheDiagnosticsSource | interface |
ICacheStore | interface |
ICircuitBreaker | interface |
ICliApi | interface |
ICommandBus | interface |
ICommandHandler | interface |
IConfig | interface |
IConfigDiagnosticsSource | interface |
IContainer | interface |
ICounter | interface |
ICqrsFacade | interface |
IDatabaseAdapter | interface |
IDataSource | interface |
IDecoratorApi | interface |
IdempotencyClaimRequest | interface |
IdempotentIngressCommonOptions | interface |
IdempotentRouteOptions | interface |
IdempotentWithinOptions | interface |
IdempotentWithinResult | interface |
IDiagnosticsSource | interface |
IDnsResolver | interface |
IDomainEvent | interface |
IEnvironmentApi | interface |
IErrorResponder | interface |
IEventBus | interface |
IEventDiagnosticsSource | interface |
IFeatureFlags | interface |
IFileSystem | interface |
IGauge | interface |
IGrantSource | interface |
IGraphqlService | interface |
IGrpcService | interface |
IHealthApi | interface |
IHealthDiagnosticsSource | interface |
IHealthIndicator | interface |
IHealthService | interface |
IHistogram | interface |
IHttpAdapter | interface |
IIdempotencyService | interface |
IIdempotencyStore | interface |
IInboxStore | interface |
IIngressBehavior | interface |
IJob | interface |
IJwtService | interface |
ILifecycleApi | interface |
ILocalDiagnosticsListener | interface |
ILocalDiagnosticsListenerFactory | interface |
ILocalizer | interface |
ILogger | interface |
IMailer | interface |
IMessageBroker | interface |
IMetadataStore | interface |
IMetric | interface |
IMetricsApi | interface |
IMetricsService | interface |
IMiddleware | interface |
IMiddlewareApi | interface |
IMultiTenancyService | interface |
InboxFailureUpdate | interface |
InboxIds | interface |
InboxRecord | interface |
InboxStoreStats | interface |
IngressContext | interface |
InjectionToken | interface |
INotifier | interface |
IOpenApiApi | interface |
IOrmAdapter | interface |
IOutboundHttpDiagnosticsSource | interface |
IOutboxStore | interface |
IOutboxWriteScope | interface |
IPipelineBehavior | interface |
IPlugin | interface |
IPluginContext | interface |
IPrincipal | interface |
IQueryBus | interface |
IQueryHandler | interface |
IQueue | interface |
IQueueDiagnosticsSource | interface |
IRealtimeBackplane | interface |
IRealtimeDiagnosticsSource | interface |
IRealtimeHandlerErrorReporter | interface |
IRealtimeObservationCollector | interface |
IRedactionService | interface |
IRequest | interface |
IRequestContext | interface |
IResilienceService | interface |
IResponse | interface |
IRouterApi | interface |
IRuntimeServices | interface |
IScheduler | interface |
ISchedulerDiagnosticsSource | interface |
IScopedRoleSource | interface |
ISecretManager | interface |
IServiceDiscovery | interface |
IServiceRegistry | interface |
ISession | interface |
ISessionService | interface |
ISessionStore | interface |
ISpan | interface |
ISseConnection | interface |
ISseService | interface |
ISsrService | interface |
IStorage | interface |
IStorageDiagnosticsSource | interface |
ISubscription | interface |
ISummary | interface |
ITelemetryService | interface |
ITenant | interface |
ITenantDataStore | interface |
ITenantRepository | interface |
ITenantResolver | interface |
ITraceDiagnosticsSource | interface |
ITransaction | interface |
ITransactionalIdempotencyStore | interface |
ITransactionIsolationSupport | interface |
IValidationService | interface |
IViewEngine | interface |
IWebSocketConnection | interface |
IWebSocketService | interface |
IWebSocketTransport | interface |
IWorkerHandle | interface |
IWorkerHost | interface |
IWorkerPool | interface |
JwtSignOptions | interface |
LocalDiagnosticsListenerOptions | interface |
MailMessage | interface |
MaskOptions | interface |
MessageMetadata | interface |
MetricConfig | interface |
MetricOptions | interface |
MiddlewareOptions | interface |
None | interface |
NormalizedQuery | interface |
NotificationMessage | interface |
Ok | interface |
OutboundHttpDiagnosticsRecord | interface |
OutboundHttpDiagnosticsResponse | interface |
OutboundHttpDiagnosticsSnapshot | interface |
OutboxKey | interface |
OutboxRecord | interface |
OutboxStoreStats | interface |
PageResult | interface |
ParsedPublishOptions | interface |
PendingSignIn | interface |
PickOptions | interface |
PluralForms | interface |
PolicyAbilityInfo | interface |
PolicyDefinition | interface |
ProbeTiming | interface |
ProcessOptions | interface |
ProviderOptions | interface |
PublishOptions | interface |
PutObjectOptions | interface |
QueueAttemptObservation | interface |
QueueDepthObservation | interface |
QueueDiagnosticsBatch | interface |
QueueDiagnosticsSourceBatch | interface |
QueueDiagnosticsSourceStatus | interface |
QueueSourceAttemptObservation | interface |
QueueSourceDepthObservation | interface |
RbacConfig | interface |
RealtimeDiagnosticsGauges | interface |
RealtimeDiagnosticsOptions | interface |
RealtimeDiagnosticsRecord | interface |
RealtimeDiagnosticsResponse | interface |
RealtimeDiagnosticsSnapshot | interface |
RealtimeFrame | interface |
RealtimeGaugeReading | interface |
RealtimeHandlerErrorReporterOptions | interface |
RealtimeObservationCollectorInit | interface |
ReconnectSupervisorOptions | interface |
RecurringOptions | interface |
RedactionContext | interface |
RedactionPolicy | interface |
RegisterOptions | interface |
RequestOptions | interface |
RetryOptions | interface |
RetryPolicy | interface |
RoleDefinition | interface |
RoomBroadcastOptions | interface |
RouteDefinition | interface |
RouteInfo | interface |
RouteResponseMetadata | interface |
RouteSchema | interface |
RouteSecurityMetadata | interface |
RouteValidationMetadata | interface |
ScheduledJob | interface |
ScheduleOptions | interface |
SchedulerDiagnosticsRecord | interface |
SchedulerDiagnosticsResponse | interface |
SchedulerDiagnosticsSnapshot | interface |
ScopedGrant | interface |
ScopedRbacTarget | interface |
ScopedRoleDefinition | interface |
ScopeRef | interface |
SerializedError | interface |
ServiceInstance | interface |
SignedUrlOptions | interface |
SignInOptions | interface |
Some | interface |
SpanContext | interface |
SpanOptions | interface |
SplitWorkerEnv | interface |
SrvRecord | interface |
SseChannel | interface |
SseMessage | interface |
StartOptions | interface |
StatResult | interface |
StorageDiagnosticsRecord | interface |
StorageDiagnosticsResponse | interface |
StorageDiagnosticsSnapshot | interface |
SubscribeOptions | interface |
TaskPoolStats | interface |
TelemetryContext | interface |
TraceDiagnosticsBatch | interface |
TraceLinkRelationship | interface |
TraceObservation | interface |
TraceparentSource | interface |
TraceSamplerDescription | interface |
TransactionalIdempotencyClaim | interface |
TransactionalIdempotencyRecord | interface |
TransactionOptions | interface |
ValidationIssue | interface |
ValidationMiddlewareOptions | interface |
ValueProvider | interface |
WebSocketCloseEvent | interface |
WebSocketConnectionContext | interface |
WebSocketEventSink | interface |
WebSocketHandlers | interface |
WebSocketRoom | interface |
WebSocketRouteOptions | interface |
WebSocketUpgradeIntent | interface |
WorkerErrorShape | interface |
WorkerReadySignal | interface |
WorkerRunOptions | interface |
WorkerTaskReply | interface |
WorkerTaskRequest | interface |
WrapOptions | interface |
AnonymousPolicyCheck | type |
AuthMethod | type |
AuthorizationCoverage | type |
AuthorizationDecisionOperation | type |
AuthorizationDecisionReason | type |
AuthorizationFailure | type |
AuthorizationSourceState | type |
BackoffStrategy | type |
CacheDiagnosticsOperation | type |
CapabilityToken | type |
ChannelSendResult | type |
CircuitState | type |
CliCommandHandler | type |
Component | type |
ConfigProvenanceOrigin | type |
ConfigSchemaEffect | type |
ConnectionErrorLogger | type |
Constructor | type |
ContainerToken | type |
CursorValue | type |
DataClassification | type |
DecoratorHandler | type |
DiagnosticsEdgeKind | type |
DiagnosticsEventKind | type |
DiagnosticsEventOutcome | type |
DiagnosticsEventStage | type |
DiagnosticsFailureCode | type |
DiagnosticsInspectorState | type |
DiagnosticsNodeKind | type |
DiagnosticsSnapshotState | type |
EntityKey | type |
EventDiagnosticsCoverage | type |
EventDiagnosticsState | type |
EventHandler | type |
EventObservationOperation | type |
FilterComparison | type |
FilterExpression | type |
FilterOperator | type |
FormEncoding | type |
FormValue | type |
GrantQuery | type |
GraphqlSubscriptionOutcome | type |
GrpcServingStatus | type |
HardenedCall | type |
HealthIndicatorFn | type |
HealthObservationState | type |
HealthStatus | type |
HttpMethod | type |
IdempotencyClaimResult | type |
IdempotencyFingerprintSource | type |
IdempotencyKeySource | type |
IdempotencySettleResult | type |
IdempotentIngressOptions | type |
InboxReleaseOutcome | type |
InboxStatus | type |
IngressIdempotencyFingerprintSource | type |
IngressIdempotencyKeySource | type |
IngressKind | type |
InjectEntry | type |
ITenantIsolationStrategy | type |
JobProcessor | type |
JsonValue | type |
LifecyclePhase | type |
LoadBalanceStrategy | type |
LocalizationMessage | type |
LogLevel | type |
LogMetadata | type |
MessageCatalogue | type |
MessageHandler | type |
MetricType | type |
MiddlewareFunction | type |
NextFunction | type |
NodeRedisAdapter | type |
NodeRedisAdapterOptions | type |
NodeRedisClientFactory | type |
NodeRedisClientLike | type |
Option | type |
OrderDirection | type |
OutboundHttpStatusClass | type |
OutboxStatus | type |
OutboxTransition | type |
PathPattern | type |
PluginPriority | type |
PolicyAbility | type |
PolicyCheck | type |
PolicyRef | type |
PolicyTarget | type |
Provider | type |
QueueDepthCoverage | type |
QueueDepthCycleCoverage | type |
QueueDepthScope | type |
QueueDiagnosticsFailure | type |
QueueProcessorOutcome | type |
QueueSettlementState | type |
QueueSourceFailure | type |
QueueSourceState | type |
RealtimeFrameHandler | type |
RealtimeFrameKind | type |
RealtimeGaugeState | type |
RealtimeObservationOperation | type |
RealtimeSourceKind | type |
ReconnectMode | type |
Redactor | type |
RegistryFactory | type |
RequestHandler | type |
RequestOrigin | type |
ResilientCall | type |
ResponseSnapshot | type |
ResponseSnapshotInit | type |
Result | type |
RouteHandler | type |
RpcFetchHandler | type |
RuntimePlatform | type |
RuntimeSignal | type |
SchedulerBackoff | type |
SchedulerDiagnosticsOperation | type |
SchedulerJobHandler | type |
ScopeSource | type |
SecurityRequirement | type |
ServerHandle | type |
ServiceFactory | type |
ServiceOutcome | type |
ServiceScope | type |
SessionData | type |
SessionView | type |
SignInOutcome | type |
SpanAttributeValue | type |
SpanKind | type |
SpanStatus | type |
StandardCapability | type |
StorageDiagnosticsOperation | type |
TimerHandle | type |
TraceCoverage | type |
TraceInstrumentationKind | type |
TraceOutcome | type |
TraceParentVisibility | type |
TraceSourceState | type |
TransactionIsolationLevel | type |
Unsubscribe | type |
ValidationTarget | type |
WebSocketGuardDecision | type |
WebSocketReadyState | type |
WebSocketUpgradeDecision | type |
WebSocketUpgradeGuard | type |
WebSocketUpgradeRouter | type |
WritePrecondition | type |
WritePreconditionCheck | type |
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)andsubscriber: 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;0disables; otherwise a finite number up to 2 147 483 647, elseRangeError) withError('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 owncommandTimeoutMsoption 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
redisinstrumentation patches node-redis as Node loads it, andTelemetryPluginregisters it when the application starts — after a configuration that importsredishas already loaded it, so those commands record no spans. Pass a factory instead of a client andredisloads on the first connect:fromNodeRedis(async () => (await import('redis')).createClient({ url })). The factory runs once (a failure is retried on the next connect), andon/offlisteners registered before it wait for the client. Asetu new --redis-client node-redisproject does this insrc/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 todestroy()when that exceeds the bound) and is idempotent;statusreports'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 member | Consumer | Behavior |
|---|---|---|
IRuntimeServices.reportError?(record: Readonly<Record<string, unknown>>): void | Kernel unhandled errors and errorHandler 5xx, only without a logger | Last-resort JSON line to stderr (Workers platform log channel); serialization and write failures never throw |
IErrorResponder.statusFor?(error: unknown): number | Request logger | Pure 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:
| Member | Type | Purpose |
|---|---|---|
fields | Readonly<Record<string, DataClassification | FieldRedaction>> | Dot-path patterns and the classification or FieldRedaction each carries |
redactors | Readonly<Record<string, Redactor>> | Redactor selected by the matched classification |
defaultRedactor | Redactor | Fallback 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>]):
| Constant | Value |
|---|---|
DATA_CLASSIFICATIONS.PII | 'pii' |
DATA_CLASSIFICATIONS.PHI | 'phi' |
DATA_CLASSIFICATIONS.PCI | 'pci' |
DATA_CLASSIFICATIONS.SECRET | 'secret' |
Values (runtime exports)
| Export | Kind | Purpose |
|---|---|---|
CAPABILITIES | const | Standard 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) | function | Validates and creates a custom (optionally dot-namespaced) token; throws TypeError on invalid names |
isConnectionFault(error) | function | Recognises connection outages through a bounded, cycle-safe cause walk, including measured ioredis rejection shapes. |
fromNodeRedis(client, options?) | function | M117: wraps a node-redis client in the ioredis-shaped adapter every Redis-backed plugin’s client option accepts |
NodeRedisClientFactory | type | M117: creates the node-redis client on the adapter’s first connect, so redis can load after TelemetryPlugin registers its instrumentation |
NodeRedisClientLike | type | M117: the structural node-redis client fromNodeRedis takes |
NodeRedisAdapterOptions | type | M117: commandTimeoutMs, onError |
NodeRedisAdapter | type | M117: the adapter fromNodeRedis returns |
isConnectionFaultCode(code) | function | Returns true only for a string in the private eight-code node/net connection-fault set. |
encodeFrameData(data) | function | Encodes a WebSocket payload for a realtime backplane; binary becomes base64 |
decodeFrameData(payload) | function | Decodes a backplane payload back to string or Uint8Array |
createCachedProbe(options) | function | Builds 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) | function | Since 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) | function | Since 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 |
DeadlineOptions | type | Since M101a. { timeoutMs, onTimeout, timing? } for withDeadline; timing is the setTimer/clearTimer surface, typically resolveProbeTiming(ctx.runtime) |
resolveProbeTiming(runtime) | function | Resolves 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) | function | Builds 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?) | function | Attaches 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) | function | Parses 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?) | function | Serializes 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) | function | Guard: narrows a worker message to a WorkerReadySignal |
isWorkerTaskRequest(m) | function | Guard: narrows a worker message to a WorkerTaskRequest |
isWorkerTaskReply(m) | function | Guard: narrows a worker message to a WorkerTaskReply |
PLUGIN_PRIORITY | const | Well-known plugin priority bands (HIGHEST…LOWEST) |
createToken<T>(name) | function | Creates 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) | function | Result constructors |
isOk(r) / isErr(r) | function | Result type guards |
unwrap(r) | function | Returns the Ok value or throws the Err error |
some(value) / none() | function | Option constructors (none() returns a frozen singleton) |
isSome(o) / isNone(o) | function | Option type guards |
fromNullable(v) | function | Converts T | null | undefined to Option<T> |
serializeError(value) | function | Serializes 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) | function | Renders 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) | function | Extracts 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) | function | Parses 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) |
DuplicateKeyError | class | A 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 |
assertPrimaryKeyUnchanged | function | Since 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 |
PrimaryKeyChangeError | class | Since 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 |
InvalidCursorError | class | Since 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 |
OptionalDependencyUnavailableError | class | Thrown 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 |
MalformedRequestBodyError | class | The 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) | function | Parses 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) | function | Classifies 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 / FormEncoding | types | The 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) |
UnsupportedFormEncodingError | class | The 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) | function | Returns 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) | function | Writes 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) | function | Writes 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) | function | Since 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_KEY | const | exceptions:error-responder, the ctx.state key under which errorHandler publishes its IErrorResponder |
brandErrorResponder(fn, r) | function | Attaches 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) | function | Reads 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_BRAND | const | A 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) | function | Brands 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) | function | Reads 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_HINT | const | Symbol.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) | function | Returns `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_KEY | const | http-security-plugin:request-origin; the trusted { protocol, host } IP security publishes for the common accessor (M113n) |
RequestOrigin | type | { readonly protocol: 'http' | 'https'; readonly host: string; readonly origin: string } returned by requestOriginOf (M113n) |
requestOriginOf(ctx) | function | Returns the published public origin or one derived from ctx.request.url; makes no proxy trust decision (M113n) |
publicRequestUrl(ctx) | function | Substitutes the published origin while keeping path and query; returns the original URL byte for byte without publication (M113n) |
CLIENT_IP_STATE_KEY | const | http-security-plugin:client-ip, the cross-package key ipSecurityMiddleware writes and rateLimitMiddleware reads |
SESSION_STATE_KEY | const | session-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_KEY | const 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_KEY | const | __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_KEY | const | session-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) | function | The 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) | function | Builds a path-exclusion predicate from readonly PathPattern[]; strings match exactly and regular expressions are tested against the path. |
installRequestIdentityGuard(prototype) | function | Installs 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) | function | Installs the one-implicit-write request identity guard for user, tenant and locale |
replacePrincipal(request, principal) | function | Deliberately replaces request.user after it has been guarded |
replaceTenant(request, tenant) | function | Deliberately replaces request.tenant after it has been guarded |
replaceLocale(request, locale) | function | Deliberately 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) | function | Duck-typed thenable test (M87) — see the note below the Types table |
Types
| Group | Exports |
|---|---|
| Tokens | CapabilityToken, StandardCapability |
| Shared types | HttpMethod, 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 |
| Utilities | Result<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 contract | IPlugin, IPluginContext, IApplication, StartOptions |
| Plugin context APIs | IMiddlewareApi, MiddlewareOptions, IRouterApi, IEnvironmentApi, EnvVarSpec, IHealthApi, IMetricsApi, IOpenApiApi, IDecoratorApi, DecoratorHandler, ICliApi, CliCommandHandler, ILifecycleApi, IMetadataStore |
| Service registry | IServiceRegistry, RegisterOptions, ServiceFactory<T>, RegistryFactory<T>, resolveRegistryEntry |
| HTTP | IRequest, 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 |
| Runtime | IRuntimeServices, 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 |
| Logging | ILogger, LogMetadata |
| Config | IConfig |
| Validation | IValidationService, ValidationTarget, ValidationIssue |
| Health | IHealthIndicator, HealthIndicatorFn, HealthIndicatorOptions, HealthCheckResult, IHealthService, HealthReport, HealthStatus, CachedProbeOptions, ProbeTiming, ConnectionErrorReporter, ConnectionErrorReporterOptions, ConnectionErrorLogger |
| Metrics | IMetric, MetricConfig, IMetricsService, ICounter, IGauge, IHistogram, ISummary, MetricOptions |
| Auth | IPrincipal, IJwtService, JwtSignOptions — IPrincipal is { id: string; roles?: readonly string[]; permissions?: readonly string[]; claims?: Readonly<Record<string, unknown>> } |
| Database | IOrmAdapter, 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) |
| Cache | ICacheStore |
| Events | IEventBus, IDomainEvent<T>, EventHandler<T>, Unsubscribe |
| Messaging | IMessageBroker, 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 |
| Ingress | IngressKind, IngressContext<TPayload>, BehaviorLike<TWork, TResult>, IIngressBehavior, composeBehaviorChain |
| Queue | IQueue, IJob<T>, JobProcessor<T>, AddJobOptions, ProcessOptions, RecurringOptions |
| Scheduler | IScheduler, ScheduledJob<T>, SchedulerJobHandler<T>, ScheduleOptions<T>, RetryOptions, SchedulerBackoff |
| Secrets | ISecretManager |
| Audit | IAuditLogger, AuditEntry |
| Resilience | ICircuitBreaker, CircuitState, IResilienceService, WrapOptions, CircuitBreakerPolicy, RetryPolicy, BulkheadPolicy, BackoffStrategy, ResilientCall, HardenedCall |
| Storage | IStorage, SignedUrlOptions |
IMailer, MailMessage | |
| Notifications | INotifier (with optional sendSettled?), NotificationMessage, ChannelSendResult |
| Errors | IErrorResponder, ErrorResponseInit, ErrorResponderTarget, SerializedError, AuthorizationFailure — the request-scoped error responder seam, standard authorization refusals, and the pure error serializer (M70f) |
| Feature flags | IFeatureFlags, FlagContext |
| Multi-tenancy | IMultiTenancyService, ITenantRepository, ITenantResolver, ITenant |
| SSR | ISsrService |
| SSE | ISseService, ISseConnection, SseChannel, SseMessage |
| Realtime backplane | IRealtimeBackplane, RealtimeFrame, RealtimeFrameHandler, RealtimeFrameKind, EncodedPayload |
| WebSocket | IWebSocketService, IWebSocketConnection, IWebSocketTransport, WebSocketRoom, RoomBroadcastOptions, WebSocketHandlers, WebSocketRouteOptions, WebSocketConnectionContext, WebSocketCloseEvent, WebSocketReadyState, WebSocketEventSink, WebSocketUpgradeDecision, WebSocketUpgradeRouter, WebSocketUpgradeGuard, WebSocketGuardDecision |
| Worker pool | IWorkerPool, WorkerRunOptions, TaskPoolStats, WorkerReadySignal, WorkerTaskRequest, WorkerTaskReply, WorkerErrorShape |
| Session | ISessionService, ISession, ISessionStore, SessionData, SessionView, CookieAttributes |
| Auth session | IAuthSessionService, SignInOptions, SignInOutcome, AuthMethod |
| Authorization policies | IAuthorizationPolicyService, 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 RBAC | SCOPED_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 discovery | IServiceDiscovery, ServiceInstance, PickOptions, LoadBalanceStrategy, ServiceOutcome |
| DNS | IDnsResolver, SrvRecord |
| gRPC | IGrpcService, GrpcServiceDefinition, GrpcServingStatus, RpcFetchHandler |
| Cloudflare | splitWorkerEnv, SplitWorkerEnv |
| View rendering | IViewEngine, Component — the view port (render(component, props): string | Promise<string>) and the structural component type it renders, named by @Render and renderView (M92) |
| Localization | ILocalizer, 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) |
| Outbox | IOutboxStore, 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) |
| Inbox | IInboxStore, 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) |
| Idempotency | IIdempotencyStore, 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
IContainermethod takes aContainerToken<T>: a string, anInjectionToken<T>fromcreateToken<T>(name), or a class (Constructor<T>). Object tokens are matched by identity.resolveon anInjectionToken<T>or a class returnsTwithout a type argument;resolve<T>(token: string)is unchanged. - Visible dependencies.
ClassProvider.injectand the newFactoryProvider.injectarereadonly InjectEntry[].FactoryProvider.useFactoryis(...deps: never[]) => Tand is called with the resolved dependencies ininjectorder; an absent optional dependency arrives asundefined. A factory withoutinjectis opaque to boot validation. ProviderOptions<T>(generic, defaultunknown) addsdispose?: (instance: T) => void | Promise<void>, called bydispose()for each instance the container created.forRequest(ctx: IRequestContext): IContainerreturns the request’s scope, created on first call and disposed throughctx.deferwhen the request finishes. Withoutctx.deferit 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) oneAggregateError. 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.scopedmeans one instance per scope; a request reaches its scope throughforRequest. Resolving ascopedtoken on the root container is refused, and so is atransientthere whose graph reaches ascopedprovider.- Breaking for implementors:
forRequestanddisposeare 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.runtimeis 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 ofstop(), 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 inonShutdownleaves 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 fromstop(), but only after the drain, the socket close, and theonShutdown/onClosephases have run: a failing hook must not be able to prevent the application from stopping. With no hook registered the phase is skipped andstop()behaves exactly as before. -
Schema positions (
RouteSchema,IValidationService,IOpenApiApi) are typedunknownsocommoncarries no validator dependency; the validation plugin narrows them (Zod by default). -
RouteSchema.operationId?: stringandRouteSchema.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;OperationIdErrorfrom@setu-ts/openapi-pluginrefuses invalid declarations at bootstrap and document generation. -
RouteSchema.securityis 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.
withSecurityMetadatabrands aMiddlewareFunctionwith aRouteSecurityMetadata({ authenticated: boolean }), andsecurityMetadataOfreads it back — the channel by which@setu-ts/openapi-pluginderives an operation’s requirement from the guards@setu-ts/auth-pluginproduced, without either package importing the other.SECURITY_METADATAis created withSymbol.for, so two copies ofcommonin 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, sorequireRole('admin')brands{ authenticated: true }and nothing more. -
withHttpStatusHintbrands anErrorrather than a middleware function, and the reason it exists is thatrespondWithErrorcannot reach every site: a data source throwing from deep inside an adapter holds noIRequestContext, so its error arrives aterrorHandleras a plainError, is normalized to a500, and is masked. That is right for a driver fault and wrong for a refusal the caller caused.HttpStatusHintis anErrorResponseInitwhosedetailis required, anderrorHandlerbuilds the response from the hint’s ownstatus/title/detail— never from the error’smessage, which is the operator-facing diagnostic and stays log-only. Both entry points construct the response through one implementation, so a hinted throw and arespondWithErrorcall carrying the same values answer byte-identically under every configured format. A hinted error is exempt frommaskInternalErrors, 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 thrownHttpErroris ignored — that error already states its own status.statusmust be an integer in400–599, and a hint outside that range is treated as ABSENT so the error takes the ordinary masked-500path. Two reasons, and the second is why the floor is400rather than200: the webResponseconstructor throwsRangeErroroutside[200, 599], so an unserveable status would make the error handler itself the fault — a throw escaping the onecatchthat 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 theErrorResponseInitit extends, whosestatusis a plainnumberthat an application authors (FlagGuardOptions.statusCodeis the shipped example) and so may legitimately be any serveable status, not only an error one.ErrorResponseInit.statusis guarded separately, byresolveResponseStatus, against the serveability bound[200, 599]rather than this error-ness one. -
withValidationMetadatabrands aMiddlewareFunctionwith aRouteValidationMetadata({ target: ValidationTarget; schema: unknown }), andvalidationMetadataOfreads it back — the same mechanism, for the same reason, applied to request shape rather than authentication. Every helper@setu-ts/validation-pluginships carries it, as doesIValidationService.middleware(schema, target), and@setu-ts/openapi-pluginreads it to fill an operation’srequestBodyandparameters.VALIDATION_METADATAis created withSymbol.forfor the same cross-copy reason, the brand is symbol-keyed and non-enumerable, and theschematravels 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. -
withResponseMetadatabrands aRouteHandlerwith aRouteResponseMetadata({ status: number }), andresponseMetadataOfreads 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@HttpCodeor@Redirectproduces carries it, and@setu-ts/openapi-pluginreads it (deriveResponseStatus, on by default) to document the operation under that status instead of the assumed200.RESPONSE_METADATAis created withSymbol.forfor 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.
statusmust be an integer in200–599, the range the webResponseconstructor 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 withMalformedRequestBodyError— branded400— when the body is not valid JSON (X37-1). The read is lazy (M87) and happens inside the handler, so the rejection reacheserrorHandlerthrough the ordinary throw path and is answered400in the configured format; a handler that CATCHES its ownjson()rejection keeps full control of what is served. The same rejection comes from all three producers, so an in-processinject()observes exactly what a served request observes. Added in Milestone 90f. -
IRequest.formData?()resolves aFormBodyfor both form encodings through the sharedparseFormBody, memoized exactly likejson()— the in-flight promise is cached, a rejection with it. It throwsUnsupportedFormEncodingError, branded415, 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-processinject()observes exactly what a served request observes; a request WITHOUT the optional accessor (an out-of-repo implementor) falls back to the sameparseFormBody(await request.bytes(), contentType)— the fallback is the same function, not a second implementation. Optional on thesignal?/fs?/raw?precedent. Added in Milestone 94b. -
IRequest.raw?: RequestandIRequestContext.raw?: Requestcarry the undisturbed webRequestthe 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 M42signal?/ M44fs?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 the404— it never throws.inject()populates it, so an injected request can exercise both paths. Added in Milestone 70a. -
setUpgradeIntent(request, intent)brands anIRequestwith aWebSocketUpgradeIntent({ sink, protocol? }) andupgradeIntentOf(request)reads it back, under theUPGRADE_INTENTsymbol. This is how the kernel tells the HTTP adapter “the pipeline ran, nothing short-circuited, perform the handshake”. TheIRequestis the channel rather thanIRequestContext.statebecause the adapter holds the former and never sees the context — the kernel builds and discards it internally, so nothing written toctx.statecould reach the adapter.UPGRADE_INTENTusesSymbol.for, so two copies ofcommonin one process agree. Added in Milestone 70a. -
isWebSocketUpgradeRequest(headers)is the shared RFC 6455 §4.2.1 predicate:Upgrademust equalwebsocketcase-insensitively andConnectionmust contain theupgradetoken, matched after splitting on commas — a substring test would also claimConnection: no-upgrade. It lives incommonbecause 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. -
HandlerResultis an opaque brand only the kernel constructs; handlers obtain it fromIResponseterminal methods (json,text,send,redirect,stream). -
IResponsehas two header setters with distinct semantics:header(name, value)replaces any existing value forname(Headers.set), whileappendHeader(name, value)adds a value without removing existing ones (Headers.append).appendHeaderis the correct way to emit multiple headers of the same name — most notably severalSet-Cookieheaders (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 asheader(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’sreply.headers(obj), Express’sres.set(obj)and Koa’sctx.set(obj)are the precedent for the batch call; the template cache is Setu’s. Breaking forIResponseimplementors, including test fakes typed asIResponse: add the member as a loop overheader(), or use@setu-ts/testing’screateTestContext. -
IResponse.html(body: string): HandlerResult— sends an HTML response withcontent-type: text/html; charset=utf-8. The charset is not optional: a baretext/htmllets a browser sniff the encoding. Added because every CSRF-protected form example was setting the header by hand. Breaking for out-of-repoIResponseimplementors (both in-repo implementors — the kernel’sResponseBuilderandtesting’sMockResponse— implement it). -
IResponse.stream(body: ReadableStream<Uint8Array>): HandlerResult— sends a streaming response body. The runtime maps this tonew 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 onstreaming: whenfalse,bodyisUint8Array | string | null(buffered); whentrue,bodyisReadableStream<Uint8Array>(live stream). This allows middleware to safely inspect the response without draining a live stream — middleware that reads the body must checkstreamingfirst. 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 webResponse, and a stream body iscancel()led so its source is released. TheResponseconstructor rejects a body at these statuses, and the mapping runs in the HTTP adapter after the pipeline, so passing one through raised aTypeErrorno middleware orerrorHandlercould 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 readCLIENT_IP_STATE_KEYfor 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 nativeRequest.signal; optional because injected / test requests may not carry one. Added in Milestone 42. -
IRequest.user,IRequest.tenantandIRequest.localeeach allow one implicit assignment per request; a later assignment throws.replacePrincipal,replaceTenantandreplaceLocaleare 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, aninject()request and acreateTestContextrequest — carries this guard on its class prototype (installRequestIdentityGuard, 0.11.0), so on those the three fields are inherited accessors:Object.keys(request)andJSON.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 thetenantprecedent. Three things to keep in mind: a reader running BELOW priority 45 seesundefined(the priority table orders it, exactly as fortenantat 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(). Itsregister,registerFactory, andunregistermethods then throw; request-scoped child registries remain mutable. Startup-timeoverride: truemutations log atinfo, and successful unregisters log atwarnthrough the logger capability. -
IRequestContext.signal: AbortSignal— required abort signal (always present). Populated bycreateRequestContextfrom the nativeRequest.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 theISseService. The service provides real-time, one-way server-to-client messaging over an SSE stream built onIResponse.stream(). Added in Milestone 43. -
CAPABILITIES.SSR('ssr') — the capability token under which the React Router plugin registers theISsrService. The service provides server-side rendering by delegating to React Router’s request handler and writing back the result viaIResponse. Added in Milestone 44. -
CAPABILITIES.REALTIME_BACKPLANE('realtime-backplane') — the capability token under whichRealtimeBackplanePluginregisters anIRealtimeBackplane. 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 theIWebSocketService. The service provides full-duplex, bidirectional real-time messaging: exact-path routes with lifecycle handlers, named broadcast rooms, and an application-level heartbeat. Distinct fromSSE, which is one-way. Added in Milestone 46. -
CAPABILITIES.SESSION('session') — the capability token under whichSessionPluginregisters theISessionService. Provides cookie-backed sessions for server-rendered applications: an encrypted self-contained cookie by default, or an opaque id over anISessionStore. Distinct fromAUTHENTICATION, 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 whichGrpcPluginregisters theIGrpcService. The service provides gRPC/Connect co-serving on the same port as ordinary Hono routes through the kernel terminal handler. The deprecated optionalIHttpAdapter.setRpcHandler?seam is no longer consulted. Added in Milestone 49. -
CAPABILITIES.CLOUDFLARE('cloudflare') — the capability token under whichCloudflarePluginregistersICloudflareBindings: typed access to a Worker’s KV, R2, D1, Queues, service and Durable Object bindings, its string variables, andwaitUntil. Added in Milestone 52. -
CAPABILITIES.GRAPHQL('graphql') — the capability token under whichGraphqlPluginregisters theIGraphqlService. 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_ADAPTERand 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 viaservices.getAll(); the corresponding first-party plugins aggregate and expose them.HTTP_ADAPTERis single-provider — the runtime plugin registers itsIHttpAdapterthere. -
METADATA_STORE('metadata-store') is the single-provider capability backingIPluginContext.metadata; the DecoratorPlugin registers itsIMetadataStorethere. It is distinct fromOPENAPIso an OpenAPI plugin registering underOPENAPIdoes not populatectx.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.