@Partial
The @Partial() decorator implements a TypeScript mixin pattern similar to C# partial classes. It allows a class to be composed from multiple source classes, with methods from each mixin copied onto the target prototype.
Package: io-oidis-commons
import { Partial } from "@io-oidis-commons/.../Primitives/Decorators.js";
Syntax
@Partial(MixinClassA, MixinClassB, MixinClassC)
export class CompositeClass extends BaseClass {
// Methods from MixinClassA, MixinClassB, MixinClassC are
// copied onto CompositeClass.prototype at runtime
}
How It Works
@Partial(ClassA, ClassB)registers the mixin list inglobalThis.oidisPartials- At runtime,
Reflection.resolvePartials()iterates the registered partials - Methods from each mixin class are copied onto the target class prototype
- The target class gains all methods from all mixins as if they were defined directly
Primary Use Case: AuthManager
The canonical example in Oidis is the Hub's AuthManager, which is composed from 11 partial classes:
@Partial(
UsersManager,
GroupsManager,
BasicAuthenticationAPI,
TwoFactorAuthenticationAPI,
OAuth2Helper,
PasswordResetManager,
SessionManager,
RegistrationManager,
PermissionsManager,
TokenManager,
AuditManager
)
export class AuthManager extends BaseAuthManager {
// AuthManager has all methods from all 11 mixins
// Each mixin handles a specific auth concern
}
This keeps the AuthManager file manageable while providing a single composite class to the rest of the application.
Typing Delegates vs Runtime Injection
Composite classes commonly pair the decorator with typing delegates — class fields that point at the mixin prototype methods:
@Partial(CalendarsHandler, ImportsHandler, ExportsHandler /* , ... */)
export class CalendarManager extends BaseConnector {
public ImportCSV = ImportsHandler.prototype.ImportCSV;
public GetEconomicSummary = ExportsHandler.prototype.GetEconomicSummary;
}
These delegates exist only for TypeScript (IntelliSense, type-checking of this.ImportCSV(...) call sites). At runtime they are instance fields assigned in the constructor — the methods available on CompositeClass.prototype come solely from the @Partial(...) argument list via resolvePartials().
When adding a new handler class you must update both the @Partial(...) argument list and the typing delegates. A delegate without the decorator entry compiles fine but leaves CompositeClass.prototype.<Method> undefined at runtime. Typical symptom: server startup crash while registering authorized methods —
[ERROR] Failed to init auth system. Cannot read properties of undefined (reading 'name')
at AuthMethodsManager ... generateAuthMethodsFrom
because generateAuthMethodsFrom(CompositeClass, (proto) => [proto.<Method>, ...]) reads methods from the prototype, where only @Partial-injected methods exist.
@Route and @Partial
When a partial mixin has @Route decorated methods, the route resolution uses __partialTarget to resolve the ClassName back to the composite class:
// In a partial mixin:
export class BasicAuthenticationAPI {
@Route("POST /api/auth/login")
public async Login($credentials : ICredentials) : Promise<IAuthResult> {
// Route resolves to AuthManager (the composite), not BasicAuthenticationAPI
}
}
Rules
- Partials are resolved once at startup
- Method name conflicts: methods are copied only when the target prototype does not already own them (
hasOwnPropertycheck inresolvePartials()) — so the target's own methods always win and among mixins the first one in the list wins - A mixin class can be injected into only one composite (
__partialTargetguard throws on the second injection) - Works on both backend and frontend classes
- Mixins should be standalone classes — avoid inter-mixin dependencies
When to Use
@Partial is designed for large service classes that would otherwise become unwieldy. Consider it when:
- A class exceeds ~500 lines with distinct functional areas
- Multiple developers need to work on different aspects of the same class
- The class has clear, separable concerns (auth, users, sessions, etc.)
For simpler cases, prefer standard composition or inheritance.
See also: @Extern | @Route | Architecture Overview | Reflection (resolvePartials mechanism)