every-plugin 3.0.0-rc.0 → 3.0.0-rc.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/errors.cjs +1 -0
  2. package/dist/errors.cjs.map +1 -1
  3. package/dist/errors.d.cts +2 -2
  4. package/dist/errors.d.mts +2 -2
  5. package/dist/errors.mjs +2 -2
  6. package/dist/errors.mjs.map +1 -1
  7. package/dist/index.cjs +1 -0
  8. package/dist/index.d.cts +3 -3
  9. package/dist/index.d.mts +3 -3
  10. package/dist/index.mjs +2 -2
  11. package/dist/plugin.cjs +1 -0
  12. package/dist/plugin.cjs.map +1 -1
  13. package/dist/plugin.d.cts +16 -2
  14. package/dist/plugin.d.cts.map +1 -1
  15. package/dist/plugin.d.mts +16 -2
  16. package/dist/plugin.d.mts.map +1 -1
  17. package/dist/plugin.mjs +1 -0
  18. package/dist/plugin.mjs.map +1 -1
  19. package/dist/runtime/errors.cjs +20 -0
  20. package/dist/runtime/errors.cjs.map +1 -1
  21. package/dist/runtime/errors.d.cts +23 -1
  22. package/dist/runtime/errors.d.cts.map +1 -1
  23. package/dist/runtime/errors.d.mts +23 -1
  24. package/dist/runtime/errors.d.mts.map +1 -1
  25. package/dist/runtime/errors.mjs +20 -1
  26. package/dist/runtime/errors.mjs.map +1 -1
  27. package/dist/runtime/mf-config.cjs +1 -1
  28. package/dist/runtime/mf-config.mjs +1 -1
  29. package/dist/runtime/services/module-federation.service.cjs +1 -7
  30. package/dist/runtime/services/module-federation.service.cjs.map +1 -1
  31. package/dist/runtime/services/module-federation.service.mjs +1 -7
  32. package/dist/runtime/services/module-federation.service.mjs.map +1 -1
  33. package/dist/runtime/services/plugin-loader.service.cjs +26 -1
  34. package/dist/runtime/services/plugin-loader.service.cjs.map +1 -1
  35. package/dist/runtime/services/plugin-loader.service.mjs +26 -1
  36. package/dist/runtime/services/plugin-loader.service.mjs.map +1 -1
  37. package/dist/runtime/services/shared-identity.cjs +1 -1
  38. package/dist/runtime/services/shared-identity.cjs.map +1 -1
  39. package/dist/runtime/services/shared-identity.mjs +1 -1
  40. package/dist/runtime/services/shared-identity.mjs.map +1 -1
  41. package/dist/types.d.cts +3 -0
  42. package/dist/types.d.cts.map +1 -1
  43. package/dist/types.d.mts +3 -0
  44. package/dist/types.d.mts.map +1 -1
  45. package/dist/version-manifest.cjs +1 -1
  46. package/dist/version-manifest.cjs.map +1 -1
  47. package/dist/version-manifest.mjs +1 -1
  48. package/dist/version-manifest.mjs.map +1 -1
  49. package/package.json +1 -1
  50. package/src/errors.ts +2 -1
  51. package/src/plugin.ts +20 -1
  52. package/src/runtime/errors.ts +44 -0
  53. package/src/runtime/services/module-federation.service.ts +4 -7
  54. package/src/runtime/services/plugin-loader.service.ts +32 -0
  55. package/src/runtime/services/shared-identity.ts +1 -1
  56. package/src/types.ts +3 -0
  57. package/src/version-manifest.ts +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"plugin-loader.service.mjs","names":[],"sources":["../../../src/runtime/services/plugin-loader.service.ts"],"sourcesContent":["import type { InferSchemaInput, InferSchemaOutput } from \"@orpc/contract\";\nimport { Context, Effect, Exit, Layer, Scope } from \"effect\";\nimport type { z } from \"zod\";\nimport { PluginIdTag } from \"../../plugin\";\nimport type {\n AnyPlugin,\n AnyPluginConstructor,\n InitializedPlugin,\n LoadedPlugin,\n PluginInstance,\n PluginMetadata,\n PluginRegistry,\n} from \"../../types\";\nimport { PluginRuntimeError, toPluginRuntimeError } from \"../errors\";\nimport { validate } from \"../validation\";\nimport { ModuleFederationService } from \"./module-federation.service\";\nimport { SecretsService } from \"./secrets.service\";\n\nexport class PluginRegistryTag extends Context.Service<PluginRegistryTag, PluginRegistry>()(\n \"PluginRegistry\",\n) {}\n\nexport class PluginMapTag extends Context.Service<\n PluginMapTag,\n Record<string, AnyPluginConstructor>\n>()(\"PluginMap\") {}\n\nexport interface RegistryServiceShape {\n get: (\n pluginId: string,\n ) => Effect.Effect<\n { constructor: (new () => AnyPlugin) | null; metadata: PluginMetadata },\n PluginRuntimeError\n >;\n getModule: (pluginId: string) => Effect.Effect<AnyPluginConstructor | null>;\n}\n\nexport class RegistryService extends Context.Service<RegistryService, RegistryServiceShape>()(\n \"RegistryService\",\n) {}\n\nexport const RegistryServiceDefault = Layer.effect(\n RegistryService,\n Effect.gen(function* () {\n const registry = yield* PluginRegistryTag;\n const pluginMap = yield* PluginMapTag;\n\n return {\n get: (pluginId: string) =>\n Effect.gen(function* () {\n const entry = registry[pluginId];\n\n if (!entry) {\n return yield* new PluginRuntimeError({\n pluginId,\n operation: \"validate-plugin-id\",\n cause: new Error(`Plugin ${pluginId} not found in registry`),\n });\n }\n\n if (\"module\" in entry) {\n return {\n constructor: entry.module,\n metadata: {\n remoteUrl: entry.remote || \"\",\n version: entry.version,\n description: entry.description,\n } as PluginMetadata,\n };\n }\n\n return {\n constructor: null,\n metadata: {\n remoteUrl: entry.remote,\n version: entry.version,\n description: entry.description,\n } as PluginMetadata,\n };\n }),\n\n getModule: (pluginId: string) => Effect.succeed(pluginMap[pluginId] || null),\n };\n }),\n);\n\nexport interface PluginLoaderServiceShape {\n loadPlugin: (pluginId: string) => Effect.Effect<LoadedPlugin, PluginRuntimeError>;\n instantiatePlugin: <T extends AnyPlugin>(\n pluginId: string,\n loadedPlugin: LoadedPlugin<T>,\n ) => Effect.Effect<PluginInstance<T>, PluginRuntimeError>;\n initializePlugin: <T extends AnyPlugin>(\n pluginInstance: PluginInstance<T>,\n config: {\n variables: InferSchemaInput<T[\"configSchema\"][\"variables\"]>;\n secrets: InferSchemaInput<T[\"configSchema\"][\"secrets\"]>;\n },\n plugins?: Record<string, unknown>,\n ) => Effect.Effect<InitializedPlugin<T>, PluginRuntimeError>;\n}\n\nexport class PluginLoaderService extends Context.Service<\n PluginLoaderService,\n PluginLoaderServiceShape\n>()(\"PluginLoaderService\") {}\n\nexport const PluginLoaderServiceDefault = Layer.effect(\n PluginLoaderService,\n Effect.gen(function* () {\n const moduleFederationService = yield* ModuleFederationService;\n const secretsService = yield* SecretsService;\n const registryService = yield* RegistryService;\n\n const resolveUrl = (baseUrl: string, version?: string): string =>\n version && version !== \"latest\" ? baseUrl.replace(\"@latest\", `@${version}`) : baseUrl;\n\n return {\n loadPlugin: (pluginId: string) =>\n Effect.gen(function* () {\n const entry = yield* registryService.get(pluginId);\n\n if (entry.constructor) {\n yield* Effect.logDebug(\"Loading plugin from direct module\", { pluginId });\n\n return {\n ctor: entry.constructor,\n metadata: entry.metadata,\n } satisfies LoadedPlugin;\n }\n\n const url = entry.metadata.remoteUrl;\n if (!url) {\n return yield* new PluginRuntimeError({\n pluginId,\n operation: \"load-plugin\",\n cause: new Error(`Plugin ${pluginId} has no module or remote URL configured`),\n });\n }\n\n const resolvedUrl = resolveUrl(url);\n\n yield* moduleFederationService.registerRemote(pluginId, resolvedUrl).pipe(\n Effect.tapError((error) =>\n Effect.logError(`Plugin ${pluginId} failed during register-remote: ${String(error)}`),\n ),\n Effect.mapError((error) =>\n toPluginRuntimeError(error, pluginId, undefined, \"register-remote\"),\n ),\n );\n\n yield* Effect.logDebug(\"Loading plugin from remote\", { pluginId, url: resolvedUrl });\n\n const ctor = yield* moduleFederationService\n .loadRemoteConstructor(pluginId, resolvedUrl)\n .pipe(\n Effect.tapError((error) =>\n Effect.logError(`Plugin ${pluginId} failed during load-remote: ${String(error)}`),\n ),\n Effect.mapError((error) =>\n toPluginRuntimeError(error, pluginId, undefined, \"load-remote\"),\n ),\n );\n\n return {\n ctor,\n metadata: entry.metadata,\n } satisfies LoadedPlugin;\n }),\n\n instantiatePlugin: <T extends AnyPlugin>(pluginId: string, loadedPlugin: LoadedPlugin<T>) =>\n Effect.gen(function* () {\n const instance = yield* Effect.try(() => new loadedPlugin.ctor()).pipe(\n Effect.tapError((error) =>\n Effect.logError(`Plugin ${pluginId} failed during instantiate-plugin: ${error}`),\n ),\n Effect.mapError((error) =>\n toPluginRuntimeError(error, pluginId, undefined, \"instantiate-plugin\"),\n ),\n );\n\n (instance.id as string) = pluginId;\n\n return {\n plugin: instance,\n metadata: loadedPlugin.metadata,\n } satisfies PluginInstance<T>;\n }),\n\n initializePlugin: <T extends AnyPlugin>(\n pluginInstance: PluginInstance<T>,\n config: {\n variables: InferSchemaInput<T[\"configSchema\"][\"variables\"]>;\n secrets: InferSchemaInput<T[\"configSchema\"][\"secrets\"]>;\n },\n plugins?: Record<string, unknown>,\n ) =>\n Effect.gen(function* () {\n const { plugin } = pluginInstance;\n\n // Validate and hydrate config\n const validatedVariables = yield* validate(\n plugin.configSchema.variables as z.ZodSchema<\n InferSchemaOutput<T[\"configSchema\"][\"variables\"]>\n >,\n config.variables,\n plugin.id,\n \"config\",\n ).pipe(\n Effect.mapError(\n (validationError) =>\n new PluginRuntimeError({\n pluginId: plugin.id,\n operation: \"validate-config\",\n cause: validationError.zodError,\n }),\n ),\n );\n\n // Validate secrets\n const validatedSecrets = yield* validate(\n plugin.configSchema.secrets as z.ZodSchema<\n InferSchemaOutput<T[\"configSchema\"][\"secrets\"]>\n >,\n config.secrets,\n plugin.id,\n \"config\",\n ).pipe(\n Effect.mapError(\n (validationError) =>\n new PluginRuntimeError({\n pluginId: plugin.id,\n operation: \"validate-secrets\",\n cause: validationError.zodError,\n }),\n ),\n );\n\n // Hydrate secrets in variables\n const hydratedConfig = yield* secretsService.hydrateSecrets({\n variables: validatedVariables,\n secrets: validatedSecrets,\n });\n\n const _variables = yield* validate(\n plugin.configSchema.variables as z.ZodSchema<\n InferSchemaOutput<T[\"configSchema\"][\"variables\"]>\n >,\n hydratedConfig.variables,\n plugin.id,\n \"config\",\n ).pipe(\n Effect.mapError(\n (validationError) =>\n new PluginRuntimeError({\n pluginId: plugin.id,\n operation: \"validate-hydrated-config\",\n cause: validationError.zodError,\n }),\n ),\n );\n\n // Create a long-lived scope for this plugin instance. Initialize\n // returns a Layer which is built against this scope, so scoped\n // resources (DB pools, caches) release on plugin shutdown.\n const scope = yield* Scope.make();\n\n // Build the plugin's Layer within the scope.\n // On failure, close the scope immediately so scoped resources\n // (DB pools, caches) are released — otherwise every failed\n // initialization leaks until the process exits.\n const effectContext = yield* Effect.gen(function* () {\n const layer = yield* plugin\n .initialize({ variables: _variables, secrets: hydratedConfig.secrets }, plugins ?? {})\n .pipe(\n Effect.provideService(PluginIdTag, plugin.id),\n Effect.provideService(Scope.Scope, scope),\n );\n\n return yield* Layer.buildWithScope(layer, scope).pipe(\n Effect.provideService(PluginIdTag, plugin.id),\n Effect.provideService(Scope.Scope, scope),\n Effect.mapError((error) =>\n toPluginRuntimeError(error, plugin.id, undefined, \"initialize-layer\"),\n ),\n );\n }).pipe(\n Effect.onExit((exit) =>\n Exit.isSuccess(exit)\n ? Effect.void\n : Scope.close(scope, exit).pipe(\n Effect.catchCause((closeCause) =>\n Effect.logWarning(\n `Failed to close scope for plugin ${plugin.id} after initialize error`,\n closeCause,\n ),\n ),\n ),\n ),\n Effect.tapError((error) =>\n Effect.logError(\n `Plugin ${plugin.id} failed during initialize-plugin: ${String(error)}`,\n ),\n ),\n Effect.mapError((error) =>\n toPluginRuntimeError(error, plugin.id, undefined, \"initialize-plugin\"),\n ),\n );\n\n return {\n plugin,\n metadata: pluginInstance.metadata,\n config: { variables: _variables, secrets: hydratedConfig.secrets },\n effectContext,\n scope,\n } satisfies InitializedPlugin<T>;\n }),\n };\n }),\n);\n"],"mappings":"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"}
