Shared service composition foundation
This package composes the same importable NestJS service modules into either a standalone application or one process with one public HTTP listener. It uses the canonical @appneurox/service-runtime package for bootstrap, authentication, tenant context, correlation, errors and shutdown. It creates no second AsyncLocalStorage, authentication decorators or runtime copies. Build that runtime first on the remote Node24 runner; the workspace root owns dependency installation and the lockfile.
ServiceDefinition contains a safe lowercase service slug and a public label. composeServiceModule(ModuleType, definition, ApplicationClass, options) accepts a unique module class and an application class extending BaseServiceApplication; its dynamic module exports the application class and the stable serviceApplicationToken(slug) symbol. Application classes can use normal Nest DI. Domain modules do not bootstrap applications, register global guards, install process handlers, read process configuration or open sockets. Optional provider/import options support explicit infrastructure injection; they do not provision or verify infrastructure.
@Injectable()
class VendorApplication extends BaseServiceApplication {
constructor() { super({ slug: 'vendor-service', label: 'Vendor' }); }
}
@Module({})
class VendorModule {}
const definition = { slug: 'vendor-service', label: 'Vendor' };
const service = {
definition,
module: composeServiceModule(VendorModule, definition, VendorApplication),
applicationToken: serviceApplicationToken(definition.slug),
};
Standalone hosts import service.module through the canonical runtime bootstrap. createMonolithApplication({ services, config, authVerifier, logger }) initializes without listening; bootstrapMonolith opens the one host listener and owns graceful shutdown. Both accept the canonical runtime options. Each service's controllers are mounted under its slug, for example /vendor-service/v1/module and /vendor-service/v1/execute. One host runtime serves health/discovery, and public /modules lists metadata. Duplicate slugs, shared module classes, unsafe definitions and mismatched application tokens fail before bootstrap.
BaseServiceApplication.metadata() reports BOILERPLATE, businessReady: false and businessImplementation: not_implemented. Its execute boundary accepts exactly { operation, payload }. Operation names use lowercase letters/numbers with optional dot, underscore or hyphen separators, at most 63 characters. Payloads must be plain JSON objects; accessors, symbols, prototype keys, cycles, sparse arrays and non-JSON values are rejected. Input is limited to 64KiB, 16 nesting levels and 4096 JSON nodes. The shared ExecuteOperationDto adds HTTP validation, while the application validates again for direct calls.
authorizeOperation(scope, requireTenant = true) reads only the canonical runtime's verified request context. It rechecks the principal, selected tenant membership and exact scope; request headers alone never grant authority. It does not verify bearer tokens or mint principals: the host's real AuthVerifier must establish that context first. Direct trusted callers must enter through the same verified authority boundary. execute requires ${slug}:${operation} and then returns501 because no business implementation exists. HTTP adapters may additionally require a fixed ingress scope such as ${slug}:invoke.
Application services call exported application providers directly through DI in monolith mode. Standalone dependency adapters can implement the same explicit ports. Redis pools, persistence, provider clients and background jobs remain opt-in owner implementations. This foundation includes no Redis driver, WS/gRPC listener or readiness claim; those transports require explicit adapters, authentication and lifecycle tests. It neither converts the existing 82 implementations or native 54 modules nor cuts over their APIs, data or deployments.