every-plugin
Version:
1 lines • 12.3 kB
Source Map (JSON)
{"version":3,"file":"index.cjs","names":["Hash","Effect","PluginRuntimeError","effect","Exit","Cause","Option","PluginService","ManagedRuntime"],"sources":["../../src/runtime/index.ts"],"sourcesContent":["import { createRouterClient } from \"@orpc/server\";\nimport { Cause, Effect, Exit, Hash, ManagedRuntime, Option } from \"effect\";\nimport type {\n AnyPlugin,\n AnyPluginConstructor,\n InferRegistryFromEntries,\n InitializedPlugin,\n LoadedPlugin,\n PluginConfigInput,\n PluginInstance,\n PluginRegistry,\n PluginRegistryEntry,\n PluginRouterType,\n PluginRuntimeConfig,\n RegisteredPlugin,\n RegisteredPlugins,\n UsePluginResult,\n} from \"../types\";\nimport { PluginRuntimeError } from \"./errors\";\nimport { PluginService } from \"./services/plugin.service\";\n\nexport class PluginRuntime<R = RegisteredPlugins> {\n readonly __registryType?: R;\n\n private pluginCache = new Map<\n string,\n Effect.Effect<InitializedPlugin<AnyPlugin>, PluginRuntimeError>\n >();\n\n constructor(\n private runtime: ManagedRuntime.ManagedRuntime<PluginService, never>,\n private registry: PluginRegistry,\n ) {}\n\n private generateCacheKey(pluginId: string, config: unknown): string {\n const configHash = Hash.structure(config as object).toString();\n return `${pluginId}:${configHash}`;\n }\n\n private validatePluginId(pluginId: string): Effect.Effect<string, PluginRuntimeError> {\n if (!(pluginId in this.registry)) {\n return Effect.fail(\n new PluginRuntimeError({\n pluginId: String(pluginId),\n operation: \"validate-plugin-id\",\n cause: new Error(`Plugin ID '${String(pluginId)}' not found in registry.`),\n retryable: false,\n }),\n );\n }\n return Effect.succeed(String(pluginId));\n }\n\n private async runPromise<A, E>(effect: Effect.Effect<A, E, PluginService>): Promise<A> {\n const exit = await this.runtime.runPromiseExit(effect);\n\n if (Exit.isFailure(exit)) {\n const error = Cause.failureOption(exit.cause);\n if (Option.isSome(error)) {\n throw error.value;\n }\n throw Cause.squash(exit.cause);\n }\n\n return exit.value;\n }\n\n async usePlugin<K extends keyof R & string>(\n pluginId: K,\n config: PluginConfigInput<R[K]>,\n plugins?: Record<string, unknown>,\n ): Promise<UsePluginResult<K, R>> {\n const cacheKey = this.generateCacheKey(pluginId, { ...config, __plugins: plugins ?? {} });\n\n let cachedPlugin = this.pluginCache.get(cacheKey);\n if (!cachedPlugin) {\n const operation = Effect.gen(this, function* () {\n const pluginService = yield* PluginService;\n const validatedId = yield* this.validatePluginId(pluginId);\n\n // Load → Instantiate → Initialize\n const ctor = yield* pluginService.loadPlugin(validatedId);\n const instance = yield* pluginService.instantiatePlugin(pluginId, ctor);\n const initialized = yield* pluginService.initializePlugin(instance, config, plugins);\n\n return initialized;\n }).pipe(Effect.provide(this.runtime));\n\n cachedPlugin = Effect.cached(operation).pipe(Effect.flatten);\n this.pluginCache.set(cacheKey, cachedPlugin);\n }\n\n const initialized = await this.runPromise(cachedPlugin);\n\n // Create client factory that accepts request context\n const createClient = (context?: any) => {\n const router = initialized.plugin.createRouter(initialized.context);\n return createRouterClient(router, { context: context ?? {} });\n };\n\n return {\n createClient: createClient as any,\n router: initialized.plugin.createRouter(initialized.context) as PluginRouterType<R[K]>,\n metadata: initialized.metadata,\n initialized: initialized as InitializedPlugin<RegisteredPlugin<K, R>>,\n } as UsePluginResult<K, R>;\n }\n\n async loadPlugin<K extends keyof R & string>(\n pluginId: K,\n ): Promise<LoadedPlugin<RegisteredPlugin<K, R>>> {\n const effect = Effect.gen(function* () {\n const pluginService = yield* PluginService;\n return yield* pluginService.loadPlugin(pluginId);\n });\n return this.runPromise(effect) as Promise<LoadedPlugin<RegisteredPlugin<K, R>>>;\n }\n\n async instantiatePlugin<K extends keyof R & string>(\n pluginId: K,\n loadedPlugin: LoadedPlugin<RegisteredPlugin<K, R>>,\n ): Promise<PluginInstance<RegisteredPlugin<K, R>>> {\n const effect = Effect.gen(function* () {\n const pluginService = yield* PluginService;\n return yield* pluginService.instantiatePlugin(pluginId, loadedPlugin);\n });\n return this.runPromise(effect) as Promise<PluginInstance<RegisteredPlugin<K, R>>>;\n }\n\n async initializePlugin<T extends AnyPlugin>(\n instance: PluginInstance<T>,\n config: any,\n plugins?: Record<string, unknown>,\n ): Promise<InitializedPlugin<T>> {\n const effect = Effect.gen(function* () {\n const pluginService = yield* PluginService;\n return yield* pluginService.initializePlugin(instance, config, plugins);\n });\n return this.runPromise(effect);\n }\n\n async shutdown(): Promise<void> {\n const effect = Effect.gen(function* () {\n const pluginService = yield* PluginService;\n yield* pluginService.cleanup();\n });\n await this.runPromise(effect);\n await this.runtime.dispose();\n }\n\n async evictPlugin<K extends keyof R & string>(\n pluginId: K,\n config: PluginConfigInput<R[K]>,\n ): Promise<void> {\n const cacheKey = this.generateCacheKey(pluginId, config);\n\n const effect = Effect.gen(this, function* () {\n const pluginService = yield* PluginService;\n const cachedPlugin = this.pluginCache.get(cacheKey);\n\n if (cachedPlugin) {\n this.pluginCache.delete(cacheKey);\n\n const pluginResult = yield* cachedPlugin.pipe(Effect.catchAll(() => Effect.succeed(null)));\n\n if (pluginResult) {\n yield* pluginService\n .shutdownPlugin(pluginResult)\n .pipe(\n Effect.catchAll((error) =>\n Effect.logWarning(`Failed to shutdown evicted plugin ${pluginId}`, error),\n ),\n );\n }\n }\n }).pipe(\n Effect.catchAll((error) =>\n Effect.logWarning(`Plugin eviction failed for ${pluginId}`, error),\n ),\n );\n\n return this.runPromise(effect);\n }\n}\n\n/**\n * Normalizes a remote URL to ensure it points to remoteEntry.js\n * If the URL doesn't end with a file extension, appends /remoteEntry.js\n */\nfunction normalizeRemoteUrl(url: string): string {\n if (!url) return url;\n if (url.endsWith(\".js\") || url.endsWith(\".json\")) return url;\n return `${url.endsWith(\"/\") ? url.slice(0, -1) : url}/remoteEntry.js`;\n}\n\n/**\n * Extract plugin map (module constructors) from registry entries\n */\nfunction extractPluginMap(\n registry: Record<string, PluginRegistryEntry>,\n): Record<string, AnyPluginConstructor> {\n const pluginMap: Record<string, AnyPluginConstructor> = {};\n\n for (const [pluginId, entry] of Object.entries(registry)) {\n if (\"module\" in entry && entry.module) {\n pluginMap[pluginId] = entry.module;\n }\n }\n\n return pluginMap;\n}\n\n/**\n * Normalize registry entries - ensure remote URLs are properly formatted\n */\nfunction normalizeRegistry(registry: Record<string, PluginRegistryEntry>): PluginRegistry {\n const normalized: Record<string, PluginRegistryEntry> = {};\n\n for (const [pluginId, entry] of Object.entries(registry)) {\n if (\"module\" in entry) {\n normalized[pluginId] = {\n ...entry,\n remote: entry.remote ? normalizeRemoteUrl(entry.remote) : undefined,\n };\n } else {\n normalized[pluginId] = {\n ...entry,\n remote: normalizeRemoteUrl(entry.remote),\n };\n }\n }\n\n return normalized as PluginRegistry;\n}\n\n/**\n * Creates a plugin runtime with support for both module and remote plugin entries.\n *\n * @example\n * ```typescript\n * // With module entries (types inferred automatically)\n * const runtime = createPluginRuntime({\n * registry: {\n * telegram: { module: TelegramPlugin },\n * gopher: { remote: \"https://cdn.example.com/gopher/remoteEntry.js\" }\n * },\n * secrets: { API_KEY: \"...\" }\n * });\n *\n * // Types are automatically inferred from module entries!\n * const { router } = await runtime.usePlugin(\"telegram\", config);\n * ```\n */\nexport function createPluginRuntime<TRegistry extends Record<string, PluginRegistryEntry>>(\n config: PluginRuntimeConfig<TRegistry>,\n): PluginRuntime<InferRegistryFromEntries<TRegistry>> {\n const secrets = config.secrets || {};\n const normalizedRegistry = normalizeRegistry(config.registry);\n const pluginMap = extractPluginMap(config.registry);\n\n const layer = PluginService.Live(normalizedRegistry, secrets, pluginMap);\n const runtime = ManagedRuntime.make(layer);\n\n return new PluginRuntime(runtime, normalizedRegistry) as PluginRuntime<\n InferRegistryFromEntries<TRegistry>\n >;\n}\n"],"mappings":"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"}