1
+ {"version":3,"file":"plugin-loader.service.mjs","names":[],"sources":["../../../src/runtime/services/plugin-loader.service.ts"],"sourcesContent":["import type { InferSchemaInput, InferSchemaOutput } from \"@orpc/contract\";\nimport { Context, Effect, Exit, Layer, Scope } from \"effect\";\nimport type { z } from \"zod\";\nimport { PluginIdTag } from \"../../plugin\";\nimport type {\n AnyPlugin,\n AnyPluginConstructor,\n InitializedPlugin,\n LoadedPlugin,\n PluginInstance,\n PluginMetadata,\n PluginRegistry,\n} from \"../../types\";\nimport type { PluginWarningInfo } from \"../errors\";\nimport { PluginRuntimeError, toPluginRuntimeError } from \"../errors\";\nimport { validate } from \"../validation\";\nimport { ModuleFederationService } from \"./module-federation.service\";\nimport { SecretsService } from \"./secrets.service\";\n\n/**\n * Evaluate a plugin's `warnings` function against the validated config.\n * Fail-safe: a throwing warnings function becomes a warning itself rather\n * than failing initialization. Strings are normalized to `PluginWarningInfo`\n * with the plugin's registry key.\n */\nconst collectPluginWarnings = (\n plugin: AnyPlugin,\n config: { variables: unknown; secrets: unknown },\n): PluginWarningInfo[] => {\n const warningsFn = plugin.warnings;\n if (!warningsFn) return [];\n try {\n const raw = warningsFn(config as never);\n return raw.map((warning) =>\n typeof warning === \"string\" ? { pluginKey: plugin.id, message: warning } : warning,\n );\n } catch (error) {\n return [\n {\n pluginKey: plugin.id,\n message: `Plugin warnings check failed: ${error instanceof Error ? error.message : String(error)}`,\n },\n ];\n }\n};\n\nexport class PluginRegistryTag extends Context.Service<PluginRegistryTag, PluginRegistry>()(\n \"PluginRegistry\",\n) {}\n\nexport class PluginMapTag extends Context.Service<\n PluginMapTag,\n Record<string, AnyPluginConstructor>\n>()(\"PluginMap\") {}\n\nexport interface RegistryServiceShape {\n get: (\n pluginId: string,\n ) => Effect.Effect<\n { constructor: (new () => AnyPlugin) | null; metadata: PluginMetadata },\n PluginRuntimeError\n >;\n getModule: (pluginId: string) => Effect.Effect<AnyPluginConstructor | null>;\n}\n\nexport class RegistryService extends Context.Service<RegistryService, RegistryServiceShape>()(\n \"RegistryService\",\n) {}\n\nexport const RegistryServiceDefault = Layer.effect(\n RegistryService,\n Effect.gen(function* () {\n const registry = yield* PluginRegistryTag;\n const pluginMap = yield* PluginMapTag;\n\n return {\n get: (pluginId: string) =>\n Effect.gen(function* () {\n const entry = registry[pluginId];\n\n if (!entry) {\n return yield* new PluginRuntimeError({\n pluginId,\n operation: \"validate-plugin-id\",\n cause: new Error(`Plugin ${pluginId} not found in registry`),\n });\n }\n\n if (\"module\" in entry) {\n return {\n constructor: entry.module,\n metadata: {\n remoteUrl: entry.remote || \"\",\n version: entry.version,\n description: entry.description,\n } as PluginMetadata,\n };\n }\n\n return {\n constructor: null,\n metadata: {\n remoteUrl: entry.remote,\n version: entry.version,\n description: entry.description,\n } as PluginMetadata,\n };\n }),\n\n getModule: (pluginId: string) => Effect.succeed(pluginMap[pluginId] || null),\n };\n }),\n);\n\nexport interface PluginLoaderServiceShape {\n loadPlugin: (pluginId: string) => Effect.Effect<LoadedPlugin, PluginRuntimeError>;\n instantiatePlugin: <T extends AnyPlugin>(\n pluginId: string,\n loadedPlugin: LoadedPlugin<T>,\n ) => Effect.Effect<PluginInstance<T>, PluginRuntimeError>;\n initializePlugin: <T extends AnyPlugin>(\n pluginInstance: PluginInstance<T>,\n config: {\n variables: InferSchemaInput<T[\"configSchema\"][\"variables\"]>;\n secrets: InferSchemaInput<T[\"configSchema\"][\"secrets\"]>;\n },\n plugins?: Record<string, unknown>,\n ) => Effect.Effect<InitializedPlugin<T>, PluginRuntimeError>;\n}\n\nexport class PluginLoaderService extends Context.Service<\n PluginLoaderService,\n PluginLoaderServiceShape\n>()(\"PluginLoaderService\") {}\n\nexport const PluginLoaderServiceDefault = Layer.effect(\n PluginLoaderService,\n Effect.gen(function* () {\n const moduleFederationService = yield* ModuleFederationService;\n const secretsService = yield* SecretsService;\n const registryService = yield* RegistryService;\n\n const resolveUrl = (baseUrl: string, version?: string): string =>\n version && version !== \"latest\" ? baseUrl.replace(\"@latest\", `@${version}`) : baseUrl;\n\n return {\n loadPlugin: (pluginId: string) =>\n Effect.gen(function* () {\n const entry = yield* registryService.get(pluginId);\n\n if (entry.constructor) {\n yield* Effect.logDebug(\"Loading plugin from direct module\", { pluginId });\n\n return {\n ctor: entry.constructor,\n metadata: entry.metadata,\n } satisfies LoadedPlugin;\n }\n\n const url = entry.metadata.remoteUrl;\n if (!url) {\n return yield* new PluginRuntimeError({\n pluginId,\n operation: \"load-plugin\",\n cause: new Error(`Plugin ${pluginId} has no module or remote URL configured`),\n });\n }\n\n const resolvedUrl = resolveUrl(url);\n\n yield* moduleFederationService.registerRemote(pluginId, resolvedUrl).pipe(\n Effect.tapError((error) =>\n Effect.logError(`Plugin ${pluginId} failed during register-remote: ${String(error)}`),\n ),\n Effect.mapError((error) =>\n toPluginRuntimeError(error, pluginId, undefined, \"register-remote\"),\n ),\n );\n\n yield* Effect.logDebug(\"Loading plugin from remote\", { pluginId, url: resolvedUrl });\n\n const ctor = yield* moduleFederationService\n .loadRemoteConstructor(pluginId, resolvedUrl)\n .pipe(\n Effect.tapError((error) =>\n Effect.logError(`Plugin ${pluginId} failed during load-remote: ${String(error)}`),\n ),\n Effect.mapError((error) =>\n toPluginRuntimeError(error, pluginId, undefined, \"load-remote\"),\n ),\n );\n\n return {\n ctor,\n metadata: entry.metadata,\n } satisfies LoadedPlugin;\n }),\n\n instantiatePlugin: <T extends AnyPlugin>(pluginId: string, loadedPlugin: LoadedPlugin<T>) =>\n Effect.gen(function* () {\n const instance = yield* Effect.try(() => new loadedPlugin.ctor()).pipe(\n Effect.tapError((error) =>\n Effect.logError(`Plugin ${pluginId} failed during instantiate-plugin: ${error}`),\n ),\n Effect.mapError((error) =>\n toPluginRuntimeError(error, pluginId, undefined, \"instantiate-plugin\"),\n ),\n );\n\n (instance.id as string) = pluginId;\n\n return {\n plugin: instance,\n metadata: loadedPlugin.metadata,\n } satisfies PluginInstance<T>;\n }),\n\n initializePlugin: <T extends AnyPlugin>(\n pluginInstance: PluginInstance<T>,\n config: {\n variables: InferSchemaInput<T[\"configSchema\"][\"variables\"]>;\n secrets: InferSchemaInput<T[\"configSchema\"][\"secrets\"]>;\n },\n plugins?: Record<string, unknown>,\n ) =>\n Effect.gen(function* () {\n const { plugin } = pluginInstance;\n\n // Validate and hydrate config\n const validatedVariables = yield* validate(\n plugin.configSchema.variables as z.ZodSchema<\n InferSchemaOutput<T[\"configSchema\"][\"variables\"]>\n >,\n config.variables,\n plugin.id,\n \"config\",\n ).pipe(\n Effect.mapError(\n (validationError) =>\n new PluginRuntimeError({\n pluginId: plugin.id,\n operation: \"validate-config\",\n cause: validationError.zodError,\n }),\n ),\n );\n\n // Validate secrets\n const validatedSecrets = yield* validate(\n plugin.configSchema.secrets as z.ZodSchema<\n InferSchemaOutput<T[\"configSchema\"][\"secrets\"]>\n >,\n config.secrets,\n plugin.id,\n \"config\",\n ).pipe(\n Effect.mapError(\n (validationError) =>\n new PluginRuntimeError({\n pluginId: plugin.id,\n operation: \"validate-secrets\",\n cause: validationError.zodError,\n }),\n ),\n );\n\n // Hydrate secrets in variables\n const hydratedConfig = yield* secretsService.hydrateSecrets({\n variables: validatedVariables,\n secrets: validatedSecrets,\n });\n\n const _variables = yield* validate(\n plugin.configSchema.variables as z.ZodSchema<\n InferSchemaOutput<T[\"configSchema\"][\"variables\"]>\n >,\n hydratedConfig.variables,\n plugin.id,\n \"config\",\n ).pipe(\n Effect.mapError(\n (validationError) =>\n new PluginRuntimeError({\n pluginId: plugin.id,\n operation: \"validate-hydrated-config\",\n cause: validationError.zodError,\n }),\n ),\n );\n\n // Create a long-lived scope for this plugin instance. Initialize\n // returns a Layer which is built against this scope, so scoped\n // resources (DB pools, caches) release on plugin shutdown.\n const scope = yield* Scope.make();\n\n // Build the plugin's Layer within the scope.\n // On failure, close the scope immediately so scoped resources\n // (DB pools, caches) are released — otherwise every failed\n // initialization leaks until the process exits.\n const effectContext = yield* Effect.gen(function* () {\n const layer = yield* plugin\n .initialize({ variables: _variables, secrets: hydratedConfig.secrets }, plugins ?? {})\n .pipe(\n Effect.provideService(PluginIdTag, plugin.id),\n Effect.provideService(Scope.Scope, scope),\n );\n\n return yield* Layer.buildWithScope(layer, scope).pipe(\n Effect.provideService(PluginIdTag, plugin.id),\n Effect.provideService(Scope.Scope, scope),\n Effect.mapError((error) =>\n toPluginRuntimeError(error, plugin.id, undefined, \"initialize-layer\"),\n ),\n );\n }).pipe(\n Effect.onExit((exit) =>\n Exit.isSuccess(exit)\n ? Effect.void\n : Scope.close(scope, exit).pipe(\n Effect.catchCause((closeCause) =>\n Effect.logWarning(\n `Failed to close scope for plugin ${plugin.id} after initialize error`,\n closeCause,\n ),\n ),\n ),\n ),\n Effect.tapError((error) =>\n Effect.logError(\n `Plugin ${plugin.id} failed during initialize-plugin: ${String(error)}`,\n ),\n ),\n Effect.mapError((error) =>\n toPluginRuntimeError(error, plugin.id, undefined, \"initialize-plugin\"),\n ),\n );\n\n return {\n plugin,\n metadata: pluginInstance.metadata,\n config: { variables: _variables, secrets: hydratedConfig.secrets },\n effectContext,\n scope,\n warnings: collectPluginWarnings(plugin, {\n variables: _variables,\n secrets: hydratedConfig.secrets,\n }),\n } satisfies InitializedPlugin<T>;\n }),\n };\n }),\n);\n"],"mappings":"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"}
@@ -6,7 +6,7 @@ function manifestUrl(remoteUrl) {
6
6
  return remoteUrl.endsWith("/") ? `${remoteUrl}mf-manifest.json` : `${remoteUrl}/mf-manifest.json`;
7
7
  }
