every-plugin 0.0.5 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +320 -6
  2. package/dist/errors.d.ts +4 -4
  3. package/dist/errors.d.ts.map +1 -1
  4. package/dist/errors.js +10 -10
  5. package/dist/index.d.ts +4 -4
  6. package/dist/index.js +3 -3
  7. package/dist/plugin.d.ts +99 -54
  8. package/dist/plugin.d.ts.map +1 -1
  9. package/dist/plugin.js +18 -16
  10. package/dist/runtime/errors.d.ts +62 -0
  11. package/dist/runtime/errors.d.ts.map +1 -0
  12. package/dist/runtime/errors.js +9 -0
  13. package/dist/runtime/index.d.ts +73 -0
  14. package/dist/runtime/index.d.ts.map +1 -0
  15. package/dist/runtime/index.js +133 -0
  16. package/dist/runtime/mf.test.d.ts +2 -0
  17. package/dist/runtime/mf.test.d.ts.map +1 -0
  18. package/dist/runtime/mf.test.js +135 -0
  19. package/dist/runtime/runtime.test.d.ts +2 -0
  20. package/dist/runtime/runtime.test.d.ts.map +1 -0
  21. package/dist/runtime/runtime.test.js +378 -0
  22. package/dist/runtime/services/module-federation.service.d.ts +44 -0
  23. package/dist/runtime/services/module-federation.service.d.ts.map +1 -0
  24. package/dist/runtime/services/module-federation.service.js +131 -0
  25. package/dist/runtime/services/plugin.service.d.ts +75 -0
  26. package/dist/runtime/services/plugin.service.d.ts.map +1 -0
  27. package/dist/runtime/services/plugin.service.js +338 -0
  28. package/dist/runtime/services/secrets.service.d.ts +37 -0
  29. package/dist/runtime/services/secrets.service.d.ts.map +1 -0
  30. package/dist/runtime/services/secrets.service.js +36 -0
  31. package/dist/runtime/services.d.ts +4 -0
  32. package/dist/runtime/services.d.ts.map +1 -0
  33. package/dist/runtime/services.js +3 -0
  34. package/dist/runtime/streaming.d.ts +29 -0
  35. package/dist/runtime/streaming.d.ts.map +1 -0
  36. package/dist/runtime/streaming.js +59 -0
  37. package/dist/runtime/streaming.test.d.ts +2 -0
  38. package/dist/runtime/streaming.test.d.ts.map +1 -0
  39. package/dist/runtime/streaming.test.js +271 -0
  40. package/dist/runtime/test-utils.d.ts +3 -0
  41. package/dist/runtime/test-utils.d.ts.map +1 -0
  42. package/dist/runtime/test-utils.js +13 -0
  43. package/dist/runtime/types.d.ts +40 -0
  44. package/dist/runtime/types.d.ts.map +1 -0
  45. package/dist/runtime/types.js +1 -0
  46. package/dist/runtime/validation.d.ts +10 -0
  47. package/dist/runtime/validation.d.ts.map +1 -0
  48. package/dist/runtime/validation.js +16 -0
  49. package/dist/source.d.ts +63 -191
  50. package/dist/source.d.ts.map +1 -1
  51. package/dist/source.js +33 -70
  52. package/package.json +44 -22
