every-plugin 0.8.9 → 2.2.2
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/_virtual/{rolldown_runtime.cjs → _rolldown/runtime.cjs} +15 -1
- package/dist/_virtual/{rolldown_runtime.mjs → _rolldown/runtime.mjs} +15 -2
- package/dist/build/rspack/dev-server-middleware.cjs +15 -4
- package/dist/build/rspack/dev-server-middleware.cjs.map +1 -1
- package/dist/build/rspack/dev-server-middleware.mjs +15 -4
- package/dist/build/rspack/dev-server-middleware.mjs.map +1 -1
- package/dist/build/rspack/fix-mf-data-uri-plugin.cjs +44 -0
- package/dist/build/rspack/fix-mf-data-uri-plugin.cjs.map +1 -0
- package/dist/build/rspack/fix-mf-data-uri-plugin.d.cts +11 -0
- package/dist/build/rspack/fix-mf-data-uri-plugin.d.cts.map +1 -0
- package/dist/build/rspack/fix-mf-data-uri-plugin.d.mts +11 -0
- package/dist/build/rspack/fix-mf-data-uri-plugin.d.mts.map +1 -0
- package/dist/build/rspack/fix-mf-data-uri-plugin.mjs +43 -0
- package/dist/build/rspack/fix-mf-data-uri-plugin.mjs.map +1 -0
- package/dist/build/rspack/index.cjs +8 -6
- package/dist/build/rspack/index.d.cts +4 -3
- package/dist/build/rspack/index.d.mts +4 -3
- package/dist/build/rspack/index.mjs +4 -3
- package/dist/build/rspack/module-federation.cjs +3 -3
- package/dist/build/rspack/module-federation.cjs.map +1 -1
- package/dist/build/rspack/module-federation.mjs +1 -1
- package/dist/build/rspack/module-federation.mjs.map +1 -1
- package/dist/build/rspack/plugin.cjs +83 -5
- package/dist/build/rspack/plugin.cjs.map +1 -1
- package/dist/build/rspack/plugin.d.cts +12 -1
- package/dist/build/rspack/plugin.d.cts.map +1 -1
- package/dist/build/rspack/plugin.d.mts +12 -1
- package/dist/build/rspack/plugin.d.mts.map +1 -1
- package/dist/build/rspack/plugin.mjs +80 -5
- package/dist/build/rspack/plugin.mjs.map +1 -1
- package/dist/build/rspack/utils.cjs +5 -4
- package/dist/build/rspack/utils.cjs.map +1 -1
- package/dist/build/rspack/utils.mjs +3 -2
- package/dist/build/rspack/utils.mjs.map +1 -1
- package/dist/build/shared-deps.cjs +50 -65
- package/dist/build/shared-deps.cjs.map +1 -1
- package/dist/build/shared-deps.d.cts +27 -4
- package/dist/build/shared-deps.d.cts.map +1 -1
- package/dist/build/shared-deps.d.mts +27 -4
- package/dist/build/shared-deps.d.mts.map +1 -1
- package/dist/build/shared-deps.mjs +48 -64
- package/dist/build/shared-deps.mjs.map +1 -1
- package/dist/errors.cjs +1 -0
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.d.cts.map +1 -1
- package/dist/errors.d.mts.map +1 -1
- package/dist/errors.mjs.map +1 -1
- package/dist/index.cjs +2 -0
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/orpc-client.cjs +17 -0
- package/dist/orpc-client.d.cts +2 -0
- package/dist/orpc-client.d.mts +2 -0
- package/dist/orpc-client.mjs +5 -0
- package/dist/orpc-openapi.cjs +17 -0
- package/dist/orpc-openapi.d.cts +2 -0
- package/dist/orpc-openapi.d.mts +2 -0
- package/dist/orpc-openapi.mjs +5 -0
- package/dist/orpc.cjs +8 -7
- package/dist/plugin.cjs +10 -9
- package/dist/plugin.cjs.map +1 -1
- package/dist/plugin.d.cts +28 -29
- package/dist/plugin.d.cts.map +1 -1
- package/dist/plugin.d.mts +28 -29
- package/dist/plugin.d.mts.map +1 -1
- package/dist/plugin.mjs +10 -10
- package/dist/plugin.mjs.map +1 -1
- package/dist/runtime/errors.cjs +2 -2
- package/dist/runtime/errors.cjs.map +1 -1
- package/dist/runtime/errors.d.cts +4 -4
- package/dist/runtime/errors.d.cts.map +1 -1
- package/dist/runtime/errors.d.mts +4 -4
- package/dist/runtime/errors.d.mts.map +1 -1
- package/dist/runtime/errors.mjs +1 -1
- package/dist/runtime/errors.mjs.map +1 -1
- package/dist/runtime/index.cjs +24 -20
- package/dist/runtime/index.cjs.map +1 -1
- package/dist/runtime/index.d.cts +3 -3
- package/dist/runtime/index.d.cts.map +1 -1
- package/dist/runtime/index.d.mts +3 -3
- package/dist/runtime/index.d.mts.map +1 -1
- package/dist/runtime/index.mjs +12 -9
- package/dist/runtime/index.mjs.map +1 -1
- package/dist/runtime/mf-config.cjs +33 -0
- package/dist/runtime/mf-config.cjs.map +1 -0
- package/dist/runtime/mf-config.d.cts +41 -0
- package/dist/runtime/mf-config.d.cts.map +1 -0
- package/dist/runtime/mf-config.d.mts +41 -0
- package/dist/runtime/mf-config.d.mts.map +1 -0
- package/dist/runtime/mf-config.mjs +29 -0
- package/dist/runtime/mf-config.mjs.map +1 -0
- package/dist/runtime/services/module-federation.service.cjs +19 -56
- package/dist/runtime/services/module-federation.service.cjs.map +1 -1
- package/dist/runtime/services/module-federation.service.d.cts.map +1 -1
- package/dist/runtime/services/module-federation.service.d.mts.map +1 -1
- package/dist/runtime/services/module-federation.service.mjs +18 -55
- package/dist/runtime/services/module-federation.service.mjs.map +1 -1
- package/dist/runtime/services/normalize.cjs +3 -2
- package/dist/runtime/services/normalize.cjs.map +1 -1
- package/dist/runtime/services/normalize.d.cts.map +1 -1
- package/dist/runtime/services/normalize.d.mts.map +1 -1
- package/dist/runtime/services/normalize.mjs +2 -2
- package/dist/runtime/services/normalize.mjs.map +1 -1
- package/dist/runtime/services/plugin-lifecycle.service.cjs +1 -1
- package/dist/runtime/services/plugin-lifecycle.service.cjs.map +1 -1
- package/dist/runtime/services/plugin-lifecycle.service.d.cts.map +1 -1
- package/dist/runtime/services/plugin-lifecycle.service.d.mts.map +1 -1
- package/dist/runtime/services/plugin-lifecycle.service.mjs.map +1 -1
- package/dist/runtime/services/plugin-loader.service.cjs +3 -3
- package/dist/runtime/services/plugin-loader.service.cjs.map +1 -1
- package/dist/runtime/services/plugin-loader.service.d.cts +1 -1
- package/dist/runtime/services/plugin-loader.service.d.cts.map +1 -1
- package/dist/runtime/services/plugin-loader.service.d.mts +1 -1
- package/dist/runtime/services/plugin-loader.service.d.mts.map +1 -1
- package/dist/runtime/services/plugin-loader.service.mjs +2 -2
- package/dist/runtime/services/plugin-loader.service.mjs.map +1 -1
- package/dist/runtime/services/plugin.service.cjs +1 -1
- package/dist/runtime/services/plugin.service.cjs.map +1 -1
- package/dist/runtime/services/plugin.service.d.cts +6 -7
- package/dist/runtime/services/plugin.service.d.cts.map +1 -1
- package/dist/runtime/services/plugin.service.d.mts +6 -7
- package/dist/runtime/services/plugin.service.d.mts.map +1 -1
- package/dist/runtime/services/plugin.service.mjs.map +1 -1
- package/dist/runtime/services/secrets.service.cjs +4 -4
- package/dist/runtime/services/secrets.service.cjs.map +1 -1
- package/dist/runtime/services/secrets.service.d.cts.map +1 -1
- package/dist/runtime/services/secrets.service.d.mts.map +1 -1
- package/dist/runtime/services/secrets.service.mjs +3 -3
- package/dist/runtime/services/secrets.service.mjs.map +1 -1
- package/dist/runtime/validation.cjs +1 -1
- package/dist/runtime/validation.cjs.map +1 -1
- package/dist/runtime/validation.mjs.map +1 -1
- package/dist/testing/index.cjs +2 -1
- package/dist/testing/index.cjs.map +1 -1
- package/dist/testing/index.d.cts +0 -2
- package/dist/testing/index.d.cts.map +1 -1
- package/dist/testing/index.d.mts +0 -2
- package/dist/testing/index.d.mts.map +1 -1
- package/dist/testing/index.mjs +1 -1
- package/dist/testing/index.mjs.map +1 -1
- package/dist/types.d.cts +22 -22
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.mts +22 -22
- package/dist/types.d.mts.map +1 -1
- package/dist/zod.cjs +4 -3
- package/dist/zod.mjs +2 -2
- package/package.json +204 -194
- package/src/build/rspack/dev-server-middleware.ts +233 -0
- package/src/build/rspack/fix-mf-data-uri-plugin.ts +49 -0
- package/src/build/rspack/index.ts +14 -0
- package/src/build/rspack/module-federation.ts +6 -0
- package/src/build/rspack/plugin.ts +252 -0
- package/src/build/rspack/utils.ts +60 -0
- package/src/build/shared-deps.ts +79 -0
- package/src/effect.ts +1 -0
- package/src/errors.ts +140 -0
- package/src/index.ts +26 -0
- package/src/orpc-client.ts +2 -0
- package/src/orpc-openapi.ts +2 -0
- package/src/orpc.ts +9 -0
- package/src/plugin.ts +220 -0
- package/src/runtime/errors.ts +332 -0
- package/src/runtime/index.ts +258 -0
- package/src/runtime/mf-config.ts +29 -0
- package/src/runtime/services/module-federation.service.ts +181 -0
- package/src/runtime/services/normalize.ts +18 -0
- package/src/runtime/services/plugin-lifecycle.service.ts +86 -0
- package/src/runtime/services/plugin-loader.service.ts +254 -0
- package/src/runtime/services/plugin.service.ts +66 -0
- package/src/runtime/services/secrets.service.ts +74 -0
- package/src/runtime/services.ts +5 -0
- package/src/runtime/validation.ts +23 -0
- package/src/testing/index.ts +87 -0
- package/src/testing/mocks/module-federation.service.ts +41 -0
- package/src/types.ts +309 -0
- package/src/zod-core.ts +4 -0
- package/src/zod.ts +1 -0
- package/dist/package.cjs +0 -47
- package/dist/package.cjs.map +0 -1
- package/dist/package.mjs +0 -29
- package/dist/package.mjs.map +0 -1
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import { ORPCError } from "@orpc/contract";
|
|
2
|
+
import { Cause, Data } from "effect";
|
|
3
|
+
import type { z } from "zod";
|
|
4
|
+
|
|
5
|
+
export class PluginRuntimeError extends Data.TaggedError("PluginRuntimeError")<{
|
|
6
|
+
readonly pluginId?: string;
|
|
7
|
+
readonly operation?: string;
|
|
8
|
+
readonly procedureName?: string;
|
|
9
|
+
readonly cause?: Error;
|
|
10
|
+
readonly retryable: boolean;
|
|
11
|
+
}> {}
|
|
12
|
+
|
|
13
|
+
export class ModuleFederationError extends Data.TaggedError("ModuleFederationError")<{
|
|
14
|
+
readonly pluginId: string;
|
|
15
|
+
readonly remoteUrl: string;
|
|
16
|
+
readonly cause?: Error;
|
|
17
|
+
}> {}
|
|
18
|
+
|
|
19
|
+
export class ValidationError extends Data.TaggedError("ValidationError")<{
|
|
20
|
+
readonly pluginId: string;
|
|
21
|
+
readonly stage: "config" | "input" | "output" | "state";
|
|
22
|
+
readonly zodError: z.ZodError;
|
|
23
|
+
}> {}
|
|
24
|
+
|
|
25
|
+
const extractErrorMessage = (error: unknown): string => {
|
|
26
|
+
if (!error) return "Unknown error";
|
|
27
|
+
|
|
28
|
+
if (error instanceof Error) {
|
|
29
|
+
if (error.message) return error.message;
|
|
30
|
+
if ((error as any).cause instanceof Error) {
|
|
31
|
+
return extractErrorMessage((error as any).cause);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (error instanceof AggregateError && error.errors?.length) {
|
|
36
|
+
return error.errors.map((e) => extractErrorMessage(e)).join("; ");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (typeof error === "object" && "message" in error) {
|
|
40
|
+
return String((error as any).message);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return String(error);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const formatValidationIssue = (issue: any, index: number, maxDisplay: number): string => {
|
|
47
|
+
if (index >= maxDisplay) return "";
|
|
48
|
+
|
|
49
|
+
const path =
|
|
50
|
+
Array.isArray(issue.path) && issue.path.length > 0
|
|
51
|
+
? issue.path.join(".")
|
|
52
|
+
: issue.path || "root";
|
|
53
|
+
|
|
54
|
+
const message = issue.message || "Validation failed";
|
|
55
|
+
|
|
56
|
+
return `│ ${index + 1}. ${path}: ${message}`;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const formatDataPreview = (data: unknown, maxLength = 100): string => {
|
|
60
|
+
if (!data) return "undefined";
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const str = JSON.stringify(data);
|
|
64
|
+
if (str.length <= maxLength) return str;
|
|
65
|
+
|
|
66
|
+
if (typeof data === "object" && data !== null) {
|
|
67
|
+
if (Array.isArray(data)) {
|
|
68
|
+
return `Array(${data.length}) [...]`;
|
|
69
|
+
}
|
|
70
|
+
const keys = Object.keys(data);
|
|
71
|
+
return `{ ${keys.slice(0, 3).join(", ")}${keys.length > 3 ? ", ..." : ""} }`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return `${str.slice(0, maxLength)}...`;
|
|
75
|
+
} catch {
|
|
76
|
+
return String(data).slice(0, maxLength);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const formatORPCValidationError = (error: any): string[] | null => {
|
|
81
|
+
const cause = error?.cause || error;
|
|
82
|
+
|
|
83
|
+
if (!cause?.issues || !Array.isArray(cause.issues) || cause.issues.length === 0) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const lines: string[] = [];
|
|
88
|
+
const errorType = error?.message || cause?.message || "Validation failed";
|
|
89
|
+
|
|
90
|
+
lines.push(`\n╭─ oRPC Validation Error ${"─".repeat(30)}`);
|
|
91
|
+
lines.push(`│ ${errorType}`);
|
|
92
|
+
lines.push(`│`);
|
|
93
|
+
|
|
94
|
+
const maxDisplay = 10;
|
|
95
|
+
const totalIssues = cause.issues.length;
|
|
96
|
+
|
|
97
|
+
lines.push(`│ Issues (${totalIssues}):`);
|
|
98
|
+
|
|
99
|
+
const displayedIssues = cause.issues.slice(0, maxDisplay);
|
|
100
|
+
displayedIssues.forEach((issue: any, idx: number) => {
|
|
101
|
+
const formatted = formatValidationIssue(issue, idx, maxDisplay);
|
|
102
|
+
if (formatted) lines.push(formatted);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
if (totalIssues > maxDisplay) {
|
|
106
|
+
lines.push(`│ ... and ${totalIssues - maxDisplay} more`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (cause.data !== undefined) {
|
|
110
|
+
lines.push(`│`);
|
|
111
|
+
lines.push(`│ Data preview: ${formatDataPreview(cause.data, 80)}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
lines.push(`╰${"─".repeat(50)}\n`);
|
|
115
|
+
|
|
116
|
+
return lines;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export const formatORPCError = (error: any): void => {
|
|
120
|
+
if (!(error instanceof ORPCError)) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const validationLines = formatORPCValidationError(error);
|
|
125
|
+
if (validationLines) {
|
|
126
|
+
console.error(validationLines.join("\n"));
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const lines: string[] = [];
|
|
131
|
+
const code = error.code || "UNKNOWN";
|
|
132
|
+
const status = error.status || 500;
|
|
133
|
+
const message = error.message || "An error occurred";
|
|
134
|
+
|
|
135
|
+
lines.push(`\n╭─ oRPC Error ${"─".repeat(40)}`);
|
|
136
|
+
lines.push(`│ ${message}`);
|
|
137
|
+
lines.push(`│ Code: ${code} (${status})`);
|
|
138
|
+
lines.push(`│`);
|
|
139
|
+
|
|
140
|
+
if (error.data) {
|
|
141
|
+
const dataType = typeof error.data;
|
|
142
|
+
if (dataType === "object" && error.data !== null) {
|
|
143
|
+
if ("retryAfter" in error.data) {
|
|
144
|
+
lines.push(`│ Retry after: ${error.data.retryAfter} seconds`);
|
|
145
|
+
}
|
|
146
|
+
if ("remainingRequests" in error.data) {
|
|
147
|
+
lines.push(`│ Remaining: ${error.data.remainingRequests} requests`);
|
|
148
|
+
}
|
|
149
|
+
if ("host" in error.data) {
|
|
150
|
+
lines.push(`│ Host: ${error.data.host}`);
|
|
151
|
+
}
|
|
152
|
+
if ("port" in error.data) {
|
|
153
|
+
lines.push(`│ Port: ${error.data.port}`);
|
|
154
|
+
}
|
|
155
|
+
if ("suggestion" in error.data) {
|
|
156
|
+
lines.push(`│ → ${error.data.suggestion}`);
|
|
157
|
+
}
|
|
158
|
+
if ("resource" in error.data) {
|
|
159
|
+
lines.push(`│ Resource: ${error.data.resource}`);
|
|
160
|
+
}
|
|
161
|
+
if ("resourceId" in error.data) {
|
|
162
|
+
lines.push(`│ ID: ${error.data.resourceId}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
switch (code) {
|
|
168
|
+
case "UNAUTHORIZED":
|
|
169
|
+
lines.push(`│ → Check your API key or credentials`);
|
|
170
|
+
break;
|
|
171
|
+
case "TOO_MANY_REQUESTS":
|
|
172
|
+
lines.push(`│ → Wait before retrying`);
|
|
173
|
+
break;
|
|
174
|
+
case "SERVICE_UNAVAILABLE":
|
|
175
|
+
case "BAD_GATEWAY":
|
|
176
|
+
case "GATEWAY_TIMEOUT":
|
|
177
|
+
lines.push(`│ → The service may be temporarily unavailable`);
|
|
178
|
+
break;
|
|
179
|
+
case "TIMEOUT":
|
|
180
|
+
lines.push(`│ → The operation took too long`);
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
lines.push(`╰${"─".repeat(50)}\n`);
|
|
185
|
+
console.error(lines.join("\n"));
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const formatPluginError = (
|
|
189
|
+
pluginId: string | undefined,
|
|
190
|
+
operation: string | undefined,
|
|
191
|
+
message: string,
|
|
192
|
+
): void => {
|
|
193
|
+
const lines: string[] = [];
|
|
194
|
+
|
|
195
|
+
lines.push(`\n╭─ Plugin Error ${"─".repeat(40)}`);
|
|
196
|
+
if (pluginId) lines.push(`│ Plugin: ${pluginId}`);
|
|
197
|
+
if (operation) lines.push(`│ During: ${operation}`);
|
|
198
|
+
lines.push(`│`);
|
|
199
|
+
|
|
200
|
+
if (message.includes("ECONNREFUSED")) {
|
|
201
|
+
lines.push(`│ ❌ Connection refused`);
|
|
202
|
+
lines.push(`│ `);
|
|
203
|
+
lines.push(`│ A required service is not running.`);
|
|
204
|
+
lines.push(`│ → Run: docker compose up -d`);
|
|
205
|
+
} else if (message.includes("ENOTFOUND")) {
|
|
206
|
+
lines.push(`│ ❌ Host not found`);
|
|
207
|
+
lines.push(`│ `);
|
|
208
|
+
lines.push(`│ Check your connection URL or network settings.`);
|
|
209
|
+
} else if (message.includes("ETIMEDOUT") || message.includes("timeout")) {
|
|
210
|
+
lines.push(`│ ❌ Connection timeout`);
|
|
211
|
+
lines.push(`│ `);
|
|
212
|
+
lines.push(`│ The service took too long to respond.`);
|
|
213
|
+
} else if (message.includes("EACCES") || message.includes("permission")) {
|
|
214
|
+
lines.push(`│ ❌ Permission denied`);
|
|
215
|
+
lines.push(`│ `);
|
|
216
|
+
lines.push(`│ Check credentials or access permissions.`);
|
|
217
|
+
} else if (message.includes("401") || message.includes("unauthorized")) {
|
|
218
|
+
lines.push(`│ ❌ Authentication failed`);
|
|
219
|
+
lines.push(`│ `);
|
|
220
|
+
lines.push(`│ Check your API key or credentials.`);
|
|
221
|
+
} else {
|
|
222
|
+
lines.push(`│ ❌ ${message}`);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
lines.push(`╰${"─".repeat(50)}\n`);
|
|
226
|
+
|
|
227
|
+
console.error(lines.join("\n"));
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const isRetryableError = (message: string): boolean => {
|
|
231
|
+
const retryablePatterns = ["ETIMEDOUT", "ECONNRESET", "timeout", "503", "429"];
|
|
232
|
+
return retryablePatterns.some((p) => message.toLowerCase().includes(p.toLowerCase()));
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
// Helper to determine if an oRPC error code is retryable
|
|
236
|
+
export const isRetryableORPCCode = (code: string): boolean => {
|
|
237
|
+
switch (code) {
|
|
238
|
+
case "TOO_MANY_REQUESTS":
|
|
239
|
+
case "SERVICE_UNAVAILABLE":
|
|
240
|
+
case "BAD_GATEWAY":
|
|
241
|
+
case "GATEWAY_TIMEOUT":
|
|
242
|
+
case "TIMEOUT":
|
|
243
|
+
return true;
|
|
244
|
+
default:
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
// Convert ORPC errors from plugin procedures to PluginRuntimeError
|
|
250
|
+
export const wrapORPCError = (
|
|
251
|
+
orpcError: ORPCError<string, unknown>,
|
|
252
|
+
pluginId?: string,
|
|
253
|
+
procedureName?: string,
|
|
254
|
+
operation?: string,
|
|
255
|
+
): PluginRuntimeError => {
|
|
256
|
+
const validationLines = formatORPCValidationError(orpcError);
|
|
257
|
+
if (validationLines) {
|
|
258
|
+
console.error(validationLines.join("\n"));
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return new PluginRuntimeError({
|
|
262
|
+
pluginId,
|
|
263
|
+
operation,
|
|
264
|
+
procedureName,
|
|
265
|
+
retryable: isRetryableORPCCode(orpcError.code),
|
|
266
|
+
cause: orpcError as Error,
|
|
267
|
+
});
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Extracts the underlying error from Effect's FiberFailure wrapper.
|
|
272
|
+
* When Effect.runPromise rejects, errors are wrapped in FiberFailure.
|
|
273
|
+
* This extracts the original error so oRPC can handle it properly.
|
|
274
|
+
*/
|
|
275
|
+
export const extractFromFiberFailure = (error: unknown): unknown => {
|
|
276
|
+
if (!error || typeof error !== "object") return error;
|
|
277
|
+
|
|
278
|
+
if ("cause" in error) {
|
|
279
|
+
const cause = (error as { cause?: unknown }).cause;
|
|
280
|
+
|
|
281
|
+
if (cause && typeof cause === "object" && "_tag" in cause) {
|
|
282
|
+
try {
|
|
283
|
+
const squashed = Cause.squash(cause as Cause.Cause<unknown>);
|
|
284
|
+
if (squashed instanceof ORPCError) {
|
|
285
|
+
return squashed;
|
|
286
|
+
}
|
|
287
|
+
return squashed;
|
|
288
|
+
} catch {
|
|
289
|
+
// Not a valid Cause
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (cause instanceof ORPCError) {
|
|
294
|
+
return cause;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return error;
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
// Universal error converter for the runtime
|
|
302
|
+
export const toPluginRuntimeError = (
|
|
303
|
+
error: unknown,
|
|
304
|
+
pluginId?: string,
|
|
305
|
+
procedureName?: string,
|
|
306
|
+
operation?: string,
|
|
307
|
+
defaultRetryable = false,
|
|
308
|
+
): PluginRuntimeError => {
|
|
309
|
+
if (error instanceof ORPCError) {
|
|
310
|
+
return wrapORPCError(error, pluginId, procedureName, operation);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (error instanceof PluginRuntimeError) {
|
|
314
|
+
return error;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const validationLines = formatORPCValidationError(error);
|
|
318
|
+
if (validationLines) {
|
|
319
|
+
console.error(validationLines.join("\n"));
|
|
320
|
+
} else {
|
|
321
|
+
const message = extractErrorMessage(error);
|
|
322
|
+
formatPluginError(pluginId, operation, message);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return new PluginRuntimeError({
|
|
326
|
+
pluginId,
|
|
327
|
+
operation,
|
|
328
|
+
procedureName,
|
|
329
|
+
retryable: defaultRetryable || isRetryableError(extractErrorMessage(error)),
|
|
330
|
+
cause: error instanceof Error ? error : new Error(extractErrorMessage(error)),
|
|
331
|
+
});
|
|
332
|
+
};
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { createRouterClient } from "@orpc/server";
|
|
2
|
+
import { Cause, Effect, Exit, Hash, ManagedRuntime, Option } from "effect";
|
|
3
|
+
import type {
|
|
4
|
+
AnyPlugin,
|
|
5
|
+
AnyPluginConstructor,
|
|
6
|
+
InferRegistryFromEntries,
|
|
7
|
+
InitializedPlugin,
|
|
8
|
+
LoadedPlugin,
|
|
9
|
+
PluginConfigInput,
|
|
10
|
+
PluginInstance,
|
|
11
|
+
PluginRegistry,
|
|
12
|
+
PluginRegistryEntry,
|
|
13
|
+
PluginRouterType,
|
|
14
|
+
PluginRuntimeConfig,
|
|
15
|
+
RegisteredPlugin,
|
|
16
|
+
RegisteredPlugins,
|
|
17
|
+
UsePluginResult,
|
|
18
|
+
} from "../types";
|
|
19
|
+
import { PluginRuntimeError } from "./errors";
|
|
20
|
+
import { PluginService } from "./services/plugin.service";
|
|
21
|
+
|
|
22
|
+
export class PluginRuntime<R = RegisteredPlugins> {
|
|
23
|
+
readonly __registryType?: R;
|
|
24
|
+
|
|
25
|
+
private pluginCache = new Map<
|
|
26
|
+
string,
|
|
27
|
+
Effect.Effect<InitializedPlugin<AnyPlugin>, PluginRuntimeError>
|
|
28
|
+
>();
|
|
29
|
+
|
|
30
|
+
constructor(
|
|
31
|
+
private runtime: ManagedRuntime.ManagedRuntime<PluginService, never>,
|
|
32
|
+
private registry: PluginRegistry,
|
|
33
|
+
) {}
|
|
34
|
+
|
|
35
|
+
private generateCacheKey(pluginId: string, config: unknown): string {
|
|
36
|
+
const configHash = Hash.structure(config as object).toString();
|
|
37
|
+
return `${pluginId}:${configHash}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private validatePluginId(pluginId: string): Effect.Effect<string, PluginRuntimeError> {
|
|
41
|
+
if (!(pluginId in this.registry)) {
|
|
42
|
+
return Effect.fail(
|
|
43
|
+
new PluginRuntimeError({
|
|
44
|
+
pluginId: String(pluginId),
|
|
45
|
+
operation: "validate-plugin-id",
|
|
46
|
+
cause: new Error(`Plugin ID '${String(pluginId)}' not found in registry.`),
|
|
47
|
+
retryable: false,
|
|
48
|
+
}),
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
return Effect.succeed(String(pluginId));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private async runPromise<A, E>(effect: Effect.Effect<A, E, PluginService>): Promise<A> {
|
|
55
|
+
const exit = await this.runtime.runPromiseExit(effect);
|
|
56
|
+
|
|
57
|
+
if (Exit.isFailure(exit)) {
|
|
58
|
+
const error = Cause.failureOption(exit.cause);
|
|
59
|
+
if (Option.isSome(error)) {
|
|
60
|
+
throw error.value;
|
|
61
|
+
}
|
|
62
|
+
throw Cause.squash(exit.cause);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return exit.value;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async usePlugin<K extends keyof R & string>(
|
|
69
|
+
pluginId: K,
|
|
70
|
+
config: PluginConfigInput<R[K]>,
|
|
71
|
+
plugins?: Record<string, unknown>,
|
|
72
|
+
): Promise<UsePluginResult<K, R>> {
|
|
73
|
+
const cacheKey = this.generateCacheKey(pluginId, { ...config, __plugins: plugins ?? {} });
|
|
74
|
+
|
|
75
|
+
let cachedPlugin = this.pluginCache.get(cacheKey);
|
|
76
|
+
if (!cachedPlugin) {
|
|
77
|
+
const operation = Effect.gen(this, function* () {
|
|
78
|
+
const pluginService = yield* PluginService;
|
|
79
|
+
const validatedId = yield* this.validatePluginId(pluginId);
|
|
80
|
+
|
|
81
|
+
// Load → Instantiate → Initialize
|
|
82
|
+
const ctor = yield* pluginService.loadPlugin(validatedId);
|
|
83
|
+
const instance = yield* pluginService.instantiatePlugin(pluginId, ctor);
|
|
84
|
+
const initialized = yield* pluginService.initializePlugin(instance, config, plugins);
|
|
85
|
+
|
|
86
|
+
return initialized;
|
|
87
|
+
}).pipe(Effect.provide(this.runtime));
|
|
88
|
+
|
|
89
|
+
cachedPlugin = Effect.cached(operation).pipe(Effect.flatten);
|
|
90
|
+
this.pluginCache.set(cacheKey, cachedPlugin);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const initialized = await this.runPromise(cachedPlugin);
|
|
94
|
+
|
|
95
|
+
// Create client factory that accepts request context
|
|
96
|
+
const createClient = (context?: any) => {
|
|
97
|
+
const router = initialized.plugin.createRouter(initialized.context);
|
|
98
|
+
return createRouterClient(router, { context: context ?? {} });
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
createClient: createClient as any,
|
|
103
|
+
router: initialized.plugin.createRouter(initialized.context) as PluginRouterType<R[K]>,
|
|
104
|
+
metadata: initialized.metadata,
|
|
105
|
+
initialized: initialized as InitializedPlugin<RegisteredPlugin<K, R>>,
|
|
106
|
+
} as UsePluginResult<K, R>;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async loadPlugin<K extends keyof R & string>(
|
|
110
|
+
pluginId: K,
|
|
111
|
+
): Promise<LoadedPlugin<RegisteredPlugin<K, R>>> {
|
|
112
|
+
const effect = Effect.gen(function* () {
|
|
113
|
+
const pluginService = yield* PluginService;
|
|
114
|
+
return yield* pluginService.loadPlugin(pluginId);
|
|
115
|
+
});
|
|
116
|
+
return this.runPromise(effect) as Promise<LoadedPlugin<RegisteredPlugin<K, R>>>;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async instantiatePlugin<K extends keyof R & string>(
|
|
120
|
+
pluginId: K,
|
|
121
|
+
loadedPlugin: LoadedPlugin<RegisteredPlugin<K, R>>,
|
|
122
|
+
): Promise<PluginInstance<RegisteredPlugin<K, R>>> {
|
|
123
|
+
const effect = Effect.gen(function* () {
|
|
124
|
+
const pluginService = yield* PluginService;
|
|
125
|
+
return yield* pluginService.instantiatePlugin(pluginId, loadedPlugin);
|
|
126
|
+
});
|
|
127
|
+
return this.runPromise(effect) as Promise<PluginInstance<RegisteredPlugin<K, R>>>;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async initializePlugin<T extends AnyPlugin>(
|
|
131
|
+
instance: PluginInstance<T>,
|
|
132
|
+
config: any,
|
|
133
|
+
plugins?: Record<string, unknown>,
|
|
134
|
+
): Promise<InitializedPlugin<T>> {
|
|
135
|
+
const effect = Effect.gen(function* () {
|
|
136
|
+
const pluginService = yield* PluginService;
|
|
137
|
+
return yield* pluginService.initializePlugin(instance, config, plugins);
|
|
138
|
+
});
|
|
139
|
+
return this.runPromise(effect);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async shutdown(): Promise<void> {
|
|
143
|
+
const effect = Effect.gen(function* () {
|
|
144
|
+
const pluginService = yield* PluginService;
|
|
145
|
+
yield* pluginService.cleanup();
|
|
146
|
+
});
|
|
147
|
+
return this.runPromise(effect);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async evictPlugin<K extends keyof R & string>(
|
|
151
|
+
pluginId: K,
|
|
152
|
+
config: PluginConfigInput<R[K]>,
|
|
153
|
+
): Promise<void> {
|
|
154
|
+
const cacheKey = this.generateCacheKey(pluginId, config);
|
|
155
|
+
|
|
156
|
+
const effect = Effect.gen(this, function* () {
|
|
157
|
+
const pluginService = yield* PluginService;
|
|
158
|
+
const cachedPlugin = this.pluginCache.get(cacheKey);
|
|
159
|
+
|
|
160
|
+
if (cachedPlugin) {
|
|
161
|
+
this.pluginCache.delete(cacheKey);
|
|
162
|
+
|
|
163
|
+
const pluginResult = yield* cachedPlugin.pipe(Effect.catchAll(() => Effect.succeed(null)));
|
|
164
|
+
|
|
165
|
+
if (pluginResult) {
|
|
166
|
+
yield* pluginService
|
|
167
|
+
.shutdownPlugin(pluginResult)
|
|
168
|
+
.pipe(Effect.catchAll(() => Effect.void));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}).pipe(Effect.catchAll(() => Effect.void));
|
|
172
|
+
|
|
173
|
+
return this.runPromise(effect);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Normalizes a remote URL to ensure it points to remoteEntry.js
|
|
179
|
+
* If the URL doesn't end with a file extension, appends /remoteEntry.js
|
|
180
|
+
*/
|
|
181
|
+
function normalizeRemoteUrl(url: string): string {
|
|
182
|
+
if (!url) return url;
|
|
183
|
+
if (url.endsWith(".js") || url.endsWith(".json")) return url;
|
|
184
|
+
return `${url.endsWith("/") ? url.slice(0, -1) : url}/remoteEntry.js`;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Extract plugin map (module constructors) from registry entries
|
|
189
|
+
*/
|
|
190
|
+
function extractPluginMap(
|
|
191
|
+
registry: Record<string, PluginRegistryEntry>,
|
|
192
|
+
): Record<string, AnyPluginConstructor> {
|
|
193
|
+
const pluginMap: Record<string, AnyPluginConstructor> = {};
|
|
194
|
+
|
|
195
|
+
for (const [pluginId, entry] of Object.entries(registry)) {
|
|
196
|
+
if ("module" in entry && entry.module) {
|
|
197
|
+
pluginMap[pluginId] = entry.module;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return pluginMap;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Normalize registry entries - ensure remote URLs are properly formatted
|
|
206
|
+
*/
|
|
207
|
+
function normalizeRegistry(registry: Record<string, PluginRegistryEntry>): PluginRegistry {
|
|
208
|
+
const normalized: Record<string, PluginRegistryEntry> = {};
|
|
209
|
+
|
|
210
|
+
for (const [pluginId, entry] of Object.entries(registry)) {
|
|
211
|
+
if ("module" in entry) {
|
|
212
|
+
normalized[pluginId] = {
|
|
213
|
+
...entry,
|
|
214
|
+
remote: entry.remote ? normalizeRemoteUrl(entry.remote) : undefined,
|
|
215
|
+
};
|
|
216
|
+
} else {
|
|
217
|
+
normalized[pluginId] = {
|
|
218
|
+
...entry,
|
|
219
|
+
remote: normalizeRemoteUrl(entry.remote),
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return normalized as PluginRegistry;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Creates a plugin runtime with support for both module and remote plugin entries.
|
|
229
|
+
*
|
|
230
|
+
* @example
|
|
231
|
+
* ```typescript
|
|
232
|
+
* // With module entries (types inferred automatically)
|
|
233
|
+
* const runtime = createPluginRuntime({
|
|
234
|
+
* registry: {
|
|
235
|
+
* telegram: { module: TelegramPlugin },
|
|
236
|
+
* gopher: { remote: "https://cdn.example.com/gopher/remoteEntry.js" }
|
|
237
|
+
* },
|
|
238
|
+
* secrets: { API_KEY: "..." }
|
|
239
|
+
* });
|
|
240
|
+
*
|
|
241
|
+
* // Types are automatically inferred from module entries!
|
|
242
|
+
* const { router } = await runtime.usePlugin("telegram", config);
|
|
243
|
+
* ```
|
|
244
|
+
*/
|
|
245
|
+
export function createPluginRuntime<TRegistry extends Record<string, PluginRegistryEntry>>(
|
|
246
|
+
config: PluginRuntimeConfig<TRegistry>,
|
|
247
|
+
): PluginRuntime<InferRegistryFromEntries<TRegistry>> {
|
|
248
|
+
const secrets = config.secrets || {};
|
|
249
|
+
const normalizedRegistry = normalizeRegistry(config.registry);
|
|
250
|
+
const pluginMap = extractPluginMap(config.registry);
|
|
251
|
+
|
|
252
|
+
const layer = PluginService.Live(normalizedRegistry, secrets, pluginMap);
|
|
253
|
+
const runtime = ManagedRuntime.make(layer);
|
|
254
|
+
|
|
255
|
+
return new PluginRuntime(runtime, normalizedRegistry) as PluginRuntime<
|
|
256
|
+
InferRegistryFromEntries<TRegistry>
|
|
257
|
+
>;
|
|
258
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
|
|
3
|
+
const require = createRequire(import.meta.url);
|
|
4
|
+
const pkg = require("../../package.json") as { version: string };
|
|
5
|
+
export const PLUGIN_VERSION = pkg.version;
|
|
6
|
+
|
|
7
|
+
export const SHARE_CONFIG = {
|
|
8
|
+
singleton: true,
|
|
9
|
+
requiredVersion: false,
|
|
10
|
+
strictVersion: false,
|
|
11
|
+
eager: false,
|
|
12
|
+
} as const;
|
|
13
|
+
|
|
14
|
+
export const MF_SHARED_DEPS = {
|
|
15
|
+
"every-plugin": {
|
|
16
|
+
version: PLUGIN_VERSION,
|
|
17
|
+
shareConfig: SHARE_CONFIG,
|
|
18
|
+
},
|
|
19
|
+
effect: {
|
|
20
|
+
version: "^3.21.0",
|
|
21
|
+
shareConfig: SHARE_CONFIG,
|
|
22
|
+
},
|
|
23
|
+
zod: {
|
|
24
|
+
version: "^4.3.6",
|
|
25
|
+
shareConfig: SHARE_CONFIG,
|
|
26
|
+
},
|
|
27
|
+
} as const;
|
|
28
|
+
|
|
29
|
+
export type SharedDepName = keyof typeof MF_SHARED_DEPS;
|