8
8
  function normalizeBaseUrl(remoteUrl) {
9
- return (remoteUrl.split("#")[0] ?? remoteUrl).replace(/\/(mf-manifest\.json|remoteEntry\.js)$/i, "");
9
+ return (remoteUrl.split("#")[0] ?? remoteUrl).replace(/\/(mf-manifest\.json|remoteEntry(?:\.[^/]+)?\.js)$/i, "");
10
10
  }
11
11
  function compareSharedIdentity(remoteShared, expectedVersions) {
12
12
  const mismatches = [];
@@ -1 +1 @@
1
- {"version":3,"file":"shared-identity.cjs","names":["isEffectCriticalSharedDep"],"sources":["../../../src/runtime/services/shared-identity.ts"],"sourcesContent":["import { isEffectCriticalSharedDep } from \"../../shared-deps-spec\";\n\ninterface IdentityManifest {\n metaData?: { pluginVersion?: string; name?: string };\n shared?: Array<{ name: string; version?: string }>;\n}\n\nexport interface SharedIdentityMismatch {\n name: string;\n expected: string;\n actual: string | null;\n}\n\nconst MANIFEST_TIMEOUT_MS = 15_000;\n\nfunction manifestUrl(remoteUrl: string): string {\n return remoteUrl.endsWith(\"/\") ? `${remoteUrl}mf-manifest.json` : `${remoteUrl}/mf-manifest.json`;\n}\n\nfunction normalizeBaseUrl(remoteUrl: string): string {\n const withoutHash = remoteUrl.split(\"#\")[0] ?? remoteUrl;\n return withoutHash.replace(/\\/(mf-manifest\\.json|remoteEntry\\.js)$/i, \"\");\n}\n\nexport function compareSharedIdentity(\n remoteShared: Array<{ name: string; version?: string }> | undefined,\n expectedVersions: Map<string, string>,\n): SharedIdentityMismatch[] {\n const mismatches: SharedIdentityMismatch[] = [];\n const remoteByName = new Map((remoteShared ?? []).map((s) => [s.name, s]));\n\n for (const [name, expected] of expectedVersions.entries()) {\n if (!isEffectCriticalSharedDep(name)) continue;\n const remoteEntry = remoteByName.get(name);\n if (!remoteEntry) continue;\n if (remoteEntry.version && remoteEntry.version !== expected) {\n mismatches.push({ name, expected, actual: remoteEntry.version });\n }\n }\n\n return mismatches;\n}\n\nexport function describeSharedIdentityMismatch(\n pluginId: string,\n mismatches: SharedIdentityMismatch[],\n hostExpectation: string,\n): string {\n const lines = mismatches.map(\n (m) =>\n ` ${m.name}: expected ${m.expected} (from ${hostExpectation}), got ${m.actual ?? \"missing\"}`,\n );\n return (\n `[SharedIdentity] Refusing to load plugin \"${pluginId}\": shared dependency version skew.\\n` +\n `${lines.join(\"\\n\")}\\n` +\n `Run \"bos mf check\" for the full report. Fix: rebuild and redeploy the host and this plugin together (single release train, e.g. \"bos publish --deploy --packages local\").`\n );\n}\n\nexport async function fetchRemoteIdentityManifest(\n remoteUrl: string,\n): Promise<IdentityManifest | null> {\n const base = normalizeBaseUrl(remoteUrl);\n const url = manifestUrl(base);\n const res = await fetch(url, { signal: AbortSignal.timeout(MANIFEST_TIMEOUT_MS) });\n if (!res.ok) return null;\n return (await res.json()) as IdentityManifest;\n}\n"],"mappings":";;;AAaA,MAAM,sBAAsB;AAE5B,SAAS,YAAY,WAA2B;CAC9C,OAAO,UAAU,SAAS,GAAG,IAAI,GAAG,UAAU,oBAAoB,GAAG,UAAU;AACjF;AAEA,SAAS,iBAAiB,WAA2B;CAEnD,QADoB,UAAU,MAAM,GAAG,CAAC,CAAC,MAAM,UAC7B,CAAC,QAAQ,2CAA2C,EAAE;AAC1E;AAEA,SAAgB,sBACd,cACA,kBAC0B;CAC1B,MAAM,aAAuC,CAAC;CAC9C,MAAM,eAAe,IAAI,KAAK,gBAAgB,CAAC,EAAC,CAAE,KAAK,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;CAEzE,KAAK,MAAM,CAAC,MAAM,aAAa,iBAAiB,QAAQ,GAAG;EACzD,IAAI,CAACA,mDAA0B,IAAI,GAAG;EACtC,MAAM,cAAc,aAAa,IAAI,IAAI;EACzC,IAAI,CAAC,aAAa;EAClB,IAAI,YAAY,WAAW,YAAY,YAAY,UACjD,WAAW,KAAK;GAAE;GAAM;GAAU,QAAQ,YAAY;EAAQ,CAAC;CAEnE;CAEA,OAAO;AACT;AAEA,SAAgB,+BACd,UACA,YACA,iBACQ;CAKR,OACE,6CAA6C,SAAS,sCAL1C,WAAW,KACtB,MACC,KAAK,EAAE,KAAK,aAAa,EAAE,SAAS,SAAS,gBAAgB,SAAS,EAAE,UAAU,WAI7E,CAAC,CAAC,KAAK,IAAI,EAAE;AAGxB;AAEA,eAAsB,4BACpB,WACkC;CAElC,MAAM,MAAM,YADC,iBAAiB,SACH,CAAC;CAC5B,MAAM,MAAM,MAAM,MAAM,KAAK,EAAE,QAAQ,YAAY,QAAQ,mBAAmB,EAAE,CAAC;CACjF,IAAI,CAAC,IAAI,IAAI,OAAO;CACpB,OAAQ,MAAM,IAAI,KAAK;AACzB"}
1
+ {"version":3,"file":"shared-identity.cjs","names":["isEffectCriticalSharedDep"],"sources":["../../../src/runtime/services/shared-identity.ts"],"sourcesContent":["import { isEffectCriticalSharedDep } from \"../../shared-deps-spec\";\n\ninterface IdentityManifest {\n metaData?: { pluginVersion?: string; name?: string };\n shared?: Array<{ name: string; version?: string }>;\n}\n\nexport interface SharedIdentityMismatch {\n name: string;\n expected: string;\n actual: string | null;\n}\n\nconst MANIFEST_TIMEOUT_MS = 15_000;\n\nfunction manifestUrl(remoteUrl: string): string {\n return remoteUrl.endsWith(\"/\") ? `${remoteUrl}mf-manifest.json` : `${remoteUrl}/mf-manifest.json`;\n}\n\nfunction normalizeBaseUrl(remoteUrl: string): string {\n const withoutHash = remoteUrl.split(\"#\")[0] ?? remoteUrl;\n return withoutHash.replace(/\\/(mf-manifest\\.json|remoteEntry(?:\\.[^/]+)?\\.js)$/i, \"\");\n}\n\nexport function compareSharedIdentity(\n remoteShared: Array<{ name: string; version?: string }> | undefined,\n expectedVersions: Map<string, string>,\n): SharedIdentityMismatch[] {\n const mismatches: SharedIdentityMismatch[] = [];\n const remoteByName = new Map((remoteShared ?? []).map((s) => [s.name, s]));\n\n for (const [name, expected] of expectedVersions.entries()) {\n if (!isEffectCriticalSharedDep(name)) continue;\n const remoteEntry = remoteByName.get(name);\n if (!remoteEntry) continue;\n if (remoteEntry.version && remoteEntry.version !== expected) {\n mismatches.push({ name, expected, actual: remoteEntry.version });\n }\n }\n\n return mismatches;\n}\n\nexport function describeSharedIdentityMismatch(\n pluginId: string,\n mismatches: SharedIdentityMismatch[],\n hostExpectation: string,\n): string {\n const lines = mismatches.map(\n (m) =>\n ` ${m.name}: expected ${m.expected} (from ${hostExpectation}), got ${m.actual ?? \"missing\"}`,\n );\n return (\n `[SharedIdentity] Refusing to load plugin \"${pluginId}\": shared dependency version skew.\\n` +\n `${lines.join(\"\\n\")}\\n` +\n `Run \"bos mf check\" for the full report. Fix: rebuild and redeploy the host and this plugin together (single release train, e.g. \"bos publish --deploy --packages local\").`\n );\n}\n\nexport async function fetchRemoteIdentityManifest(\n remoteUrl: string,\n): Promise<IdentityManifest | null> {\n const base = normalizeBaseUrl(remoteUrl);\n const url = manifestUrl(base);\n const res = await fetch(url, { signal: AbortSignal.timeout(MANIFEST_TIMEOUT_MS) });\n if (!res.ok) return null;\n return (await res.json()) as IdentityManifest;\n}\n"],"mappings":";;;AAaA,MAAM,sBAAsB;AAE5B,SAAS,YAAY,WAA2B;CAC9C,OAAO,UAAU,SAAS,GAAG,IAAI,GAAG,UAAU,oBAAoB,GAAG,UAAU;AACjF;AAEA,SAAS,iBAAiB,WAA2B;CAEnD,QADoB,UAAU,MAAM,GAAG,CAAC,CAAC,MAAM,UAC7B,CAAC,QAAQ,uDAAuD,EAAE;AACtF;AAEA,SAAgB,sBACd,cACA,kBAC0B;CAC1B,MAAM,aAAuC,CAAC;CAC9C,MAAM,eAAe,IAAI,KAAK,gBAAgB,CAAC,EAAC,CAAE,KAAK,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;CAEzE,KAAK,MAAM,CAAC,MAAM,aAAa,iBAAiB,QAAQ,GAAG;EACzD,IAAI,CAACA,mDAA0B,IAAI,GAAG;EACtC,MAAM,cAAc,aAAa,IAAI,IAAI;EACzC,IAAI,CAAC,aAAa;EAClB,IAAI,YAAY,WAAW,YAAY,YAAY,UACjD,WAAW,KAAK;GAAE;GAAM;GAAU,QAAQ,YAAY;EAAQ,CAAC;CAEnE;CAEA,OAAO;AACT;AAEA,SAAgB,+BACd,UACA,YACA,iBACQ;CAKR,OACE,6CAA6C,SAAS,sCAL1C,WAAW,KACtB,MACC,KAAK,EAAE,KAAK,aAAa,EAAE,SAAS,SAAS,gBAAgB,SAAS,EAAE,UAAU,WAI7E,CAAC,CAAC,KAAK,IAAI,EAAE;AAGxB;AAEA,eAAsB,4BACpB,WACkC;CAElC,MAAM,MAAM,YADC,iBAAiB,SACH,CAAC;CAC5B,MAAM,MAAM,MAAM,MAAM,KAAK,EAAE,QAAQ,YAAY,QAAQ,mBAAmB,EAAE,CAAC;CACjF,IAAI,CAAC,IAAI,IAAI,OAAO;CACpB,OAAQ,MAAM,IAAI,KAAK;AACzB"}
@@ -6,7 +6,7 @@ function manifestUrl(remoteUrl) {
6
6
  return remoteUrl.endsWith("/") ? `${remoteUrl}mf-manifest.json` : `${remoteUrl}/mf-manifest.json`;
7
7
  }
8
8
  function normalizeBaseUrl(remoteUrl) {
9
- return (remoteUrl.split("#")[0] ?? remoteUrl).replace(/\/(mf-manifest\.json|remoteEntry\.js)$/i, "");
9
+ return (remoteUrl.split("#")[0] ?? remoteUrl).replace(/\/(mf-manifest\.json|remoteEntry(?:\.[^/]+)?\.js)$/i, "");
10
10
  }
11
11
  function compareSharedIdentity(remoteShared, expectedVersions) {
12
12
  const mismatches = [];
@@ -1 +1 @@
1
- {"version":3,"file":"shared-identity.mjs","names":[],"sources":["../../../src/runtime/services/shared-identity.ts"],"sourcesContent":["import { isEffectCriticalSharedDep } from \"../../shared-deps-spec\";\n\ninterface IdentityManifest {\n metaData?: { pluginVersion?: string; name?: string };\n shared?: Array<{ name: string; version?: string }>;\n}\n\nexport interface SharedIdentityMismatch {\n name: string;\n expected: string;\n actual: string | null;\n}\n\nconst MANIFEST_TIMEOUT_MS = 15_000;\n\nfunction manifestUrl(remoteUrl: string): string {\n return remoteUrl.endsWith(\"/\") ? `${remoteUrl}mf-manifest.json` : `${remoteUrl}/mf-manifest.json`;\n}\n\nfunction normalizeBaseUrl(remoteUrl: string): string {\n const withoutHash = remoteUrl.split(\"#\")[0] ?? remoteUrl;\n return withoutHash.replace(/\\/(mf-manifest\\.json|remoteEntry\\.js)$/i, \"\");\n}\n\nexport function compareSharedIdentity(\n remoteShared: Array<{ name: string; version?: string }> | undefined,\n expectedVersions: Map<string, string>,\n): SharedIdentityMismatch[] {\n const mismatches: SharedIdentityMismatch[] = [];\n const remoteByName = new Map((remoteShared ?? []).map((s) => [s.name, s]));\n\n for (const [name, expected] of expectedVersions.entries()) {\n if (!isEffectCriticalSharedDep(name)) continue;\n const remoteEntry = remoteByName.get(name);\n if (!remoteEntry) continue;\n if (remoteEntry.version && remoteEntry.version !== expected) {\n mismatches.push({ name, expected, actual: remoteEntry.version });\n }\n }\n\n return mismatches;\n}\n\nexport function describeSharedIdentityMismatch(\n pluginId: string,\n mismatches: SharedIdentityMismatch[],\n hostExpectation: string,\n): string {\n const lines = mismatches.map(\n (m) =>\n ` ${m.name}: expected ${m.expected} (from ${hostExpectation}), got ${m.actual ?? \"missing\"}`,\n );\n return (\n `[SharedIdentity] Refusing to load plugin \"${pluginId}\": shared dependency version skew.\\n` +\n `${lines.join(\"\\n\")}\\n` +\n `Run \"bos mf check\" for the full report. Fix: rebuild and redeploy the host and this plugin together (single release train, e.g. \"bos publish --deploy --packages local\").`\n );\n}\n\nexport async function fetchRemoteIdentityManifest(\n remoteUrl: string,\n): Promise<IdentityManifest | null> {\n const base = normalizeBaseUrl(remoteUrl);\n const url = manifestUrl(base);\n const res = await fetch(url, { signal: AbortSignal.timeout(MANIFEST_TIMEOUT_MS) });\n if (!res.ok) return null;\n return (await res.json()) as IdentityManifest;\n}\n"],"mappings":";;;AAaA,MAAM,sBAAsB;AAE5B,SAAS,YAAY,WAA2B;CAC9C,OAAO,UAAU,SAAS,GAAG,IAAI,GAAG,UAAU,oBAAoB,GAAG,UAAU;AACjF;AAEA,SAAS,iBAAiB,WAA2B;CAEnD,QADoB,UAAU,MAAM,GAAG,CAAC,CAAC,MAAM,UAC7B,CAAC,QAAQ,2CAA2C,EAAE;AAC1E;AAEA,SAAgB,sBACd,cACA,kBAC0B;CAC1B,MAAM,aAAuC,CAAC;CAC9C,MAAM,eAAe,IAAI,KAAK,gBAAgB,CAAC,EAAC,CAAE,KAAK,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;CAEzE,KAAK,MAAM,CAAC,MAAM,aAAa,iBAAiB,QAAQ,GAAG;EACzD,IAAI,CAAC,0BAA0B,IAAI,GAAG;EACtC,MAAM,cAAc,aAAa,IAAI,IAAI;EACzC,IAAI,CAAC,aAAa;EAClB,IAAI,YAAY,WAAW,YAAY,YAAY,UACjD,WAAW,KAAK;GAAE;GAAM;GAAU,QAAQ,YAAY;EAAQ,CAAC;CAEnE;CAEA,OAAO;AACT;AAEA,SAAgB,+BACd,UACA,YACA,iBACQ;CAKR,OACE,6CAA6C,SAAS,sCAL1C,WAAW,KACtB,MACC,KAAK,EAAE,KAAK,aAAa,EAAE,SAAS,SAAS,gBAAgB,SAAS,EAAE,UAAU,WAI7E,CAAC,CAAC,KAAK,IAAI,EAAE;AAGxB;AAEA,eAAsB,4BACpB,WACkC;CAElC,MAAM,MAAM,YADC,iBAAiB,SACH,CAAC;CAC5B,MAAM,MAAM,MAAM,MAAM,KAAK,EAAE,QAAQ,YAAY,QAAQ,mBAAmB,EAAE,CAAC;CACjF,IAAI,CAAC,IAAI,IAAI,OAAO;CACpB,OAAQ,MAAM,IAAI,KAAK;AACzB"}
1
+ {"version":3,"file":"shared-identity.mjs","names":[],"sources":["../../../src/runtime/services/shared-identity.ts"],"sourcesContent":["import { isEffectCriticalSharedDep } from \"../../shared-deps-spec\";\n\ninterface IdentityManifest {\n metaData?: { pluginVersion?: string; name?: string };\n shared?: Array<{ name: string; version?: string }>;\n}\n\nexport interface SharedIdentityMismatch {\n name: string;\n expected: string;\n actual: string | null;\n}\n\nconst MANIFEST_TIMEOUT_MS = 15_000;\n\nfunction manifestUrl(remoteUrl: string): string {\n return remoteUrl.endsWith(\"/\") ? `${remoteUrl}mf-manifest.json` : `${remoteUrl}/mf-manifest.json`;\n}\n\nfunction normalizeBaseUrl(remoteUrl: string): string {\n const withoutHash = remoteUrl.split(\"#\")[0] ?? remoteUrl;\n return withoutHash.replace(/\\/(mf-manifest\\.json|remoteEntry(?:\\.[^/]+)?\\.js)$/i, \"\");\n}\n\nexport function compareSharedIdentity(\n remoteShared: Array<{ name: string; version?: string }> | undefined,\n expectedVersions: Map<string, string>,\n): SharedIdentityMismatch[] {\n const mismatches: SharedIdentityMismatch[] = [];\n const remoteByName = new Map((remoteShared ?? []).map((s) => [s.name, s]));\n\n for (const [name, expected] of expectedVersions.entries()) {\n if (!isEffectCriticalSharedDep(name)) continue;\n const remoteEntry = remoteByName.get(name);\n if (!remoteEntry) continue;\n if (remoteEntry.version && remoteEntry.version !== expected) {\n mismatches.push({ name, expected, actual: remoteEntry.version });\n }\n }\n\n return mismatches;\n}\n\nexport function describeSharedIdentityMismatch(\n pluginId: string,\n mismatches: SharedIdentityMismatch[],\n hostExpectation: string,\n): string {\n const lines = mismatches.map(\n (m) =>\n ` ${m.name}: expected ${m.expected} (from ${hostExpectation}), got ${m.actual ?? \"missing\"}`,\n );\n return (\n `[SharedIdentity] Refusing to load plugin \"${pluginId}\": shared dependency version skew.\\n` +\n `${lines.join(\"\\n\")}\\n` +\n `Run \"bos mf check\" for the full report. Fix: rebuild and redeploy the host and this plugin together (single release train, e.g. \"bos publish --deploy --packages local\").`\n );\n}\n\nexport async function fetchRemoteIdentityManifest(\n remoteUrl: string,\n): Promise<IdentityManifest | null> {\n const base = normalizeBaseUrl(remoteUrl);\n const url = manifestUrl(base);\n const res = await fetch(url, { signal: AbortSignal.timeout(MANIFEST_TIMEOUT_MS) });\n if (!res.ok) return null;\n return (await res.json()) as IdentityManifest;\n}\n"],"mappings":";;;AAaA,MAAM,sBAAsB;AAE5B,SAAS,YAAY,WAA2B;CAC9C,OAAO,UAAU,SAAS,GAAG,IAAI,GAAG,UAAU,oBAAoB,GAAG,UAAU;AACjF;AAEA,SAAS,iBAAiB,WAA2B;CAEnD,QADoB,UAAU,MAAM,GAAG,CAAC,CAAC,MAAM,UAC7B,CAAC,QAAQ,uDAAuD,EAAE;AACtF;AAEA,SAAgB,sBACd,cACA,kBAC0B;CAC1B,MAAM,aAAuC,CAAC;CAC9C,MAAM,eAAe,IAAI,KAAK,gBAAgB,CAAC,EAAC,CAAE,KAAK,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;CAEzE,KAAK,MAAM,CAAC,MAAM,aAAa,iBAAiB,QAAQ,GAAG;EACzD,IAAI,CAAC,0BAA0B,IAAI,GAAG;EACtC,MAAM,cAAc,aAAa,IAAI,IAAI;EACzC,IAAI,CAAC,aAAa;EAClB,IAAI,YAAY,WAAW,YAAY,YAAY,UACjD,WAAW,KAAK;GAAE;GAAM;GAAU,QAAQ,YAAY;EAAQ,CAAC;CAEnE;CAEA,OAAO;AACT;AAEA,SAAgB,+BACd,UACA,YACA,iBACQ;CAKR,OACE,6CAA6C,SAAS,sCAL1C,WAAW,KACtB,MACC,KAAK,EAAE,KAAK,aAAa,EAAE,SAAS,SAAS,gBAAgB,SAAS,EAAE,UAAU,WAI7E,CAAC,CAAC,KAAK,IAAI,EAAE;AAGxB;AAEA,eAAsB,4BACpB,WACkC;CAElC,MAAM,MAAM,YADC,iBAAiB,SACH,CAAC;CAC5B,MAAM,MAAM,MAAM,MAAM,KAAK,EAAE,QAAQ,YAAY,QAAQ,mBAAmB,EAAE,CAAC;CACjF,IAAI,CAAC,IAAI,IAAI,OAAO;CACpB,OAAQ,MAAM,IAAI,KAAK;AACzB"}
package/dist/types.d.cts CHANGED
@@ -1,3 +1,4 @@
1
+ import { PluginWarningInfo } from "./runtime/errors.cjs";
1
2
  import { Plugin } from "./plugin.cjs";
2
3
  import { ContractedRouter, RouterClient } from "@orpc/server";
3
4
  import { Context, Scope } from "effect";
@@ -181,6 +182,8 @@ interface InitializedPlugin<T extends AnyPlugin = AnyPlugin> {
181
182
  */
182
183
  readonly effectContext: Context.Context<any>;
183
184
  readonly scope: Scope.Closeable;
185
+ /** Non-fatal config warnings collected during initialization (empty when none). */
186
+ readonly warnings: PluginWarningInfo[];
184
187
  }
185
188
  /**
186
189
  * Helper type to detect type errors when looking up RegisteredPlugins
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.cts","names":[],"sources":["../src/types.ts"],"mappings":";;;;;;;;;;;;;;;;;UAuBiB;;;;KAKL,YAAY,OAAO,mBAAmB,WAAW,WAAW;;;;KAK5D;UACF;EACR;IACE,UAAU;IACV,WAAW;IACX,SAAS;IACT,SAAS;;;;;;KAOD;EACN,QAAQ;EAAsB;EAAiB;EAAkB;;EACjE;EAAgB;EAAkB;;;;;KAK5B,kBAAkB,KAAK;EAAY,cAAc;IAAM;;;;KAKvD,yBAAyB,UAAU,eAAe,2BAC3D,WAAW,IAAI,EAAE;EAAa,cAAc;IACzC,IACA,gBAAgB,oBACd,kBAAkB;;;;KAOd,eAAe,KAAK;EAC9B;IAAW,gBAAgB,UAAU;;IAEnC;;;;KAMQ,gBAAgB,KAAK;EAAY;IAAW,iBAAiB,UAAU;;IAC/E;;;;KAMQ,cAAc,KAAK;EAAY;IAAW,eAAe,UAAU;;IAC3E;;;;KAMQ,cAAc,KAAK;EAC7B;IAAW,eAAe,UAAU;;IAElC;;;;;KAOQ,iBAAiB,KAAK,iBAAiB,eAAe;;;;KAKtD,iBAAiB,KAAK,aAAa,iBAAiB;;;;;;KAOpD,oBAAoB;EAC9B,SAAS,UAAU,mBAAmB,OAAO,iBAAiB;EAC9D,QAAQ,iBAAiB;;;;;;;KAQf,iBAAiB,gBAAgB,GAAG,IAAI,qBAAqB,EAAE;EACzE,eAAe;IAEb;EACE,gBAAgB,UAAU;EAC1B,iBAAiB,UAAU;EAC3B,eAAe,UAAU;EACzB,eAAe,wBAAwB;IAEvC,OAAO,GAAG,GAAG,GAAG;;;;KAOV,mBAAmB,KAC7B,cAAc,WAAW,YAAY,iBAAiB,cAAc;;;;KAK1D,kBAAkB;EAC5B,WAAW,iBAAiB,gBAAgB;EAC5C,SAAS,iBAAiB,cAAc;;;;;KAM9B;WACD;WACA;WACA;;;;;KAMC,iBAAiB,eAAe;;;;;UAM3B;GACd;;;;;UAMc,aAAa,UAAU,YAAY;WACzC,gBAAgB;WAChB,UAAU;;;;;UAMJ,eAAe,UAAU,YAAY;WAC3C,QAAQ;WACR,UAAU;;;;;UAMJ,kBAAkB,UAAU,YAAY;WAC9C,QAAQ;WACR,UAAU;WACV;IACP,WAAW,kBAAkB;IAC7B,SAAS,kBAAkB;;;;;;;WAOpB,eAAe,QAAQ;WACvB,OAAO,MAAM;;;;;KAMnB,oBAAoB,gBAAgB,GAAG,IAAI,qBAAqB,EAAE;EACrE,eAAe;IAEb;EACE,UAAU;EACV,WAAW;EACX,SAAS;EACT,SAAS;wBAGI,4IACF;;;;;;KAOL,gBAAgB,gBAAgB,GAAG,IAAI,qBACjD,oBAAoB,GAAG;WAER,eAAe,UAAU,mBAAmB,EAAE,QAAQ,iBAAiB,EAAE;WACzE,QAAQ,iBAAiB,EAAE;WAC3B,UAAU;WACV,aAAa,kBAAkB,iBAAiB,GAAG;IAE9D,oBAAoB,GAAG;;;;;;;KAQxB,WAAW,KAAK;EAAY,uBAAuB;IAAM,IAAI;;;;kBAKjD;;;;;;;;;;;;;;;;;;OAkBH,MACV,kBACA,SAAS,qBACP,gBAAgB,WAAW,UAAU,gBAAgB,GAAG,WAAW;;;;;UAMxD,oBACf,UAAU,eAAe,uBAAuB,eAAe;EAE/D,UAAU;EACV,UAAU"}
1
+ {"version":3,"file":"types.d.cts","names":[],"sources":["../src/types.ts"],"mappings":";;;;;;;;;;;;;;;;;;UAwBiB;;;;KAKL,YAAY,OAAO,mBAAmB,WAAW,WAAW;;;;KAK5D;UACF;EACR;IACE,UAAU;IACV,WAAW;IACX,SAAS;IACT,SAAS;;;;;;KAOD;EACN,QAAQ;EAAsB;EAAiB;EAAkB;;EACjE;EAAgB;EAAkB;;;;;KAK5B,kBAAkB,KAAK;EAAY,cAAc;IAAM;;;;KAKvD,yBAAyB,UAAU,eAAe,2BAC3D,WAAW,IAAI,EAAE;EAAa,cAAc;IACzC,IACA,gBAAgB,oBACd,kBAAkB;;;;KAOd,eAAe,KAAK;EAC9B;IAAW,gBAAgB,UAAU;;IAEnC;;;;KAMQ,gBAAgB,KAAK;EAAY;IAAW,iBAAiB,UAAU;;IAC/E;;;;KAMQ,cAAc,KAAK;EAAY;IAAW,eAAe,UAAU;;IAC3E;;;;KAMQ,cAAc,KAAK;EAC7B;IAAW,eAAe,UAAU;;IAElC;;;;;KAOQ,iBAAiB,KAAK,iBAAiB,eAAe;;;;KAKtD,iBAAiB,KAAK,aAAa,iBAAiB;;;;;;KAOpD,oBAAoB;EAC9B,SAAS,UAAU,mBAAmB,OAAO,iBAAiB;EAC9D,QAAQ,iBAAiB;;;;;;;KAQf,iBAAiB,gBAAgB,GAAG,IAAI,qBAAqB,EAAE;EACzE,eAAe;IAEb;EACE,gBAAgB,UAAU;EAC1B,iBAAiB,UAAU;EAC3B,eAAe,UAAU;EACzB,eAAe,wBAAwB;IAEvC,OAAO,GAAG,GAAG,GAAG;;;;KAOV,mBAAmB,KAC7B,cAAc,WAAW,YAAY,iBAAiB,cAAc;;;;KAK1D,kBAAkB;EAC5B,WAAW,iBAAiB,gBAAgB;EAC5C,SAAS,iBAAiB,cAAc;;;;;KAM9B;WACD;WACA;WACA;;;;;KAMC,iBAAiB,eAAe;;;;;UAM3B;GACd;;;;;UAMc,aAAa,UAAU,YAAY;WACzC,gBAAgB;WAChB,UAAU;;;;;UAMJ,eAAe,UAAU,YAAY;WAC3C,QAAQ;WACR,UAAU;;;;;UAMJ,kBAAkB,UAAU,YAAY;WAC9C,QAAQ;WACR,UAAU;WACV;IACP,WAAW,kBAAkB;IAC7B,SAAS,kBAAkB;;;;;;;WAOpB,eAAe,QAAQ;WACvB,OAAO,MAAM;;WAEb,UAAU;;;;;KAMhB,oBAAoB,gBAAgB,GAAG,IAAI,qBAAqB,EAAE;EACrE,eAAe;IAEb;EACE,UAAU;EACV,WAAW;EACX,SAAS;EACT,SAAS;wBAGI,4IACF;;;;;;KAOL,gBAAgB,gBAAgB,GAAG,IAAI,qBACjD,oBAAoB,GAAG;WAER,eAAe,UAAU,mBAAmB,EAAE,QAAQ,iBAAiB,EAAE;WACzE,QAAQ,iBAAiB,EAAE;WAC3B,UAAU;WACV,aAAa,kBAAkB,iBAAiB,GAAG;IAE9D,oBAAoB,GAAG;;;;;;;KAQxB,WAAW,KAAK;EAAY,uBAAuB;IAAM,IAAI;;;;kBAKjD;;;;;;;;;;;;;;;;;;OAkBH,MACV,kBACA,SAAS,qBACP,gBAAgB,WAAW,UAAU,gBAAgB,GAAG,WAAW;;;;;UAMxD,oBACf,UAAU,eAAe,uBAAuB,eAAe;EAE/D,UAAU;EACV,UAAU"}
package/dist/types.d.mts CHANGED
@@ -1,3 +1,4 @@
1
+ import { PluginWarningInfo } from "./runtime/errors.mjs";
1
2
  import { Plugin } from "./plugin.mjs";
2
3
  import { Context, Scope } from "effect";
3
4
  import { ContractedRouter, RouterClient } from "@orpc/server";
@@ -181,6 +182,8 @@ interface InitializedPlugin<T extends AnyPlugin = AnyPlugin> {
181
182
  */
182
183
  readonly effectContext: Context.Context<any>;
183
184
  readonly scope: Scope.Closeable;
185
+ /** Non-fatal config warnings collected during initialization (empty when none). */
186
+ readonly warnings: PluginWarningInfo[];
184
187
  }
185
188
  /**
186
189
  * Helper type to detect type errors when looking up RegisteredPlugins
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.mts","names":[],"sources":["../src/types.ts"],"mappings":";;;;;;;;;;;;;;;;;UAuBiB;;;;KAKL,YAAY,OAAO,mBAAmB,WAAW,WAAW;;;;KAK5D;UACF;EACR;IACE,UAAU;IACV,WAAW;IACX,SAAS;IACT,SAAS;;;;;;KAOD;EACN,QAAQ;EAAsB;EAAiB;EAAkB;;EACjE;EAAgB;EAAkB;;;;;KAK5B,kBAAkB,KAAK;EAAY,cAAc;IAAM;;;;KAKvD,yBAAyB,UAAU,eAAe,2BAC3D,WAAW,IAAI,EAAE;EAAa,cAAc;IACzC,IACA,gBAAgB,oBACd,kBAAkB;;;;KAOd,eAAe,KAAK;EAC9B;IAAW,gBAAgB,UAAU;;IAEnC;;;;KAMQ,gBAAgB,KAAK;EAAY;IAAW,iBAAiB,UAAU;;IAC/E;;;;KAMQ,cAAc,KAAK;EAAY;IAAW,eAAe,UAAU;;IAC3E;;;;KAMQ,cAAc,KAAK;EAC7B;IAAW,eAAe,UAAU;;IAElC;;;;;KAOQ,iBAAiB,KAAK,iBAAiB,eAAe;;;;KAKtD,iBAAiB,KAAK,aAAa,iBAAiB;;;;;;KAOpD,oBAAoB;EAC9B,SAAS,UAAU,mBAAmB,OAAO,iBAAiB;EAC9D,QAAQ,iBAAiB;;;;;;;KAQf,iBAAiB,gBAAgB,GAAG,IAAI,qBAAqB,EAAE;EACzE,eAAe;IAEb;EACE,gBAAgB,UAAU;EAC1B,iBAAiB,UAAU;EAC3B,eAAe,UAAU;EACzB,eAAe,wBAAwB;IAEvC,OAAO,GAAG,GAAG,GAAG;;;;KAOV,mBAAmB,KAC7B,cAAc,WAAW,YAAY,iBAAiB,cAAc;;;;KAK1D,kBAAkB;EAC5B,WAAW,iBAAiB,gBAAgB;EAC5C,SAAS,iBAAiB,cAAc;;;;;KAM9B;WACD;WACA;WACA;;;;;KAMC,iBAAiB,eAAe;;;;;UAM3B;GACd;;;;;UAMc,aAAa,UAAU,YAAY;WACzC,gBAAgB;WAChB,UAAU;;;;;UAMJ,eAAe,UAAU,YAAY;WAC3C,QAAQ;WACR,UAAU;;;;;UAMJ,kBAAkB,UAAU,YAAY;WAC9C,QAAQ;WACR,UAAU;WACV;IACP,WAAW,kBAAkB;IAC7B,SAAS,kBAAkB;;;;;;;WAOpB,eAAe,QAAQ;WACvB,OAAO,MAAM;;;;;KAMnB,oBAAoB,gBAAgB,GAAG,IAAI,qBAAqB,EAAE;EACrE,eAAe;IAEb;EACE,UAAU;EACV,WAAW;EACX,SAAS;EACT,SAAS;wBAGI,4IACF;;;;;;KAOL,gBAAgB,gBAAgB,GAAG,IAAI,qBACjD,oBAAoB,GAAG;WAER,eAAe,UAAU,mBAAmB,EAAE,QAAQ,iBAAiB,EAAE;WACzE,QAAQ,iBAAiB,EAAE;WAC3B,UAAU;WACV,aAAa,kBAAkB,iBAAiB,GAAG;IAE9D,oBAAoB,GAAG;;;;;;;KAQxB,WAAW,KAAK;EAAY,uBAAuB;IAAM,IAAI;;;;kBAKjD;;;;;;;;;;;;;;;;;;OAkBH,MACV,kBACA,SAAS,qBACP,gBAAgB,WAAW,UAAU,gBAAgB,GAAG,WAAW;;;;;UAMxD,oBACf,UAAU,eAAe,uBAAuB,eAAe;EAE/D,UAAU;EACV,UAAU"}
1
+ {"version":3,"file":"types.d.mts","names":[],"sources":["../src/types.ts"],"mappings":";;;;;;;;;;;;;;;;;;UAwBiB;;;;KAKL,YAAY,OAAO,mBAAmB,WAAW,WAAW;;;;KAK5D;UACF;EACR;IACE,UAAU;IACV,WAAW;IACX,SAAS;IACT,SAAS;;;;;;KAOD;EACN,QAAQ;EAAsB;EAAiB;EAAkB;;EACjE;EAAgB;EAAkB;;;;;KAK5B,kBAAkB,KAAK;EAAY,cAAc;IAAM;;;;KAKvD,yBAAyB,UAAU,eAAe,2BAC3D,WAAW,IAAI,EAAE;EAAa,cAAc;IACzC,IACA,gBAAgB,oBACd,kBAAkB;;;;KAOd,eAAe,KAAK;EAC9B;IAAW,gBAAgB,UAAU;;IAEnC;;;;KAMQ,gBAAgB,KAAK;EAAY;IAAW,iBAAiB,UAAU;;IAC/E;;;;KAMQ,cAAc,KAAK;EAAY;IAAW,eAAe,UAAU;;IAC3E;;;;KAMQ,cAAc,KAAK;EAC7B;IAAW,eAAe,UAAU;;IAElC;;;;;KAOQ,iBAAiB,KAAK,iBAAiB,eAAe;;;;KAKtD,iBAAiB,KAAK,aAAa,iBAAiB;;;;;;KAOpD,oBAAoB;EAC9B,SAAS,UAAU,mBAAmB,OAAO,iBAAiB;EAC9D,QAAQ,iBAAiB;;;;;;;KAQf,iBAAiB,gBAAgB,GAAG,IAAI,qBAAqB,EAAE;EACzE,eAAe;IAEb;EACE,gBAAgB,UAAU;EAC1B,iBAAiB,UAAU;EAC3B,eAAe,UAAU;EACzB,eAAe,wBAAwB;IAEvC,OAAO,GAAG,GAAG,GAAG;;;;KAOV,mBAAmB,KAC7B,cAAc,WAAW,YAAY,iBAAiB,cAAc;;;;KAK1D,kBAAkB;EAC5B,WAAW,iBAAiB,gBAAgB;EAC5C,SAAS,iBAAiB,cAAc;;;;;KAM9B;WACD;WACA;WACA;;;;;KAMC,iBAAiB,eAAe;;;;;UAM3B;GACd;;;;;UAMc,aAAa,UAAU,YAAY;WACzC,gBAAgB;WAChB,UAAU;;;;;UAMJ,eAAe,UAAU,YAAY;WAC3C,QAAQ;WACR,UAAU;;;;;UAMJ,kBAAkB,UAAU,YAAY;WAC9C,QAAQ;WACR,UAAU;WACV;IACP,WAAW,kBAAkB;IAC7B,SAAS,kBAAkB;;;;;;;WAOpB,eAAe,QAAQ;WACvB,OAAO,MAAM;;WAEb,UAAU;;;;;KAMhB,oBAAoB,gBAAgB,GAAG,IAAI,qBAAqB,EAAE;EACrE,eAAe;IAEb;EACE,UAAU;EACV,WAAW;EACX,SAAS;EACT,SAAS;wBAGI,4IACF;;;;;;KAOL,gBAAgB,gBAAgB,GAAG,IAAI,qBACjD,oBAAoB,GAAG;WAER,eAAe,UAAU,mBAAmB,EAAE,QAAQ,iBAAiB,EAAE;WACzE,QAAQ,iBAAiB,EAAE;WAC3B,UAAU;WACV,aAAa,kBAAkB,iBAAiB,GAAG;IAE9D,oBAAoB,GAAG;;;;;;;KAQxB,WAAW,KAAK;EAAY,uBAAuB;IAAM,IAAI;;;;kBAKjD;;;;;;;;;;;;;;;;;;OAkBH,MACV,kBACA,SAAS,qBACP,gBAAgB,WAAW,UAAU,gBAAgB,GAAG,WAAW;;;;;UAMxD,oBACf,UAAU,eAAe,uBAAuB,eAAe;EAE/D,UAAU;EACV,UAAU"}
@@ -46,7 +46,7 @@ const WorkspaceVersionManifestSchema = zod.z.object({
46
46
  * coordinate → a new version (and a new file).
47
47
  */
48
48
  function composeVersionManifest(input) {
49
- const { builtAt, ...hashedInputs } = input;
49
+ const { builtAt: _builtAt, ...hashedInputs } = input;
50
50
  const version = (0, node_crypto.createHash)("sha256").update(stableStringify(hashedInputs)).digest("hex").slice(0, 16);
51
51
  return WorkspaceVersionManifestSchema.parse({
52
52
  ...input,
@@ -1 +1 @@
1
- {"version":3,"file":"version-manifest.cjs","names":["z","createHash"],"sources":["../src/version-manifest.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { z } from \"zod\";\n\n/**\n * WorkspaceVersionManifest — the immutable per-deploy document at\n * `bundles/<account>/<gateway>/<workspace>/versions/<version>.json`\n * (ADR 0020, as amended; atomic-deploys MAP decision 1). The published\n * config's slot pins this document via its `pin` (`manifest` + the\n * manifest's own SRI); every artifact coordinate lives here, so the config\n * stops growing with artifact kinds and old versions stay fully servable.\n */\n\nconst SRI = z.string().regex(/^sha384-[A-Za-z0-9+/=]+$/);\n\nexport const WorkspaceVersionManifestSchema = z.object({\n version: z.string().regex(/^[0-9a-f]{16}$/),\n builtAt: z.string().optional(),\n gitSha: z.string().optional(),\n entry: z.string().min(1),\n entryIntegrity: SRI,\n ssr: z\n .object({\n entry: z.string().min(1),\n integrity: SRI,\n })\n .optional(),\n browserManifest: z\n .object({\n file: z.string().min(1),\n integrity: SRI,\n })\n .optional(),\n /** extra assets (css, icons) — logical name → served-object SRI */\n assets: z.record(z.string(), SRI).optional(),\n /** shared-dependency versions this build was built against */\n shared: z.record(z.string(), z.string()).optional(),\n /**\n * Full per-file digest map of the dist (object path → served-object SRI).\n * Written by `every-plugin`-based deploys so the next deploy can diff against\n * it and skip re-uploading unchanged files; readers that predate the field\n * ignore it (zod strips unknown keys). Participates in the version hash.\n */\n files: z.record(z.string(), SRI).optional(),\n});\nexport type WorkspaceVersionManifest = z.infer<typeof WorkspaceVersionManifestSchema>;\n\nexport interface VersionManifestInput {\n /** Omit: a per-build timestamp makes the same version id map to different\n * manifest bytes per machine, breaking the pin's SRI against the image's\n * staged copy and the CDN's immutability for republished versions. */\n builtAt?: string;\n gitSha?: string;\n entry: string;\n entryIntegrity: string;\n ssr?: { entry: string; integrity: string };\n browserManifest?: { file: string; integrity: string };\n assets?: Record<string, string>;\n shared?: Record<string, string>;\n files?: Record<string, string>;\n}\n\n/**\n * Compose the manifest with its `version` derived from the canonical content\n * of the artifact coordinates alone (key-sorted, `builtAt` excluded) —\n * identical bytes → identical version id regardless of when they were built,\n * so republishing unchanged content is a pointer no-op. Any changed\n * coordinate → a new version (and a new file).\n */\nexport function composeVersionManifest(input: VersionManifestInput): WorkspaceVersionManifest {\n const { builtAt, ...hashedInputs } = input;\n const version = createHash(\"sha256\")\n .update(stableStringify(hashedInputs))\n .digest(\"hex\")\n .slice(0, 16);\n return WorkspaceVersionManifestSchema.parse({ ...input, version });\n}\n\nfunction stableStringify(value: unknown): string {\n if (Array.isArray(value)) return `[${value.map(stableStringify).join(\",\")}]`;\n if (value && typeof value === \"object\") {\n return `{${Object.entries(value)\n .filter(([, v]) => v !== undefined)\n .map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`)\n .sort()\n .join(\",\")}}`;\n }\n return JSON.stringify(value) ?? \"null\";\n}\n"],"mappings":";;;;;;;;;;;;;AAYA,MAAM,MAAMA,MAAE,OAAO,CAAC,CAAC,MAAM,0BAA0B;AAEvD,MAAa,iCAAiCA,MAAE,OAAO;CACrD,SAASA,MAAE,OAAO,CAAC,CAAC,MAAM,gBAAgB;CAC1C,SAASA,MAAE,OAAO,CAAC,CAAC,SAAS;CAC7B,QAAQA,MAAE,OAAO,CAAC,CAAC,SAAS;CAC5B,OAAOA,MAAE,OAAO,CAAC,CAAC,IAAI,CAAC;CACvB,gBAAgB;CAChB,KAAKA,MACF,OAAO;EACN,OAAOA,MAAE,OAAO,CAAC,CAAC,IAAI,CAAC;EACvB,WAAW;CACb,CAAC,CAAC,CACD,SAAS;CACZ,iBAAiBA,MACd,OAAO;EACN,MAAMA,MAAE,OAAO,CAAC,CAAC,IAAI,CAAC;EACtB,WAAW;CACb,CAAC,CAAC,CACD,SAAS;;CAEZ,QAAQA,MAAE,OAAOA,MAAE,OAAO,GAAG,GAAG,CAAC,CAAC,SAAS;;CAE3C,QAAQA,MAAE,OAAOA,MAAE,OAAO,GAAGA,MAAE,OAAO,CAAC,CAAC,CAAC,SAAS;;;;;;;CAOlD,OAAOA,MAAE,OAAOA,MAAE,OAAO,GAAG,GAAG,CAAC,CAAC,SAAS;AAC5C,CAAC;;;;;;;;AAyBD,SAAgB,uBAAuB,OAAuD;CAC5F,MAAM,EAAE,SAAS,GAAG,iBAAiB;CACrC,MAAM,cAAUC,wBAAW,QAAQ,CAAC,CACjC,OAAO,gBAAgB,YAAY,CAAC,CAAC,CACrC,OAAO,KAAK,CAAC,CACb,MAAM,GAAG,EAAE;CACd,OAAO,+BAA+B,MAAM;EAAE,GAAG;EAAO;CAAQ,CAAC;AACnE;AAEA,SAAS,gBAAgB,OAAwB;CAC/C,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,IAAI,MAAM,IAAI,eAAe,CAAC,CAAC,KAAK,GAAG,EAAE;CAC1E,IAAI,SAAS,OAAO,UAAU,UAC5B,OAAO,IAAI,OAAO,QAAQ,KAAK,CAAC,CAC7B,QAAQ,GAAG,OAAO,MAAM,MAAS,CAAC,CAClC,KAAK,CAAC,GAAG,OAAO,GAAG,KAAK,UAAU,CAAC,EAAE,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAC7D,KAAK,CAAC,CACN,KAAK,GAAG,EAAE;CAEf,OAAO,KAAK,UAAU,KAAK,KAAK;AAClC"}
1
+ {"version":3,"file":"version-manifest.cjs","names":["z","createHash"],"sources":["../src/version-manifest.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { z } from \"zod\";\n\n/**\n * WorkspaceVersionManifest — the immutable per-deploy document at\n * `bundles/<account>/<gateway>/<workspace>/versions/<version>.json`\n * (ADR 0020, as amended; atomic-deploys MAP decision 1). The published\n * config's slot pins this document via its `pin` (`manifest` + the\n * manifest's own SRI); every artifact coordinate lives here, so the config\n * stops growing with artifact kinds and old versions stay fully servable.\n */\n\nconst SRI = z.string().regex(/^sha384-[A-Za-z0-9+/=]+$/);\n\nexport const WorkspaceVersionManifestSchema = z.object({\n version: z.string().regex(/^[0-9a-f]{16}$/),\n builtAt: z.string().optional(),\n gitSha: z.string().optional(),\n entry: z.string().min(1),\n entryIntegrity: SRI,\n ssr: z\n .object({\n entry: z.string().min(1),\n integrity: SRI,\n })\n .optional(),\n browserManifest: z\n .object({\n file: z.string().min(1),\n integrity: SRI,\n })\n .optional(),\n /** extra assets (css, icons) — logical name → served-object SRI */\n assets: z.record(z.string(), SRI).optional(),\n /** shared-dependency versions this build was built against */\n shared: z.record(z.string(), z.string()).optional(),\n /**\n * Full per-file digest map of the dist (object path → served-object SRI).\n * Written by `every-plugin`-based deploys so the next deploy can diff against\n * it and skip re-uploading unchanged files; readers that predate the field\n * ignore it (zod strips unknown keys). Participates in the version hash.\n */\n files: z.record(z.string(), SRI).optional(),\n});\nexport type WorkspaceVersionManifest = z.infer<typeof WorkspaceVersionManifestSchema>;\n\nexport interface VersionManifestInput {\n /** Omit: a per-build timestamp makes the same version id map to different\n * manifest bytes per machine, breaking the pin's SRI against the image's\n * staged copy and the CDN's immutability for republished versions. */\n builtAt?: string;\n gitSha?: string;\n entry: string;\n entryIntegrity: string;\n ssr?: { entry: string; integrity: string };\n browserManifest?: { file: string; integrity: string };\n assets?: Record<string, string>;\n shared?: Record<string, string>;\n files?: Record<string, string>;\n}\n\n/**\n * Compose the manifest with its `version` derived from the canonical content\n * of the artifact coordinates alone (key-sorted, `builtAt` excluded) —\n * identical bytes → identical version id regardless of when they were built,\n * so republishing unchanged content is a pointer no-op. Any changed\n * coordinate → a new version (and a new file).\n */\nexport function composeVersionManifest(input: VersionManifestInput): WorkspaceVersionManifest {\n const { builtAt: _builtAt, ...hashedInputs } = input;\n const version = createHash(\"sha256\")\n .update(stableStringify(hashedInputs))\n .digest(\"hex\")\n .slice(0, 16);\n return WorkspaceVersionManifestSchema.parse({ ...input, version });\n}\n\nfunction stableStringify(value: unknown): string {\n if (Array.isArray(value)) return `[${value.map(stableStringify).join(\",\")}]`;\n if (value && typeof value === \"object\") {\n return `{${Object.entries(value)\n .filter(([, v]) => v !== undefined)\n .map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`)\n .sort()\n .join(\",\")}}`;\n }\n return JSON.stringify(value) ?? \"null\";\n}\n"],"mappings":";;;;;;;;;;;;;AAYA,MAAM,MAAMA,MAAE,OAAO,CAAC,CAAC,MAAM,0BAA0B;AAEvD,MAAa,iCAAiCA,MAAE,OAAO;CACrD,SAASA,MAAE,OAAO,CAAC,CAAC,MAAM,gBAAgB;CAC1C,SAASA,MAAE,OAAO,CAAC,CAAC,SAAS;CAC7B,QAAQA,MAAE,OAAO,CAAC,CAAC,SAAS;CAC5B,OAAOA,MAAE,OAAO,CAAC,CAAC,IAAI,CAAC;CACvB,gBAAgB;CAChB,KAAKA,MACF,OAAO;EACN,OAAOA,MAAE,OAAO,CAAC,CAAC,IAAI,CAAC;EACvB,WAAW;CACb,CAAC,CAAC,CACD,SAAS;CACZ,iBAAiBA,MACd,OAAO;EACN,MAAMA,MAAE,OAAO,CAAC,CAAC,IAAI,CAAC;EACtB,WAAW;CACb,CAAC,CAAC,CACD,SAAS;;CAEZ,QAAQA,MAAE,OAAOA,MAAE,OAAO,GAAG,GAAG,CAAC,CAAC,SAAS;;CAE3C,QAAQA,MAAE,OAAOA,MAAE,OAAO,GAAGA,MAAE,OAAO,CAAC,CAAC,CAAC,SAAS;;;;;;;CAOlD,OAAOA,MAAE,OAAOA,MAAE,OAAO,GAAG,GAAG,CAAC,CAAC,SAAS;AAC5C,CAAC;;;;;;;;AAyBD,SAAgB,uBAAuB,OAAuD;CAC5F,MAAM,EAAE,SAAS,UAAU,GAAG,iBAAiB;CAC/C,MAAM,cAAUC,wBAAW,QAAQ,CAAC,CACjC,OAAO,gBAAgB,YAAY,CAAC,CAAC,CACrC,OAAO,KAAK,CAAC,CACb,MAAM,GAAG,EAAE;CACd,OAAO,+BAA+B,MAAM;EAAE,GAAG;EAAO;CAAQ,CAAC;AACnE;AAEA,SAAS,gBAAgB,OAAwB;CAC/C,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,IAAI,MAAM,IAAI,eAAe,CAAC,CAAC,KAAK,GAAG,EAAE;CAC1E,IAAI,SAAS,OAAO,UAAU,UAC5B,OAAO,IAAI,OAAO,QAAQ,KAAK,CAAC,CAC7B,QAAQ,GAAG,OAAO,MAAM,MAAS,CAAC,CAClC,KAAK,CAAC,GAAG,OAAO,GAAG,KAAK,UAAU,CAAC,EAAE,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAC7D,KAAK,CAAC,CACN,KAAK,GAAG,EAAE;CAEf,OAAO,KAAK,UAAU,KAAK,KAAK;AAClC"}
@@ -45,7 +45,7 @@ const WorkspaceVersionManifestSchema = z.object({
45
45
  * coordinate → a new version (and a new file).
46
46
  */
47
47
  function composeVersionManifest(input) {
48
- const { builtAt, ...hashedInputs } = input;
48
+ const { builtAt: _builtAt, ...hashedInputs } = input;
49
49
  const version = createHash("sha256").update(stableStringify(hashedInputs)).digest("hex").slice(0, 16);
50
50
  return WorkspaceVersionManifestSchema.parse({
51
51
  ...input,
@@ -1 +1 @@
1
- {"version":3,"file":"version-manifest.mjs","names":[],"sources":["../src/version-manifest.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { z } from \"zod\";\n\n/**\n * WorkspaceVersionManifest — the immutable per-deploy document at\n * `bundles/<account>/<gateway>/<workspace>/versions/<version>.json`\n * (ADR 0020, as amended; atomic-deploys MAP decision 1). The published\n * config's slot pins this document via its `pin` (`manifest` + the\n * manifest's own SRI); every artifact coordinate lives here, so the config\n * stops growing with artifact kinds and old versions stay fully servable.\n */\n\nconst SRI = z.string().regex(/^sha384-[A-Za-z0-9+/=]+$/);\n\nexport const WorkspaceVersionManifestSchema = z.object({\n version: z.string().regex(/^[0-9a-f]{16}$/),\n builtAt: z.string().optional(),\n gitSha: z.string().optional(),\n entry: z.string().min(1),\n entryIntegrity: SRI,\n ssr: z\n .object({\n entry: z.string().min(1),\n integrity: SRI,\n })\n .optional(),\n browserManifest: z\n .object({\n file: z.string().min(1),\n integrity: SRI,\n })\n .optional(),\n /** extra assets (css, icons) — logical name → served-object SRI */\n assets: z.record(z.string(), SRI).optional(),\n /** shared-dependency versions this build was built against */\n shared: z.record(z.string(), z.string()).optional(),\n /**\n * Full per-file digest map of the dist (object path → served-object SRI).\n * Written by `every-plugin`-based deploys so the next deploy can diff against\n * it and skip re-uploading unchanged files; readers that predate the field\n * ignore it (zod strips unknown keys). Participates in the version hash.\n */\n files: z.record(z.string(), SRI).optional(),\n});\nexport type WorkspaceVersionManifest = z.infer<typeof WorkspaceVersionManifestSchema>;\n\nexport interface VersionManifestInput {\n /** Omit: a per-build timestamp makes the same version id map to different\n * manifest bytes per machine, breaking the pin's SRI against the image's\n * staged copy and the CDN's immutability for republished versions. */\n builtAt?: string;\n gitSha?: string;\n entry: string;\n entryIntegrity: string;\n ssr?: { entry: string; integrity: string };\n browserManifest?: { file: string; integrity: string };\n assets?: Record<string, string>;\n shared?: Record<string, string>;\n files?: Record<string, string>;\n}\n\n/**\n * Compose the manifest with its `version` derived from the canonical content\n * of the artifact coordinates alone (key-sorted, `builtAt` excluded) —\n * identical bytes → identical version id regardless of when they were built,\n * so republishing unchanged content is a pointer no-op. Any changed\n * coordinate → a new version (and a new file).\n */\nexport function composeVersionManifest(input: VersionManifestInput): WorkspaceVersionManifest {\n const { builtAt, ...hashedInputs } = input;\n const version = createHash(\"sha256\")\n .update(stableStringify(hashedInputs))\n .digest(\"hex\")\n .slice(0, 16);\n return WorkspaceVersionManifestSchema.parse({ ...input, version });\n}\n\nfunction stableStringify(value: unknown): string {\n if (Array.isArray(value)) return `[${value.map(stableStringify).join(\",\")}]`;\n if (value && typeof value === \"object\") {\n return `{${Object.entries(value)\n .filter(([, v]) => v !== undefined)\n .map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`)\n .sort()\n .join(\",\")}}`;\n }\n return JSON.stringify(value) ?? \"null\";\n}\n"],"mappings":";;;;;;;;;;;;AAYA,MAAM,MAAM,EAAE,OAAO,CAAC,CAAC,MAAM,0BAA0B;AAEvD,MAAa,iCAAiC,EAAE,OAAO;CACrD,SAAS,EAAE,OAAO,CAAC,CAAC,MAAM,gBAAgB;CAC1C,SAAS,EAAE,OAAO,CAAC,CAAC,SAAS;CAC7B,QAAQ,EAAE,OAAO,CAAC,CAAC,SAAS;CAC5B,OAAO,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;CACvB,gBAAgB;CAChB,KAAK,EACF,OAAO;EACN,OAAO,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;EACvB,WAAW;CACb,CAAC,CAAC,CACD,SAAS;CACZ,iBAAiB,EACd,OAAO;EACN,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;EACtB,WAAW;CACb,CAAC,CAAC,CACD,SAAS;;CAEZ,QAAQ,EAAE,OAAO,EAAE,OAAO,GAAG,GAAG,CAAC,CAAC,SAAS;;CAE3C,QAAQ,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS;;;;;;;CAOlD,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,GAAG,CAAC,CAAC,SAAS;AAC5C,CAAC;;;;;;;;AAyBD,SAAgB,uBAAuB,OAAuD;CAC5F,MAAM,EAAE,SAAS,GAAG,iBAAiB;CACrC,MAAM,UAAU,WAAW,QAAQ,CAAC,CACjC,OAAO,gBAAgB,YAAY,CAAC,CAAC,CACrC,OAAO,KAAK,CAAC,CACb,MAAM,GAAG,EAAE;CACd,OAAO,+BAA+B,MAAM;EAAE,GAAG;EAAO;CAAQ,CAAC;AACnE;AAEA,SAAS,gBAAgB,OAAwB;CAC/C,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,IAAI,MAAM,IAAI,eAAe,CAAC,CAAC,KAAK,GAAG,EAAE;CAC1E,IAAI,SAAS,OAAO,UAAU,UAC5B,OAAO,IAAI,OAAO,QAAQ,KAAK,CAAC,CAC7B,QAAQ,GAAG,OAAO,MAAM,MAAS,CAAC,CAClC,KAAK,CAAC,GAAG,OAAO,GAAG,KAAK,UAAU,CAAC,EAAE,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAC7D,KAAK,CAAC,CACN,KAAK,GAAG,EAAE;CAEf,OAAO,KAAK,UAAU,KAAK,KAAK;AAClC"}
1
+ {"version":3,"file":"version-manifest.mjs","names":[],"sources":["../src/version-manifest.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { z } from \"zod\";\n\n/**\n * WorkspaceVersionManifest — the immutable per-deploy document at\n * `bundles/<account>/<gateway>/<workspace>/versions/<version>.json`\n * (ADR 0020, as amended; atomic-deploys MAP decision 1). The published\n * config's slot pins this document via its `pin` (`manifest` + the\n * manifest's own SRI); every artifact coordinate lives here, so the config\n * stops growing with artifact kinds and old versions stay fully servable.\n */\n\nconst SRI = z.string().regex(/^sha384-[A-Za-z0-9+/=]+$/);\n\nexport const WorkspaceVersionManifestSchema = z.object({\n version: z.string().regex(/^[0-9a-f]{16}$/),\n builtAt: z.string().optional(),\n gitSha: z.string().optional(),\n entry: z.string().min(1),\n entryIntegrity: SRI,\n ssr: z\n .object({\n entry: z.string().min(1),\n integrity: SRI,\n })\n .optional(),\n browserManifest: z\n .object({\n file: z.string().min(1),\n integrity: SRI,\n })\n .optional(),\n /** extra assets (css, icons) — logical name → served-object SRI */\n assets: z.record(z.string(), SRI).optional(),\n /** shared-dependency versions this build was built against */\n shared: z.record(z.string(), z.string()).optional(),\n /**\n * Full per-file digest map of the dist (object path → served-object SRI).\n * Written by `every-plugin`-based deploys so the next deploy can diff against\n * it and skip re-uploading unchanged files; readers that predate the field\n * ignore it (zod strips unknown keys). Participates in the version hash.\n */\n files: z.record(z.string(), SRI).optional(),\n});\nexport type WorkspaceVersionManifest = z.infer<typeof WorkspaceVersionManifestSchema>;\n\nexport interface VersionManifestInput {\n /** Omit: a per-build timestamp makes the same version id map to different\n * manifest bytes per machine, breaking the pin's SRI against the image's\n * staged copy and the CDN's immutability for republished versions. */\n builtAt?: string;\n gitSha?: string;\n entry: string;\n entryIntegrity: string;\n ssr?: { entry: string; integrity: string };\n browserManifest?: { file: string; integrity: string };\n assets?: Record<string, string>;\n shared?: Record<string, string>;\n files?: Record<string, string>;\n}\n\n/**\n * Compose the manifest with its `version` derived from the canonical content\n * of the artifact coordinates alone (key-sorted, `builtAt` excluded) —\n * identical bytes → identical version id regardless of when they were built,\n * so republishing unchanged content is a pointer no-op. Any changed\n * coordinate → a new version (and a new file).\n */\nexport function composeVersionManifest(input: VersionManifestInput): WorkspaceVersionManifest {\n const { builtAt: _builtAt, ...hashedInputs } = input;\n const version = createHash(\"sha256\")\n .update(stableStringify(hashedInputs))\n .digest(\"hex\")\n .slice(0, 16);\n return WorkspaceVersionManifestSchema.parse({ ...input, version });\n}\n\nfunction stableStringify(value: unknown): string {\n if (Array.isArray(value)) return `[${value.map(stableStringify).join(\",\")}]`;\n if (value && typeof value === \"object\") {\n return `{${Object.entries(value)\n .filter(([, v]) => v !== undefined)\n .map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`)\n .sort()\n .join(\",\")}}`;\n }\n return JSON.stringify(value) ?? \"null\";\n}\n"],"mappings":";;;;;;;;;;;;AAYA,MAAM,MAAM,EAAE,OAAO,CAAC,CAAC,MAAM,0BAA0B;AAEvD,MAAa,iCAAiC,EAAE,OAAO;CACrD,SAAS,EAAE,OAAO,CAAC,CAAC,MAAM,gBAAgB;CAC1C,SAAS,EAAE,OAAO,CAAC,CAAC,SAAS;CAC7B,QAAQ,EAAE,OAAO,CAAC,CAAC,SAAS;CAC5B,OAAO,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;CACvB,gBAAgB;CAChB,KAAK,EACF,OAAO;EACN,OAAO,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;EACvB,WAAW;CACb,CAAC,CAAC,CACD,SAAS;CACZ,iBAAiB,EACd,OAAO;EACN,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;EACtB,WAAW;CACb,CAAC,CAAC,CACD,SAAS;;CAEZ,QAAQ,EAAE,OAAO,EAAE,OAAO,GAAG,GAAG,CAAC,CAAC,SAAS;;CAE3C,QAAQ,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS;;;;;;;CAOlD,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,GAAG,CAAC,CAAC,SAAS;AAC5C,CAAC;;;;;;;;AAyBD,SAAgB,uBAAuB,OAAuD;CAC5F,MAAM,EAAE,SAAS,UAAU,GAAG,iBAAiB;CAC/C,MAAM,UAAU,WAAW,QAAQ,CAAC,CACjC,OAAO,gBAAgB,YAAY,CAAC,CAAC,CACrC,OAAO,KAAK,CAAC,CACb,MAAM,GAAG,EAAE;CACd,OAAO,+BAA+B,MAAM;EAAE,GAAG;EAAO;CAAQ,CAAC;AACnE;AAEA,SAAS,gBAAgB,OAAwB;CAC/C,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,IAAI,MAAM,IAAI,eAAe,CAAC,CAAC,KAAK,GAAG,EAAE;CAC1E,IAAI,SAAS,OAAO,UAAU,UAC5B,OAAO,IAAI,OAAO,QAAQ,KAAK,CAAC,CAC7B,QAAQ,GAAG,OAAO,MAAM,MAAS,CAAC,CAClC,KAAK,CAAC,GAAG,OAAO,GAAG,KAAK,UAAU,CAAC,EAAE,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAC7D,KAAK,CAAC,CACN,KAAK,GAAG,EAAE;CAEf,OAAO,KAAK,UAAU,KAAK,KAAK;AAClC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "every-plugin",
3
- "version": "3.0.0-rc.0",
3
+ "version": "3.0.0-rc.3",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
package/src/errors.ts CHANGED
@@ -125,9 +125,10 @@ export const PLUGIN_ERROR_STATUS_MAP: Record<string, number> = {
125
125
  CONNECTION_ERROR: 502,
126
126
  } as const;
127
127
 
128
- export type { PluginLoadFailureInfo } from "./runtime/errors";
128
+ export type { PluginLoadFailureInfo, PluginWarningInfo } from "./runtime/errors";
129
129
  export {
130
130
  classifyPluginFailure,
131
+ defaultMissingSecretWarnings,
131
132
  extractFromFiberFailure,
132
133
  formatORPCError,
133
134
  ModuleFederationError,
package/src/plugin.ts CHANGED
@@ -5,6 +5,7 @@ import type { ContractedRouter, Implementer } from "@orpc/server";
5
5
  import { implement } from "@orpc/server";
6
6
  import type { Scope } from "effect";
7
7
  import { Context, Effect, Layer } from "effect";
8
+ import type { PluginWarningInfo } from "./runtime/errors";
8
9
  import { type PluginRuntimeError, toPluginRuntimeError } from "./runtime/errors";
9
10
 
10
11
  type ContextOutput<T> = T extends AnySchema ? InferSchemaOutput<T> : Record<string, never>;
@@ -19,11 +20,20 @@ export type PluginConfigFor<
19
20
  context: TRequestContext;
20
21
  };
21
22
 
22
- type PluginInitializeInput<V extends AnySchema, S extends AnySchema> = {
23
+ export type PluginInitializeInput<V extends AnySchema, S extends AnySchema> = {
23
24
  variables: InferSchemaOutput<V>;
24
25
  secrets: InferSchemaOutput<S>;
25
26
  };
26
27
 
28
+ /**
29
+ * A plugin's own config-derived warnings, evaluated once with the validated
30
+ * config after initialization. Declaring `warnings` opts the plugin out of
31
+ * the loader's default missing-secret warning — these messages replace it.
32
+ */
33
+ export type PluginWarningsFn<V extends AnySchema, S extends AnySchema> = (
34
+ config: PluginInitializeInput<V, S>,
35
+ ) => PluginWarningInfo[] | string[];
36
+
27
37
  export class PluginIdTag extends Context.Service<PluginIdTag, string>()("PluginId") {}
28
38
 
29
39
  /**
@@ -50,6 +60,13 @@ type PluginDefinition<
50
60
  * module statically.
51
61
  */
52
62
  servicesTag?: Context.Key<any, any>;
63
+ /**
64
+ * Optional warnings function evaluated once with the validated config after
65
+ * initialization. Returns structured non-fatal warnings (e.g. an optional
66
+ * secret was not set, so the feature depending on it is disabled).
67
+ * Declaring it opts out of the loader's default missing-secret warning.
68
+ */
69
+ warnings?: PluginWarningsFn<V, S>;
53
70
  /**
54
71
  * Initialize the plugin by returning an Effect `Layer`.
55
72
  *
@@ -107,6 +124,7 @@ export interface Plugin<
107
124
  readonly contract: TContract;
108
125
  readonly configSchema: PluginConfigFor<TVariables, TSecrets, TRequestContext>;
109
126
  readonly servicesTag?: Context.Key<any, any>;
127
+ readonly warnings?: PluginWarningsFn<TVariables, TSecrets>;
110
128
 
111
129
  initialize(
112
130
  config: PluginInitializeInput<TVariables, TSecrets>,
@@ -156,6 +174,7 @@ export const createPlugin: CreatePluginFn = function createPlugin<
156
174
  readonly contract = config.contract;
157
175
  readonly configSchema = configSchema;
158
176
  readonly servicesTag = config.servicesTag;
177
+ readonly warnings = config.warnings;
159
178
 
160
179
  initialize(
161
180
  pluginConfig: PluginInitializeInput<V, S>,
@@ -282,6 +282,50 @@ export interface PluginLoadFailureInfo {
282
282
  dbUrlMasked?: string;
283
283
  }
284
284
 
285
+ /**
286
+ * Structured, serializable warning surfaced alongside plugin failures in
287
+ * health endpoints and dashboards. Warnings are non-fatal: the plugin loaded
288
+ * and initialized successfully, but runs with reduced functionality (e.g. an
289
+ * optional secret was not set, so the feature depending on it is disabled).
290
+ */
291
+ export interface PluginWarningInfo {
292
+ /** loader-side key (host entry key, dev plugin id) */
293
+ pluginKey: string;
294
+ message: string;
295
+ hint?: string;
296
+ }
297
+
298
+ /**
299
+ * The loader's default warning policy for declared-but-unset secrets: a
300
+ * successfully initialized plugin whose declared secret names resolved to
301
+ * nothing (missing or empty in the environment) gets one consolidated
302
+ * warning. `*_DATABASE_URL` keys are excluded — they have the shared-API-DB
303
+ * fallback, and a genuinely-missing DB URL fails initialization (a `failure`,
304
+ * not a warning). A plugin that declares its own `warnings` function opts out
305
+ * of this default.
306
+ */
307
+ export const defaultMissingSecretWarnings = (
308
+ pluginKey: string,
309
+ declaredKeys: readonly string[],
310
+ resolvedKeys: readonly string[],
311
+ ): PluginWarningInfo[] => {
312
+ const resolved = new Set(resolvedKeys);
313
+ const missing = declaredKeys.filter(
314
+ (key) => !resolved.has(key) && !key.endsWith("_DATABASE_URL"),
315
+ );
316
+ if (missing.length === 0) return [];
317
+ return [
318
+ {
319
+ pluginKey,
320
+ message:
321
+ missing.length === 1
322
+ ? `Declared secret ${missing[0]} is not set — features depending on it are disabled`
323
+ : `${missing.length} declared secrets are not set: ${missing.join(", ")} — features depending on them are disabled`,
324
+ hint: "Set the secret(s) in your environment (.env in development); see the plugin's config schema for what each enables",
325
+ },
326
+ ];
327
+ };
328
+
285
329
  const NETWORK_RETRYABLE_PATTERNS = ["ETIMEDOUT", "ECONNRESET", "timeout", "503", "429"] as const;
286
330
  const NETWORK_DEAD_PATTERNS = ["ECONNREFUSED", "ENOTFOUND", "EHOSTUNREACH"] as const;
287
331
  const RETRYABLE_ORPC_CODES = new Set([
@@ -111,13 +111,10 @@ const resolveRemoteSelfName = (pluginId: string, url: string) =>
111
111
  const memo = remoteSelfNames.get(memoKey);
112
112
  if (memo) return memo;
113
113
 
114
- let selfName: string | null = null;
115
- try {
116
- const manifest = yield* Effect.promise(() => fetchRemoteIdentityManifest(url));
117
- selfName = manifest?.metaData?.name?.trim() || null;
118
- } catch {
119
- selfName = null;
120
- }
114
+ const manifest = yield* Effect.tryPromise(() => fetchRemoteIdentityManifest(url)).pipe(
115
+ Effect.orElseSucceed(() => null),
116
+ );
117
+ const selfName = manifest?.metaData?.name?.trim() || null;
121
118
 
122
119
  const resolved = selfName ?? getNormalizedRemoteName(pluginId);
123
120
  remoteSelfNames.set(memoKey, resolved);
@@ -11,11 +11,39 @@ import type {
11
11
  PluginMetadata,
12
12
  PluginRegistry,
13
13
  } from "../../types";
14
+ import type { PluginWarningInfo } from "../errors";
14
15
  import { PluginRuntimeError, toPluginRuntimeError } from "../errors";
15
16
  import { validate } from "../validation";
16
17
  import { ModuleFederationService } from "./module-federation.service";
17
18
  import { SecretsService } from "./secrets.service";
18
19
 
20
+ /**
21
+ * Evaluate a plugin's `warnings` function against the validated config.
22
+ * Fail-safe: a throwing warnings function becomes a warning itself rather
23
+ * than failing initialization. Strings are normalized to `PluginWarningInfo`
24
+ * with the plugin's registry key.
25
+ */
26
+ const collectPluginWarnings = (
27
+ plugin: AnyPlugin,
28
+ config: { variables: unknown; secrets: unknown },
29
+ ): PluginWarningInfo[] => {
30
+ const warningsFn = plugin.warnings;
31
+ if (!warningsFn) return [];
32
+ try {
33
+ const raw = warningsFn(config as never);
34
+ return raw.map((warning) =>
35
+ typeof warning === "string" ? { pluginKey: plugin.id, message: warning } : warning,
36
+ );
37
+ } catch (error) {
38
+ return [
39
+ {
40
+ pluginKey: plugin.id,
41
+ message: `Plugin warnings check failed: ${error instanceof Error ? error.message : String(error)}`,
42
+ },
43
+ ];
44
+ }
45
+ };
46
+
19
47
  export class PluginRegistryTag extends Context.Service<PluginRegistryTag, PluginRegistry>()(
20
48
  "PluginRegistry",
21
49
  ) {}
@@ -313,6 +341,10 @@ export const PluginLoaderServiceDefault = Layer.effect(
313
341
  config: { variables: _variables, secrets: hydratedConfig.secrets },
314
342
  effectContext,
315
343
  scope,
344
+ warnings: collectPluginWarnings(plugin, {
345
+ variables: _variables,
346
+ secrets: hydratedConfig.secrets,
347
+ }),
316
348
  } satisfies InitializedPlugin<T>;
317
349
  }),
318
350
  };
@@ -19,7 +19,7 @@ function manifestUrl(remoteUrl: string): string {
19
19
 
20
20
  function normalizeBaseUrl(remoteUrl: string): string {
21
21
  const withoutHash = remoteUrl.split("#")[0] ?? remoteUrl;
22
- return withoutHash.replace(/\/(mf-manifest\.json|remoteEntry\.js)$/i, "");
22
+ return withoutHash.replace(/\/(mf-manifest\.json|remoteEntry(?:\.[^/]+)?\.js)$/i, "");
23
23
  }
24
24
 
25
25
  export function compareSharedIdentity(
package/src/types.ts CHANGED
@@ -7,6 +7,7 @@ import type {
7
7
  import type { ContractedRouter, RouterClient } from "@orpc/server";
8
8
  import type { Context, Scope } from "effect";
9
9
  import type { Plugin } from "./plugin";
10
+ import type { PluginWarningInfo } from "./runtime/errors";
10
11
 
11
12
  /**
12
13
  * Registry bindings interface - populated via module augmentation
@@ -204,6 +205,8 @@ export interface InitializedPlugin<T extends AnyPlugin = AnyPlugin> {
204
205
  */
205
206
  readonly effectContext: Context.Context<any>;
206
207
  readonly scope: Scope.Closeable;
208
+ /** Non-fatal config warnings collected during initialization (empty when none). */
209
+ readonly warnings: PluginWarningInfo[];
207
210
  }
208
211
 
209
212
  /**
@@ -67,7 +67,7 @@ export interface VersionManifestInput {
67
67
  * coordinate → a new version (and a new file).
68
68
  */
69
69
  export function composeVersionManifest(input: VersionManifestInput): WorkspaceVersionManifest {
70
- const { builtAt, ...hashedInputs } = input;
70
+ const { builtAt: _builtAt, ...hashedInputs } = input;
71
71
  const version = createHash("sha256")
72
72
  .update(stableStringify(hashedInputs))
73
73
  .digest("hex")