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.
- package/dist/errors.cjs +1 -0
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.d.cts +2 -2
- package/dist/errors.d.mts +2 -2
- package/dist/errors.mjs +2 -2
- package/dist/errors.mjs.map +1 -1
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +3 -3
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +2 -2
- package/dist/plugin.cjs +1 -0
- package/dist/plugin.cjs.map +1 -1
- package/dist/plugin.d.cts +16 -2
- package/dist/plugin.d.cts.map +1 -1
- package/dist/plugin.d.mts +16 -2
- package/dist/plugin.d.mts.map +1 -1
- package/dist/plugin.mjs +1 -0
- package/dist/plugin.mjs.map +1 -1
- package/dist/runtime/errors.cjs +20 -0
- package/dist/runtime/errors.cjs.map +1 -1
- package/dist/runtime/errors.d.cts +23 -1
- package/dist/runtime/errors.d.cts.map +1 -1
- package/dist/runtime/errors.d.mts +23 -1
- package/dist/runtime/errors.d.mts.map +1 -1
- package/dist/runtime/errors.mjs +20 -1
- package/dist/runtime/errors.mjs.map +1 -1
- package/dist/runtime/mf-config.cjs +1 -1
- package/dist/runtime/mf-config.mjs +1 -1
- package/dist/runtime/services/module-federation.service.cjs +1 -7
- package/dist/runtime/services/module-federation.service.cjs.map +1 -1
- package/dist/runtime/services/module-federation.service.mjs +1 -7
- package/dist/runtime/services/module-federation.service.mjs.map +1 -1
- package/dist/runtime/services/plugin-loader.service.cjs +26 -1
- package/dist/runtime/services/plugin-loader.service.cjs.map +1 -1
- package/dist/runtime/services/plugin-loader.service.mjs +26 -1
- package/dist/runtime/services/plugin-loader.service.mjs.map +1 -1
- package/dist/runtime/services/shared-identity.cjs +1 -1
- package/dist/runtime/services/shared-identity.cjs.map +1 -1
- package/dist/runtime/services/shared-identity.mjs +1 -1
- package/dist/runtime/services/shared-identity.mjs.map +1 -1
- package/dist/types.d.cts +3 -0
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.mts +3 -0
- package/dist/types.d.mts.map +1 -1
- package/dist/version-manifest.cjs +1 -1
- package/dist/version-manifest.cjs.map +1 -1
- package/dist/version-manifest.mjs +1 -1
- package/dist/version-manifest.mjs.map +1 -1
- package/package.json +1 -1
- package/src/errors.ts +2 -1
- package/src/plugin.ts +20 -1
- package/src/runtime/errors.ts +44 -0
- package/src/runtime/services/module-federation.service.ts +4 -7
- package/src/runtime/services/plugin-loader.service.ts +32 -0
- package/src/runtime/services/shared-identity.ts +1 -1
- package/src/types.ts +3 -0
- package/src/version-manifest.ts +1 -1
|
@@ -57,6 +57,28 @@ interface PluginLoadFailureInfo {
|
|
|
57
57
|
dbSecret?: string;
|
|
58
58
|
dbUrlMasked?: string;
|
|
59
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Structured, serializable warning surfaced alongside plugin failures in
|
|
62
|
+
* health endpoints and dashboards. Warnings are non-fatal: the plugin loaded
|
|
63
|
+
* and initialized successfully, but runs with reduced functionality (e.g. an
|
|
64
|
+
* optional secret was not set, so the feature depending on it is disabled).
|
|
65
|
+
*/
|
|
66
|
+
interface PluginWarningInfo {
|
|
67
|
+
/** loader-side key (host entry key, dev plugin id) */
|
|
68
|
+
pluginKey: string;
|
|
69
|
+
message: string;
|
|
70
|
+
hint?: string;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The loader's default warning policy for declared-but-unset secrets: a
|
|
74
|
+
* successfully initialized plugin whose declared secret names resolved to
|
|
75
|
+
* nothing (missing or empty in the environment) gets one consolidated
|
|
76
|
+
* warning. `*_DATABASE_URL` keys are excluded — they have the shared-API-DB
|
|
77
|
+
* fallback, and a genuinely-missing DB URL fails initialization (a `failure`,
|
|
78
|
+
* not a warning). A plugin that declares its own `warnings` function opts out
|
|
79
|
+
* of this default.
|
|
80
|
+
*/
|
|
81
|
+
declare const defaultMissingSecretWarnings: (pluginKey: string, declaredKeys: readonly string[], resolvedKeys: readonly string[]) => PluginWarningInfo[];
|
|
60
82
|
/**
|
|
61
83
|
* One classification for plugin lifecycle failures across MF / validation /
|
|
62
84
|
* network sources. This is the recovery policy — nothing in the runtime
|
|
@@ -74,5 +96,5 @@ declare const wrapORPCError: (orpcError: ORPCError<string, unknown>, pluginId?:
|
|
|
74
96
|
declare const extractFromFiberFailure: (error: unknown) => unknown;
|
|
75
97
|
declare const toPluginRuntimeError: (error: unknown, pluginId?: string, procedureName?: string, operation?: string) => PluginRuntimeError;
|
|
76
98
|
//#endregion
|
|
77
|
-
export { ModuleFederationError, PluginFailureClassification, PluginFailureKind, PluginLoadFailureInfo, PluginRuntimeError, ValidationError, classifyPluginFailure, extractFromFiberFailure, formatORPCError, toPluginRuntimeError, wrapORPCError };
|
|
99
|
+
export { ModuleFederationError, PluginFailureClassification, PluginFailureKind, PluginLoadFailureInfo, PluginRuntimeError, PluginWarningInfo, ValidationError, classifyPluginFailure, defaultMissingSecretWarnings, extractFromFiberFailure, formatORPCError, toPluginRuntimeError, wrapORPCError };
|
|
78
100
|
//# sourceMappingURL=errors.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.mts","names":[],"sources":["../../src/runtime/errors.ts"],"mappings":";;;;;;;cAIa,2BAA2B;WAC7B;WACA;WACA;WACA,QAAQ;;MAEJ;;;;;cAKF,8BAA8B;WAChC;WACA;WACA,QAAQ;;;;;cAGN,wBAAwB;WAC1B;WACA;WACA,UAAU,EAAE;;cAuJV,kBAAe;KAyEhB;UAQK;EACf,MAAM;EACN;EACA;EACA;;;;;;;;UASe;;EAEf;EACA;;EAEA;EACA;EACA;EACA;EACA;;EAEA;EACA
|
|
1
|
+
{"version":3,"file":"errors.d.mts","names":[],"sources":["../../src/runtime/errors.ts"],"mappings":";;;;;;;cAIa,2BAA2B;WAC7B;WACA;WACA;WACA,QAAQ;;MAEJ;;;;;cAKF,8BAA8B;WAChC;WACA;WACA,QAAQ;;;;;cAGN,wBAAwB;WAC1B;WACA;WACA,UAAU,EAAE;;cAuJV,kBAAe;KAyEhB;UAQK;EACf,MAAM;EACN;EACA;EACA;;;;;;;;UASe;;EAEf;EACA;;EAEA;EACA;EACA;EACA;EACA;;EAEA;EACA;;;;;;;;UASe;;EAEf;EACA;EACA;;;;;;;;;;;cAYW,+BAA4B,mBACtB,iCACc,oCAE9B;;;;;;;;cAmCU,wBAAqB,mBAAqB;cAsE1C,gBAAa,WACb,4BAA0B,mBACpB,wBACK,uBAErB;;;;;;cAcU,0BAAuB;cA2BvB,uBAAoB,gBACjB,mBACG,wBACK,uBAErB"}
|
package/dist/runtime/errors.mjs
CHANGED
|
@@ -148,6 +148,25 @@ const formatORPCError = (error) => {
|
|
|
148
148
|
lines.push(`╰${"─".repeat(50)}\n`);
|
|
149
149
|
return lines.join("\n");
|
|
150
150
|
};
|
|
151
|
+
/**
|
|
152
|
+
* The loader's default warning policy for declared-but-unset secrets: a
|
|
153
|
+
* successfully initialized plugin whose declared secret names resolved to
|
|
154
|
+
* nothing (missing or empty in the environment) gets one consolidated
|
|
155
|
+
* warning. `*_DATABASE_URL` keys are excluded — they have the shared-API-DB
|
|
156
|
+
* fallback, and a genuinely-missing DB URL fails initialization (a `failure`,
|
|
157
|
+
* not a warning). A plugin that declares its own `warnings` function opts out
|
|
158
|
+
* of this default.
|
|
159
|
+
*/
|
|
160
|
+
const defaultMissingSecretWarnings = (pluginKey, declaredKeys, resolvedKeys) => {
|
|
161
|
+
const resolved = new Set(resolvedKeys);
|
|
162
|
+
const missing = declaredKeys.filter((key) => !resolved.has(key) && !key.endsWith("_DATABASE_URL"));
|
|
163
|
+
if (missing.length === 0) return [];
|
|
164
|
+
return [{
|
|
165
|
+
pluginKey,
|
|
166
|
+
message: missing.length === 1 ? `Declared secret ${missing[0]} is not set — features depending on it are disabled` : `${missing.length} declared secrets are not set: ${missing.join(", ")} — features depending on them are disabled`,
|
|
167
|
+
hint: "Set the secret(s) in your environment (.env in development); see the plugin's config schema for what each enables"
|
|
168
|
+
}];
|
|
169
|
+
};
|
|
151
170
|
const NETWORK_RETRYABLE_PATTERNS = [
|
|
152
171
|
"ETIMEDOUT",
|
|
153
172
|
"ECONNRESET",
|
|
@@ -266,5 +285,5 @@ const toPluginRuntimeError = (error, pluginId, procedureName, operation) => {
|
|
|
266
285
|
};
|
|
267
286
|
|
|
268
287
|
//#endregion
|
|
269
|
-
export { ModuleFederationError, PluginRuntimeError, ValidationError, classifyPluginFailure, deepestErrorStack, extractFromFiberFailure, formatORPCError, toPluginRuntimeError, wrapORPCError };
|
|
288
|
+
export { ModuleFederationError, PluginRuntimeError, ValidationError, classifyPluginFailure, deepestErrorStack, defaultMissingSecretWarnings, extractFromFiberFailure, formatORPCError, toPluginRuntimeError, wrapORPCError };
|
|
270
289
|
//# sourceMappingURL=errors.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.mjs","names":[],"sources":["../../src/runtime/errors.ts"],"sourcesContent":["import { ORPCError } from \"@orpc/server\";\nimport { Cause, Data } from \"effect\";\nimport type { z } from \"zod\";\n\nexport class PluginRuntimeError extends Data.TaggedError(\"PluginRuntimeError\")<{\n readonly pluginId?: string;\n readonly operation?: string;\n readonly procedureName?: string;\n readonly cause?: Error;\n}> {\n override get message() {\n return this.cause?.message ?? \"Plugin operation failed\";\n }\n}\n\nexport class ModuleFederationError extends Data.TaggedError(\"ModuleFederationError\")<{\n readonly pluginId: string;\n readonly remoteUrl: string;\n readonly cause?: Error;\n}> {}\n\nexport class ValidationError extends Data.TaggedError(\"ValidationError\")<{\n readonly pluginId: string;\n readonly stage: \"config\" | \"input\" | \"output\" | \"state\";\n readonly zodError: z.ZodError;\n}> {}\n\nconst stringifyUnknown = (value: unknown): string => {\n if (value === null) return \"null\";\n if (value === undefined) return \"undefined\";\n if (value instanceof Error) return value.message;\n if (typeof value === \"string\") return value;\n if (typeof value === \"number\" || typeof value === \"bigint\" || typeof value === \"boolean\") {\n return `${value}`;\n }\n if (typeof value === \"symbol\") return value.description ?? \"\";\n try {\n return JSON.stringify(value) ?? \"[Unserializable value]\";\n } catch {\n return \"[Unserializable value]\";\n }\n};\n\nconst extractErrorMessage = (error: unknown): string => {\n if (!error) return \"Unknown error\";\n\n if (error instanceof Error) {\n if (error.message) return error.message;\n if ((error as any).cause instanceof Error) {\n return extractErrorMessage((error as any).cause);\n }\n }\n\n if (error instanceof AggregateError && error.errors?.length) {\n return error.errors.map((e) => extractErrorMessage(e)).join(\"; \");\n }\n\n if (typeof error === \"object\" && \"message\" in error) {\n return String((error as any).message);\n }\n\n return stringifyUnknown(error);\n};\n\n/**\n * The deepest Error along the cause chain — the original throw site. Wrapper\n * errors (PluginRuntimeError, ModuleFederationError) carry their own stacks\n * (the wrapping frames); diagnosis needs the cause's stack (the actual\n * failing module inside the evaluated remote entry).\n */\nexport const deepestErrorStack = (error: unknown): string | undefined => {\n let current = error;\n let deepest: string | undefined;\n let hops = 0;\n while (current instanceof Error && hops < 8) {\n if (current.stack) deepest = current.stack;\n current = (current as { cause?: unknown }).cause as unknown;\n hops += 1;\n }\n if (current instanceof Error && current.stack) deepest = current.stack;\n return deepest;\n};\n\nconst formatValidationIssue = (issue: any, index: number, maxDisplay: number): string => {\n if (index >= maxDisplay) return \"\";\n\n const path =\n Array.isArray(issue.path) && issue.path.length > 0\n ? issue.path.join(\".\")\n : issue.path || \"root\";\n\n const message = issue.message || \"Validation failed\";\n\n return `│ ${index + 1}. ${path}: ${message}`;\n};\n\nconst formatDataPreview = (data: unknown, maxLength = 100): string => {\n if (!data) return \"undefined\";\n\n try {\n const str = JSON.stringify(data);\n if (str.length <= maxLength) return str;\n\n if (typeof data === \"object\" && data !== null) {\n if (Array.isArray(data)) {\n return `Array(${data.length}) [...]`;\n }\n const keys = Object.keys(data);\n return `{ ${keys.slice(0, 3).join(\", \")}${keys.length > 3 ? \", ...\" : \"\"} }`;\n }\n\n return `${str.slice(0, maxLength)}...`;\n } catch {\n return stringifyUnknown(data).slice(0, maxLength);\n }\n};\n\nconst formatORPCValidationError = (error: any): string[] | null => {\n const cause = error?.cause || error;\n\n if (!cause?.issues || !Array.isArray(cause.issues) || cause.issues.length === 0) {\n return null;\n }\n\n const lines: string[] = [];\n const errorType = error?.message || cause?.message || \"Validation failed\";\n\n lines.push(`\\n╭─ oRPC Validation Error ${\"─\".repeat(30)}`);\n lines.push(`│ ${errorType}`);\n lines.push(`│`);\n\n const maxDisplay = 10;\n const totalIssues = cause.issues.length;\n\n lines.push(`│ Issues (${totalIssues}):`);\n\n const displayedIssues = cause.issues.slice(0, maxDisplay);\n displayedIssues.forEach((issue: any, idx: number) => {\n const formatted = formatValidationIssue(issue, idx, maxDisplay);\n if (formatted) lines.push(formatted);\n });\n\n if (totalIssues > maxDisplay) {\n lines.push(`│ ... and ${totalIssues - maxDisplay} more`);\n }\n\n if (cause.data !== undefined) {\n lines.push(`│`);\n lines.push(`│ Data preview: ${formatDataPreview(cause.data, 80)}`);\n }\n\n lines.push(`╰${\"─\".repeat(50)}\\n`);\n\n return lines;\n};\n\nconst formatUnknownError = (error: any): string | null => {\n if (!error) return null;\n const message = extractErrorMessage(error);\n const lines: string[] = [];\n lines.push(`\\n╭─ Unhandled error ${\"─\".repeat(37)}`);\n for (const line of message.split(\"\\n\")) {\n lines.push(`│ ${line}`);\n }\n const stack = error instanceof Error ? error.stack?.split(\"\\n\").slice(1, 6) : undefined;\n if (stack && stack.length > 0) {\n lines.push(`│`);\n for (const line of stack) {\n lines.push(`│ ${line.trim()}`);\n }\n }\n lines.push(`╰${\"─\".repeat(50)}\\n`);\n return lines.join(\"\\n\");\n};\n\nexport const formatORPCError = (error: any): string | null => {\n if (!(error instanceof ORPCError)) {\n // Unknown errors used to return null, leaving internal failures (e.g. a\n // storage backend throw) invisible in host logs while oRPC serialized\n // them client-side as a bare INTERNAL_SERVER_ERROR.\n return formatUnknownError(error);\n }\n\n const validationLines = formatORPCValidationError(error);\n if (validationLines) {\n return validationLines.join(\"\\n\");\n }\n\n const lines: string[] = [];\n const code = error.code || \"UNKNOWN\";\n const message = error.message || \"An error occurred\";\n\n lines.push(`\\n╭─ oRPC Error ${\"─\".repeat(40)}`);\n const messageLines = message.split(\"\\n\");\n for (const line of messageLines) {\n lines.push(`│ ${line}`);\n }\n lines.push(`│ Code: ${code}`);\n lines.push(`│`);\n\n if (error.data) {\n const dataType = typeof error.data;\n if (dataType === \"object\" && error.data !== null) {\n if (\"retryAfter\" in error.data) {\n lines.push(`│ Retry after: ${error.data.retryAfter} seconds`);\n }\n if (\"remainingRequests\" in error.data) {\n lines.push(`│ Remaining: ${error.data.remainingRequests} requests`);\n }\n if (\"host\" in error.data) {\n lines.push(`│ Host: ${error.data.host}`);\n }\n if (\"port\" in error.data) {\n lines.push(`│ Port: ${error.data.port}`);\n }\n if (\"suggestion\" in error.data) {\n lines.push(`│ → ${error.data.suggestion}`);\n }\n if (\"resource\" in error.data) {\n lines.push(`│ Resource: ${error.data.resource}`);\n }\n if (\"resourceId\" in error.data) {\n lines.push(`│ ID: ${error.data.resourceId}`);\n }\n }\n }\n\n switch (code) {\n case \"UNAUTHORIZED\":\n lines.push(`│ → Check your API key or credentials`);\n break;\n case \"TOO_MANY_REQUESTS\":\n lines.push(`│ → Wait before retrying`);\n break;\n case \"SERVICE_UNAVAILABLE\":\n case \"BAD_GATEWAY\":\n case \"GATEWAY_TIMEOUT\":\n lines.push(`│ → The service may be temporarily unavailable`);\n break;\n case \"TIMEOUT\":\n lines.push(`│ → The operation took too long`);\n break;\n }\n\n lines.push(`╰${\"─\".repeat(50)}\\n`);\n return lines.join(\"\\n\");\n};\n\nexport type PluginFailureKind =\n | \"mf\"\n | \"integrity\"\n | \"config\"\n | \"validation\"\n | \"network\"\n | \"unknown\";\n\nexport interface PluginFailureClassification {\n kind: PluginFailureKind;\n retryable: boolean;\n message: string;\n suggestion?: string;\n}\n\n/**\n * Structured report of one plugin-load failure, built by a loader (the host's\n * plugin boot, the dev server) from a `PluginFailureClassification` plus\n * loader context. This is the shape health endpoints and dashboards consume —\n * keep it serializable.\n */\nexport interface PluginLoadFailureInfo {\n /** loader-side key (host entry key, dev plugin id) */\n pluginKey: string;\n pluginUrl?: string;\n /** failing runtime stage when the cause is stage-attributed */\n operation?: string;\n kind: string;\n retryable: boolean;\n message: string;\n suggestion?: string;\n /** loader context: which env secret the plugin's DB binding expects */\n dbSecret?: string;\n dbUrlMasked?: string;\n}\n\nconst NETWORK_RETRYABLE_PATTERNS = [\"ETIMEDOUT\", \"ECONNRESET\", \"timeout\", \"503\", \"429\"] as const;\nconst NETWORK_DEAD_PATTERNS = [\"ECONNREFUSED\", \"ENOTFOUND\", \"EHOSTUNREACH\"] as const;\nconst RETRYABLE_ORPC_CODES = new Set([\n \"TOO_MANY_REQUESTS\",\n \"SERVICE_UNAVAILABLE\",\n \"BAD_GATEWAY\",\n \"GATEWAY_TIMEOUT\",\n \"TIMEOUT\",\n]);\n\n/**\n * One classification for plugin lifecycle failures across MF / validation /\n * network sources. This is the recovery policy — nothing in the runtime\n * retries on it today by design (the host degrades and runs without a failed\n * plugin); consumers (host bootstrap messages, the dev server retry loop)\n * read kind / retryable / suggestion to decide what to tell the author.\n */\nexport const classifyPluginFailure = (error: unknown): PluginFailureClassification => {\n let current: unknown = extractFromFiberFailure(error);\n let hops = 0;\n while (current && typeof current === \"object\" && hops < 8) {\n if (current instanceof ModuleFederationError) {\n return {\n kind: \"mf\",\n retryable: false,\n message: extractErrorMessage(current),\n suggestion:\n \"Run bos mf check — the deployed plugin bundle is likely stale (sharedDep / pluginVersion skew), then redeploy the plugin and bos publish --deploy --packages local\",\n };\n }\n if (current instanceof ValidationError) {\n return {\n kind: \"validation\",\n retryable: false,\n message: `${current.stage}: ${extractErrorMessage(current.zodError)}`,\n suggestion: `Fix the plugin's ${current.stage} schema at plugins/${current.pluginId}`,\n };\n }\n if (current instanceof ORPCError) {\n const retryableByCode = RETRYABLE_ORPC_CODES.has(current.code);\n return {\n kind: \"network\",\n retryable: retryableByCode,\n message: current.message,\n suggestion: retryableByCode\n ? \"The plugin procedure failed transiently — retry the call\"\n : undefined,\n };\n }\n current = (current as { cause?: unknown }).cause;\n hops += 1;\n }\n\n const message = extractErrorMessage(error);\n const lower = message.toLowerCase();\n\n if (NETWORK_RETRYABLE_PATTERNS.some((p) => lower.includes(p.toLowerCase()))) {\n return {\n kind: \"network\",\n retryable: true,\n message,\n suggestion: \"The remote may be temporarily unavailable — retry after redeploying the plugin\",\n };\n }\n if (NETWORK_DEAD_PATTERNS.some((p) => lower.includes(p.toLowerCase()))) {\n return {\n kind: \"network\",\n retryable: false,\n message,\n suggestion:\n \"The plugin host is unreachable — check plugins.<id> entry URLs in bos.config.json\",\n };\n }\n if (lower.includes(\"integrity\") || lower.includes(\"sri\")) {\n return {\n kind: \"integrity\",\n retryable: true,\n message,\n suggestion:\n \"The deployed bundle content does not match its recorded integrity — redeploy the plugin so bos.config.json is republished with a fresh hash\",\n };\n }\n\n return { kind: \"unknown\", retryable: false, message };\n};\n\n// Convert ORPC errors from plugin procedures to PluginRuntimeError\nexport const wrapORPCError = (\n orpcError: ORPCError<string, unknown>,\n pluginId?: string,\n procedureName?: string,\n operation?: string,\n): PluginRuntimeError => {\n return new PluginRuntimeError({\n pluginId,\n operation,\n procedureName,\n cause: orpcError as Error,\n });\n};\n\n/**\n * Extracts the underlying error from Effect's FiberFailure wrapper.\n * When Effect.runPromise rejects, errors are wrapped in FiberFailure.\n * This extracts the original error so oRPC can handle it properly.\n */\nexport const extractFromFiberFailure = (error: unknown): unknown => {\n if (!error || typeof error !== \"object\") return error;\n\n if (\"cause\" in error) {\n const cause = (error as { cause?: unknown }).cause;\n\n if (cause && typeof cause === \"object\" && \"_tag\" in cause) {\n try {\n const squashed = Cause.squash(cause as unknown as Cause.Cause<unknown>);\n if (squashed instanceof ORPCError) {\n return squashed;\n }\n return squashed;\n } catch {\n // Not a valid Cause\n }\n }\n\n if (cause instanceof ORPCError) {\n return cause;\n }\n }\n\n return error;\n};\n\n// Universal error converter for the runtime\nexport const toPluginRuntimeError = (\n error: unknown,\n pluginId?: string,\n procedureName?: string,\n operation?: string,\n): PluginRuntimeError => {\n if (error instanceof ORPCError) {\n return wrapORPCError(error, pluginId, procedureName, operation);\n }\n\n if (error instanceof PluginRuntimeError) {\n return error;\n }\n\n return new PluginRuntimeError({\n pluginId,\n operation,\n procedureName,\n cause: error instanceof Error ? error : new Error(extractErrorMessage(error)),\n });\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"errors.mjs","names":[],"sources":["../../src/runtime/errors.ts"],"sourcesContent":["import { ORPCError } from \"@orpc/server\";\nimport { Cause, Data } from \"effect\";\nimport type { z } from \"zod\";\n\nexport class PluginRuntimeError extends Data.TaggedError(\"PluginRuntimeError\")<{\n readonly pluginId?: string;\n readonly operation?: string;\n readonly procedureName?: string;\n readonly cause?: Error;\n}> {\n override get message() {\n return this.cause?.message ?? \"Plugin operation failed\";\n }\n}\n\nexport class ModuleFederationError extends Data.TaggedError(\"ModuleFederationError\")<{\n readonly pluginId: string;\n readonly remoteUrl: string;\n readonly cause?: Error;\n}> {}\n\nexport class ValidationError extends Data.TaggedError(\"ValidationError\")<{\n readonly pluginId: string;\n readonly stage: \"config\" | \"input\" | \"output\" | \"state\";\n readonly zodError: z.ZodError;\n}> {}\n\nconst stringifyUnknown = (value: unknown): string => {\n if (value === null) return \"null\";\n if (value === undefined) return \"undefined\";\n if (value instanceof Error) return value.message;\n if (typeof value === \"string\") return value;\n if (typeof value === \"number\" || typeof value === \"bigint\" || typeof value === \"boolean\") {\n return `${value}`;\n }\n if (typeof value === \"symbol\") return value.description ?? \"\";\n try {\n return JSON.stringify(value) ?? \"[Unserializable value]\";\n } catch {\n return \"[Unserializable value]\";\n }\n};\n\nconst extractErrorMessage = (error: unknown): string => {\n if (!error) return \"Unknown error\";\n\n if (error instanceof Error) {\n if (error.message) return error.message;\n if ((error as any).cause instanceof Error) {\n return extractErrorMessage((error as any).cause);\n }\n }\n\n if (error instanceof AggregateError && error.errors?.length) {\n return error.errors.map((e) => extractErrorMessage(e)).join(\"; \");\n }\n\n if (typeof error === \"object\" && \"message\" in error) {\n return String((error as any).message);\n }\n\n return stringifyUnknown(error);\n};\n\n/**\n * The deepest Error along the cause chain — the original throw site. Wrapper\n * errors (PluginRuntimeError, ModuleFederationError) carry their own stacks\n * (the wrapping frames); diagnosis needs the cause's stack (the actual\n * failing module inside the evaluated remote entry).\n */\nexport const deepestErrorStack = (error: unknown): string | undefined => {\n let current = error;\n let deepest: string | undefined;\n let hops = 0;\n while (current instanceof Error && hops < 8) {\n if (current.stack) deepest = current.stack;\n current = (current as { cause?: unknown }).cause as unknown;\n hops += 1;\n }\n if (current instanceof Error && current.stack) deepest = current.stack;\n return deepest;\n};\n\nconst formatValidationIssue = (issue: any, index: number, maxDisplay: number): string => {\n if (index >= maxDisplay) return \"\";\n\n const path =\n Array.isArray(issue.path) && issue.path.length > 0\n ? issue.path.join(\".\")\n : issue.path || \"root\";\n\n const message = issue.message || \"Validation failed\";\n\n return `│ ${index + 1}. ${path}: ${message}`;\n};\n\nconst formatDataPreview = (data: unknown, maxLength = 100): string => {\n if (!data) return \"undefined\";\n\n try {\n const str = JSON.stringify(data);\n if (str.length <= maxLength) return str;\n\n if (typeof data === \"object\" && data !== null) {\n if (Array.isArray(data)) {\n return `Array(${data.length}) [...]`;\n }\n const keys = Object.keys(data);\n return `{ ${keys.slice(0, 3).join(\", \")}${keys.length > 3 ? \", ...\" : \"\"} }`;\n }\n\n return `${str.slice(0, maxLength)}...`;\n } catch {\n return stringifyUnknown(data).slice(0, maxLength);\n }\n};\n\nconst formatORPCValidationError = (error: any): string[] | null => {\n const cause = error?.cause || error;\n\n if (!cause?.issues || !Array.isArray(cause.issues) || cause.issues.length === 0) {\n return null;\n }\n\n const lines: string[] = [];\n const errorType = error?.message || cause?.message || \"Validation failed\";\n\n lines.push(`\\n╭─ oRPC Validation Error ${\"─\".repeat(30)}`);\n lines.push(`│ ${errorType}`);\n lines.push(`│`);\n\n const maxDisplay = 10;\n const totalIssues = cause.issues.length;\n\n lines.push(`│ Issues (${totalIssues}):`);\n\n const displayedIssues = cause.issues.slice(0, maxDisplay);\n displayedIssues.forEach((issue: any, idx: number) => {\n const formatted = formatValidationIssue(issue, idx, maxDisplay);\n if (formatted) lines.push(formatted);\n });\n\n if (totalIssues > maxDisplay) {\n lines.push(`│ ... and ${totalIssues - maxDisplay} more`);\n }\n\n if (cause.data !== undefined) {\n lines.push(`│`);\n lines.push(`│ Data preview: ${formatDataPreview(cause.data, 80)}`);\n }\n\n lines.push(`╰${\"─\".repeat(50)}\\n`);\n\n return lines;\n};\n\nconst formatUnknownError = (error: any): string | null => {\n if (!error) return null;\n const message = extractErrorMessage(error);\n const lines: string[] = [];\n lines.push(`\\n╭─ Unhandled error ${\"─\".repeat(37)}`);\n for (const line of message.split(\"\\n\")) {\n lines.push(`│ ${line}`);\n }\n const stack = error instanceof Error ? error.stack?.split(\"\\n\").slice(1, 6) : undefined;\n if (stack && stack.length > 0) {\n lines.push(`│`);\n for (const line of stack) {\n lines.push(`│ ${line.trim()}`);\n }\n }\n lines.push(`╰${\"─\".repeat(50)}\\n`);\n return lines.join(\"\\n\");\n};\n\nexport const formatORPCError = (error: any): string | null => {\n if (!(error instanceof ORPCError)) {\n // Unknown errors used to return null, leaving internal failures (e.g. a\n // storage backend throw) invisible in host logs while oRPC serialized\n // them client-side as a bare INTERNAL_SERVER_ERROR.\n return formatUnknownError(error);\n }\n\n const validationLines = formatORPCValidationError(error);\n if (validationLines) {\n return validationLines.join(\"\\n\");\n }\n\n const lines: string[] = [];\n const code = error.code || \"UNKNOWN\";\n const message = error.message || \"An error occurred\";\n\n lines.push(`\\n╭─ oRPC Error ${\"─\".repeat(40)}`);\n const messageLines = message.split(\"\\n\");\n for (const line of messageLines) {\n lines.push(`│ ${line}`);\n }\n lines.push(`│ Code: ${code}`);\n lines.push(`│`);\n\n if (error.data) {\n const dataType = typeof error.data;\n if (dataType === \"object\" && error.data !== null) {\n if (\"retryAfter\" in error.data) {\n lines.push(`│ Retry after: ${error.data.retryAfter} seconds`);\n }\n if (\"remainingRequests\" in error.data) {\n lines.push(`│ Remaining: ${error.data.remainingRequests} requests`);\n }\n if (\"host\" in error.data) {\n lines.push(`│ Host: ${error.data.host}`);\n }\n if (\"port\" in error.data) {\n lines.push(`│ Port: ${error.data.port}`);\n }\n if (\"suggestion\" in error.data) {\n lines.push(`│ → ${error.data.suggestion}`);\n }\n if (\"resource\" in error.data) {\n lines.push(`│ Resource: ${error.data.resource}`);\n }\n if (\"resourceId\" in error.data) {\n lines.push(`│ ID: ${error.data.resourceId}`);\n }\n }\n }\n\n switch (code) {\n case \"UNAUTHORIZED\":\n lines.push(`│ → Check your API key or credentials`);\n break;\n case \"TOO_MANY_REQUESTS\":\n lines.push(`│ → Wait before retrying`);\n break;\n case \"SERVICE_UNAVAILABLE\":\n case \"BAD_GATEWAY\":\n case \"GATEWAY_TIMEOUT\":\n lines.push(`│ → The service may be temporarily unavailable`);\n break;\n case \"TIMEOUT\":\n lines.push(`│ → The operation took too long`);\n break;\n }\n\n lines.push(`╰${\"─\".repeat(50)}\\n`);\n return lines.join(\"\\n\");\n};\n\nexport type PluginFailureKind =\n | \"mf\"\n | \"integrity\"\n | \"config\"\n | \"validation\"\n | \"network\"\n | \"unknown\";\n\nexport interface PluginFailureClassification {\n kind: PluginFailureKind;\n retryable: boolean;\n message: string;\n suggestion?: string;\n}\n\n/**\n * Structured report of one plugin-load failure, built by a loader (the host's\n * plugin boot, the dev server) from a `PluginFailureClassification` plus\n * loader context. This is the shape health endpoints and dashboards consume —\n * keep it serializable.\n */\nexport interface PluginLoadFailureInfo {\n /** loader-side key (host entry key, dev plugin id) */\n pluginKey: string;\n pluginUrl?: string;\n /** failing runtime stage when the cause is stage-attributed */\n operation?: string;\n kind: string;\n retryable: boolean;\n message: string;\n suggestion?: string;\n /** loader context: which env secret the plugin's DB binding expects */\n dbSecret?: string;\n dbUrlMasked?: string;\n}\n\n/**\n * Structured, serializable warning surfaced alongside plugin failures in\n * health endpoints and dashboards. Warnings are non-fatal: the plugin loaded\n * and initialized successfully, but runs with reduced functionality (e.g. an\n * optional secret was not set, so the feature depending on it is disabled).\n */\nexport interface PluginWarningInfo {\n /** loader-side key (host entry key, dev plugin id) */\n pluginKey: string;\n message: string;\n hint?: string;\n}\n\n/**\n * The loader's default warning policy for declared-but-unset secrets: a\n * successfully initialized plugin whose declared secret names resolved to\n * nothing (missing or empty in the environment) gets one consolidated\n * warning. `*_DATABASE_URL` keys are excluded — they have the shared-API-DB\n * fallback, and a genuinely-missing DB URL fails initialization (a `failure`,\n * not a warning). A plugin that declares its own `warnings` function opts out\n * of this default.\n */\nexport const defaultMissingSecretWarnings = (\n pluginKey: string,\n declaredKeys: readonly string[],\n resolvedKeys: readonly string[],\n): PluginWarningInfo[] => {\n const resolved = new Set(resolvedKeys);\n const missing = declaredKeys.filter(\n (key) => !resolved.has(key) && !key.endsWith(\"_DATABASE_URL\"),\n );\n if (missing.length === 0) return [];\n return [\n {\n pluginKey,\n message:\n missing.length === 1\n ? `Declared secret ${missing[0]} is not set — features depending on it are disabled`\n : `${missing.length} declared secrets are not set: ${missing.join(\", \")} — features depending on them are disabled`,\n hint: \"Set the secret(s) in your environment (.env in development); see the plugin's config schema for what each enables\",\n },\n ];\n};\n\nconst NETWORK_RETRYABLE_PATTERNS = [\"ETIMEDOUT\", \"ECONNRESET\", \"timeout\", \"503\", \"429\"] as const;\nconst NETWORK_DEAD_PATTERNS = [\"ECONNREFUSED\", \"ENOTFOUND\", \"EHOSTUNREACH\"] as const;\nconst RETRYABLE_ORPC_CODES = new Set([\n \"TOO_MANY_REQUESTS\",\n \"SERVICE_UNAVAILABLE\",\n \"BAD_GATEWAY\",\n \"GATEWAY_TIMEOUT\",\n \"TIMEOUT\",\n]);\n\n/**\n * One classification for plugin lifecycle failures across MF / validation /\n * network sources. This is the recovery policy — nothing in the runtime\n * retries on it today by design (the host degrades and runs without a failed\n * plugin); consumers (host bootstrap messages, the dev server retry loop)\n * read kind / retryable / suggestion to decide what to tell the author.\n */\nexport const classifyPluginFailure = (error: unknown): PluginFailureClassification => {\n let current: unknown = extractFromFiberFailure(error);\n let hops = 0;\n while (current && typeof current === \"object\" && hops < 8) {\n if (current instanceof ModuleFederationError) {\n return {\n kind: \"mf\",\n retryable: false,\n message: extractErrorMessage(current),\n suggestion:\n \"Run bos mf check — the deployed plugin bundle is likely stale (sharedDep / pluginVersion skew), then redeploy the plugin and bos publish --deploy --packages local\",\n };\n }\n if (current instanceof ValidationError) {\n return {\n kind: \"validation\",\n retryable: false,\n message: `${current.stage}: ${extractErrorMessage(current.zodError)}`,\n suggestion: `Fix the plugin's ${current.stage} schema at plugins/${current.pluginId}`,\n };\n }\n if (current instanceof ORPCError) {\n const retryableByCode = RETRYABLE_ORPC_CODES.has(current.code);\n return {\n kind: \"network\",\n retryable: retryableByCode,\n message: current.message,\n suggestion: retryableByCode\n ? \"The plugin procedure failed transiently — retry the call\"\n : undefined,\n };\n }\n current = (current as { cause?: unknown }).cause;\n hops += 1;\n }\n\n const message = extractErrorMessage(error);\n const lower = message.toLowerCase();\n\n if (NETWORK_RETRYABLE_PATTERNS.some((p) => lower.includes(p.toLowerCase()))) {\n return {\n kind: \"network\",\n retryable: true,\n message,\n suggestion: \"The remote may be temporarily unavailable — retry after redeploying the plugin\",\n };\n }\n if (NETWORK_DEAD_PATTERNS.some((p) => lower.includes(p.toLowerCase()))) {\n return {\n kind: \"network\",\n retryable: false,\n message,\n suggestion:\n \"The plugin host is unreachable — check plugins.<id> entry URLs in bos.config.json\",\n };\n }\n if (lower.includes(\"integrity\") || lower.includes(\"sri\")) {\n return {\n kind: \"integrity\",\n retryable: true,\n message,\n suggestion:\n \"The deployed bundle content does not match its recorded integrity — redeploy the plugin so bos.config.json is republished with a fresh hash\",\n };\n }\n\n return { kind: \"unknown\", retryable: false, message };\n};\n\n// Convert ORPC errors from plugin procedures to PluginRuntimeError\nexport const wrapORPCError = (\n orpcError: ORPCError<string, unknown>,\n pluginId?: string,\n procedureName?: string,\n operation?: string,\n): PluginRuntimeError => {\n return new PluginRuntimeError({\n pluginId,\n operation,\n procedureName,\n cause: orpcError as Error,\n });\n};\n\n/**\n * Extracts the underlying error from Effect's FiberFailure wrapper.\n * When Effect.runPromise rejects, errors are wrapped in FiberFailure.\n * This extracts the original error so oRPC can handle it properly.\n */\nexport const extractFromFiberFailure = (error: unknown): unknown => {\n if (!error || typeof error !== \"object\") return error;\n\n if (\"cause\" in error) {\n const cause = (error as { cause?: unknown }).cause;\n\n if (cause && typeof cause === \"object\" && \"_tag\" in cause) {\n try {\n const squashed = Cause.squash(cause as unknown as Cause.Cause<unknown>);\n if (squashed instanceof ORPCError) {\n return squashed;\n }\n return squashed;\n } catch {\n // Not a valid Cause\n }\n }\n\n if (cause instanceof ORPCError) {\n return cause;\n }\n }\n\n return error;\n};\n\n// Universal error converter for the runtime\nexport const toPluginRuntimeError = (\n error: unknown,\n pluginId?: string,\n procedureName?: string,\n operation?: string,\n): PluginRuntimeError => {\n if (error instanceof ORPCError) {\n return wrapORPCError(error, pluginId, procedureName, operation);\n }\n\n if (error instanceof PluginRuntimeError) {\n return error;\n }\n\n return new PluginRuntimeError({\n pluginId,\n operation,\n procedureName,\n cause: error instanceof Error ? error : new Error(extractErrorMessage(error)),\n });\n};\n"],"mappings":"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"}
|
|
@@ -3,7 +3,7 @@ const require_shared_deps_spec = require('../shared-deps-spec.cjs');
|
|
|
3
3
|
let node_fs = require("node:fs");
|
|
4
4
|
|
|
5
5
|
//#region src/runtime/mf-config.ts
|
|
6
|
-
const PLUGIN_VERSION = "3.0.0-rc.
|
|
6
|
+
const PLUGIN_VERSION = "3.0.0-rc.3";
|
|
7
7
|
const MF_CORE_SHARED_DEPS = require_shared_deps_spec.buildMfCoreSharedDependencies({ ownVersion: PLUGIN_VERSION });
|
|
8
8
|
|
|
9
9
|
//#endregion
|
|
@@ -2,7 +2,7 @@ import { buildMfCoreSharedDependencies } from "../shared-deps-spec.mjs";
|
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
|
|
4
4
|
//#region src/runtime/mf-config.ts
|
|
5
|
-
const PLUGIN_VERSION = "3.0.0-rc.
|
|
5
|
+
const PLUGIN_VERSION = "3.0.0-rc.3";
|
|
6
6
|
const MF_CORE_SHARED_DEPS = buildMfCoreSharedDependencies({ ownVersion: PLUGIN_VERSION });
|
|
7
7
|
|
|
8
8
|
//#endregion
|
|
@@ -54,13 +54,7 @@ const resolveRemoteSelfName = (pluginId, url) => effect.Effect.gen(function* ()
|
|
|
54
54
|
const memoKey = `${pluginId}@${url}`;
|
|
55
55
|
const memo = remoteSelfNames.get(memoKey);
|
|
56
56
|
if (memo) return memo;
|
|
57
|
-
|
|
58
|
-
try {
|
|
59
|
-
selfName = (yield* effect.Effect.promise(() => require_shared_identity.fetchRemoteIdentityManifest(url)))?.metaData?.name?.trim() || null;
|
|
60
|
-
} catch {
|
|
61
|
-
selfName = null;
|
|
62
|
-
}
|
|
63
|
-
const resolved = selfName ?? require_identity.remoteName(pluginId);
|
|
57
|
+
const resolved = ((yield* effect.Effect.tryPromise(() => require_shared_identity.fetchRemoteIdentityManifest(url)).pipe(effect.Effect.orElseSucceed(() => null)))?.metaData?.name?.trim() || null) ?? require_identity.remoteName(pluginId);
|
|
64
58
|
remoteSelfNames.set(memoKey, resolved);
|
|
65
59
|
return resolved;
|
|
66
60
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module-federation.service.cjs","names":["MF_CORE_SHARED_DEPS","Effect","getInstance","createInstance","fetchRemoteIdentityManifest","getNormalizedRemoteName","Context","Layer","compareSharedIdentity","describeSharedIdentityMismatch","ModuleFederationError"],"sources":["../../../src/runtime/services/module-federation.service.ts"],"sourcesContent":["import { createInstance, getInstance } from \"@module-federation/enhanced/runtime\";\nimport { setGlobalFederationInstance } from \"@module-federation/runtime-core\";\nimport { Context, Effect, Layer } from \"effect\";\nimport { DEV_ENTRY_FILENAME } from \"../../build/artifact-names\";\nimport type { AnyPlugin } from \"../../types\";\nimport { ModuleFederationError } from \"../errors\";\nimport { type CoreSharedDepName, MF_CORE_SHARED_DEPS } from \"../mf-config\";\nimport { getNormalizedRemoteName } from \"./normalize\";\nimport {\n compareSharedIdentity,\n describeSharedIdentityMismatch,\n fetchRemoteIdentityManifest,\n} from \"./shared-identity\";\n\nfunction expectedSharedIdentity(): Map<string, string> {\n return new Map(\n Object.entries(MF_CORE_SHARED_DEPS).map(([name, config]) => [name, config.version]),\n );\n}\n\ntype RemoteModule = (new () => AnyPlugin) | { default: new () => AnyPlugin };\n\nexport const loadEveryPluginSharedModule = () => import(\"../../index\");\n\nconst coreModuleLoaders: Record<CoreSharedDepName, () => Promise<unknown>> = {\n // Keep the host-provided framework share on the same source graph as the\n // runtime. A package self-import can resolve the published entry when a\n // workspace script is launched through its package manager shim.\n \"every-plugin\": loadEveryPluginSharedModule,\n effect: () => import(\"effect\"),\n zod: () => import(\"zod\"),\n \"@orpc/contract\": () => import(\"@orpc/contract\"),\n \"@orpc/client\": () => import(\"@orpc/client\"),\n \"@orpc/server\": () => import(\"@orpc/server\"),\n};\n\nfunction buildSharedConfig(): Record<\n string,\n {\n version: string;\n shareScope: string;\n get: () => Promise<() => unknown>;\n shareConfig: (typeof MF_CORE_SHARED_DEPS)[CoreSharedDepName][\"shareConfig\"];\n }\n> {\n return Object.fromEntries(\n (\n Object.entries(MF_CORE_SHARED_DEPS) as [\n CoreSharedDepName,\n (typeof MF_CORE_SHARED_DEPS)[CoreSharedDepName],\n ][]\n ).map(([name, config]) => {\n const load = coreModuleLoaders[name];\n\n if (!load) {\n throw new Error(`Missing core shared module loader for ${name}`);\n }\n\n return [\n name,\n {\n version: config.version,\n shareScope: config.shareScope,\n get: () => load().then((mod) => () => mod),\n shareConfig: config.shareConfig,\n },\n ];\n }),\n );\n}\n\nconst createModuleFederationInstance = Effect.cached(\n Effect.sync(() => {\n try {\n const shared = buildSharedConfig();\n let instance = getInstance();\n\n if (!instance) {\n instance = createInstance({\n name: \"host\",\n remotes: [],\n shared,\n });\n\n setGlobalFederationInstance(instance);\n } else {\n instance.registerShared(shared);\n }\n\n return instance;\n } catch (error) {\n throw new Error(`Failed to initialize Module Federation: ${String(error)}`);\n }\n }),\n);\n\n// A built container's runtime code looks up its own chunks through the node\n// runtime's entry-URL fallback keyed by the container's SELF name\n// (`mf-manifest.json` metaData.name — the composition key, e.g.\n// \"everything-dev_registry-plugin\"), while the host registers the remote\n// under the plugin id (\"registry\"). When those differ, the fallback misses,\n// resolveUrl returns null, and the node runtime hands back an empty chunk as\n// if it had loaded — the exposed module then requires ids nothing registered\n// (\"__webpack_modules__[r] is not a function\"). Registering and loading the\n// remote under the container's own name keeps that lookup a hit.\nconst remoteSelfNames = new Map<string, string>();\n\nconst resolveRemoteSelfName = (pluginId: string, url: string) =>\n Effect.gen(function* () {\n const memoKey = `${pluginId}@${url}`;\n const memo = remoteSelfNames.get(memoKey);\n if (memo) return memo;\n\n let selfName: string | null = null;\n try {\n const manifest = yield* Effect.promise(() => fetchRemoteIdentityManifest(url));\n selfName = manifest?.metaData?.name?.trim() || null;\n } catch {\n selfName = null;\n }\n\n const resolved = selfName ?? getNormalizedRemoteName(pluginId);\n remoteSelfNames.set(memoKey, resolved);\n return resolved;\n });\n\nexport interface ModuleFederationServiceShape {\n registerRemote: (pluginId: string, url: string) => Effect.Effect<void, ModuleFederationError>;\n loadRemoteConstructor: (\n pluginId: string,\n url: string,\n ) => Effect.Effect<new () => AnyPlugin, ModuleFederationError>;\n}\n\nexport class ModuleFederationService extends Context.Service<\n ModuleFederationService,\n ModuleFederationServiceShape\n>()(\"ModuleFederationService\") {}\n\nexport const ModuleFederationServiceDefault = Layer.effect(\n ModuleFederationService,\n Effect.gen(function* () {\n const mf = yield* Effect.flatten(createModuleFederationInstance);\n const expectedIdentity = expectedSharedIdentity();\n\n const buildSharedIdentityError = async (\n pluginId: string,\n url: string,\n ): Promise<ModuleFederationError | null> => {\n let manifest: Awaited<ReturnType<typeof fetchRemoteIdentityManifest>> | null = null;\n try {\n manifest = await fetchRemoteIdentityManifest(url);\n } catch (error) {\n console.warn(\n `[SharedIdentity] Could not fetch mf-manifest.json for plugin \"${pluginId}\" at ${url} (${error instanceof Error ? error.message : String(error)}); proceeding without identity verification`,\n );\n return null;\n }\n if (!manifest) return null;\n const mismatches = compareSharedIdentity(manifest.shared, expectedIdentity);\n if (mismatches.length === 0) return null;\n const warnOnly = process.env.BOS_MF_IDENTITY === \"warn\";\n if (warnOnly) {\n console.warn(\n `${describeSharedIdentityMismatch(pluginId, mismatches, \"this runtime\")} (BOS_MF_IDENTITY=warn: loading anyway)`,\n );\n return null;\n }\n return new ModuleFederationError({\n pluginId,\n remoteUrl: url,\n cause: new Error(describeSharedIdentityMismatch(pluginId, mismatches, \"this runtime\")),\n });\n };\n\n return {\n registerRemote: (pluginId: string, url: string) =>\n Effect.gen(function* () {\n const remoteName = yield* resolveRemoteSelfName(pluginId, url);\n yield* Effect.logDebug(`[MF][${pluginId}] Registering as \"${remoteName}\"`);\n\n const type = url.endsWith(\"/mf-manifest.json\")\n ? (\"manifest\" as const)\n : url.endsWith(`/${DEV_ENTRY_FILENAME}`)\n ? (\"script\" as const)\n : undefined;\n\n yield* Effect.try({\n try: () =>\n mf.registerRemotes([\n {\n name: remoteName,\n entry: url,\n ...(type ? { type } : {}),\n },\n ]),\n catch: (error): ModuleFederationError =>\n new ModuleFederationError({\n pluginId,\n remoteUrl: url,\n cause: error instanceof Error ? error : new Error(String(error)),\n }),\n });\n\n yield* Effect.logInfo(`[MF][${pluginId}] ✅ Registered`);\n }),\n\n loadRemoteConstructor: (pluginId: string, url: string) =>\n Effect.gen(function* () {\n const remoteName = yield* resolveRemoteSelfName(pluginId, url);\n yield* Effect.logDebug(`[MF][${pluginId}] Loading remote ${remoteName}`);\n\n const identityError = yield* Effect.promise(() =>\n buildSharedIdentityError(pluginId, url),\n );\n if (identityError) {\n yield* Effect.logError(\n `[MF][${pluginId}] ❌ shared identity mismatch: ${\n identityError.cause instanceof Error\n ? identityError.cause.message\n : String(identityError.cause ?? \"shared identity mismatch\")\n }`,\n );\n return yield* identityError;\n }\n\n const modulePath = `${remoteName}/plugin`;\n\n const pluginConstructor = yield* Effect.tryPromise({\n try: async () => {\n const container = await mf.loadRemote<RemoteModule>(modulePath);\n if (!container) {\n throw new Error(`No container returned for ${modulePath}`);\n }\n\n // Support multiple export patterns: direct function, default export, named exports\n let Constructor: any;\n\n if (typeof container === \"function\") {\n // Direct function export\n Constructor = container;\n } else if (container.default) {\n // Default export\n Constructor = container.default;\n } else {\n // Named export fallback - prioritize exports with 'binding' property (plugin classes)\n Constructor = Object.values(container).find(\n (exp) => typeof exp === \"function\" && (exp as any).binding !== undefined,\n );\n\n // Fallback to any function export if no binding found\n if (!Constructor) {\n Constructor = Object.values(container).find(\n (exp) => typeof exp === \"function\" && exp.prototype?.constructor === exp,\n );\n }\n }\n\n if (!Constructor || typeof Constructor !== \"function\") {\n const containerInfo =\n typeof container === \"object\"\n ? `Available exports: ${Object.keys(container).join(\", \")}`\n : `Container type: ${typeof container}`;\n\n throw new Error(\n `No valid plugin constructor found for '${pluginId}'.\\n` +\n `Supported patterns:\\n` +\n ` - export const YourPlugin = createPlugin({...})\\n` +\n ` - export default createPlugin({...})\\n` +\n `${containerInfo}`,\n );\n }\n\n // Validate it looks like a plugin constructor (has binding property)\n if (!(Constructor as any).binding) {\n const containerInfo =\n typeof container === \"object\"\n ? `Found exports: ${Object.keys(container).join(\", \")}`\n : `Container type: ${typeof container}`;\n\n throw new Error(\n `Invalid plugin constructor for '${pluginId}'. ` +\n `The exported value must be created with createPlugin(). ` +\n `Found a function but it's missing the required 'binding' property.\\n` +\n `${containerInfo}`,\n );\n }\n\n return Constructor;\n },\n catch: (error): ModuleFederationError =>\n new ModuleFederationError({\n pluginId,\n remoteUrl: url,\n cause: error instanceof Error ? error : new Error(String(error)),\n }),\n });\n\n yield* Effect.logInfo(`[MF][${pluginId}] ✅ Loaded constructor`);\n return pluginConstructor;\n }),\n };\n }),\n);\n"],"mappings":";;;;;;;;;;AAcA,SAAS,yBAA8C;CACrD,OAAO,IAAI,IACT,OAAO,QAAQA,6CAAmB,CAAC,CAAC,KAAK,CAAC,MAAM,YAAY,CAAC,MAAM,OAAO,OAAO,CAAC,CACpF;AACF;AAIA,MAAa,yEAAoC;AAEjD,MAAM,oBAAuE;CAI3E,gBAAgB;CAChB,cAAc,OAAO;CACrB,WAAW,OAAO;CAClB,wBAAwB,OAAO;CAC/B,sBAAsB,OAAO;CAC7B,sBAAsB,OAAO;AAC/B;AAEA,SAAS,oBAQP;CACA,OAAO,OAAO,YAEV,OAAO,QAAQA,6CAAmB,CAAC,CAInC,KAAK,CAAC,MAAM,YAAY;EACxB,MAAM,OAAO,kBAAkB;EAE/B,IAAI,CAAC,MACH,MAAM,IAAI,MAAM,yCAAyC,MAAM;EAGjE,OAAO,CACL,MACA;GACE,SAAS,OAAO;GAChB,YAAY,OAAO;GACnB,WAAW,KAAK,CAAC,CAAC,MAAM,cAAc,GAAG;GACzC,aAAa,OAAO;EACtB,CACF;CACF,CAAC,CACH;AACF;AAEA,MAAM,iCAAiCC,cAAO,OAC5CA,cAAO,WAAW;CAChB,IAAI;EACF,MAAM,SAAS,kBAAkB;EACjC,IAAI,eAAWC,iDAAY;EAE3B,IAAI,CAAC,UAAU;GACb,eAAWC,oDAAe;IACxB,MAAM;IACN,SAAS,CAAC;IACV;GACF,CAAC;GAED,iEAA4B,QAAQ;EACtC,OACE,SAAS,eAAe,MAAM;EAGhC,OAAO;CACT,SAAS,OAAO;EACd,MAAM,IAAI,MAAM,2CAA2C,OAAO,KAAK,GAAG;CAC5E;AACF,CAAC,CACH;AAWA,MAAM,kCAAkB,IAAI,IAAoB;AAEhD,MAAM,yBAAyB,UAAkB,QAC/CF,cAAO,IAAI,aAAa;CACtB,MAAM,UAAU,GAAG,SAAS,GAAG;CAC/B,MAAM,OAAO,gBAAgB,IAAI,OAAO;CACxC,IAAI,MAAM,OAAO;CAEjB,IAAI,WAA0B;CAC9B,IAAI;EAEF,YAAW,OADaA,cAAO,cAAcG,oDAA4B,GAAG,CAAC,EAC1D,EAAE,UAAU,MAAM,KAAK,KAAK;CACjD,QAAQ;EACN,WAAW;CACb;CAEA,MAAM,WAAW,YAAYC,4BAAwB,QAAQ;CAC7D,gBAAgB,IAAI,SAAS,QAAQ;CACrC,OAAO;AACT,CAAC;AAUH,IAAa,0BAAb,cAA6CC,eAAQ,QAGnD,CAAC,CAAC,yBAAyB,CAAC,CAAC,CAAC;AAEhC,MAAa,iCAAiCC,aAAM,OAClD,yBACAN,cAAO,IAAI,aAAa;CACtB,MAAM,KAAK,OAAOA,cAAO,QAAQ,8BAA8B;CAC/D,MAAM,mBAAmB,uBAAuB;CAEhD,MAAM,2BAA2B,OAC/B,UACA,QAC0C;EAC1C,IAAI,WAA2E;EAC/E,IAAI;GACF,WAAW,MAAMG,oDAA4B,GAAG;EAClD,SAAS,OAAO;GACd,QAAQ,KACN,iEAAiE,SAAS,OAAO,IAAI,IAAI,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAAE,4CAClJ;GACA,OAAO;EACT;EACA,IAAI,CAAC,UAAU,OAAO;EACtB,MAAM,aAAaI,8CAAsB,SAAS,QAAQ,gBAAgB;EAC1E,IAAI,WAAW,WAAW,GAAG,OAAO;EAEpC,IADiB,QAAQ,IAAI,oBAAoB,QACnC;GACZ,QAAQ,KACN,GAAGC,uDAA+B,UAAU,YAAY,cAAc,EAAE,wCAC1E;GACA,OAAO;EACT;EACA,OAAO,IAAIC,qCAAsB;GAC/B;GACA,WAAW;GACX,OAAO,IAAI,MAAMD,uDAA+B,UAAU,YAAY,cAAc,CAAC;EACvF,CAAC;CACH;CAEA,OAAO;EACL,iBAAiB,UAAkB,QACjCR,cAAO,IAAI,aAAa;GACtB,MAAM,aAAa,OAAO,sBAAsB,UAAU,GAAG;GAC7D,OAAOA,cAAO,SAAS,QAAQ,SAAS,oBAAoB,WAAW,EAAE;GAEzE,MAAM,OAAO,IAAI,SAAS,mBAAmB,IACxC,aACD,IAAI,SAAS,sBAAwB,IAClC,WACD;GAEN,OAAOA,cAAO,IAAI;IAChB,WACE,GAAG,gBAAgB,CACjB;KACE,MAAM;KACN,OAAO;KACP,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;IACzB,CACF,CAAC;IACH,QAAQ,UACN,IAAIS,qCAAsB;KACxB;KACA,WAAW;KACX,OAAO,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;IACjE,CAAC;GACL,CAAC;GAED,OAAOT,cAAO,QAAQ,QAAQ,SAAS,eAAe;EACxD,CAAC;EAEH,wBAAwB,UAAkB,QACxCA,cAAO,IAAI,aAAa;GACtB,MAAM,aAAa,OAAO,sBAAsB,UAAU,GAAG;GAC7D,OAAOA,cAAO,SAAS,QAAQ,SAAS,mBAAmB,YAAY;GAEvE,MAAM,gBAAgB,OAAOA,cAAO,cAClC,yBAAyB,UAAU,GAAG,CACxC;GACA,IAAI,eAAe;IACjB,OAAOA,cAAO,SACZ,QAAQ,SAAS,gCACf,cAAc,iBAAiB,QAC3B,cAAc,MAAM,UACpB,OAAO,cAAc,SAAS,0BAA0B,GAEhE;IACA,OAAO,OAAO;GAChB;GAEA,MAAM,aAAa,GAAG,WAAW;GAEjC,MAAM,oBAAoB,OAAOA,cAAO,WAAW;IACjD,KAAK,YAAY;KACf,MAAM,YAAY,MAAM,GAAG,WAAyB,UAAU;KAC9D,IAAI,CAAC,WACH,MAAM,IAAI,MAAM,6BAA6B,YAAY;KAI3D,IAAI;KAEJ,IAAI,OAAO,cAAc,YAEvB,cAAc;UACT,IAAI,UAAU,SAEnB,cAAc,UAAU;UACnB;MAEL,cAAc,OAAO,OAAO,SAAS,CAAC,CAAC,MACpC,QAAQ,OAAO,QAAQ,cAAe,IAAY,YAAY,MACjE;MAGA,IAAI,CAAC,aACH,cAAc,OAAO,OAAO,SAAS,CAAC,CAAC,MACpC,QAAQ,OAAO,QAAQ,cAAc,IAAI,WAAW,gBAAgB,GACvE;KAEJ;KAEA,IAAI,CAAC,eAAe,OAAO,gBAAgB,YAAY;MACrD,MAAM,gBACJ,OAAO,cAAc,WACjB,sBAAsB,OAAO,KAAK,SAAS,CAAC,CAAC,KAAK,IAAI,MACtD,mBAAmB,OAAO;MAEhC,MAAM,IAAI,MACR,0CAA0C,SAAS,sHAI9C,eACP;KACF;KAGA,IAAI,CAAE,YAAoB,SAAS;MACjC,MAAM,gBACJ,OAAO,cAAc,WACjB,kBAAkB,OAAO,KAAK,SAAS,CAAC,CAAC,KAAK,IAAI,MAClD,mBAAmB,OAAO;MAEhC,MAAM,IAAI,MACR,mCAAmC,SAAS,iIAGvC,eACP;KACF;KAEA,OAAO;IACT;IACA,QAAQ,UACN,IAAIS,qCAAsB;KACxB;KACA,WAAW;KACX,OAAO,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;IACjE,CAAC;GACL,CAAC;GAED,OAAOT,cAAO,QAAQ,QAAQ,SAAS,uBAAuB;GAC9D,OAAO;EACT,CAAC;CACL;AACF,CAAC,CACH"}
|
|
1
|
+
{"version":3,"file":"module-federation.service.cjs","names":["MF_CORE_SHARED_DEPS","Effect","getInstance","createInstance","fetchRemoteIdentityManifest","getNormalizedRemoteName","Context","Layer","compareSharedIdentity","describeSharedIdentityMismatch","ModuleFederationError"],"sources":["../../../src/runtime/services/module-federation.service.ts"],"sourcesContent":["import { createInstance, getInstance } from \"@module-federation/enhanced/runtime\";\nimport { setGlobalFederationInstance } from \"@module-federation/runtime-core\";\nimport { Context, Effect, Layer } from \"effect\";\nimport { DEV_ENTRY_FILENAME } from \"../../build/artifact-names\";\nimport type { AnyPlugin } from \"../../types\";\nimport { ModuleFederationError } from \"../errors\";\nimport { type CoreSharedDepName, MF_CORE_SHARED_DEPS } from \"../mf-config\";\nimport { getNormalizedRemoteName } from \"./normalize\";\nimport {\n compareSharedIdentity,\n describeSharedIdentityMismatch,\n fetchRemoteIdentityManifest,\n} from \"./shared-identity\";\n\nfunction expectedSharedIdentity(): Map<string, string> {\n return new Map(\n Object.entries(MF_CORE_SHARED_DEPS).map(([name, config]) => [name, config.version]),\n );\n}\n\ntype RemoteModule = (new () => AnyPlugin) | { default: new () => AnyPlugin };\n\nexport const loadEveryPluginSharedModule = () => import(\"../../index\");\n\nconst coreModuleLoaders: Record<CoreSharedDepName, () => Promise<unknown>> = {\n // Keep the host-provided framework share on the same source graph as the\n // runtime. A package self-import can resolve the published entry when a\n // workspace script is launched through its package manager shim.\n \"every-plugin\": loadEveryPluginSharedModule,\n effect: () => import(\"effect\"),\n zod: () => import(\"zod\"),\n \"@orpc/contract\": () => import(\"@orpc/contract\"),\n \"@orpc/client\": () => import(\"@orpc/client\"),\n \"@orpc/server\": () => import(\"@orpc/server\"),\n};\n\nfunction buildSharedConfig(): Record<\n string,\n {\n version: string;\n shareScope: string;\n get: () => Promise<() => unknown>;\n shareConfig: (typeof MF_CORE_SHARED_DEPS)[CoreSharedDepName][\"shareConfig\"];\n }\n> {\n return Object.fromEntries(\n (\n Object.entries(MF_CORE_SHARED_DEPS) as [\n CoreSharedDepName,\n (typeof MF_CORE_SHARED_DEPS)[CoreSharedDepName],\n ][]\n ).map(([name, config]) => {\n const load = coreModuleLoaders[name];\n\n if (!load) {\n throw new Error(`Missing core shared module loader for ${name}`);\n }\n\n return [\n name,\n {\n version: config.version,\n shareScope: config.shareScope,\n get: () => load().then((mod) => () => mod),\n shareConfig: config.shareConfig,\n },\n ];\n }),\n );\n}\n\nconst createModuleFederationInstance = Effect.cached(\n Effect.sync(() => {\n try {\n const shared = buildSharedConfig();\n let instance = getInstance();\n\n if (!instance) {\n instance = createInstance({\n name: \"host\",\n remotes: [],\n shared,\n });\n\n setGlobalFederationInstance(instance);\n } else {\n instance.registerShared(shared);\n }\n\n return instance;\n } catch (error) {\n throw new Error(`Failed to initialize Module Federation: ${String(error)}`);\n }\n }),\n);\n\n// A built container's runtime code looks up its own chunks through the node\n// runtime's entry-URL fallback keyed by the container's SELF name\n// (`mf-manifest.json` metaData.name — the composition key, e.g.\n// \"everything-dev_registry-plugin\"), while the host registers the remote\n// under the plugin id (\"registry\"). When those differ, the fallback misses,\n// resolveUrl returns null, and the node runtime hands back an empty chunk as\n// if it had loaded — the exposed module then requires ids nothing registered\n// (\"__webpack_modules__[r] is not a function\"). Registering and loading the\n// remote under the container's own name keeps that lookup a hit.\nconst remoteSelfNames = new Map<string, string>();\n\nconst resolveRemoteSelfName = (pluginId: string, url: string) =>\n Effect.gen(function* () {\n const memoKey = `${pluginId}@${url}`;\n const memo = remoteSelfNames.get(memoKey);\n if (memo) return memo;\n\n const manifest = yield* Effect.tryPromise(() => fetchRemoteIdentityManifest(url)).pipe(\n Effect.orElseSucceed(() => null),\n );\n const selfName = manifest?.metaData?.name?.trim() || null;\n\n const resolved = selfName ?? getNormalizedRemoteName(pluginId);\n remoteSelfNames.set(memoKey, resolved);\n return resolved;\n });\n\nexport interface ModuleFederationServiceShape {\n registerRemote: (pluginId: string, url: string) => Effect.Effect<void, ModuleFederationError>;\n loadRemoteConstructor: (\n pluginId: string,\n url: string,\n ) => Effect.Effect<new () => AnyPlugin, ModuleFederationError>;\n}\n\nexport class ModuleFederationService extends Context.Service<\n ModuleFederationService,\n ModuleFederationServiceShape\n>()(\"ModuleFederationService\") {}\n\nexport const ModuleFederationServiceDefault = Layer.effect(\n ModuleFederationService,\n Effect.gen(function* () {\n const mf = yield* Effect.flatten(createModuleFederationInstance);\n const expectedIdentity = expectedSharedIdentity();\n\n const buildSharedIdentityError = async (\n pluginId: string,\n url: string,\n ): Promise<ModuleFederationError | null> => {\n let manifest: Awaited<ReturnType<typeof fetchRemoteIdentityManifest>> | null = null;\n try {\n manifest = await fetchRemoteIdentityManifest(url);\n } catch (error) {\n console.warn(\n `[SharedIdentity] Could not fetch mf-manifest.json for plugin \"${pluginId}\" at ${url} (${error instanceof Error ? error.message : String(error)}); proceeding without identity verification`,\n );\n return null;\n }\n if (!manifest) return null;\n const mismatches = compareSharedIdentity(manifest.shared, expectedIdentity);\n if (mismatches.length === 0) return null;\n const warnOnly = process.env.BOS_MF_IDENTITY === \"warn\";\n if (warnOnly) {\n console.warn(\n `${describeSharedIdentityMismatch(pluginId, mismatches, \"this runtime\")} (BOS_MF_IDENTITY=warn: loading anyway)`,\n );\n return null;\n }\n return new ModuleFederationError({\n pluginId,\n remoteUrl: url,\n cause: new Error(describeSharedIdentityMismatch(pluginId, mismatches, \"this runtime\")),\n });\n };\n\n return {\n registerRemote: (pluginId: string, url: string) =>\n Effect.gen(function* () {\n const remoteName = yield* resolveRemoteSelfName(pluginId, url);\n yield* Effect.logDebug(`[MF][${pluginId}] Registering as \"${remoteName}\"`);\n\n const type = url.endsWith(\"/mf-manifest.json\")\n ? (\"manifest\" as const)\n : url.endsWith(`/${DEV_ENTRY_FILENAME}`)\n ? (\"script\" as const)\n : undefined;\n\n yield* Effect.try({\n try: () =>\n mf.registerRemotes([\n {\n name: remoteName,\n entry: url,\n ...(type ? { type } : {}),\n },\n ]),\n catch: (error): ModuleFederationError =>\n new ModuleFederationError({\n pluginId,\n remoteUrl: url,\n cause: error instanceof Error ? error : new Error(String(error)),\n }),\n });\n\n yield* Effect.logInfo(`[MF][${pluginId}] ✅ Registered`);\n }),\n\n loadRemoteConstructor: (pluginId: string, url: string) =>\n Effect.gen(function* () {\n const remoteName = yield* resolveRemoteSelfName(pluginId, url);\n yield* Effect.logDebug(`[MF][${pluginId}] Loading remote ${remoteName}`);\n\n const identityError = yield* Effect.promise(() =>\n buildSharedIdentityError(pluginId, url),\n );\n if (identityError) {\n yield* Effect.logError(\n `[MF][${pluginId}] ❌ shared identity mismatch: ${\n identityError.cause instanceof Error\n ? identityError.cause.message\n : String(identityError.cause ?? \"shared identity mismatch\")\n }`,\n );\n return yield* identityError;\n }\n\n const modulePath = `${remoteName}/plugin`;\n\n const pluginConstructor = yield* Effect.tryPromise({\n try: async () => {\n const container = await mf.loadRemote<RemoteModule>(modulePath);\n if (!container) {\n throw new Error(`No container returned for ${modulePath}`);\n }\n\n // Support multiple export patterns: direct function, default export, named exports\n let Constructor: any;\n\n if (typeof container === \"function\") {\n // Direct function export\n Constructor = container;\n } else if (container.default) {\n // Default export\n Constructor = container.default;\n } else {\n // Named export fallback - prioritize exports with 'binding' property (plugin classes)\n Constructor = Object.values(container).find(\n (exp) => typeof exp === \"function\" && (exp as any).binding !== undefined,\n );\n\n // Fallback to any function export if no binding found\n if (!Constructor) {\n Constructor = Object.values(container).find(\n (exp) => typeof exp === \"function\" && exp.prototype?.constructor === exp,\n );\n }\n }\n\n if (!Constructor || typeof Constructor !== \"function\") {\n const containerInfo =\n typeof container === \"object\"\n ? `Available exports: ${Object.keys(container).join(\", \")}`\n : `Container type: ${typeof container}`;\n\n throw new Error(\n `No valid plugin constructor found for '${pluginId}'.\\n` +\n `Supported patterns:\\n` +\n ` - export const YourPlugin = createPlugin({...})\\n` +\n ` - export default createPlugin({...})\\n` +\n `${containerInfo}`,\n );\n }\n\n // Validate it looks like a plugin constructor (has binding property)\n if (!(Constructor as any).binding) {\n const containerInfo =\n typeof container === \"object\"\n ? `Found exports: ${Object.keys(container).join(\", \")}`\n : `Container type: ${typeof container}`;\n\n throw new Error(\n `Invalid plugin constructor for '${pluginId}'. ` +\n `The exported value must be created with createPlugin(). ` +\n `Found a function but it's missing the required 'binding' property.\\n` +\n `${containerInfo}`,\n );\n }\n\n return Constructor;\n },\n catch: (error): ModuleFederationError =>\n new ModuleFederationError({\n pluginId,\n remoteUrl: url,\n cause: error instanceof Error ? error : new Error(String(error)),\n }),\n });\n\n yield* Effect.logInfo(`[MF][${pluginId}] ✅ Loaded constructor`);\n return pluginConstructor;\n }),\n };\n }),\n);\n"],"mappings":";;;;;;;;;;AAcA,SAAS,yBAA8C;CACrD,OAAO,IAAI,IACT,OAAO,QAAQA,6CAAmB,CAAC,CAAC,KAAK,CAAC,MAAM,YAAY,CAAC,MAAM,OAAO,OAAO,CAAC,CACpF;AACF;AAIA,MAAa,yEAAoC;AAEjD,MAAM,oBAAuE;CAI3E,gBAAgB;CAChB,cAAc,OAAO;CACrB,WAAW,OAAO;CAClB,wBAAwB,OAAO;CAC/B,sBAAsB,OAAO;CAC7B,sBAAsB,OAAO;AAC/B;AAEA,SAAS,oBAQP;CACA,OAAO,OAAO,YAEV,OAAO,QAAQA,6CAAmB,CAAC,CAInC,KAAK,CAAC,MAAM,YAAY;EACxB,MAAM,OAAO,kBAAkB;EAE/B,IAAI,CAAC,MACH,MAAM,IAAI,MAAM,yCAAyC,MAAM;EAGjE,OAAO,CACL,MACA;GACE,SAAS,OAAO;GAChB,YAAY,OAAO;GACnB,WAAW,KAAK,CAAC,CAAC,MAAM,cAAc,GAAG;GACzC,aAAa,OAAO;EACtB,CACF;CACF,CAAC,CACH;AACF;AAEA,MAAM,iCAAiCC,cAAO,OAC5CA,cAAO,WAAW;CAChB,IAAI;EACF,MAAM,SAAS,kBAAkB;EACjC,IAAI,eAAWC,iDAAY;EAE3B,IAAI,CAAC,UAAU;GACb,eAAWC,oDAAe;IACxB,MAAM;IACN,SAAS,CAAC;IACV;GACF,CAAC;GAED,iEAA4B,QAAQ;EACtC,OACE,SAAS,eAAe,MAAM;EAGhC,OAAO;CACT,SAAS,OAAO;EACd,MAAM,IAAI,MAAM,2CAA2C,OAAO,KAAK,GAAG;CAC5E;AACF,CAAC,CACH;AAWA,MAAM,kCAAkB,IAAI,IAAoB;AAEhD,MAAM,yBAAyB,UAAkB,QAC/CF,cAAO,IAAI,aAAa;CACtB,MAAM,UAAU,GAAG,SAAS,GAAG;CAC/B,MAAM,OAAO,gBAAgB,IAAI,OAAO;CACxC,IAAI,MAAM,OAAO;CAOjB,MAAM,aAFW,OAHOA,cAAO,iBAAiBG,oDAA4B,GAAG,CAAC,CAAC,CAAC,KAChFH,cAAO,oBAAoB,IAAI,CACjC,EACyB,EAAE,UAAU,MAAM,KAAK,KAAK,SAExBI,4BAAwB,QAAQ;CAC7D,gBAAgB,IAAI,SAAS,QAAQ;CACrC,OAAO;AACT,CAAC;AAUH,IAAa,0BAAb,cAA6CC,eAAQ,QAGnD,CAAC,CAAC,yBAAyB,CAAC,CAAC,CAAC;AAEhC,MAAa,iCAAiCC,aAAM,OAClD,yBACAN,cAAO,IAAI,aAAa;CACtB,MAAM,KAAK,OAAOA,cAAO,QAAQ,8BAA8B;CAC/D,MAAM,mBAAmB,uBAAuB;CAEhD,MAAM,2BAA2B,OAC/B,UACA,QAC0C;EAC1C,IAAI,WAA2E;EAC/E,IAAI;GACF,WAAW,MAAMG,oDAA4B,GAAG;EAClD,SAAS,OAAO;GACd,QAAQ,KACN,iEAAiE,SAAS,OAAO,IAAI,IAAI,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAAE,4CAClJ;GACA,OAAO;EACT;EACA,IAAI,CAAC,UAAU,OAAO;EACtB,MAAM,aAAaI,8CAAsB,SAAS,QAAQ,gBAAgB;EAC1E,IAAI,WAAW,WAAW,GAAG,OAAO;EAEpC,IADiB,QAAQ,IAAI,oBAAoB,QACnC;GACZ,QAAQ,KACN,GAAGC,uDAA+B,UAAU,YAAY,cAAc,EAAE,wCAC1E;GACA,OAAO;EACT;EACA,OAAO,IAAIC,qCAAsB;GAC/B;GACA,WAAW;GACX,OAAO,IAAI,MAAMD,uDAA+B,UAAU,YAAY,cAAc,CAAC;EACvF,CAAC;CACH;CAEA,OAAO;EACL,iBAAiB,UAAkB,QACjCR,cAAO,IAAI,aAAa;GACtB,MAAM,aAAa,OAAO,sBAAsB,UAAU,GAAG;GAC7D,OAAOA,cAAO,SAAS,QAAQ,SAAS,oBAAoB,WAAW,EAAE;GAEzE,MAAM,OAAO,IAAI,SAAS,mBAAmB,IACxC,aACD,IAAI,SAAS,sBAAwB,IAClC,WACD;GAEN,OAAOA,cAAO,IAAI;IAChB,WACE,GAAG,gBAAgB,CACjB;KACE,MAAM;KACN,OAAO;KACP,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;IACzB,CACF,CAAC;IACH,QAAQ,UACN,IAAIS,qCAAsB;KACxB;KACA,WAAW;KACX,OAAO,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;IACjE,CAAC;GACL,CAAC;GAED,OAAOT,cAAO,QAAQ,QAAQ,SAAS,eAAe;EACxD,CAAC;EAEH,wBAAwB,UAAkB,QACxCA,cAAO,IAAI,aAAa;GACtB,MAAM,aAAa,OAAO,sBAAsB,UAAU,GAAG;GAC7D,OAAOA,cAAO,SAAS,QAAQ,SAAS,mBAAmB,YAAY;GAEvE,MAAM,gBAAgB,OAAOA,cAAO,cAClC,yBAAyB,UAAU,GAAG,CACxC;GACA,IAAI,eAAe;IACjB,OAAOA,cAAO,SACZ,QAAQ,SAAS,gCACf,cAAc,iBAAiB,QAC3B,cAAc,MAAM,UACpB,OAAO,cAAc,SAAS,0BAA0B,GAEhE;IACA,OAAO,OAAO;GAChB;GAEA,MAAM,aAAa,GAAG,WAAW;GAEjC,MAAM,oBAAoB,OAAOA,cAAO,WAAW;IACjD,KAAK,YAAY;KACf,MAAM,YAAY,MAAM,GAAG,WAAyB,UAAU;KAC9D,IAAI,CAAC,WACH,MAAM,IAAI,MAAM,6BAA6B,YAAY;KAI3D,IAAI;KAEJ,IAAI,OAAO,cAAc,YAEvB,cAAc;UACT,IAAI,UAAU,SAEnB,cAAc,UAAU;UACnB;MAEL,cAAc,OAAO,OAAO,SAAS,CAAC,CAAC,MACpC,QAAQ,OAAO,QAAQ,cAAe,IAAY,YAAY,MACjE;MAGA,IAAI,CAAC,aACH,cAAc,OAAO,OAAO,SAAS,CAAC,CAAC,MACpC,QAAQ,OAAO,QAAQ,cAAc,IAAI,WAAW,gBAAgB,GACvE;KAEJ;KAEA,IAAI,CAAC,eAAe,OAAO,gBAAgB,YAAY;MACrD,MAAM,gBACJ,OAAO,cAAc,WACjB,sBAAsB,OAAO,KAAK,SAAS,CAAC,CAAC,KAAK,IAAI,MACtD,mBAAmB,OAAO;MAEhC,MAAM,IAAI,MACR,0CAA0C,SAAS,sHAI9C,eACP;KACF;KAGA,IAAI,CAAE,YAAoB,SAAS;MACjC,MAAM,gBACJ,OAAO,cAAc,WACjB,kBAAkB,OAAO,KAAK,SAAS,CAAC,CAAC,KAAK,IAAI,MAClD,mBAAmB,OAAO;MAEhC,MAAM,IAAI,MACR,mCAAmC,SAAS,iIAGvC,eACP;KACF;KAEA,OAAO;IACT;IACA,QAAQ,UACN,IAAIS,qCAAsB;KACxB;KACA,WAAW;KACX,OAAO,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;IACjE,CAAC;GACL,CAAC;GAED,OAAOT,cAAO,QAAQ,QAAQ,SAAS,uBAAuB;GAC9D,OAAO;EACT,CAAC;CACL;AACF,CAAC,CACH"}
|
|
@@ -54,13 +54,7 @@ const resolveRemoteSelfName = (pluginId, url) => Effect.gen(function* () {
|
|
|
54
54
|
const memoKey = `${pluginId}@${url}`;
|
|
55
55
|
const memo = remoteSelfNames.get(memoKey);
|
|
56
56
|
if (memo) return memo;
|
|
57
|
-
|
|
58
|
-
try {
|
|
59
|
-
selfName = (yield* Effect.promise(() => fetchRemoteIdentityManifest(url)))?.metaData?.name?.trim() || null;
|
|
60
|
-
} catch {
|
|
61
|
-
selfName = null;
|
|
62
|
-
}
|
|
63
|
-
const resolved = selfName ?? remoteName(pluginId);
|
|
57
|
+
const resolved = ((yield* Effect.tryPromise(() => fetchRemoteIdentityManifest(url)).pipe(Effect.orElseSucceed(() => null)))?.metaData?.name?.trim() || null) ?? remoteName(pluginId);
|
|
64
58
|
remoteSelfNames.set(memoKey, resolved);
|
|
65
59
|
return resolved;
|
|
66
60
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module-federation.service.mjs","names":["getNormalizedRemoteName"],"sources":["../../../src/runtime/services/module-federation.service.ts"],"sourcesContent":["import { createInstance, getInstance } from \"@module-federation/enhanced/runtime\";\nimport { setGlobalFederationInstance } from \"@module-federation/runtime-core\";\nimport { Context, Effect, Layer } from \"effect\";\nimport { DEV_ENTRY_FILENAME } from \"../../build/artifact-names\";\nimport type { AnyPlugin } from \"../../types\";\nimport { ModuleFederationError } from \"../errors\";\nimport { type CoreSharedDepName, MF_CORE_SHARED_DEPS } from \"../mf-config\";\nimport { getNormalizedRemoteName } from \"./normalize\";\nimport {\n compareSharedIdentity,\n describeSharedIdentityMismatch,\n fetchRemoteIdentityManifest,\n} from \"./shared-identity\";\n\nfunction expectedSharedIdentity(): Map<string, string> {\n return new Map(\n Object.entries(MF_CORE_SHARED_DEPS).map(([name, config]) => [name, config.version]),\n );\n}\n\ntype RemoteModule = (new () => AnyPlugin) | { default: new () => AnyPlugin };\n\nexport const loadEveryPluginSharedModule = () => import(\"../../index\");\n\nconst coreModuleLoaders: Record<CoreSharedDepName, () => Promise<unknown>> = {\n // Keep the host-provided framework share on the same source graph as the\n // runtime. A package self-import can resolve the published entry when a\n // workspace script is launched through its package manager shim.\n \"every-plugin\": loadEveryPluginSharedModule,\n effect: () => import(\"effect\"),\n zod: () => import(\"zod\"),\n \"@orpc/contract\": () => import(\"@orpc/contract\"),\n \"@orpc/client\": () => import(\"@orpc/client\"),\n \"@orpc/server\": () => import(\"@orpc/server\"),\n};\n\nfunction buildSharedConfig(): Record<\n string,\n {\n version: string;\n shareScope: string;\n get: () => Promise<() => unknown>;\n shareConfig: (typeof MF_CORE_SHARED_DEPS)[CoreSharedDepName][\"shareConfig\"];\n }\n> {\n return Object.fromEntries(\n (\n Object.entries(MF_CORE_SHARED_DEPS) as [\n CoreSharedDepName,\n (typeof MF_CORE_SHARED_DEPS)[CoreSharedDepName],\n ][]\n ).map(([name, config]) => {\n const load = coreModuleLoaders[name];\n\n if (!load) {\n throw new Error(`Missing core shared module loader for ${name}`);\n }\n\n return [\n name,\n {\n version: config.version,\n shareScope: config.shareScope,\n get: () => load().then((mod) => () => mod),\n shareConfig: config.shareConfig,\n },\n ];\n }),\n );\n}\n\nconst createModuleFederationInstance = Effect.cached(\n Effect.sync(() => {\n try {\n const shared = buildSharedConfig();\n let instance = getInstance();\n\n if (!instance) {\n instance = createInstance({\n name: \"host\",\n remotes: [],\n shared,\n });\n\n setGlobalFederationInstance(instance);\n } else {\n instance.registerShared(shared);\n }\n\n return instance;\n } catch (error) {\n throw new Error(`Failed to initialize Module Federation: ${String(error)}`);\n }\n }),\n);\n\n// A built container's runtime code looks up its own chunks through the node\n// runtime's entry-URL fallback keyed by the container's SELF name\n// (`mf-manifest.json` metaData.name — the composition key, e.g.\n// \"everything-dev_registry-plugin\"), while the host registers the remote\n// under the plugin id (\"registry\"). When those differ, the fallback misses,\n// resolveUrl returns null, and the node runtime hands back an empty chunk as\n// if it had loaded — the exposed module then requires ids nothing registered\n// (\"__webpack_modules__[r] is not a function\"). Registering and loading the\n// remote under the container's own name keeps that lookup a hit.\nconst remoteSelfNames = new Map<string, string>();\n\nconst resolveRemoteSelfName = (pluginId: string, url: string) =>\n Effect.gen(function* () {\n const memoKey = `${pluginId}@${url}`;\n const memo = remoteSelfNames.get(memoKey);\n if (memo) return memo;\n\n let selfName: string | null = null;\n try {\n const manifest = yield* Effect.promise(() => fetchRemoteIdentityManifest(url));\n selfName = manifest?.metaData?.name?.trim() || null;\n } catch {\n selfName = null;\n }\n\n const resolved = selfName ?? getNormalizedRemoteName(pluginId);\n remoteSelfNames.set(memoKey, resolved);\n return resolved;\n });\n\nexport interface ModuleFederationServiceShape {\n registerRemote: (pluginId: string, url: string) => Effect.Effect<void, ModuleFederationError>;\n loadRemoteConstructor: (\n pluginId: string,\n url: string,\n ) => Effect.Effect<new () => AnyPlugin, ModuleFederationError>;\n}\n\nexport class ModuleFederationService extends Context.Service<\n ModuleFederationService,\n ModuleFederationServiceShape\n>()(\"ModuleFederationService\") {}\n\nexport const ModuleFederationServiceDefault = Layer.effect(\n ModuleFederationService,\n Effect.gen(function* () {\n const mf = yield* Effect.flatten(createModuleFederationInstance);\n const expectedIdentity = expectedSharedIdentity();\n\n const buildSharedIdentityError = async (\n pluginId: string,\n url: string,\n ): Promise<ModuleFederationError | null> => {\n let manifest: Awaited<ReturnType<typeof fetchRemoteIdentityManifest>> | null = null;\n try {\n manifest = await fetchRemoteIdentityManifest(url);\n } catch (error) {\n console.warn(\n `[SharedIdentity] Could not fetch mf-manifest.json for plugin \"${pluginId}\" at ${url} (${error instanceof Error ? error.message : String(error)}); proceeding without identity verification`,\n );\n return null;\n }\n if (!manifest) return null;\n const mismatches = compareSharedIdentity(manifest.shared, expectedIdentity);\n if (mismatches.length === 0) return null;\n const warnOnly = process.env.BOS_MF_IDENTITY === \"warn\";\n if (warnOnly) {\n console.warn(\n `${describeSharedIdentityMismatch(pluginId, mismatches, \"this runtime\")} (BOS_MF_IDENTITY=warn: loading anyway)`,\n );\n return null;\n }\n return new ModuleFederationError({\n pluginId,\n remoteUrl: url,\n cause: new Error(describeSharedIdentityMismatch(pluginId, mismatches, \"this runtime\")),\n });\n };\n\n return {\n registerRemote: (pluginId: string, url: string) =>\n Effect.gen(function* () {\n const remoteName = yield* resolveRemoteSelfName(pluginId, url);\n yield* Effect.logDebug(`[MF][${pluginId}] Registering as \"${remoteName}\"`);\n\n const type = url.endsWith(\"/mf-manifest.json\")\n ? (\"manifest\" as const)\n : url.endsWith(`/${DEV_ENTRY_FILENAME}`)\n ? (\"script\" as const)\n : undefined;\n\n yield* Effect.try({\n try: () =>\n mf.registerRemotes([\n {\n name: remoteName,\n entry: url,\n ...(type ? { type } : {}),\n },\n ]),\n catch: (error): ModuleFederationError =>\n new ModuleFederationError({\n pluginId,\n remoteUrl: url,\n cause: error instanceof Error ? error : new Error(String(error)),\n }),\n });\n\n yield* Effect.logInfo(`[MF][${pluginId}] ✅ Registered`);\n }),\n\n loadRemoteConstructor: (pluginId: string, url: string) =>\n Effect.gen(function* () {\n const remoteName = yield* resolveRemoteSelfName(pluginId, url);\n yield* Effect.logDebug(`[MF][${pluginId}] Loading remote ${remoteName}`);\n\n const identityError = yield* Effect.promise(() =>\n buildSharedIdentityError(pluginId, url),\n );\n if (identityError) {\n yield* Effect.logError(\n `[MF][${pluginId}] ❌ shared identity mismatch: ${\n identityError.cause instanceof Error\n ? identityError.cause.message\n : String(identityError.cause ?? \"shared identity mismatch\")\n }`,\n );\n return yield* identityError;\n }\n\n const modulePath = `${remoteName}/plugin`;\n\n const pluginConstructor = yield* Effect.tryPromise({\n try: async () => {\n const container = await mf.loadRemote<RemoteModule>(modulePath);\n if (!container) {\n throw new Error(`No container returned for ${modulePath}`);\n }\n\n // Support multiple export patterns: direct function, default export, named exports\n let Constructor: any;\n\n if (typeof container === \"function\") {\n // Direct function export\n Constructor = container;\n } else if (container.default) {\n // Default export\n Constructor = container.default;\n } else {\n // Named export fallback - prioritize exports with 'binding' property (plugin classes)\n Constructor = Object.values(container).find(\n (exp) => typeof exp === \"function\" && (exp as any).binding !== undefined,\n );\n\n // Fallback to any function export if no binding found\n if (!Constructor) {\n Constructor = Object.values(container).find(\n (exp) => typeof exp === \"function\" && exp.prototype?.constructor === exp,\n );\n }\n }\n\n if (!Constructor || typeof Constructor !== \"function\") {\n const containerInfo =\n typeof container === \"object\"\n ? `Available exports: ${Object.keys(container).join(\", \")}`\n : `Container type: ${typeof container}`;\n\n throw new Error(\n `No valid plugin constructor found for '${pluginId}'.\\n` +\n `Supported patterns:\\n` +\n ` - export const YourPlugin = createPlugin({...})\\n` +\n ` - export default createPlugin({...})\\n` +\n `${containerInfo}`,\n );\n }\n\n // Validate it looks like a plugin constructor (has binding property)\n if (!(Constructor as any).binding) {\n const containerInfo =\n typeof container === \"object\"\n ? `Found exports: ${Object.keys(container).join(\", \")}`\n : `Container type: ${typeof container}`;\n\n throw new Error(\n `Invalid plugin constructor for '${pluginId}'. ` +\n `The exported value must be created with createPlugin(). ` +\n `Found a function but it's missing the required 'binding' property.\\n` +\n `${containerInfo}`,\n );\n }\n\n return Constructor;\n },\n catch: (error): ModuleFederationError =>\n new ModuleFederationError({\n pluginId,\n remoteUrl: url,\n cause: error instanceof Error ? error : new Error(String(error)),\n }),\n });\n\n yield* Effect.logInfo(`[MF][${pluginId}] ✅ Loaded constructor`);\n return pluginConstructor;\n }),\n };\n }),\n);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"module-federation.service.mjs","names":["getNormalizedRemoteName"],"sources":["../../../src/runtime/services/module-federation.service.ts"],"sourcesContent":["import { createInstance, getInstance } from \"@module-federation/enhanced/runtime\";\nimport { setGlobalFederationInstance } from \"@module-federation/runtime-core\";\nimport { Context, Effect, Layer } from \"effect\";\nimport { DEV_ENTRY_FILENAME } from \"../../build/artifact-names\";\nimport type { AnyPlugin } from \"../../types\";\nimport { ModuleFederationError } from \"../errors\";\nimport { type CoreSharedDepName, MF_CORE_SHARED_DEPS } from \"../mf-config\";\nimport { getNormalizedRemoteName } from \"./normalize\";\nimport {\n compareSharedIdentity,\n describeSharedIdentityMismatch,\n fetchRemoteIdentityManifest,\n} from \"./shared-identity\";\n\nfunction expectedSharedIdentity(): Map<string, string> {\n return new Map(\n Object.entries(MF_CORE_SHARED_DEPS).map(([name, config]) => [name, config.version]),\n );\n}\n\ntype RemoteModule = (new () => AnyPlugin) | { default: new () => AnyPlugin };\n\nexport const loadEveryPluginSharedModule = () => import(\"../../index\");\n\nconst coreModuleLoaders: Record<CoreSharedDepName, () => Promise<unknown>> = {\n // Keep the host-provided framework share on the same source graph as the\n // runtime. A package self-import can resolve the published entry when a\n // workspace script is launched through its package manager shim.\n \"every-plugin\": loadEveryPluginSharedModule,\n effect: () => import(\"effect\"),\n zod: () => import(\"zod\"),\n \"@orpc/contract\": () => import(\"@orpc/contract\"),\n \"@orpc/client\": () => import(\"@orpc/client\"),\n \"@orpc/server\": () => import(\"@orpc/server\"),\n};\n\nfunction buildSharedConfig(): Record<\n string,\n {\n version: string;\n shareScope: string;\n get: () => Promise<() => unknown>;\n shareConfig: (typeof MF_CORE_SHARED_DEPS)[CoreSharedDepName][\"shareConfig\"];\n }\n> {\n return Object.fromEntries(\n (\n Object.entries(MF_CORE_SHARED_DEPS) as [\n CoreSharedDepName,\n (typeof MF_CORE_SHARED_DEPS)[CoreSharedDepName],\n ][]\n ).map(([name, config]) => {\n const load = coreModuleLoaders[name];\n\n if (!load) {\n throw new Error(`Missing core shared module loader for ${name}`);\n }\n\n return [\n name,\n {\n version: config.version,\n shareScope: config.shareScope,\n get: () => load().then((mod) => () => mod),\n shareConfig: config.shareConfig,\n },\n ];\n }),\n );\n}\n\nconst createModuleFederationInstance = Effect.cached(\n Effect.sync(() => {\n try {\n const shared = buildSharedConfig();\n let instance = getInstance();\n\n if (!instance) {\n instance = createInstance({\n name: \"host\",\n remotes: [],\n shared,\n });\n\n setGlobalFederationInstance(instance);\n } else {\n instance.registerShared(shared);\n }\n\n return instance;\n } catch (error) {\n throw new Error(`Failed to initialize Module Federation: ${String(error)}`);\n }\n }),\n);\n\n// A built container's runtime code looks up its own chunks through the node\n// runtime's entry-URL fallback keyed by the container's SELF name\n// (`mf-manifest.json` metaData.name — the composition key, e.g.\n// \"everything-dev_registry-plugin\"), while the host registers the remote\n// under the plugin id (\"registry\"). When those differ, the fallback misses,\n// resolveUrl returns null, and the node runtime hands back an empty chunk as\n// if it had loaded — the exposed module then requires ids nothing registered\n// (\"__webpack_modules__[r] is not a function\"). Registering and loading the\n// remote under the container's own name keeps that lookup a hit.\nconst remoteSelfNames = new Map<string, string>();\n\nconst resolveRemoteSelfName = (pluginId: string, url: string) =>\n Effect.gen(function* () {\n const memoKey = `${pluginId}@${url}`;\n const memo = remoteSelfNames.get(memoKey);\n if (memo) return memo;\n\n const manifest = yield* Effect.tryPromise(() => fetchRemoteIdentityManifest(url)).pipe(\n Effect.orElseSucceed(() => null),\n );\n const selfName = manifest?.metaData?.name?.trim() || null;\n\n const resolved = selfName ?? getNormalizedRemoteName(pluginId);\n remoteSelfNames.set(memoKey, resolved);\n return resolved;\n });\n\nexport interface ModuleFederationServiceShape {\n registerRemote: (pluginId: string, url: string) => Effect.Effect<void, ModuleFederationError>;\n loadRemoteConstructor: (\n pluginId: string,\n url: string,\n ) => Effect.Effect<new () => AnyPlugin, ModuleFederationError>;\n}\n\nexport class ModuleFederationService extends Context.Service<\n ModuleFederationService,\n ModuleFederationServiceShape\n>()(\"ModuleFederationService\") {}\n\nexport const ModuleFederationServiceDefault = Layer.effect(\n ModuleFederationService,\n Effect.gen(function* () {\n const mf = yield* Effect.flatten(createModuleFederationInstance);\n const expectedIdentity = expectedSharedIdentity();\n\n const buildSharedIdentityError = async (\n pluginId: string,\n url: string,\n ): Promise<ModuleFederationError | null> => {\n let manifest: Awaited<ReturnType<typeof fetchRemoteIdentityManifest>> | null = null;\n try {\n manifest = await fetchRemoteIdentityManifest(url);\n } catch (error) {\n console.warn(\n `[SharedIdentity] Could not fetch mf-manifest.json for plugin \"${pluginId}\" at ${url} (${error instanceof Error ? error.message : String(error)}); proceeding without identity verification`,\n );\n return null;\n }\n if (!manifest) return null;\n const mismatches = compareSharedIdentity(manifest.shared, expectedIdentity);\n if (mismatches.length === 0) return null;\n const warnOnly = process.env.BOS_MF_IDENTITY === \"warn\";\n if (warnOnly) {\n console.warn(\n `${describeSharedIdentityMismatch(pluginId, mismatches, \"this runtime\")} (BOS_MF_IDENTITY=warn: loading anyway)`,\n );\n return null;\n }\n return new ModuleFederationError({\n pluginId,\n remoteUrl: url,\n cause: new Error(describeSharedIdentityMismatch(pluginId, mismatches, \"this runtime\")),\n });\n };\n\n return {\n registerRemote: (pluginId: string, url: string) =>\n Effect.gen(function* () {\n const remoteName = yield* resolveRemoteSelfName(pluginId, url);\n yield* Effect.logDebug(`[MF][${pluginId}] Registering as \"${remoteName}\"`);\n\n const type = url.endsWith(\"/mf-manifest.json\")\n ? (\"manifest\" as const)\n : url.endsWith(`/${DEV_ENTRY_FILENAME}`)\n ? (\"script\" as const)\n : undefined;\n\n yield* Effect.try({\n try: () =>\n mf.registerRemotes([\n {\n name: remoteName,\n entry: url,\n ...(type ? { type } : {}),\n },\n ]),\n catch: (error): ModuleFederationError =>\n new ModuleFederationError({\n pluginId,\n remoteUrl: url,\n cause: error instanceof Error ? error : new Error(String(error)),\n }),\n });\n\n yield* Effect.logInfo(`[MF][${pluginId}] ✅ Registered`);\n }),\n\n loadRemoteConstructor: (pluginId: string, url: string) =>\n Effect.gen(function* () {\n const remoteName = yield* resolveRemoteSelfName(pluginId, url);\n yield* Effect.logDebug(`[MF][${pluginId}] Loading remote ${remoteName}`);\n\n const identityError = yield* Effect.promise(() =>\n buildSharedIdentityError(pluginId, url),\n );\n if (identityError) {\n yield* Effect.logError(\n `[MF][${pluginId}] ❌ shared identity mismatch: ${\n identityError.cause instanceof Error\n ? identityError.cause.message\n : String(identityError.cause ?? \"shared identity mismatch\")\n }`,\n );\n return yield* identityError;\n }\n\n const modulePath = `${remoteName}/plugin`;\n\n const pluginConstructor = yield* Effect.tryPromise({\n try: async () => {\n const container = await mf.loadRemote<RemoteModule>(modulePath);\n if (!container) {\n throw new Error(`No container returned for ${modulePath}`);\n }\n\n // Support multiple export patterns: direct function, default export, named exports\n let Constructor: any;\n\n if (typeof container === \"function\") {\n // Direct function export\n Constructor = container;\n } else if (container.default) {\n // Default export\n Constructor = container.default;\n } else {\n // Named export fallback - prioritize exports with 'binding' property (plugin classes)\n Constructor = Object.values(container).find(\n (exp) => typeof exp === \"function\" && (exp as any).binding !== undefined,\n );\n\n // Fallback to any function export if no binding found\n if (!Constructor) {\n Constructor = Object.values(container).find(\n (exp) => typeof exp === \"function\" && exp.prototype?.constructor === exp,\n );\n }\n }\n\n if (!Constructor || typeof Constructor !== \"function\") {\n const containerInfo =\n typeof container === \"object\"\n ? `Available exports: ${Object.keys(container).join(\", \")}`\n : `Container type: ${typeof container}`;\n\n throw new Error(\n `No valid plugin constructor found for '${pluginId}'.\\n` +\n `Supported patterns:\\n` +\n ` - export const YourPlugin = createPlugin({...})\\n` +\n ` - export default createPlugin({...})\\n` +\n `${containerInfo}`,\n );\n }\n\n // Validate it looks like a plugin constructor (has binding property)\n if (!(Constructor as any).binding) {\n const containerInfo =\n typeof container === \"object\"\n ? `Found exports: ${Object.keys(container).join(\", \")}`\n : `Container type: ${typeof container}`;\n\n throw new Error(\n `Invalid plugin constructor for '${pluginId}'. ` +\n `The exported value must be created with createPlugin(). ` +\n `Found a function but it's missing the required 'binding' property.\\n` +\n `${containerInfo}`,\n );\n }\n\n return Constructor;\n },\n catch: (error): ModuleFederationError =>\n new ModuleFederationError({\n pluginId,\n remoteUrl: url,\n cause: error instanceof Error ? error : new Error(String(error)),\n }),\n });\n\n yield* Effect.logInfo(`[MF][${pluginId}] ✅ Loaded constructor`);\n return pluginConstructor;\n }),\n };\n }),\n);\n"],"mappings":"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"}
|
|
@@ -6,6 +6,27 @@ const require_secrets_service = require('./secrets.service.cjs');
|
|
|
6
6
|
let effect = require("effect");
|
|
7
7
|
|
|
8
8
|
//#region src/runtime/services/plugin-loader.service.ts
|
|
9
|
+
/**
|
|
10
|
+
* Evaluate a plugin's `warnings` function against the validated config.
|
|
11
|
+
* Fail-safe: a throwing warnings function becomes a warning itself rather
|
|
12
|
+
* than failing initialization. Strings are normalized to `PluginWarningInfo`
|
|
13
|
+
* with the plugin's registry key.
|
|
14
|
+
*/
|
|
15
|
+
const collectPluginWarnings = (plugin, config) => {
|
|
16
|
+
const warningsFn = plugin.warnings;
|
|
17
|
+
if (!warningsFn) return [];
|
|
18
|
+
try {
|
|
19
|
+
return warningsFn(config).map((warning) => typeof warning === "string" ? {
|
|
20
|
+
pluginKey: plugin.id,
|
|
21
|
+
message: warning
|
|
22
|
+
} : warning);
|
|
23
|
+
} catch (error) {
|
|
24
|
+
return [{
|
|
25
|
+
pluginKey: plugin.id,
|
|
26
|
+
message: `Plugin warnings check failed: ${error instanceof Error ? error.message : String(error)}`
|
|
27
|
+
}];
|
|
28
|
+
}
|
|
29
|
+
};
|
|
9
30
|
var PluginRegistryTag = class extends effect.Context.Service()("PluginRegistry") {};
|
|
10
31
|
var PluginMapTag = class extends effect.Context.Service()("PluginMap") {};
|
|
11
32
|
var RegistryService = class extends effect.Context.Service()("RegistryService") {};
|
|
@@ -118,7 +139,11 @@ const PluginLoaderServiceDefault = effect.Layer.effect(PluginLoaderService, effe
|
|
|
118
139
|
secrets: hydratedConfig.secrets
|
|
119
140
|
},
|
|
120
141
|
effectContext,
|
|
121
|
-
scope
|
|
142
|
+
scope,
|
|
143
|
+
warnings: collectPluginWarnings(plugin, {
|
|
144
|
+
variables: _variables,
|
|
145
|
+
secrets: hydratedConfig.secrets
|
|
146
|
+
})
|
|
122
147
|
};
|
|
123
148
|
})
|
|
124
149
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin-loader.service.cjs","names":["Context","Layer","Effect","PluginRuntimeError","ModuleFederationService","SecretsService","toPluginRuntimeError","validate","Scope","PluginIdTag","Exit"],"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.cjs","names":["Context","Layer","Effect","PluginRuntimeError","ModuleFederationService","SecretsService","toPluginRuntimeError","validate","Scope","PluginIdTag","Exit"],"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,6 +6,27 @@ import { SecretsService } from "./secrets.service.mjs";
|
|
|
6
6
|
import { Context, Effect, Exit, Layer, Scope } from "effect";
|
|
7
7
|
|
|
8
8
|
//#region src/runtime/services/plugin-loader.service.ts
|
|
9
|
+
/**
|
|
10
|
+
* Evaluate a plugin's `warnings` function against the validated config.
|
|
11
|
+
* Fail-safe: a throwing warnings function becomes a warning itself rather
|
|
12
|
+
* than failing initialization. Strings are normalized to `PluginWarningInfo`
|
|
13
|
+
* with the plugin's registry key.
|
|
14
|
+
*/
|
|
15
|
+
const collectPluginWarnings = (plugin, config) => {
|
|
16
|
+
const warningsFn = plugin.warnings;
|
|
17
|
+
if (!warningsFn) return [];
|
|
18
|
+
try {
|
|
19
|
+
return warningsFn(config).map((warning) => typeof warning === "string" ? {
|
|
20
|
+
pluginKey: plugin.id,
|
|
21
|
+
message: warning
|
|
22
|
+
} : warning);
|
|
23
|
+
} catch (error) {
|
|
24
|
+
return [{
|
|
25
|
+
pluginKey: plugin.id,
|
|
26
|
+
message: `Plugin warnings check failed: ${error instanceof Error ? error.message : String(error)}`
|
|
27
|
+
}];
|
|
28
|
+
}
|
|
29
|
+
};
|
|
9
30
|
var PluginRegistryTag = class extends Context.Service()("PluginRegistry") {};
|
|
10
31
|
var PluginMapTag = class extends Context.Service()("PluginMap") {};
|
|
11
32
|
var RegistryService = class extends Context.Service()("RegistryService") {};
|
|
@@ -118,7 +139,11 @@ const PluginLoaderServiceDefault = Layer.effect(PluginLoaderService, Effect.gen(
|
|
|
118
139
|
secrets: hydratedConfig.secrets
|
|
119
140
|
},
|
|
120
141
|
effectContext,
|
|
121
|
-
scope
|
|
142
|
+
scope,
|
|
143
|
+
warnings: collectPluginWarnings(plugin, {
|
|
144
|
+
variables: _variables,
|
|
145
|
+
secrets: hydratedConfig.secrets
|
|
146
|
+
})
|
|
122
147
|
};
|
|
123
148
|
})
|
|
124
149
|
};
|