@@ -0,0 +1,378 @@
1
+ import { Duration, Effect } from "effect";
2
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
3
+ import { createPluginRuntime, PluginRuntime } from "./index";
4
+
5
+ // Local template plugin for testing
6
+ const TEST_REGISTRY = {
7
+ "test-plugin": {
8
+ remoteUrl: "http://localhost:3000/remoteEntry.js",
9
+ type: "transformer",
10
+ version: "0.0.1",
11
+ description: "Local template plugin for testing",
12
+ },
13
+ "invalid-plugin": {
14
+ remoteUrl: "https://invalid-plugin-url-that-does-not-exist.com/plugin.js",
15
+ type: "transformer",
16
+ version: "1.0.0",
17
+ description: "Invalid plugin for testing error handling",
18
+ },
19
+ };
20
+ const TEST_CONFIG = {
21
+ variables: {
22
+ baseUrl: "http://localhost:1337",
23
+ timeout: 5000,
24
+ },
25
+ secrets: {
26
+ apiKey: "{{API_KEY}}", // This will be hydrated from secrets config
27
+ },
28
+ };
29
+ const TEST_INPUT = {
30
+ query: "test query for processing",
31
+ options: {
32
+ limit: 2,
33
+ },
34
+ };
35
+ // Secrets configuration for hydration
36
+ const SECRETS_CONFIG = {
37
+ API_KEY: "test-api-key-value",
38
+ };
39
+ describe("Plugin Runtime Integration", () => {
40
+ let runtime;
41
+ beforeEach(() => {
42
+ runtime = createPluginRuntime({
43
+ registry: TEST_REGISTRY,
44
+ secrets: SECRETS_CONFIG,
45
+ });
46
+ });
47
+ afterEach(async () => {
48
+ if (runtime) {
49
+ await runtime.dispose();
50
+ }
51
+ });
52
+ it("should create and execute plugin end-to-end", async () => {
53
+ // Execute the test within the runtime
54
+ const result = await runtime.runPromise(
55
+ Effect.gen(function* () {
56
+ const pluginRuntime = yield* PluginRuntime;
57
+ // Create plugin (load → instantiate → initialize)
58
+ const initializedPlugin = yield* pluginRuntime.usePlugin(
59
+ "test-plugin",
60
+ TEST_CONFIG,
61
+ );
62
+ // Verify plugin was initialized
63
+ expect(initializedPlugin).toBeDefined();
64
+ expect(initializedPlugin.metadata.pluginId).toBe("test-plugin");
65
+ expect(initializedPlugin.plugin).toBeDefined();
66
+ // Execute plugin
67
+ const output = yield* pluginRuntime.executePlugin(
68
+ initializedPlugin,
69
+ TEST_INPUT,
70
+ );
71
+ return output;
72
+ }),
73
+ );
74
+ // Validate output structure
75
+ expect(result).toBeDefined();
76
+ expect(typeof result).toBe("object");
77
+ const typedResult = result;
78
+ // Verify success wrapper structure
79
+ expect(typedResult).toHaveProperty("success");
80
+ expect(typedResult).toHaveProperty("data");
81
+ expect(typedResult.success).toBe(true);
82
+ // Verify data content
83
+ expect(typedResult.data).toHaveProperty("results");
84
+ expect(typedResult.data).toHaveProperty("count");
85
+ expect(Array.isArray(typedResult.data.results)).toBe(true);
86
+ expect(typedResult.data.count).toBe(2); // Should match the limit we set
87
+ expect(typedResult.data.results).toHaveLength(2);
88
+ // Verify results contain processed query
89
+ expect(typedResult.data.results[0]).toHaveProperty("id");
90
+ expect(typedResult.data.results[0]).toHaveProperty("content");
91
+ expect(typedResult.data.results[0].content).toContain(
92
+ "test query for processing",
93
+ );
94
+ expect(typedResult.data.results[1].content).toContain(
95
+ "test query for processing",
96
+ );
97
+ });
98
+ it("should handle plugin lifecycle correctly", async () => {
99
+ await runtime.runPromise(
100
+ Effect.gen(function* () {
101
+ const pluginRuntime = yield* PluginRuntime;
102
+ // Test individual lifecycle steps
103
+ // 1. Load plugin
104
+ const pluginConstructor =
105
+ yield* pluginRuntime.loadPlugin("test-plugin");
106
+ expect(pluginConstructor).toBeDefined();
107
+ expect(pluginConstructor.metadata.pluginId).toBe("test-plugin");
108
+ // 2. Instantiate plugin
109
+ const pluginInstance =
110
+ yield* pluginRuntime.instantiatePlugin(pluginConstructor);
111
+ expect(pluginInstance).toBeDefined();
112
+ expect(pluginInstance.plugin).toBeDefined();
113
+ expect(pluginInstance.metadata.pluginId).toBe("test-plugin");
114
+ // 3. Initialize plugin
115
+ const initializedPlugin = yield* pluginRuntime.initializePlugin(
116
+ pluginInstance,
117
+ TEST_CONFIG,
118
+ );
119
+ expect(initializedPlugin).toBeDefined();
120
+ expect(initializedPlugin.config).toBeDefined();
121
+ // 4. Execute plugin
122
+ const output = yield* pluginRuntime.executePlugin(
123
+ initializedPlugin,
124
+ TEST_INPUT,
125
+ );
126
+ expect(output).toBeDefined();
127
+ }),
128
+ );
129
+ });
130
+ it("should handle secret hydration", async () => {
131
+ const configWithSecrets = {
132
+ variables: {
133
+ baseUrl: "http://localhost:1337",
134
+ timeout: 10000,
135
+ },
136
+ secrets: {
137
+ apiKey: "{{API_KEY}}", // This should be hydrated
138
+ },
139
+ };
140
+ const secretsConfig = {
141
+ API_KEY: "hydrated-api-key-value",
142
+ };
143
+ const runtime = createPluginRuntime({
144
+ registry: TEST_REGISTRY,
145
+ secrets: secretsConfig,
146
+ });
147
+ await runtime.runPromise(
148
+ Effect.gen(function* () {
149
+ const pluginRuntime = yield* PluginRuntime;
150
+ const initializedPlugin = yield* pluginRuntime.usePlugin(
151
+ "test-plugin",
152
+ configWithSecrets,
153
+ );
154
+ // Verify secrets were hydrated in the config
155
+ expect(initializedPlugin.config).toBeDefined();
156
+ // Verify the config structure and that secrets were hydrated
157
+ const config = initializedPlugin.config;
158
+ expect(config).toHaveProperty("secrets");
159
+ expect(config.secrets).toHaveProperty("apiKey");
160
+ expect(config.secrets.apiKey).toBe("hydrated-api-key-value");
161
+ // Verify variables are also present
162
+ expect(config).toHaveProperty("variables");
163
+ expect(config.variables.baseUrl).toBe("http://localhost:1337");
164
+ expect(config.variables.timeout).toBe(10000);
165
+ // Verify the plugin was successfully initialized with hydrated secrets
166
+ // by executing it to ensure it works with the hydrated config
167
+ const output = yield* pluginRuntime.executePlugin(
168
+ initializedPlugin,
169
+ TEST_INPUT,
170
+ );
171
+ expect(output).toBeDefined();
172
+ }),
173
+ );
174
+ });
175
+ it("should handle validation error for missing apiKey", async () => {
176
+ const configWithoutApiKey = {
177
+ variables: {
178
+ baseUrl: "http://localhost:1337",
179
+ timeout: 5000,
180
+ },
181
+ secrets: {
182
+ // Missing required apiKey
183
+ },
184
+ };
185
+ const result = await runtime.runPromise(
186
+ Effect.gen(function* () {
187
+ const pluginRuntime = yield* PluginRuntime;
188
+ return yield* pluginRuntime
189
+ .usePlugin("test-plugin", configWithoutApiKey)
190
+ .pipe(
191
+ Effect.catchTag("PluginRuntimeError", (error) => {
192
+ // Should catch validation error
193
+ expect(error.operation).toBe("validate-config");
194
+ expect(error.retryable).toBe(false);
195
+ expect(error.pluginId).toBe("test-plugin");
196
+ expect(error.cause).toBeDefined();
197
+ // The error should mention the missing apiKey
198
+ const errorMessage = error.cause?.message || "";
199
+ // Zod validation errors might be in JSON format, so check for both formats
200
+ const lowerMessage = errorMessage.toLowerCase();
201
+ const hasApiKeyError =
202
+ lowerMessage.includes("api key") ||
203
+ lowerMessage.includes("apikey") ||
204
+ lowerMessage.includes("required");
205
+ expect(hasApiKeyError).toBe(true);
206
+ console.debug("Caught expected validation error:", {
207
+ operation: error.operation,
208
+ pluginId: error.pluginId,
209
+ retryable: error.retryable,
210
+ cause: error.cause?.message,
211
+ });
212
+ return Effect.succeed("validation-error-handled-properly");
213
+ }),
214
+ Effect.catchAll((unexpectedError) => {
215
+ console.error("Unexpected error type:", unexpectedError);
216
+ expect.fail(
217
+ `Expected PluginRuntimeError but got: ${unexpectedError._tag || typeof unexpectedError}`,
218
+ );
219
+ return Effect.succeed("should-not-reach-here");
220
+ }),
221
+ );
222
+ }),
223
+ );
224
+ expect(result).toBe("validation-error-handled-properly");
225
+ });
226
+ it("should handle runtime shutdown gracefully", async () => {
227
+ const runtime = createPluginRuntime({
228
+ registry: TEST_REGISTRY,
229
+ secrets: SECRETS_CONFIG,
230
+ });
231
+ await runtime.runPromise(
232
+ Effect.gen(function* () {
233
+ const pluginRuntime = yield* PluginRuntime;
234
+ // Test shutdown
235
+ yield* pluginRuntime.shutdown();
236
+ // Shutdown should complete without errors
237
+ expect(true).toBe(true); // If we reach here, shutdown was successful
238
+ }),
239
+ );
240
+ });
241
+ it("should handle invalid remoteUrl with proper error handling", async () => {
242
+ const result = await runtime.runPromise(
243
+ Effect.gen(function* () {
244
+ const pluginRuntime = yield* PluginRuntime;
245
+ return yield* pluginRuntime.usePlugin("invalid-plugin", {}).pipe(
246
+ Effect.catchTag("PluginRuntimeError", (error) => {
247
+ expect(error.operation).toBe("register-remote");
248
+ expect(error.retryable).toBe(true);
249
+ expect(error.pluginId).toBe("invalid-plugin");
250
+ expect(error.cause).toBeDefined();
251
+ console.debug("Caught expected PluginRuntimeError:", {
252
+ operation: error.operation,
253
+ pluginId: error.pluginId,
254
+ retryable: error.retryable,
255
+ cause: error.cause?.message,
256
+ });
257
+ return Effect.succeed("error-handled-properly");
258
+ }),
259
+ Effect.catchAll((unexpectedError) => {
260
+ // If any other error type is thrown, fail the test
261
+ console.error("Unexpected error type:", unexpectedError);
262
+ expect.fail(
263
+ `Expected PluginRuntimeError but got: ${unexpectedError._tag || typeof unexpectedError}`,
264
+ );
265
+ return Effect.succeed("should-not-reach-here");
266
+ }),
267
+ );
268
+ }),
269
+ );
270
+ expect(result).toBe("error-handled-properly");
271
+ });
272
+ it("should handle plugin not found in registry", async () => {
273
+ // Empty registry to test missing plugin
274
+ const EMPTY_REGISTRY = {};
275
+ const runtime = createPluginRuntime({
276
+ registry: EMPTY_REGISTRY,
277
+ secrets: {},
278
+ });
279
+ const result = await runtime.runPromise(
280
+ Effect.gen(function* () {
281
+ const pluginRuntime = yield* PluginRuntime;
282
+ return yield* pluginRuntime.loadPlugin("non-existent-plugin").pipe(
283
+ Effect.catchTag("PluginRuntimeError", (error) => {
284
+ // Validate error properties
285
+ expect(error.operation).toBe("load-plugin");
286
+ expect(error.retryable).toBe(false);
287
+ expect(error.pluginId).toBe("non-existent-plugin");
288
+ expect(error.cause).toBeDefined();
289
+ expect(error.cause?.message).toContain("not found in registry");
290
+ // Log error details for debugging
291
+ console.debug(
292
+ "Caught expected PluginRuntimeError for missing plugin:",
293
+ {
294
+ operation: error.operation,
295
+ pluginId: error.pluginId,
296
+ retryable: error.retryable,
297
+ cause: error.cause?.message,
298
+ },
299
+ );
300
+ // Return success to make test pass
301
+ return Effect.succeed("plugin-not-found-handled-properly");
302
+ }),
303
+ Effect.catchAll((unexpectedError) => {
304
+ // If any other error type is thrown, fail the test
305
+ console.error("Unexpected error type:", unexpectedError);
306
+ expect.fail(
307
+ `Expected PluginRuntimeError but got: ${unexpectedError._tag || typeof unexpectedError}`,
308
+ );
309
+ return Effect.succeed("should-not-reach-here");
310
+ }),
311
+ );
312
+ }),
313
+ );
314
+ expect(result).toBe("plugin-not-found-handled-properly");
315
+ });
316
+ it("should handle constructor instantiation failure", async () => {
317
+ // Registry with real remote that should fail during instantiation/initialization
318
+ const REAL_REMOTE_REGISTRY = {
319
+ "simple-transform": {
320
+ remoteUrl:
321
+ "https://unpkg.com/@curatedotfun/simple-transform@latest/dist/remoteEntry.js",
322
+ type: "transformer",
323
+ version: "latest",
324
+ description: "Real remote plugin for testing constructor failure",
325
+ },
326
+ };
327
+ const runtime = createPluginRuntime({
328
+ registry: REAL_REMOTE_REGISTRY,
329
+ secrets: {},
330
+ });
331
+ const result = await runtime.runPromise(
332
+ Effect.gen(function* () {
333
+ const pluginRuntime = yield* PluginRuntime;
334
+ return yield* pluginRuntime.usePlugin("simple-transform", {}).pipe(
335
+ Effect.timeout(Duration.seconds(1)), // Timeout after 3 seconds
336
+ Effect.catchTag("PluginRuntimeError", (error) => {
337
+ // Validate error properties
338
+ expect(["load-remote"]).toContain(error.operation);
339
+ expect(error.retryable).toBe(false);
340
+ expect(error.pluginId).toBe("simple-transform");
341
+ expect(error.cause).toBeDefined();
342
+ // Log error details for debugging
343
+ console.debug(
344
+ "Caught expected PluginRuntimeError for constructor failure:",
345
+ {
346
+ operation: error.operation,
347
+ pluginId: error.pluginId,
348
+ retryable: error.retryable,
349
+ cause: error.cause?.message,
350
+ },
351
+ );
352
+ // Return success to make test pass
353
+ return Effect.succeed("constructor-failure-handled-properly");
354
+ }),
355
+ Effect.catchTag("TimeoutException", () => {
356
+ // Handle timeout as expected failure - remote is too slow
357
+ console.debug(
358
+ "Plugin creation timed out - treating as expected failure",
359
+ );
360
+ return Effect.succeed("timeout-handled-properly");
361
+ }),
362
+ Effect.catchAll((unexpectedError) => {
363
+ // If any other error type is thrown, fail the test
364
+ console.error("Unexpected error type:", unexpectedError);
365
+ expect.fail(
366
+ `Expected PluginRuntimeError or TimeoutException but got: ${unexpectedError._tag || typeof unexpectedError}`,
367
+ );
368
+ return Effect.succeed("should-not-reach-here");
369
+ }),
370
+ );
371
+ }),
372
+ );
373
+ expect([
374
+ "constructor-failure-handled-properly",
375
+ "timeout-handled-properly",
376
+ ]).toContain(result);
377
+ });
378
+ });
@@ -0,0 +1,44 @@
1
+ import * as EffectModule from "effect";
2
+ import { Effect } from "effect";
3
+ import { ModuleFederationError } from "../errors";
4
+ export interface IModuleFederationService {
5
+ readonly registerRemote: (
6
+ pluginId: string,
7
+ url: string,
8
+ ) => Effect.Effect<void, ModuleFederationError>;
9
+ readonly loadRemoteConstructor: (
10
+ pluginId: string,
11
+ url: string,
12
+ ) => Effect.Effect<any, ModuleFederationError>;
13
+ }
14
+ declare const ModuleFederationService_base: EffectModule.Context.TagClass<
15
+ ModuleFederationService,
16
+ "ModuleFederationService",
17
+ IModuleFederationService
18
+ > &
19
+ EffectModule.Effect.Tag.Proxy<
20
+ ModuleFederationService,
21
+ IModuleFederationService
22
+ > & {
23
+ use: <X>(
24
+ body: (_: IModuleFederationService) => X,
25
+ ) => [X] extends [EffectModule.Effect.Effect<infer A, infer E, infer R>]
26
+ ? EffectModule.Effect.Effect<A, E, R | ModuleFederationService>
27
+ : [X] extends [PromiseLike<infer A_1>]
28
+ ? EffectModule.Effect.Effect<
29
+ A_1,
30
+ EffectModule.Cause.UnknownException,
31
+ ModuleFederationService
32
+ >
33
+ : EffectModule.Effect.Effect<X, never, ModuleFederationService>;
34
+ };
35
+ export declare class ModuleFederationService extends ModuleFederationService_base {
36
+ static Live: EffectModule.Layer.Layer<ModuleFederationService, never, never>;
37
+ }
38
+ export declare const ModuleFederationServiceTag: typeof ModuleFederationService;
39
+ export declare const ModuleFederationServiceLive: EffectModule.Layer.Layer<
40
+ ModuleFederationService,
41
+ never,
42
+ never
43
+ >;
44
+ //# sourceMappingURL=module-federation.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module-federation.service.d.ts","sourceRoot":"","sources":["../../../src/runtime/services/module-federation.service.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,YAAY,MAAM,QAAQ,CAAC;AACvC,OAAO,EAAE,MAAM,EAAS,MAAM,QAAQ,CAAC;AAEvC,OAAO,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAElD,MAAM,WAAW,wBAAwB;IACxC,QAAQ,CAAC,cAAc,EAAE,CACxB,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,KACP,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,qBAAqB,CAAC,CAAC;IAChD,QAAQ,CAAC,qBAAqB,EAAE,CAC/B,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,KACP,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,qBAAqB,CAAC,CAAC;CAC/C;;;;AAyCD,qBAAa,uBAAwB,SAAQ,4BAES;IACrD,MAAM,CAAC,IAAI,kEAwFT;CACF;AAED,eAAO,MAAM,0BAA0B,gCAA0B,CAAC;AAClE,eAAO,MAAM,2BAA2B,iEAA+B,CAAC"}
@@ -0,0 +1,131 @@
1
+ import { betterFetch } from "@better-fetch/fetch";
2
+ import {
3
+ createInstance,
4
+ getInstance,
5
+ } from "@module-federation/enhanced/runtime";
6
+ import { setGlobalFederationInstance } from "@module-federation/runtime-core";
7
+ import * as EffectModule from "effect";
8
+ import { Effect, Layer } from "effect";
9
+ import * as ZodModule from "zod";
10
+ import { ModuleFederationError } from "../errors";
11
+
12
+ const createModuleFederationInstance = Effect.cached(
13
+ Effect.sync(() => {
14
+ try {
15
+ let instance = getInstance();
16
+ if (!instance) {
17
+ instance = createInstance({
18
+ name: "host",
19
+ remotes: [],
20
+ shared: {
21
+ effect: {
22
+ shareConfig: {
23
+ singleton: true,
24
+ requiredVersion: "^3.17.6",
25
+ eager: true,
26
+ },
27
+ lib: () => EffectModule,
28
+ },
29
+ zod: {
30
+ shareConfig: {
31
+ singleton: true,
32
+ requiredVersion: "^4.0.8",
33
+ eager: true,
34
+ },
35
+ lib: () => ZodModule,
36
+ },
37
+ },
38
+ });
39
+ setGlobalFederationInstance(instance);
40
+ }
41
+ return instance;
42
+ } catch (error) {
43
+ throw new Error(`Failed to initialize Module Federation: ${error}`);
44
+ }
45
+ }),
46
+ );
47
+ export class ModuleFederationService extends Effect.Tag(
48
+ "ModuleFederationService",
49
+ )() {
50
+ static Live = Layer.effect(
51
+ ModuleFederationService,
52
+ Effect.gen(function* () {
53
+ const mf = yield* Effect.flatten(createModuleFederationInstance);
54
+ return {
55
+ registerRemote: (pluginId, url) =>
56
+ Effect.gen(function* () {
57
+ // Check if remote is available
58
+ const { error } = yield* Effect.tryPromise({
59
+ try: () => betterFetch(url, { method: "HEAD" }),
60
+ catch: (error) =>
61
+ new ModuleFederationError({
62
+ pluginId,
63
+ remoteUrl: url,
64
+ cause:
65
+ error instanceof Error ? error : new Error(String(error)),
66
+ }),
67
+ });
68
+ if (error) {
69
+ return yield* Effect.fail(
70
+ new ModuleFederationError({
71
+ pluginId,
72
+ remoteUrl: url,
73
+ cause: new Error(`Remote not available: ${error}`),
74
+ }),
75
+ );
76
+ }
77
+ // Register remote
78
+ const remoteName = pluginId
79
+ .toLowerCase()
80
+ .replace("@", "")
81
+ .replace("/", "_");
82
+ yield* Effect.try({
83
+ try: () => mf.registerRemotes([{ name: remoteName, entry: url }]),
84
+ catch: (error) =>
85
+ new ModuleFederationError({
86
+ pluginId,
87
+ remoteUrl: url,
88
+ cause:
89
+ error instanceof Error ? error : new Error(String(error)),
90
+ }),
91
+ });
92
+ }),
93
+ loadRemoteConstructor: (pluginId, url) =>
94
+ Effect.gen(function* () {
95
+ const remoteName = pluginId
96
+ .toLowerCase()
97
+ .replace("@", "")
98
+ .replace("/", "_");
99
+ const modulePath = `${remoteName}/plugin`;
100
+ return yield* Effect.tryPromise({
101
+ try: async () => {
102
+ const container = await mf.loadRemote(modulePath);
103
+ if (!container) {
104
+ throw new Error(`No container returned for ${modulePath}`);
105
+ }
106
+ const Constructor =
107
+ typeof container === "function"
108
+ ? container
109
+ : container?.default;
110
+ if (!Constructor || typeof Constructor !== "function") {
111
+ throw new Error(
112
+ `No valid constructor found. Container type: ${typeof container}`,
113
+ );
114
+ }
115
+ return Constructor;
116
+ },
117
+ catch: (error) =>
118
+ new ModuleFederationError({
119
+ pluginId,
120
+ remoteUrl: url,
121
+ cause:
122
+ error instanceof Error ? error : new Error(String(error)),
123
+ }),
124
+ });
125
+ }),
126
+ };
127
+ }),
128
+ );
129
+ }
130
+ export const ModuleFederationServiceTag = ModuleFederationService;
131
+ export const ModuleFederationServiceLive = ModuleFederationService.Live;
@@ -0,0 +1,75 @@
1
+ import { Effect, Layer } from "effect";
2
+ import { PluginLoggerTag, type PluginRegistry } from "../../plugin";
3
+ import { PluginRuntimeError } from "../errors";
4
+ import type {
5
+ AnyPlugin,
6
+ InitializedPlugin,
7
+ PluginConstructor,
8
+ PluginInstance,
9
+ SecretsConfig,
10
+ } from "../types";
11
+ import { ModuleFederationService } from "./module-federation.service";
12
+ import { SecretsService } from "./secrets.service";
13
+ export interface IPluginService {
14
+ readonly loadPlugin: (
15
+ pluginId: string,
16
+ ) => Effect.Effect<PluginConstructor, PluginRuntimeError>;
17
+ readonly instantiatePlugin: <T extends AnyPlugin>(
18
+ pluginConstructor: PluginConstructor,
19
+ ) => Effect.Effect<PluginInstance<T>, PluginRuntimeError>;
20
+ readonly initializePlugin: <T extends AnyPlugin>(
21
+ pluginInstance: PluginInstance<T>,
22
+ config: unknown,
23
+ ) => Effect.Effect<InitializedPlugin<T>, PluginRuntimeError>;
24
+ readonly executePlugin: <T extends AnyPlugin>(
25
+ initializedPlugin: InitializedPlugin<T>,
26
+ input: unknown,
27
+ ) => Effect.Effect<any, PluginRuntimeError>;
28
+ readonly usePlugin: <T extends AnyPlugin>(
29
+ pluginId: string,
30
+ config: unknown,
31
+ ) => Effect.Effect<InitializedPlugin<T>, PluginRuntimeError>;
32
+ readonly shutdownPlugin: (
33
+ plugin: InitializedPlugin<AnyPlugin>,
34
+ ) => Effect.Effect<void, PluginRuntimeError>;
35
+ readonly clearCache: () => Effect.Effect<void, never>;
36
+ readonly cleanup: () => Effect.Effect<void, never>;
37
+ }
38
+ declare const PluginService_base: import("effect/Context").TagClass<
39
+ PluginService,
40
+ "PluginService",
41
+ IPluginService
42
+ > &
43
+ Effect.Tag.Proxy<PluginService, IPluginService> & {
44
+ use: <X>(
45
+ body: (_: IPluginService) => X,
46
+ ) => [X] extends [Effect.Effect<infer A, infer E, infer R>]
47
+ ? Effect.Effect<A, E, R | PluginService>
48
+ : [X] extends [PromiseLike<infer A_1>]
49
+ ? Effect.Effect<
50
+ A_1,
51
+ import("effect/Cause").UnknownException,
52
+ PluginService
53
+ >
54
+ : Effect.Effect<X, never, PluginService>;
55
+ };
56
+ export declare class PluginService extends PluginService_base {
57
+ static Live: (
58
+ registry: PluginRegistry,
59
+ secrets: SecretsConfig,
60
+ ) => Layer.Layer<
61
+ PluginService,
62
+ never,
63
+ PluginLoggerTag | SecretsService | ModuleFederationService
64
+ >;
65
+ }
66
+ export declare const PluginServiceTag: typeof PluginService;
67
+ export declare const PluginServiceLive: (
68
+ registry: PluginRegistry,
69
+ secrets: SecretsConfig,
70
+ ) => Layer.Layer<
71
+ PluginService,
72
+ never,
73
+ PluginLoggerTag | SecretsService | ModuleFederationService
74
+ >;
75
+ //# sourceMappingURL=plugin.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.service.d.ts","sourceRoot":"","sources":["../../../src/runtime/services/plugin.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,MAAM,EAAQ,KAAK,EAAY,MAAM,QAAQ,CAAC;AACxE,OAAO,EAAE,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AACpE,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,KAAK,EACX,SAAS,EACT,iBAAiB,EACjB,iBAAiB,EACjB,cAAc,EACd,aAAa,EACb,MAAM,UAAU,CAAC;AAElB,OAAO,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AACtE,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnD,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,UAAU,EAAE,CACpB,QAAQ,EAAE,MAAM,KACZ,MAAM,CAAC,MAAM,CAAC,iBAAiB,EAAE,kBAAkB,CAAC,CAAC;IAC1D,QAAQ,CAAC,iBAAiB,EAAE,CAAC,CAAC,SAAS,SAAS,EAC/C,iBAAiB,EAAE,iBAAiB,KAChC,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,kBAAkB,CAAC,CAAC;IAC1D,QAAQ,CAAC,gBAAgB,EAAE,CAAC,CAAC,SAAS,SAAS,EAC9C,cAAc,EAAE,cAAc,CAAC,CAAC,CAAC,EACjC,MAAM,EAAE,OAAO,KACX,MAAM,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,kBAAkB,CAAC,CAAC;IAC7D,QAAQ,CAAC,aAAa,EAAE,CAAC,CAAC,SAAS,SAAS,EAC3C,iBAAiB,EAAE,iBAAiB,CAAC,CAAC,CAAC,EACvC,KAAK,EAAE,OAAO,KACV,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAC;IAC5C,QAAQ,CAAC,SAAS,EAAE,CAAC,CAAC,SAAS,SAAS,EACvC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,OAAO,KACX,MAAM,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,kBAAkB,CAAC,CAAC;IAC7D,QAAQ,CAAC,cAAc,EAAE,CAAC,MAAM,EAAE,iBAAiB,CAAC,SAAS,CAAC,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAAC;IAC3G,QAAQ,CAAC,UAAU,EAAE,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACtD,QAAQ,CAAC,OAAO,EAAE,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;CACnD;;;;AAED,qBAAa,aAAc,SAAQ,kBAGhC;IACF,MAAM,CAAC,IAAI,GAAI,UAAU,cAAc,EAAE,SAAS,aAAa,mGAiY5D;CACH;AAED,eAAO,MAAM,gBAAgB,sBAAgB,CAAC;AAC9C,eAAO,MAAM,iBAAiB,aArYJ,cAAc,WAAW,aAAa,kGAqYb,CAAC"}