every-plugin
Version:
310 lines (278 loc) • 8.77 kB
text/typescript
import type {
AnyContractRouter,
AnySchema,
InferSchemaInput,
InferSchemaOutput,
} from "@orpc/contract";
import type { Router, RouterClient } from "@orpc/server";
import type { Scope } from "effect";
import type { Plugin } from "./plugin";
/**
* Registry bindings interface - populated via module augmentation
* Only needed for remote-only plugin entries.
* @example
* ```typescript
* declare module "every-plugin" {
* interface RegisteredPlugins {
* "my-plugin": typeof MyPlugin;
* }
* }
* ```
*/
// biome-ignore lint/suspicious/noEmptyInterface: required for module augmentation pattern
export interface RegisteredPlugins {}
/**
* Base type for any plugin instance.
*/
export type AnyPlugin = Plugin<AnyContractRouter, AnySchema, AnySchema, AnySchema | undefined, any>;
/**
* Loaded plugin constructor with binding information
*/
export type AnyPluginConstructor = {
new (): AnyPlugin;
binding: {
contract: AnyContractRouter;
variables: AnySchema;
secrets: AnySchema;
context: AnySchema | undefined;
};
};
/**
* Registry entry that supports both direct module imports and remote URLs
*/
export type PluginRegistryEntry =
| { module: AnyPluginConstructor; remote?: string; version?: string; description?: string }
| { remote: string; version?: string; description?: string };
/**
* Extract plugin constructor type from registry entry
*/
export type ExtractPluginType<E> = E extends { module: infer M } ? M : never;
/**
* Infer registry types from plugin entries with module constructors
*/
export type InferRegistryFromEntries<R extends Record<string, PluginRegistryEntry>> = {
[K in keyof R]: R[K] extends { module: infer M }
? M
: K extends keyof RegisteredPlugins
? RegisteredPlugins[K]
: never;
};
/**
* Extract contract type from plugin binding
*/
export type PluginContract<T> = T extends {
binding: { contract: infer C extends AnyContractRouter };
}
? C
: never;
/**
* Extract variables type from plugin binding
*/
export type PluginVariables<T> = T extends { binding: { variables: infer V extends AnySchema } }
? V
: never;
/**
* Extract secrets type from plugin binding
*/
export type PluginSecrets<T> = T extends { binding: { secrets: infer S extends AnySchema } }
? S
: never;
/**
* Extract context schema type from plugin binding
*/
export type PluginContext<T> = T extends {
binding: { context: infer C extends AnySchema | undefined };
}
? C
: never;
/**
* Extract router type from plugin binding
* Uses 'any' for context to support nested router compositions
*/
export type PluginRouterType<T> = Router<PluginContract<T>, any>;
/**
* Extract client type from plugin binding
*/
export type PluginClientType<T> = RouterClient<PluginRouterType<T>>;
/**
* Extract plugin type from registered plugins by key
* @param K - The plugin key
* @param R - The registry type (defaults to RegisteredPlugins for module augmentation pattern)
*/
export type RegisteredPlugin<K extends keyof R, R = RegisteredPlugins> = R[K] extends {
binding: infer B;
}
? B extends {
contract: infer C extends AnyContractRouter;
variables: infer V extends AnySchema;
secrets: infer S extends AnySchema;
context: infer TRequestContext extends AnySchema | undefined;
}
? Plugin<C, V, S, TRequestContext, any>
: never
: never;
/**
* Extract plugin constructor type from registry entry
*/
export type PluginConstructor<K extends keyof R, R = RegisteredPlugins> = RegisteredPlugin<K, R>;
/**
* Extract context input type from plugin binding (for client creation)
*/
export type PluginContextInput<T> =
PluginContext<T> extends AnySchema ? InferSchemaInput<PluginContext<T>> : never;
/**
* Extract config input type from plugin binding
*/
export type PluginConfigInput<T> = {
variables: InferSchemaInput<PluginVariables<T>>;
secrets: InferSchemaInput<PluginSecrets<T>>;
};
/**
* Extract deps context type from plugin instance (used for initialization)
*/
export type ContextOf<T extends AnyPlugin> =
T extends Plugin<AnyContractRouter, AnySchema, AnySchema, AnySchema | undefined, infer TDeps>
? TDeps
: never;
/**
* Plugin metadata for remote loading
*/
export type PluginMetadata = {
readonly remoteUrl: string;
readonly version?: string;
readonly description?: string;
};
/**
* Runtime registry configuration supporting both module and remote entries
*/
export type PluginRegistry = Record<string, PluginRegistryEntry>;
/**
* Legacy metadata-only registry (for backwards compatibility)
*/
export type PluginMetadataRegistry = Record<string, PluginMetadata>;
/**
* Configuration for secrets injection.
* Secrets are hydrated into plugin configs using template replacement.
*/
export interface SecretsConfig {
[key: string]: string;
}
/**
* Loaded plugin
*/
export interface LoadedPlugin<T extends AnyPlugin = AnyPlugin> {
readonly ctor: new () => T;
readonly metadata: PluginMetadata;
}
/**
* Instantiated plugin
*/
export interface PluginInstance<T extends AnyPlugin = AnyPlugin> {
readonly plugin: T;
readonly metadata: PluginMetadata;
}
/**
* Fully initialized plugin ready for use
*/
export interface InitializedPlugin<T extends AnyPlugin = AnyPlugin> {
readonly plugin: T;
readonly metadata: PluginMetadata;
readonly config: {
variables: InferSchemaOutput<T["configSchema"]["variables"]>;
secrets: InferSchemaOutput<T["configSchema"]["secrets"]>;
};
readonly context: ContextOf<T>;
readonly scope: Scope.CloseableScope;
}
/**
* Helper type to detect type errors when looking up RegisteredPlugins
*/
type VerifyPluginBinding<K extends keyof R, R = RegisteredPlugins> = R[K] extends {
binding: infer B;
}
? B extends {
contract: AnyContractRouter;
variables: AnySchema;
secrets: AnySchema;
context: AnySchema | undefined;
}
? true
: `❌ Plugin "${K & string}" is not properly registered. Ensure it extends plugin binding layout { contract, variables, secrets, context }.`
: `❌ Plugin "${K & string}" is not properly registered. Missing binding property.`;
/**
* Result of runtime.usePlugin() call
* @param K - The plugin key
* @param R - The registry type (defaults to RegisteredPlugins for module augmentation pattern)
*/
export type UsePluginResult<K extends keyof R, R = RegisteredPlugins> =
VerifyPluginBinding<K, R> extends true
? {
readonly createClient: (context?: PluginContextInput<R[K]>) => PluginClientType<R[K]>;
readonly router: PluginRouterType<R[K]>;
readonly metadata: PluginMetadata;
readonly initialized: InitializedPlugin<RegisteredPlugin<K, R>>;
}
: VerifyPluginBinding<K, R>;
/**
* Runtime options
*/
export interface RuntimeOptions {
isolation?: "strict" | "shared" | "none";
memoryLimit?: string;
concurrency?: number;
resourceTimeout?: string;
debug?: boolean;
metrics?: boolean;
}
/**
* Extract registry type from runtime instance or use type directly
* This allows EveryPlugin.Infer to work with both:
* - typeof runtime (extracts registry from PluginRuntime<R> via __registryType)
* - Registry types directly
*/
type RegistryOf<T> = T extends { __registryType?: infer R } ? R : T;
/**
* Namespace containing type utilities for working with plugin results.
*/
export namespace EveryPlugin {
/**
* Extract plugin runtime instance type from registered plugins or runtime.
* Provides full type safety for plugin clients, routers, and metadata.
*
* @example
* ```ts
* // From RegisteredPlugins (module augmentation)
* type Plugin = EveryPlugin.Infer<"my-plugin">;
*
* // From runtime instance (when using module entries)
* const runtime = createPluginRuntime({ registry: { "my-plugin": { module: MyPlugin } } });
* type Plugin = EveryPlugin.Infer<"my-plugin", typeof runtime>;
*
* // From explicit registry type
* type Plugin = EveryPlugin.Infer<"my-plugin", MyRegistryType>;
* ```
*/
export type Infer<
K extends string,
Source = RegisteredPlugins,
> = K extends keyof RegistryOf<Source> ? UsePluginResult<K, RegistryOf<Source>> : never;
}
/**
* Plugin runtime configuration with support for both module and remote entries
*/
export interface PluginRuntimeConfig<
R extends Record<string, PluginRegistryEntry> = Record<string, PluginRegistryEntry>,
> {
registry: R;
secrets?: SecretsConfig;
options?: RuntimeOptions;
}
/**
* Legacy plugin runtime configuration (metadata-only registry)
* @deprecated Use PluginRuntimeConfig with module/remote entries instead
*/
export interface LegacyPluginRuntimeConfig {
registry: PluginMetadataRegistry;
secrets?: SecretsConfig;
options?: RuntimeOptions;
}