feat(upstream): Add dynamic per-request upstream provider and integrate into registries
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import type { TRegistryProtocol } from '../core/interfaces.core.js';
|
||||
import type { TRegistryProtocol, IRequestActor } from '../core/interfaces.core.js';
|
||||
|
||||
/**
|
||||
* Scope rule for routing requests to specific upstreams.
|
||||
@@ -146,6 +146,8 @@ export interface IUpstreamFetchContext {
|
||||
headers: Record<string, string>;
|
||||
/** Query parameters */
|
||||
query: Record<string, string>;
|
||||
/** Actor performing the request (for cache key isolation) */
|
||||
actor?: IRequestActor;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -193,3 +195,80 @@ export const DEFAULT_RESILIENCE_CONFIG: IUpstreamResilienceConfig = {
|
||||
circuitBreakerThreshold: 5,
|
||||
circuitBreakerResetMs: 30000,
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Upstream Provider Interfaces
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Context for resolving upstream configuration.
|
||||
* Passed to IUpstreamProvider per-request to enable dynamic upstream routing.
|
||||
*/
|
||||
export interface IUpstreamResolutionContext {
|
||||
/** Protocol being accessed */
|
||||
protocol: TRegistryProtocol;
|
||||
/** Resource identifier (package name, repository, coordinates, etc.) */
|
||||
resource: string;
|
||||
/** Extracted scope (e.g., "company" from "@company/pkg", "myorg" from "myorg/image") */
|
||||
scope: string | null;
|
||||
/** Actor performing the request */
|
||||
actor?: IRequestActor;
|
||||
/** HTTP method */
|
||||
method: string;
|
||||
/** Resource type (packument, tarball, manifest, blob, etc.) */
|
||||
resourceType: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamic upstream configuration provider.
|
||||
* Implement this interface to provide per-request upstream routing
|
||||
* based on actor context (user, organization, etc.)
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* class OrgUpstreamProvider implements IUpstreamProvider {
|
||||
* constructor(private db: Database) {}
|
||||
*
|
||||
* async resolveUpstreamConfig(ctx: IUpstreamResolutionContext) {
|
||||
* if (ctx.actor?.orgId) {
|
||||
* const orgConfig = await this.db.getOrgUpstream(ctx.actor.orgId, ctx.protocol);
|
||||
* if (orgConfig) return orgConfig;
|
||||
* }
|
||||
* return this.db.getDefaultUpstream(ctx.protocol);
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export interface IUpstreamProvider {
|
||||
/** Optional initialization */
|
||||
init?(): Promise<void>;
|
||||
|
||||
/**
|
||||
* Resolve upstream configuration for a request.
|
||||
* @param context - Information about the current request
|
||||
* @returns Upstream config to use, or null to skip upstream lookup
|
||||
*/
|
||||
resolveUpstreamConfig(context: IUpstreamResolutionContext): Promise<IProtocolUpstreamConfig | null>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Static upstream provider for simple configurations.
|
||||
* Use this when you have fixed upstream registries that don't change per-request.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const provider = new StaticUpstreamProvider({
|
||||
* npm: {
|
||||
* enabled: true,
|
||||
* upstreams: [{ id: 'npmjs', url: 'https://registry.npmjs.org', priority: 1, enabled: true, auth: { type: 'none' } }],
|
||||
* },
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export class StaticUpstreamProvider implements IUpstreamProvider {
|
||||
constructor(private configs: Partial<Record<TRegistryProtocol, IProtocolUpstreamConfig>>) {}
|
||||
|
||||
async resolveUpstreamConfig(ctx: IUpstreamResolutionContext): Promise<IProtocolUpstreamConfig | null> {
|
||||
return this.configs[ctx.protocol] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user