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

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/src/dev/serve.ts CHANGED
@@ -10,6 +10,7 @@ import { getPluginInfo, loadDevConfig } from "../build/rspack/utils";
10
10
  import { ensureGeneratedUiRsbuildConfig, pluginLayoutKey } from "../build/ui/generated-config";
11
11
  import { PLUGIN_ERROR_STATUS_MAP } from "../errors";
12
12
  import { loadRemoteWithRetry } from "../remote-entry";
13
+ import { startDirectModuleHost } from "./host";
13
14
  import { killChildEscalating, watchParentDeath } from "./watch-kill";
14
15
 
15
16
  const corsHeaders = {
@@ -81,12 +82,6 @@ const loadPluginWithRetry = (
81
82
  }),
82
83
  );
83
84
 
84
- const sendJson = (res: http.ServerResponse, status: number, body: unknown) => {
85
- res.statusCode = status;
86
- res.setHeader("content-type", "application/json; charset=utf-8");
87
- res.end(JSON.stringify(body));
88
- };
89
-
90
85
  const sendText = (res: http.ServerResponse, status: number, text: string) => {
91
86
  res.statusCode = status;
92
87
  res.setHeader("content-type", "text/plain; charset=utf-8");
@@ -125,7 +120,6 @@ export async function startPluginDevServer(
125
120
  const compositionKey = pluginLayoutKey(cwd) ?? pluginId;
126
121
  const port = options.port ?? (Number(process.env.PORT) || devConfig?.port || 3999);
127
122
  const rpcPrefix = normalizePrefix(devConfig?.prefix);
128
- const rpcBase = `/api/rpc${rpcPrefix}`;
129
123
 
130
124
  const distDir = path.join(cwd, "dist");
131
125
  const serveStatic = sirv(distDir, { dev: true });
@@ -208,134 +202,19 @@ export async function startPluginDevServer(
208
202
  const handlers: { rpc: any; api: any } = { rpc: null, api: null };
209
203
  const effectContextHolder: { context: unknown } = { context: null };
210
204
  let runtime: { shutdown: () => Promise<void> } | null = null;
211
-
212
- const pathnameOf = (req: http.IncomingMessage): string =>
213
- ((req.url ?? "/").split("?")[0] ?? "/").replace(/\/+$/, "") || "/";
214
-
215
- const buildDevContext = (_req: http.IncomingMessage, webRequest: Request) => {
216
- const rawClone =
217
- webRequest.method === "GET" || webRequest.method === "HEAD" ? null : webRequest.clone();
218
- let cachedRawBody: string | null = null;
219
- return {
220
- reqHeaders: webRequest.headers,
221
- "effect/context": effectContextHolder.context,
222
- getRawBody: async (): Promise<string> => {
223
- if (cachedRawBody !== null) return cachedRawBody;
224
- if (!rawClone) {
225
- cachedRawBody = "";
226
- return cachedRawBody;
227
- }
228
- cachedRawBody = await rawClone.text();
229
- return cachedRawBody;
230
- },
231
- };
232
- };
233
-
234
- const toWebRequest = (req: http.IncomingMessage): Request => {
235
- const url = `http://localhost:${port}${req.url}`;
236
- return new Request(url, {
237
- method: req.method,
238
- headers: req.headers,
239
- body: req.method !== "GET" && req.method !== "HEAD" ? req : undefined,
240
- duplex: req.method !== "GET" && req.method !== "HEAD" ? "half" : undefined,
241
- } as RequestInit);
242
- };
243
-
244
- const handleOrpc = async (
245
- req: http.IncomingMessage,
246
- res: http.ServerResponse,
247
- handler: any,
248
- prefix: string,
249
- errorLabel: string,
250
- ) => {
251
- try {
252
- const webRequest = toWebRequest(req);
253
- const result = await handler.handle(webRequest, {
254
- prefix,
255
- context: buildDevContext(req, webRequest),
256
- });
257
-
258
- if (result.response) {
259
- res.statusCode = result.response.status;
260
- result.response.headers.forEach((value: string, key: string) => {
261
- res.setHeader(key, value);
262
- });
263
- const text = await result.response.text();
264
- if (!res.getHeader("content-length")) {
265
- res.setHeader("content-length", Buffer.byteLength(text).toString());
266
- }
267
- res.end(text);
268
- } else {
269
- sendText(res, 404, "Not Found");
270
- }
271
- } catch (error) {
272
- console.error(`${errorLabel} error:`, error);
273
- sendJson(res, 500, { error: (error as Error).message });
274
- }
275
- };
276
-
277
- const server = http.createServer((req, res) => {
278
- applyCorsHeaders(res);
279
- const pathname = pathnameOf(req);
280
-
281
- if (req.method === "OPTIONS") {
282
- res.statusCode = 200;
283
- res.end();
284
- return;
285
- }
286
-
287
- if (pathname === "/" && (req.method === "GET" || req.method === "HEAD")) {
288
- sendJson(res, 200, {
289
- ok: true,
290
- plugin: pluginInfo.normalizedName,
291
- version: pluginInfo.version,
292
- status: handlers.rpc ? "ready" : "loading",
293
- endpoints: {
294
- health: "/",
295
- docs: "/api",
296
- rpc: rpcBase,
297
- },
298
- });
299
- return;
300
- }
301
-
302
- if (pathname === "/health" && (req.method === "GET" || req.method === "HEAD")) {
303
- sendText(res, 200, "OK");
304
- return;
305
- }
306
-
307
- if (pathname.startsWith(`${rpcBase}/`)) {
308
- if (!handlers.rpc) {
309
- sendJson(res, 503, { error: "Plugin still loading..." });
310
- return;
311
- }
312
- handleOrpc(req, res, handlers.rpc, rpcBase, "RPC").catch(() => {});
313
- return;
314
- }
315
-
316
- if (pathname === "/api" || pathname.startsWith("/api/")) {
317
- if (!handlers.api) {
318
- sendJson(res, 503, { error: "Plugin still loading..." });
319
- return;
320
- }
321
- handleOrpc(req, res, handlers.api, "/api", "OpenAPI").catch(() => {});
322
- return;
323
- }
324
-
325
- serveStatic(req, res, () => {
326
- sendText(res, 404, "Not Found");
327
- });
328
- });
329
-
330
- await new Promise<void>((resolve, reject) => {
331
- server.once("error", reject);
332
- server.listen(port, () => resolve());
205
+ const host = await startDirectModuleHost({
206
+ name: pluginInfo.normalizedName,
207
+ version: pluginInfo.version,
208
+ handlers,
209
+ effectContext: () => effectContextHolder.context,
210
+ port,
211
+ staticHandler: serveStatic,
333
212
  });
334
213
 
335
- console.log(`🚀 ${pluginInfo.name} dev server listening on port ${port}`);
336
- console.log(`├─ 📡 RPC: http://localhost:${port}/api/rpc${rpcPrefix}`);
337
- console.log(`├─ 📖 Docs: http://localhost:${port}/api`);
338
- console.log(`└─ 💚 Health: http://localhost:${port}/`);
214
+ console.log(`🚀 ${pluginInfo.name} dev server listening on port ${host.port}`);
215
+ console.log(`├─ 📡 RPC: http://localhost:${host.port}/api/rpc${rpcPrefix}`);
216
+ console.log(`├─ 📖 Docs: http://localhost:${host.port}/api`);
217
+ console.log(`└─ 💚 Health: http://localhost:${host.port}/`);
339
218
 
340
219
  const load = async () => {
341
220
  try {
@@ -360,7 +239,7 @@ export async function startPluginDevServer(
360
239
 
361
240
  const registry: Record<string, { remote: string }> = {
362
241
  [pluginId]: {
363
- remote: `http://localhost:${port}/${DEV_ENTRY_FILENAME}`,
242
+ remote: `http://localhost:${host.port}/${DEV_ENTRY_FILENAME}`,
364
243
  },
365
244
  ...siblings,
366
245
  };
@@ -440,8 +319,8 @@ export async function startPluginDevServer(
440
319
  if (uiStaticServer) {
441
320
  await new Promise<void>((resolve) => uiStaticServer.close(() => resolve()));
442
321
  }
322
+ await host.close();
443
323
  if (runtime) await runtime.shutdown().catch(() => {});
444
- await new Promise<void>((resolve) => server.close(() => resolve()));
445
324
  };
446
325
 
447
326
  process.once("SIGINT", async () => {
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import "@orpc/openapi/extensions/route";
2
2
 
3
+ export * from "./client";
3
4
  export * from "./effect-helpers";
4
5
  export * from "./errors";
5
6
  export * from "./plugin";
@@ -13,10 +14,16 @@ export type {
13
14
  EveryPlugin,
14
15
  InitializedPlugin,
15
16
  LoadedPlugin,
17
+ ModuleClientEntry,
18
+ ModuleEndpoint,
19
+ ModuleSurface,
20
+ ModuleTransport,
21
+ PluginClientEntry,
16
22
  PluginClientType,
17
23
  PluginConfigInput,
18
24
  PluginContext,
19
25
  PluginContract,
26
+ PluginEndpoint,
20
27
  PluginInstance,
21
28
  PluginMetadata,
22
29
  PluginRegistry,
@@ -24,6 +31,8 @@ export type {
24
31
  PluginRuntimeConfig,
25
32
  PluginSecrets,
26
33
  PluginServicesEntry,
34
+ PluginSurface,
35
+ PluginTransport,
27
36
  PluginVariables,
28
37
  RegisteredPlugins,
29
38
  SecretsConfig,
package/src/types.ts CHANGED
@@ -24,6 +24,24 @@ import type { PluginWarningInfo } from "./runtime/errors";
24
24
  // biome-ignore lint/suspicious/noEmptyInterface: required for module augmentation pattern
25
25
  export interface RegisteredPlugins {}
26
26
 
27
+ export type ModuleTransport = "inproc" | "rpc" | "mf";
28
+
29
+ export type ModuleSurface = "api" | "ui" | "jobs";
30
+
31
+ export interface ModuleEndpoint {
32
+ readonly transport: ModuleTransport;
33
+ readonly surface?: ModuleSurface;
34
+ readonly url: string;
35
+ readonly path?: `/${string}`;
36
+ readonly manifest?: string;
37
+ }
38
+
39
+ export type PluginTransport = ModuleTransport;
40
+
41
+ export type PluginSurface = ModuleSurface;
42
+
43
+ export type PluginEndpoint = ModuleEndpoint;
44
+
27
45
  /**
28
46
  * Base type for any plugin instance.
29
47
  */
@@ -46,8 +64,14 @@ export type AnyPluginConstructor = {
46
64
  * Registry entry that supports both direct module imports and remote URLs
47
65
  */
48
66
  export type PluginRegistryEntry =
49
- | { module: AnyPluginConstructor; remote?: string; version?: string; description?: string }
50
- | { remote: string; version?: string; description?: string };
67
+ | {
68
+ module: AnyPluginConstructor;
69
+ remote?: string;
70
+ endpoint?: PluginEndpoint;
71
+ version?: string;
72
+ description?: string;
73
+ }
74
+ | { remote: string; endpoint?: PluginEndpoint; version?: string; description?: string };
51
75
 
52
76
  /**
53
77
  * Extract plugin constructor type from registry entry
@@ -116,8 +140,18 @@ export type PluginClientType<T> = RouterClient<PluginRouterType<T>>;
116
140
  export type PluginServicesEntry<T = any> = {
117
141
  client: (context?: PluginContextInput<T>) => PluginClientType<T>;
118
142
  router: PluginRouterType<T>;
143
+ endpoint?: PluginEndpoint;
119
144
  };
120
145
 
146
+ export type ModuleClientEntry<C extends AnyContractRouter = AnyContractRouter> = {
147
+ client: (context?: Record<string, unknown>) => RouterClient<ContractedRouter<C, any>>;
148
+ router?: ContractedRouter<C, any>;
149
+ endpoint?: PluginEndpoint;
150
+ };
151
+
152
+ export type PluginClientEntry<C extends AnyContractRouter = AnyContractRouter> =
153
+ ModuleClientEntry<C>;
154
+
121
155
  /**
122
156
  * Extract plugin type from registered plugins by key
123
157
  * @param K - The plugin key