@open-matrix/hosting (0.1.7)
Installation
@open-matrix:registry=npm install @open-matrix/hosting@0.1.7"@open-matrix/hosting": "0.1.7"About this package
@open-matrix/hosting
Browser-neutral TypeScript implementation of the .NET Generic Host programming model. It is an application-lifetime kernel, not an HTTP server and not a Matrix actor runtime.
What it owns
- composition through
Host.CreateApplicationBuilder(); - immutable root dependency injection;
- configuration, options, logging and metrics builders;
- startup validation;
- hosted-service activation;
- application lifetime signals;
- background-service failure policy;
- ordered startup and reverse-ordered shutdown;
- synchronous and asynchronous resource disposal.
What it deliberately does not own
- Node process signals;
- browser page or custom-element events;
- transport connection establishment;
- NATS reconnection or drain;
- package loading;
- Matrix actors, mailboxes, readiness or supervision.
Those are leaf services or runtime-domain responsibilities attached through
IHostLifetime and IHostedService.
const builder = Host.CreateApplicationBuilder({
ApplicationName: 'worker',
ValidateScopes: true,
ValidateOnBuild: true,
});
builder.Configuration.AddInMemoryCollection({
Worker: { IntervalMs: 1000 },
});
builder.Services.AddHostedService(Worker);
await using host = builder.Build();
await host.RunAsync();
Build() resolves the host infrastructure and freezes registrations, but it
does not resolve or start hosted services. Operational work begins inside
StartAsync().
AddHostedService() is idempotent per implementation type. Repeated
registrations reuse one stable forwarding descriptor, so one singleton is
constructed, started, and stopped exactly once. Duplicate detection remains a
property of IServiceCollection; a repeated registration after Build() still
fails through the collection's read-only guard rather than being silently
accepted by an out-of-band registration cache.
Startup rollback is a retryable resource transaction. Once
IHostLifetime.WaitForStartAsync() has completed, a validator or pre-service
lifecycle failure must attempt IHostLifetime.StopAsync() even when no hosted
service reached StartAsync(). A transient lifetime-stop failure remains
observable in the startup aggregate, retains the lifetime as the incomplete
cleanup phase, leaves the host non-operational, and is retried by a later
StopAsync() or DisposeAsync() before provider ownership can be released.
Options validation policy
ValidateOnStart() is scoped to an options-instance name rather than to the
options type as a whole. The default instance and every explicitly named
instance each receive an independent startup validator. Repeating
ValidateOnStart() for the same name is idempotent, while requesting a second
name never suppresses its validator. Startup validation resolves the named
value through IOptionsMonitor<T>.Get(name) and reports
OptionsValidationError.OptionsName for the exact failing instance.
Logging-scope policy
Portable scopes use ILogger.WithScope(). Each call snapshots and freezes the
supplied state and returns a new logger whose scope chain is immutable. Parent,
child, and sibling loggers therefore share no mutable stack: caller mutation
after scope creation, overlapping asynchronous continuations, or derivation of
a sibling scope cannot rewrite another logger's correlation context. Scope
order remains outermost to innermost in each emitted LogRecord.
The .NET-shaped BeginScope() form is deliberately ambient and requires an
execution-environment-specific IAmbientLogScopeProvider. The browser-neutral
package fails loudly when no provider is installed rather than maintaining a
factory-wide mutable stack. Node hosts may install the AsyncLocalStorage
adapter from @open-matrix/hosting-node; browser hosts require an explicit
execution-context bridge. Ambient scopes are prepended to immutable explicit
scopes when a record is emitted.
Dependency-injection policy
The included provider supports singleton, scoped and transient lifetimes,
multiple and keyed registrations, constructor activation through explicit
static Inject metadata, validation and LIFO disposal. It intentionally does
not support asynchronous factories. Connectors and other asynchronous resources
must be inert services initialized by hosted services.
Provider disposal is a retryable, dependency-preserving resource transaction.
Open child scopes are disposed in reverse creation order before root/singleton
resources. Within each scope, successfully completed LIFO phases are retained as
complete; a failed phase keeps its exact resource and cursor for a later
Dispose() or DisposeAsync() attempt. Resolution and scope creation are
refused after disposal begins, so a partially torn-down object graph cannot
return to operational use while cleanup remains incomplete.
The default provider is replaceable through IServiceProviderFactory.
TypeScript/.NET adaptation points
CancellationTokenis implemented overAbortController.CancellationTokenSource.CancelAfter()owns at most one replaceable timer. A later finite deadline replaces the earlier timer,-1andInfinitydisable it, stale queued callbacks cannot cancel the source, and a fired deadline releases its timer handle before publishing cancellation.- service interfaces use runtime
ServiceKey<T>values because TypeScript interfaces are erased; AggregateErrorrepresents aggregated lifecycle failures;Symbol.asyncDisposeprovidesawait usingintegration;- PascalCase is confined to this bounded context to preserve the .NET mental model without renaming the rest of Matrix.
Attribution
The lifecycle model and selected algorithms are structurally adapted from the
MIT-licensed .NET runtime. See NOTICE.md.
Dependencies
Development Dependencies
| ID | Version |
|---|---|
| @types/node | ^25.0.10 |
| typescript | ^5.7.0 |