every-plugin 2.3.0 → 2.4.1
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/build/rspack/dev-server-middleware.cjs +1 -2
- package/dist/build/rspack/dev-server-middleware.cjs.map +1 -1
- package/dist/build/rspack/dev-server-middleware.mjs +1 -2
- package/dist/build/rspack/dev-server-middleware.mjs.map +1 -1
- package/dist/build/rspack/plugin.cjs +21 -1
- package/dist/build/rspack/plugin.cjs.map +1 -1
- package/dist/build/rspack/plugin.d.cts +5 -0
- package/dist/build/rspack/plugin.d.cts.map +1 -1
- package/dist/build/rspack/plugin.d.mts +5 -0
- package/dist/build/rspack/plugin.d.mts.map +1 -1
- package/dist/build/rspack/plugin.mjs +21 -1
- package/dist/build/rspack/plugin.mjs.map +1 -1
- package/dist/build/shared-deps.cjs +16 -0
- package/dist/build/shared-deps.cjs.map +1 -1
- package/dist/build/shared-deps.d.cts +28 -0
- package/dist/build/shared-deps.d.cts.map +1 -1
- package/dist/build/shared-deps.d.mts +28 -0
- package/dist/build/shared-deps.d.mts.map +1 -1
- package/dist/build/shared-deps.mjs +16 -0
- package/dist/build/shared-deps.mjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/plugin.cjs +2 -1
- package/dist/plugin.cjs.map +1 -1
- package/dist/plugin.d.cts.map +1 -1
- package/dist/plugin.d.mts.map +1 -1
- package/dist/plugin.mjs +2 -1
- package/dist/plugin.mjs.map +1 -1
- package/dist/runtime/index.cjs +2 -2
- package/dist/runtime/index.cjs.map +1 -1
- package/dist/runtime/index.d.cts.map +1 -1
- package/dist/runtime/index.d.mts.map +1 -1
- package/dist/runtime/index.mjs +2 -2
- package/dist/runtime/index.mjs.map +1 -1
- package/dist/runtime/mf-config.cjs +17 -1
- package/dist/runtime/mf-config.cjs.map +1 -1
- package/dist/runtime/mf-config.d.cts +36 -0
- package/dist/runtime/mf-config.d.cts.map +1 -1
- package/dist/runtime/mf-config.d.mts +36 -0
- package/dist/runtime/mf-config.d.mts.map +1 -1
- package/dist/runtime/mf-config.mjs +17 -1
- package/dist/runtime/mf-config.mjs.map +1 -1
- package/dist/runtime/services/module-federation.service.cjs +19 -11
- 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 +19 -11
- package/dist/runtime/services/module-federation.service.mjs.map +1 -1
- package/dist/runtime/services/plugin-lifecycle.service.cjs +5 -2
- package/dist/runtime/services/plugin-lifecycle.service.cjs.map +1 -1
- package/dist/runtime/services/plugin-lifecycle.service.mjs +6 -3
- package/dist/runtime/services/plugin-lifecycle.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.map +1 -1
- package/dist/runtime/services/plugin.service.d.mts.map +1 -1
- package/dist/runtime/services/plugin.service.mjs +1 -1
- package/dist/runtime/services/plugin.service.mjs.map +1 -1
- package/package.json +7 -3
- package/skills/plugin-development/SKILL.md +208 -0
- package/skills/plugin-testing/SKILL.md +152 -0
- package/src/build/rspack/dev-server-middleware.ts +1 -6
- package/src/build/rspack/plugin.ts +39 -2
- package/src/build/shared-deps.ts +16 -0
- package/src/index.ts +1 -0
- package/src/plugin.ts +8 -1
- package/src/runtime/index.ts +10 -2
- package/src/runtime/mf-config.ts +16 -0
- package/src/runtime/services/module-federation.service.ts +37 -24
- package/src/runtime/services/plugin-lifecycle.service.ts +18 -23
- package/src/runtime/services/plugin.service.ts +7 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module-federation.service.cjs","names":["
|
|
1
|
+
{"version":3,"file":"module-federation.service.cjs","names":["MF_SHARED_DEPS","Effect","getNormalizedRemoteName","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 { Effect } from \"effect\";\nimport type { AnyPlugin } from \"../../types\";\nimport { ModuleFederationError } from \"../errors\";\nimport { MF_SHARED_DEPS, type SharedDepName } from \"../mf-config\";\nimport { getNormalizedRemoteName } from \"./normalize\";\n\ntype RemoteModule = (new () => AnyPlugin) | { default: new () => AnyPlugin };\n\nconst sharedModuleLoaders = {\n \"every-plugin\": () => import(\"every-plugin\"),\n effect: () => import(\"effect\"),\n zod: () => import(\"zod\"),\n \"better-auth\": () => import(\"better-auth\"),\n \"drizzle-orm\": () => import(\"drizzle-orm\"),\n \"@orpc/contract\": () => import(\"@orpc/contract\"),\n \"@orpc/server\": () => import(\"@orpc/server\"),\n} satisfies Record<SharedDepName, () => Promise<unknown>>;\n\nfunction buildSharedConfig(): Record<\n string,\n {\n version: string;\n get: () => Promise<() => unknown>;\n shareConfig: (typeof MF_SHARED_DEPS)[SharedDepName][\"shareConfig\"];\n }\n> {\n return Object.fromEntries(\n (\n Object.entries(MF_SHARED_DEPS) as [SharedDepName, (typeof MF_SHARED_DEPS)[SharedDepName]][]\n ).map(([name, config]) => {\n const load = sharedModuleLoaders[name];\n\n if (!load) {\n throw new Error(`Missing shared module loader for ${name}`);\n }\n\n return [\n name,\n {\n version: config.version,\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: ${error}`);\n }\n }),\n);\n\nexport class ModuleFederationService extends Effect.Service<ModuleFederationService>()(\n \"ModuleFederationService\",\n {\n effect: Effect.gen(function* () {\n const mf = yield* Effect.flatten(createModuleFederationInstance);\n\n return {\n registerRemote: (pluginId: string, url: string) =>\n Effect.gen(function* () {\n console.log(`[MF] Registering ${pluginId}`);\n\n const remoteName = getNormalizedRemoteName(pluginId);\n const type = url.endsWith(\"/mf-manifest.json\")\n ? (\"manifest\" as const)\n : url.endsWith(\"/remoteEntry.js\")\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 console.log(`[MF] ✅ Registered ${pluginId}`);\n }),\n\n loadRemoteConstructor: (pluginId: string, url: string) =>\n Effect.gen(function* () {\n const remoteName = getNormalizedRemoteName(pluginId);\n console.log(`[MF] Loading remote ${remoteName}`);\n const modulePath = `${remoteName}/plugin`;\n\n return 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 console.log(`[MF] ✅ Loaded constructor for ${pluginId}`);\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 };\n }),\n },\n) {}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module-federation.service.d.cts","names":[],"sources":["../../../src/runtime/services/module-federation.service.ts"],"mappings":";;;;cAIkD,4BAAA;;
|
|
1
|
+
{"version":3,"file":"module-federation.service.d.cts","names":[],"sources":["../../../src/runtime/services/module-federation.service.ts"],"mappings":";;;;cAIkD,4BAAA;;uCA8ET,GAAA,aAAa,MAAA,CAAA,MAAA,OAAA,qBAAA;8CA+BN,GAAA,aAAa,MAAA,CAAA,MAAA,MAAA,qBAAA;EAAA;AAAA;AAAA,cAtChD,uBAAA,SAAgC,4BAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module-federation.service.d.mts","names":[],"sources":["../../../src/runtime/services/module-federation.service.ts"],"mappings":";;;;cAIkD,4BAAA;;
|
|
1
|
+
{"version":3,"file":"module-federation.service.d.mts","names":[],"sources":["../../../src/runtime/services/module-federation.service.ts"],"mappings":";;;;cAIkD,4BAAA;;uCA8ET,GAAA,aAAa,MAAA,CAAA,MAAA,OAAA,qBAAA;8CA+BN,GAAA,aAAa,MAAA,CAAA,MAAA,MAAA,qBAAA;EAAA;AAAA;AAAA,cAtChD,uBAAA,SAAgC,4BAAA"}
|
|
@@ -9,27 +9,35 @@ import { setGlobalFederationInstance } from "@module-federation/runtime-core";
|
|
|
9
9
|
const sharedModuleLoaders = {
|
|
10
10
|
"every-plugin": () => import("../../index.mjs"),
|
|
11
11
|
effect: () => import("effect"),
|
|
12
|
-
zod: () => import("zod")
|
|
12
|
+
zod: () => import("zod"),
|
|
13
|
+
"better-auth": () => import("../../node_modules/.bun/better-auth@1.6.9_a51fe808ef526527/node_modules/better-auth/dist/index.mjs"),
|
|
14
|
+
"drizzle-orm": () => import("../../node_modules/.bun/drizzle-orm@0.45.2_5d168cad3842e387/node_modules/drizzle-orm/index.mjs"),
|
|
15
|
+
"@orpc/contract": () => import("@orpc/contract"),
|
|
16
|
+
"@orpc/server": () => import("@orpc/server")
|
|
13
17
|
};
|
|
18
|
+
function buildSharedConfig() {
|
|
19
|
+
return Object.fromEntries(Object.entries(MF_SHARED_DEPS).map(([name, config]) => {
|
|
20
|
+
const load = sharedModuleLoaders[name];
|
|
21
|
+
if (!load) throw new Error(`Missing shared module loader for ${name}`);
|
|
22
|
+
return [name, {
|
|
23
|
+
version: config.version,
|
|
24
|
+
get: () => load().then((mod) => () => mod),
|
|
25
|
+
shareConfig: config.shareConfig
|
|
26
|
+
}];
|
|
27
|
+
}));
|
|
28
|
+
}
|
|
14
29
|
const createModuleFederationInstance = Effect.cached(Effect.sync(() => {
|
|
15
30
|
try {
|
|
31
|
+
const shared = buildSharedConfig();
|
|
16
32
|
let instance = getInstance();
|
|
17
33
|
if (!instance) {
|
|
18
34
|
instance = createInstance({
|
|
19
35
|
name: "host",
|
|
20
36
|
remotes: [],
|
|
21
|
-
shared
|
|
22
|
-
const load = sharedModuleLoaders[name];
|
|
23
|
-
if (!load) throw new Error(`Missing shared module loader for ${name}`);
|
|
24
|
-
return [name, {
|
|
25
|
-
version: config.version,
|
|
26
|
-
get: () => load().then((mod) => () => mod),
|
|
27
|
-
shareConfig: config.shareConfig
|
|
28
|
-
}];
|
|
29
|
-
}))
|
|
37
|
+
shared
|
|
30
38
|
});
|
|
31
39
|
setGlobalFederationInstance(instance);
|
|
32
|
-
}
|
|
40
|
+
} else instance.registerShared(shared);
|
|
33
41
|
return instance;
|
|
34
42
|
} catch (error) {
|
|
35
43
|
throw new Error(`Failed to initialize Module Federation: ${error}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module-federation.service.mjs","names":[],"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 { Effect } from \"effect\";\nimport type { AnyPlugin } from \"../../types\";\nimport { ModuleFederationError } from \"../errors\";\nimport { MF_SHARED_DEPS, type SharedDepName } from \"../mf-config\";\nimport { getNormalizedRemoteName } from \"./normalize\";\n\ntype RemoteModule = (new () => AnyPlugin) | { default: new () => AnyPlugin };\n\nconst sharedModuleLoaders = {\n \"every-plugin\": () => import(\"every-plugin\"),\n effect: () => import(\"effect\"),\n zod: () => import(\"zod\"),\n} satisfies Record<SharedDepName, () => Promise<unknown>>;\n\
|
|
1
|
+
{"version":3,"file":"module-federation.service.mjs","names":[],"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 { Effect } from \"effect\";\nimport type { AnyPlugin } from \"../../types\";\nimport { ModuleFederationError } from \"../errors\";\nimport { MF_SHARED_DEPS, type SharedDepName } from \"../mf-config\";\nimport { getNormalizedRemoteName } from \"./normalize\";\n\ntype RemoteModule = (new () => AnyPlugin) | { default: new () => AnyPlugin };\n\nconst sharedModuleLoaders = {\n \"every-plugin\": () => import(\"every-plugin\"),\n effect: () => import(\"effect\"),\n zod: () => import(\"zod\"),\n \"better-auth\": () => import(\"better-auth\"),\n \"drizzle-orm\": () => import(\"drizzle-orm\"),\n \"@orpc/contract\": () => import(\"@orpc/contract\"),\n \"@orpc/server\": () => import(\"@orpc/server\"),\n} satisfies Record<SharedDepName, () => Promise<unknown>>;\n\nfunction buildSharedConfig(): Record<\n string,\n {\n version: string;\n get: () => Promise<() => unknown>;\n shareConfig: (typeof MF_SHARED_DEPS)[SharedDepName][\"shareConfig\"];\n }\n> {\n return Object.fromEntries(\n (\n Object.entries(MF_SHARED_DEPS) as [SharedDepName, (typeof MF_SHARED_DEPS)[SharedDepName]][]\n ).map(([name, config]) => {\n const load = sharedModuleLoaders[name];\n\n if (!load) {\n throw new Error(`Missing shared module loader for ${name}`);\n }\n\n return [\n name,\n {\n version: config.version,\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: ${error}`);\n }\n }),\n);\n\nexport class ModuleFederationService extends Effect.Service<ModuleFederationService>()(\n \"ModuleFederationService\",\n {\n effect: Effect.gen(function* () {\n const mf = yield* Effect.flatten(createModuleFederationInstance);\n\n return {\n registerRemote: (pluginId: string, url: string) =>\n Effect.gen(function* () {\n console.log(`[MF] Registering ${pluginId}`);\n\n const remoteName = getNormalizedRemoteName(pluginId);\n const type = url.endsWith(\"/mf-manifest.json\")\n ? (\"manifest\" as const)\n : url.endsWith(\"/remoteEntry.js\")\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 console.log(`[MF] ✅ Registered ${pluginId}`);\n }),\n\n loadRemoteConstructor: (pluginId: string, url: string) =>\n Effect.gen(function* () {\n const remoteName = getNormalizedRemoteName(pluginId);\n console.log(`[MF] Loading remote ${remoteName}`);\n const modulePath = `${remoteName}/plugin`;\n\n return 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 console.log(`[MF] ✅ Loaded constructor for ${pluginId}`);\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 };\n }),\n },\n) {}\n"],"mappings":"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"}
|
|
@@ -22,9 +22,12 @@ var PluginLifecycleService = class extends effect.Effect.Service()("PluginLifecy
|
|
|
22
22
|
}),
|
|
23
23
|
cleanup: () => effect.Effect.gen(function* () {
|
|
24
24
|
const plugins = yield* effect.Ref.get(activePlugins);
|
|
25
|
-
yield* effect.Effect.forEach(plugins, (plugin) =>
|
|
25
|
+
yield* effect.Effect.forEach(plugins, (plugin) => effect.Effect.gen(function* () {
|
|
26
|
+
yield* plugin.plugin.shutdown().pipe(effect.Effect.catchAll((error) => effect.Effect.logWarning(`Failed to shutdown plugin ${plugin.plugin.id}`, error)));
|
|
27
|
+
yield* effect.Scope.close(plugin.scope, effect.Exit.succeed(void 0)).pipe(effect.Effect.catchAll((error) => effect.Effect.logWarning(`Failed to close scope for plugin ${plugin.plugin.id}`, error)));
|
|
28
|
+
}), { concurrency: "unbounded" });
|
|
26
29
|
yield* effect.Ref.set(activePlugins, /* @__PURE__ */ new Set());
|
|
27
|
-
}).pipe(effect.Effect.catchAll(() => effect.Effect.
|
|
30
|
+
}).pipe(effect.Effect.catchAll((error) => effect.Effect.logWarning("Plugin cleanup failed", error)))
|
|
28
31
|
};
|
|
29
32
|
}) }) {};
|
|
30
33
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin-lifecycle.service.cjs","names":["Effect","Ref","toPluginRuntimeError"],"sources":["../../../src/runtime/services/plugin-lifecycle.service.ts"],"sourcesContent":["import { Effect, Ref } from \"effect\";\nimport type { AnyPlugin, InitializedPlugin } from \"../../types\";\nimport { toPluginRuntimeError } from \"../errors\";\n\nexport class PluginLifecycleService extends Effect.Service<PluginLifecycleService>()(\n \"PluginLifecycleService\",\n {\n effect: Effect.gen(function* () {\n const activePlugins = yield* Ref.make(new Set<InitializedPlugin<AnyPlugin>>());\n\n return {\n register: <T extends AnyPlugin>(plugin: InitializedPlugin<T>) =>\n Ref.update(activePlugins, (plugins) =>\n new Set(plugins).add(plugin as InitializedPlugin<AnyPlugin>),\n ),\n\n unregister: (plugin: InitializedPlugin<AnyPlugin>) =>\n Ref.update(activePlugins, (plugins) => {\n const newSet = new Set(plugins);\n newSet.delete(plugin);\n return newSet;\n }),\n\n shutdown: (plugin: InitializedPlugin<AnyPlugin>) =>\n Effect.gen(function* () {\n // Remove from active plugins\n yield* Ref.update(activePlugins, (plugins) => {\n const newSet = new Set(plugins);\n newSet.delete(plugin);\n return newSet;\n });\n\n // Shutdown the plugin\n yield* plugin.plugin\n .shutdown()\n .pipe(\n Effect.mapError((error) =>\n toPluginRuntimeError(\n error,\n plugin.plugin.id,\n undefined,\n \"shutdown-plugin\",\n false,\n ),\n ),\n );\n }),\n\n cleanup: () =>\n Effect.gen(function* () {\n const plugins = yield* Ref.get(activePlugins);\n\n
|
|
1
|
+
{"version":3,"file":"plugin-lifecycle.service.cjs","names":["Effect","Ref","toPluginRuntimeError","Scope","Exit"],"sources":["../../../src/runtime/services/plugin-lifecycle.service.ts"],"sourcesContent":["import { Effect, Exit, Ref, Scope } from \"effect\";\nimport type { AnyPlugin, InitializedPlugin } from \"../../types\";\nimport { toPluginRuntimeError } from \"../errors\";\n\nexport class PluginLifecycleService extends Effect.Service<PluginLifecycleService>()(\n \"PluginLifecycleService\",\n {\n effect: Effect.gen(function* () {\n const activePlugins = yield* Ref.make(new Set<InitializedPlugin<AnyPlugin>>());\n\n return {\n register: <T extends AnyPlugin>(plugin: InitializedPlugin<T>) =>\n Ref.update(activePlugins, (plugins) =>\n new Set(plugins).add(plugin as InitializedPlugin<AnyPlugin>),\n ),\n\n unregister: (plugin: InitializedPlugin<AnyPlugin>) =>\n Ref.update(activePlugins, (plugins) => {\n const newSet = new Set(plugins);\n newSet.delete(plugin);\n return newSet;\n }),\n\n shutdown: (plugin: InitializedPlugin<AnyPlugin>) =>\n Effect.gen(function* () {\n // Remove from active plugins\n yield* Ref.update(activePlugins, (plugins) => {\n const newSet = new Set(plugins);\n newSet.delete(plugin);\n return newSet;\n });\n\n // Shutdown the plugin\n yield* plugin.plugin\n .shutdown()\n .pipe(\n Effect.mapError((error) =>\n toPluginRuntimeError(\n error,\n plugin.plugin.id,\n undefined,\n \"shutdown-plugin\",\n false,\n ),\n ),\n );\n }),\n\n cleanup: () =>\n Effect.gen(function* () {\n const plugins = yield* Ref.get(activePlugins);\n\n yield* Effect.forEach(\n plugins,\n (plugin) =>\n Effect.gen(function* () {\n yield* plugin.plugin\n .shutdown()\n .pipe(\n Effect.catchAll((error) =>\n Effect.logWarning(`Failed to shutdown plugin ${plugin.plugin.id}`, error),\n ),\n );\n yield* Scope.close(plugin.scope, Exit.succeed(undefined)).pipe(\n Effect.catchAll((error) =>\n Effect.logWarning(\n `Failed to close scope for plugin ${plugin.plugin.id}`,\n error,\n ),\n ),\n );\n }),\n { concurrency: \"unbounded\" },\n );\n\n yield* Ref.set(activePlugins, new Set());\n }).pipe(Effect.catchAll((error) => Effect.logWarning(\"Plugin cleanup failed\", error))),\n };\n }),\n },\n) {}\n"],"mappings":";;;;;AAIA,IAAa,yBAAb,cAA4CA,cAAO,SAAiC,CAClF,0BACA,EACE,QAAQA,cAAO,IAAI,aAAa;CAC9B,MAAM,gBAAgB,OAAOC,WAAI,qBAAK,IAAI,KAAmC,CAAC;AAE9E,QAAO;EACL,WAAgC,WAC9BA,WAAI,OAAO,gBAAgB,YACzB,IAAI,IAAI,QAAQ,CAAC,IAAI,OAAuC,CAC7D;EAEH,aAAa,WACXA,WAAI,OAAO,gBAAgB,YAAY;GACrC,MAAM,SAAS,IAAI,IAAI,QAAQ;AAC/B,UAAO,OAAO,OAAO;AACrB,UAAO;IACP;EAEJ,WAAW,WACTD,cAAO,IAAI,aAAa;AAEtB,UAAOC,WAAI,OAAO,gBAAgB,YAAY;IAC5C,MAAM,SAAS,IAAI,IAAI,QAAQ;AAC/B,WAAO,OAAO,OAAO;AACrB,WAAO;KACP;AAGF,UAAO,OAAO,OACX,UAAU,CACV,KACCD,cAAO,UAAU,UACfE,oCACE,OACA,OAAO,OAAO,IACd,QACA,mBACA,MACD,CACF,CACF;IACH;EAEJ,eACEF,cAAO,IAAI,aAAa;GACtB,MAAM,UAAU,OAAOC,WAAI,IAAI,cAAc;AAE7C,UAAOD,cAAO,QACZ,UACC,WACCA,cAAO,IAAI,aAAa;AACtB,WAAO,OAAO,OACX,UAAU,CACV,KACCA,cAAO,UAAU,UACfA,cAAO,WAAW,6BAA6B,OAAO,OAAO,MAAM,MAAM,CAC1E,CACF;AACH,WAAOG,aAAM,MAAM,OAAO,OAAOC,YAAK,QAAQ,OAAU,CAAC,CAAC,KACxDJ,cAAO,UAAU,UACfA,cAAO,WACL,oCAAoC,OAAO,OAAO,MAClD,MACD,CACF,CACF;KACD,EACJ,EAAE,aAAa,aAAa,CAC7B;AAED,UAAOC,WAAI,IAAI,+BAAe,IAAI,KAAK,CAAC;IACxC,CAAC,KAAKD,cAAO,UAAU,UAAUA,cAAO,WAAW,yBAAyB,MAAM,CAAC,CAAC;EACzF;EACD,EACH,CACF,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { toPluginRuntimeError } from "../errors.mjs";
|
|
2
|
-
import { Effect, Ref } from "effect";
|
|
2
|
+
import { Effect, Exit, Ref, Scope } from "effect";
|
|
3
3
|
|
|
4
4
|
//#region src/runtime/services/plugin-lifecycle.service.ts
|
|
5
5
|
var PluginLifecycleService = class extends Effect.Service()("PluginLifecycleService", { effect: Effect.gen(function* () {
|
|
@@ -21,9 +21,12 @@ var PluginLifecycleService = class extends Effect.Service()("PluginLifecycleServ
|
|
|
21
21
|
}),
|
|
22
22
|
cleanup: () => Effect.gen(function* () {
|
|
23
23
|
const plugins = yield* Ref.get(activePlugins);
|
|
24
|
-
yield* Effect.forEach(plugins, (plugin) =>
|
|
24
|
+
yield* Effect.forEach(plugins, (plugin) => Effect.gen(function* () {
|
|
25
|
+
yield* plugin.plugin.shutdown().pipe(Effect.catchAll((error) => Effect.logWarning(`Failed to shutdown plugin ${plugin.plugin.id}`, error)));
|
|
26
|
+
yield* Scope.close(plugin.scope, Exit.succeed(void 0)).pipe(Effect.catchAll((error) => Effect.logWarning(`Failed to close scope for plugin ${plugin.plugin.id}`, error)));
|
|
27
|
+
}), { concurrency: "unbounded" });
|
|
25
28
|
yield* Ref.set(activePlugins, /* @__PURE__ */ new Set());
|
|
26
|
-
}).pipe(Effect.catchAll(() => Effect.
|
|
29
|
+
}).pipe(Effect.catchAll((error) => Effect.logWarning("Plugin cleanup failed", error)))
|
|
27
30
|
};
|
|
28
31
|
}) }) {};
|
|
29
32
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin-lifecycle.service.mjs","names":[],"sources":["../../../src/runtime/services/plugin-lifecycle.service.ts"],"sourcesContent":["import { Effect, Ref } from \"effect\";\nimport type { AnyPlugin, InitializedPlugin } from \"../../types\";\nimport { toPluginRuntimeError } from \"../errors\";\n\nexport class PluginLifecycleService extends Effect.Service<PluginLifecycleService>()(\n \"PluginLifecycleService\",\n {\n effect: Effect.gen(function* () {\n const activePlugins = yield* Ref.make(new Set<InitializedPlugin<AnyPlugin>>());\n\n return {\n register: <T extends AnyPlugin>(plugin: InitializedPlugin<T>) =>\n Ref.update(activePlugins, (plugins) =>\n new Set(plugins).add(plugin as InitializedPlugin<AnyPlugin>),\n ),\n\n unregister: (plugin: InitializedPlugin<AnyPlugin>) =>\n Ref.update(activePlugins, (plugins) => {\n const newSet = new Set(plugins);\n newSet.delete(plugin);\n return newSet;\n }),\n\n shutdown: (plugin: InitializedPlugin<AnyPlugin>) =>\n Effect.gen(function* () {\n // Remove from active plugins\n yield* Ref.update(activePlugins, (plugins) => {\n const newSet = new Set(plugins);\n newSet.delete(plugin);\n return newSet;\n });\n\n // Shutdown the plugin\n yield* plugin.plugin\n .shutdown()\n .pipe(\n Effect.mapError((error) =>\n toPluginRuntimeError(\n error,\n plugin.plugin.id,\n undefined,\n \"shutdown-plugin\",\n false,\n ),\n ),\n );\n }),\n\n cleanup: () =>\n Effect.gen(function* () {\n const plugins = yield* Ref.get(activePlugins);\n\n
|
|
1
|
+
{"version":3,"file":"plugin-lifecycle.service.mjs","names":[],"sources":["../../../src/runtime/services/plugin-lifecycle.service.ts"],"sourcesContent":["import { Effect, Exit, Ref, Scope } from \"effect\";\nimport type { AnyPlugin, InitializedPlugin } from \"../../types\";\nimport { toPluginRuntimeError } from \"../errors\";\n\nexport class PluginLifecycleService extends Effect.Service<PluginLifecycleService>()(\n \"PluginLifecycleService\",\n {\n effect: Effect.gen(function* () {\n const activePlugins = yield* Ref.make(new Set<InitializedPlugin<AnyPlugin>>());\n\n return {\n register: <T extends AnyPlugin>(plugin: InitializedPlugin<T>) =>\n Ref.update(activePlugins, (plugins) =>\n new Set(plugins).add(plugin as InitializedPlugin<AnyPlugin>),\n ),\n\n unregister: (plugin: InitializedPlugin<AnyPlugin>) =>\n Ref.update(activePlugins, (plugins) => {\n const newSet = new Set(plugins);\n newSet.delete(plugin);\n return newSet;\n }),\n\n shutdown: (plugin: InitializedPlugin<AnyPlugin>) =>\n Effect.gen(function* () {\n // Remove from active plugins\n yield* Ref.update(activePlugins, (plugins) => {\n const newSet = new Set(plugins);\n newSet.delete(plugin);\n return newSet;\n });\n\n // Shutdown the plugin\n yield* plugin.plugin\n .shutdown()\n .pipe(\n Effect.mapError((error) =>\n toPluginRuntimeError(\n error,\n plugin.plugin.id,\n undefined,\n \"shutdown-plugin\",\n false,\n ),\n ),\n );\n }),\n\n cleanup: () =>\n Effect.gen(function* () {\n const plugins = yield* Ref.get(activePlugins);\n\n yield* Effect.forEach(\n plugins,\n (plugin) =>\n Effect.gen(function* () {\n yield* plugin.plugin\n .shutdown()\n .pipe(\n Effect.catchAll((error) =>\n Effect.logWarning(`Failed to shutdown plugin ${plugin.plugin.id}`, error),\n ),\n );\n yield* Scope.close(plugin.scope, Exit.succeed(undefined)).pipe(\n Effect.catchAll((error) =>\n Effect.logWarning(\n `Failed to close scope for plugin ${plugin.plugin.id}`,\n error,\n ),\n ),\n );\n }),\n { concurrency: \"unbounded\" },\n );\n\n yield* Ref.set(activePlugins, new Set());\n }).pipe(Effect.catchAll((error) => Effect.logWarning(\"Plugin cleanup failed\", error))),\n };\n }),\n },\n) {}\n"],"mappings":";;;;AAIA,IAAa,yBAAb,cAA4C,OAAO,SAAiC,CAClF,0BACA,EACE,QAAQ,OAAO,IAAI,aAAa;CAC9B,MAAM,gBAAgB,OAAO,IAAI,qBAAK,IAAI,KAAmC,CAAC;AAE9E,QAAO;EACL,WAAgC,WAC9B,IAAI,OAAO,gBAAgB,YACzB,IAAI,IAAI,QAAQ,CAAC,IAAI,OAAuC,CAC7D;EAEH,aAAa,WACX,IAAI,OAAO,gBAAgB,YAAY;GACrC,MAAM,SAAS,IAAI,IAAI,QAAQ;AAC/B,UAAO,OAAO,OAAO;AACrB,UAAO;IACP;EAEJ,WAAW,WACT,OAAO,IAAI,aAAa;AAEtB,UAAO,IAAI,OAAO,gBAAgB,YAAY;IAC5C,MAAM,SAAS,IAAI,IAAI,QAAQ;AAC/B,WAAO,OAAO,OAAO;AACrB,WAAO;KACP;AAGF,UAAO,OAAO,OACX,UAAU,CACV,KACC,OAAO,UAAU,UACf,qBACE,OACA,OAAO,OAAO,IACd,QACA,mBACA,MACD,CACF,CACF;IACH;EAEJ,eACE,OAAO,IAAI,aAAa;GACtB,MAAM,UAAU,OAAO,IAAI,IAAI,cAAc;AAE7C,UAAO,OAAO,QACZ,UACC,WACC,OAAO,IAAI,aAAa;AACtB,WAAO,OAAO,OACX,UAAU,CACV,KACC,OAAO,UAAU,UACf,OAAO,WAAW,6BAA6B,OAAO,OAAO,MAAM,MAAM,CAC1E,CACF;AACH,WAAO,MAAM,MAAM,OAAO,OAAO,KAAK,QAAQ,OAAU,CAAC,CAAC,KACxD,OAAO,UAAU,UACf,OAAO,WACL,oCAAoC,OAAO,OAAO,MAClD,MACD,CACF,CACF;KACD,EACJ,EAAE,aAAa,aAAa,CAC7B;AAED,UAAO,IAAI,IAAI,+BAAe,IAAI,KAAK,CAAC;IACxC,CAAC,KAAK,OAAO,UAAU,UAAU,OAAO,WAAW,yBAAyB,MAAM,CAAC,CAAC;EACzF;EACD,EACH,CACF,CAAC"}
|
|
@@ -14,7 +14,7 @@ var PluginService = class PluginService extends effect.Effect.Service()("PluginS
|
|
|
14
14
|
instantiatePlugin: loader.instantiatePlugin,
|
|
15
15
|
initializePlugin: loader.initializePlugin,
|
|
16
16
|
shutdownPlugin: (plugin) => effect.Effect.gen(function* () {
|
|
17
|
-
yield* plugin.plugin.shutdown().pipe(effect.Effect.catchAll(() => effect.Effect.
|
|
17
|
+
yield* plugin.plugin.shutdown().pipe(effect.Effect.catchAll((error) => effect.Effect.logWarning(`Failed to shutdown plugin ${plugin.plugin.id}`, error)));
|
|
18
18
|
yield* effect.Scope.close(plugin.scope, effect.Exit.succeed(void 0));
|
|
19
19
|
yield* lifecycle.unregister(plugin);
|
|
20
20
|
}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.service.cjs","names":["Effect","PluginLoaderService","PluginLifecycleService","Scope","Exit","Layer","PluginRegistryTag","SecretsConfigTag","PluginMapTag","ModuleFederationService","SecretsService","RegistryService"],"sources":["../../../src/runtime/services/plugin.service.ts"],"sourcesContent":["import { Effect, Exit, Layer, Scope } from \"effect\";\nimport type {\n AnyPlugin,\n AnyPluginConstructor,\n InitializedPlugin,\n PluginRegistry,\n SecretsConfig,\n} from \"../../types\";\nimport { ModuleFederationService } from \"./module-federation.service\";\nimport { PluginLifecycleService } from \"./plugin-lifecycle.service\";\nimport {\n PluginLoaderService,\n PluginMapTag,\n PluginRegistryTag,\n RegistryService,\n} from \"./plugin-loader.service\";\nimport { SecretsConfigTag, SecretsService } from \"./secrets.service\";\n\nexport class PluginService extends Effect.Service<PluginService>()(\"PluginService\", {\n scoped: Effect.gen(function* () {\n const loader = yield* PluginLoaderService;\n const lifecycle = yield* PluginLifecycleService;\n\n return {\n loadPlugin: loader.loadPlugin,\n instantiatePlugin: loader.instantiatePlugin,\n initializePlugin: loader.initializePlugin,\n shutdownPlugin: (plugin: InitializedPlugin<AnyPlugin>) =>\n Effect.gen(function* () {\n
|
|
1
|
+
{"version":3,"file":"plugin.service.cjs","names":["Effect","PluginLoaderService","PluginLifecycleService","Scope","Exit","Layer","PluginRegistryTag","SecretsConfigTag","PluginMapTag","ModuleFederationService","SecretsService","RegistryService"],"sources":["../../../src/runtime/services/plugin.service.ts"],"sourcesContent":["import { Effect, Exit, Layer, Scope } from \"effect\";\nimport type {\n AnyPlugin,\n AnyPluginConstructor,\n InitializedPlugin,\n PluginRegistry,\n SecretsConfig,\n} from \"../../types\";\nimport { ModuleFederationService } from \"./module-federation.service\";\nimport { PluginLifecycleService } from \"./plugin-lifecycle.service\";\nimport {\n PluginLoaderService,\n PluginMapTag,\n PluginRegistryTag,\n RegistryService,\n} from \"./plugin-loader.service\";\nimport { SecretsConfigTag, SecretsService } from \"./secrets.service\";\n\nexport class PluginService extends Effect.Service<PluginService>()(\"PluginService\", {\n scoped: Effect.gen(function* () {\n const loader = yield* PluginLoaderService;\n const lifecycle = yield* PluginLifecycleService;\n\n return {\n loadPlugin: loader.loadPlugin,\n instantiatePlugin: loader.instantiatePlugin,\n initializePlugin: loader.initializePlugin,\n shutdownPlugin: (plugin: InitializedPlugin<AnyPlugin>) =>\n Effect.gen(function* () {\n yield* plugin.plugin\n .shutdown()\n .pipe(\n Effect.catchAll((error) =>\n Effect.logWarning(`Failed to shutdown plugin ${plugin.plugin.id}`, error),\n ),\n );\n\n yield* Scope.close(plugin.scope, Exit.succeed(undefined));\n\n yield* lifecycle.unregister(plugin);\n }),\n cleanup: lifecycle.cleanup,\n };\n }),\n}) {\n static Live = (\n registry: PluginRegistry,\n secrets: SecretsConfig,\n pluginMap: Record<string, AnyPluginConstructor> = {},\n ) => {\n const contextLayer = Layer.mergeAll(\n Layer.succeed(PluginRegistryTag, registry),\n Layer.succeed(SecretsConfigTag, secrets),\n Layer.succeed(PluginMapTag, pluginMap),\n );\n\n const servicesLayer = Layer.mergeAll(\n ModuleFederationService.Default,\n SecretsService.Default,\n RegistryService.Default,\n PluginLifecycleService.Default,\n ).pipe(Layer.provide(contextLayer));\n\n return PluginService.Default.pipe(\n Layer.provide(PluginLoaderService.Default),\n Layer.provide(servicesLayer),\n );\n };\n}\n"],"mappings":";;;;;;;;AAkBA,IAAa,gBAAb,MAAa,sBAAsBA,cAAO,SAAwB,CAAC,iBAAiB,EAClF,QAAQA,cAAO,IAAI,aAAa;CAC9B,MAAM,SAAS,OAAOC;CACtB,MAAM,YAAY,OAAOC;AAEzB,QAAO;EACL,YAAY,OAAO;EACnB,mBAAmB,OAAO;EAC1B,kBAAkB,OAAO;EACzB,iBAAiB,WACfF,cAAO,IAAI,aAAa;AACtB,UAAO,OAAO,OACX,UAAU,CACV,KACCA,cAAO,UAAU,UACfA,cAAO,WAAW,6BAA6B,OAAO,OAAO,MAAM,MAAM,CAC1E,CACF;AAEH,UAAOG,aAAM,MAAM,OAAO,OAAOC,YAAK,QAAQ,OAAU,CAAC;AAEzD,UAAO,UAAU,WAAW,OAAO;IACnC;EACJ,SAAS,UAAU;EACpB;EACD,EACH,CAAC,CAAC;CACD,OAAO,QACL,UACA,SACA,YAAkD,EAAE,KACjD;EACH,MAAM,eAAeC,aAAM,SACzBA,aAAM,QAAQC,iDAAmB,SAAS,EAC1CD,aAAM,QAAQE,0CAAkB,QAAQ,EACxCF,aAAM,QAAQG,4CAAc,UAAU,CACvC;EAED,MAAM,gBAAgBH,aAAM,SAC1BI,0DAAwB,SACxBC,uCAAe,SACfC,8CAAgB,SAChBT,wDAAuB,QACxB,CAAC,KAAKG,aAAM,QAAQ,aAAa,CAAC;AAEnC,SAAO,cAAc,QAAQ,KAC3BA,aAAM,QAAQJ,kDAAoB,QAAQ,EAC1CI,aAAM,QAAQ,cAAc,CAC7B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.service.d.cts","names":[],"sources":["../../../src/runtime/services/plugin.service.ts"],"mappings":";;;;;;;;cAeiC,kBAAA;;;;;;;;;;;;;;;;;;;;;;;6BAYF,iBAAA,CAAkB,SAAA,MAAU,MAAA,CAAA,MAAA;;;;cAT9C,aAAA,SAAsB,kBAAA;EAAA,
|
|
1
|
+
{"version":3,"file":"plugin.service.d.cts","names":[],"sources":["../../../src/runtime/services/plugin.service.ts"],"mappings":";;;;;;;;cAeiC,kBAAA;;;;;;;;;;;;;;;;;;;;;;;6BAYF,iBAAA,CAAkB,SAAA,MAAU,MAAA,CAAA,MAAA;;;;cAT9C,aAAA,SAAsB,kBAAA;EAAA,OA2B1B,IAAA,GACL,QAAA,EAAU,cAAA,EACV,OAAA,EAAS,aAAA,EACT,SAAA,GAAW,MAAA,SAAe,oBAAA,MAA0B,KAAA,CAAA,KAAA,CAAA,aAAA;AAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.service.d.mts","names":[],"sources":["../../../src/runtime/services/plugin.service.ts"],"mappings":";;;;;;;;cAeiC,kBAAA;;;;;;;;;;;;;;;;;;;;;;;6BAYF,iBAAA,CAAkB,SAAA,MAAU,MAAA,CAAA,MAAA;;;;cAT9C,aAAA,SAAsB,kBAAA;EAAA,
|
|
1
|
+
{"version":3,"file":"plugin.service.d.mts","names":[],"sources":["../../../src/runtime/services/plugin.service.ts"],"mappings":";;;;;;;;cAeiC,kBAAA;;;;;;;;;;;;;;;;;;;;;;;6BAYF,iBAAA,CAAkB,SAAA,MAAU,MAAA,CAAA,MAAA;;;;cAT9C,aAAA,SAAsB,kBAAA;EAAA,OA2B1B,IAAA,GACL,QAAA,EAAU,cAAA,EACV,OAAA,EAAS,aAAA,EACT,SAAA,GAAW,MAAA,SAAe,oBAAA,MAA0B,KAAA,CAAA,KAAA,CAAA,aAAA;AAAA"}
|
|
@@ -13,7 +13,7 @@ var PluginService = class PluginService extends Effect.Service()("PluginService"
|
|
|
13
13
|
instantiatePlugin: loader.instantiatePlugin,
|
|
14
14
|
initializePlugin: loader.initializePlugin,
|
|
15
15
|
shutdownPlugin: (plugin) => Effect.gen(function* () {
|
|
16
|
-
yield* plugin.plugin.shutdown().pipe(Effect.catchAll(() => Effect.
|
|
16
|
+
yield* plugin.plugin.shutdown().pipe(Effect.catchAll((error) => Effect.logWarning(`Failed to shutdown plugin ${plugin.plugin.id}`, error)));
|
|
17
17
|
yield* Scope.close(plugin.scope, Exit.succeed(void 0));
|
|
18
18
|
yield* lifecycle.unregister(plugin);
|
|
19
19
|
}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.service.mjs","names":[],"sources":["../../../src/runtime/services/plugin.service.ts"],"sourcesContent":["import { Effect, Exit, Layer, Scope } from \"effect\";\nimport type {\n AnyPlugin,\n AnyPluginConstructor,\n InitializedPlugin,\n PluginRegistry,\n SecretsConfig,\n} from \"../../types\";\nimport { ModuleFederationService } from \"./module-federation.service\";\nimport { PluginLifecycleService } from \"./plugin-lifecycle.service\";\nimport {\n PluginLoaderService,\n PluginMapTag,\n PluginRegistryTag,\n RegistryService,\n} from \"./plugin-loader.service\";\nimport { SecretsConfigTag, SecretsService } from \"./secrets.service\";\n\nexport class PluginService extends Effect.Service<PluginService>()(\"PluginService\", {\n scoped: Effect.gen(function* () {\n const loader = yield* PluginLoaderService;\n const lifecycle = yield* PluginLifecycleService;\n\n return {\n loadPlugin: loader.loadPlugin,\n instantiatePlugin: loader.instantiatePlugin,\n initializePlugin: loader.initializePlugin,\n shutdownPlugin: (plugin: InitializedPlugin<AnyPlugin>) =>\n Effect.gen(function* () {\n
|
|
1
|
+
{"version":3,"file":"plugin.service.mjs","names":[],"sources":["../../../src/runtime/services/plugin.service.ts"],"sourcesContent":["import { Effect, Exit, Layer, Scope } from \"effect\";\nimport type {\n AnyPlugin,\n AnyPluginConstructor,\n InitializedPlugin,\n PluginRegistry,\n SecretsConfig,\n} from \"../../types\";\nimport { ModuleFederationService } from \"./module-federation.service\";\nimport { PluginLifecycleService } from \"./plugin-lifecycle.service\";\nimport {\n PluginLoaderService,\n PluginMapTag,\n PluginRegistryTag,\n RegistryService,\n} from \"./plugin-loader.service\";\nimport { SecretsConfigTag, SecretsService } from \"./secrets.service\";\n\nexport class PluginService extends Effect.Service<PluginService>()(\"PluginService\", {\n scoped: Effect.gen(function* () {\n const loader = yield* PluginLoaderService;\n const lifecycle = yield* PluginLifecycleService;\n\n return {\n loadPlugin: loader.loadPlugin,\n instantiatePlugin: loader.instantiatePlugin,\n initializePlugin: loader.initializePlugin,\n shutdownPlugin: (plugin: InitializedPlugin<AnyPlugin>) =>\n Effect.gen(function* () {\n yield* plugin.plugin\n .shutdown()\n .pipe(\n Effect.catchAll((error) =>\n Effect.logWarning(`Failed to shutdown plugin ${plugin.plugin.id}`, error),\n ),\n );\n\n yield* Scope.close(plugin.scope, Exit.succeed(undefined));\n\n yield* lifecycle.unregister(plugin);\n }),\n cleanup: lifecycle.cleanup,\n };\n }),\n}) {\n static Live = (\n registry: PluginRegistry,\n secrets: SecretsConfig,\n pluginMap: Record<string, AnyPluginConstructor> = {},\n ) => {\n const contextLayer = Layer.mergeAll(\n Layer.succeed(PluginRegistryTag, registry),\n Layer.succeed(SecretsConfigTag, secrets),\n Layer.succeed(PluginMapTag, pluginMap),\n );\n\n const servicesLayer = Layer.mergeAll(\n ModuleFederationService.Default,\n SecretsService.Default,\n RegistryService.Default,\n PluginLifecycleService.Default,\n ).pipe(Layer.provide(contextLayer));\n\n return PluginService.Default.pipe(\n Layer.provide(PluginLoaderService.Default),\n Layer.provide(servicesLayer),\n );\n };\n}\n"],"mappings":";;;;;;;AAkBA,IAAa,gBAAb,MAAa,sBAAsB,OAAO,SAAwB,CAAC,iBAAiB,EAClF,QAAQ,OAAO,IAAI,aAAa;CAC9B,MAAM,SAAS,OAAO;CACtB,MAAM,YAAY,OAAO;AAEzB,QAAO;EACL,YAAY,OAAO;EACnB,mBAAmB,OAAO;EAC1B,kBAAkB,OAAO;EACzB,iBAAiB,WACf,OAAO,IAAI,aAAa;AACtB,UAAO,OAAO,OACX,UAAU,CACV,KACC,OAAO,UAAU,UACf,OAAO,WAAW,6BAA6B,OAAO,OAAO,MAAM,MAAM,CAC1E,CACF;AAEH,UAAO,MAAM,MAAM,OAAO,OAAO,KAAK,QAAQ,OAAU,CAAC;AAEzD,UAAO,UAAU,WAAW,OAAO;IACnC;EACJ,SAAS,UAAU;EACpB;EACD,EACH,CAAC,CAAC;CACD,OAAO,QACL,UACA,SACA,YAAkD,EAAE,KACjD;EACH,MAAM,eAAe,MAAM,SACzB,MAAM,QAAQ,mBAAmB,SAAS,EAC1C,MAAM,QAAQ,kBAAkB,QAAQ,EACxC,MAAM,QAAQ,cAAc,UAAU,CACvC;EAED,MAAM,gBAAgB,MAAM,SAC1B,wBAAwB,SACxB,eAAe,SACf,gBAAgB,SAChB,uBAAuB,QACxB,CAAC,KAAK,MAAM,QAAQ,aAAa,CAAC;AAEnC,SAAO,cAAc,QAAQ,KAC3B,MAAM,QAAQ,oBAAoB,QAAQ,EAC1C,MAAM,QAAQ,cAAc,CAC7B"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "every-plugin",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -81,7 +81,9 @@
|
|
|
81
81
|
},
|
|
82
82
|
"files": [
|
|
83
83
|
"dist",
|
|
84
|
-
"src"
|
|
84
|
+
"src",
|
|
85
|
+
"skills",
|
|
86
|
+
"!skills/_artifacts"
|
|
85
87
|
],
|
|
86
88
|
"scripts": {
|
|
87
89
|
"build": "tsdown",
|
|
@@ -111,6 +113,7 @@
|
|
|
111
113
|
"@orpc/client": "^1.13.4",
|
|
112
114
|
"@orpc/openapi": "^1.13.4",
|
|
113
115
|
"@rspack/cli": "^1.7.4",
|
|
116
|
+
"@tanstack/intent": "^0.0.40",
|
|
114
117
|
"@types/bun": "^1.3.7",
|
|
115
118
|
"@types/node": "^25.1.0",
|
|
116
119
|
"@vitest/ui": "4.1.2",
|
|
@@ -134,7 +137,8 @@
|
|
|
134
137
|
"module-federation",
|
|
135
138
|
"orpc",
|
|
136
139
|
"zod",
|
|
137
|
-
"typescript"
|
|
140
|
+
"typescript",
|
|
141
|
+
"tanstack-intent"
|
|
138
142
|
],
|
|
139
143
|
"author": "plugin.every.near",
|
|
140
144
|
"license": "MIT",
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: plugin-development
|
|
3
|
+
description: Build every-plugin modules with oRPC contracts, Effect services, and Module Federation. Use when creating or modifying plugins under plugins/ or the _template scaffold.
|
|
4
|
+
metadata:
|
|
5
|
+
sources: "src/plugin.ts,src/orpc.ts,src/errors.ts,src/zod.ts,src/types.ts"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# every-plugin Development
|
|
9
|
+
|
|
10
|
+
## Plugin Structure
|
|
11
|
+
|
|
12
|
+
Every plugin has three core files:
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
plugins/your-plugin/
|
|
16
|
+
├── src/
|
|
17
|
+
│ ├── contract.ts # oRPC route definitions + Zod schemas
|
|
18
|
+
│ ├── service.ts # Business logic (plain class, Effect error handling)
|
|
19
|
+
│ ├── index.ts # createPlugin() wiring
|
|
20
|
+
│ └── __tests__/ # Integration & unit tests
|
|
21
|
+
├── package.json
|
|
22
|
+
├── rspack.config.js # Build config (every-plugin provides defaults)
|
|
23
|
+
├── plugin.dev.ts # Dev server config (port, variables, secrets)
|
|
24
|
+
└── tsconfig.json
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Step 1: Define the Contract
|
|
28
|
+
|
|
29
|
+
Import from `every-plugin` subpath exports — not from `@orpc/*` or `zod` directly:
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { eventIterator, oc } from "every-plugin/orpc";
|
|
33
|
+
import { z } from "every-plugin/zod";
|
|
34
|
+
|
|
35
|
+
const Errors = {
|
|
36
|
+
UNAUTHORIZED: { status: 401, message: "Auth required" },
|
|
37
|
+
NOT_FOUND: { status: 404, message: "Not found" },
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const contract = oc.router({
|
|
41
|
+
getById: oc
|
|
42
|
+
.route({ method: "GET", path: "/items/{id}" })
|
|
43
|
+
.input(z.object({ id: z.string() }))
|
|
44
|
+
.output(z.object({ item: ItemSchema }))
|
|
45
|
+
.errors(Errors),
|
|
46
|
+
|
|
47
|
+
search: oc
|
|
48
|
+
.route({ method: "GET", path: "/search" })
|
|
49
|
+
.input(z.object({ query: z.string(), limit: z.number().default(10) }))
|
|
50
|
+
.output(eventIterator(SearchResultSchema)),
|
|
51
|
+
|
|
52
|
+
ping: oc
|
|
53
|
+
.route({ method: "GET", path: "/ping" })
|
|
54
|
+
.output(z.object({ status: z.literal("ok"), timestamp: z.string().datetime() })),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
export type ContractType = typeof contract;
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Key points:
|
|
61
|
+
- Always import `oc` from `every-plugin/orpc`, `z` from `every-plugin/zod`, `Effect` from `every-plugin/effect`
|
|
62
|
+
- Use `eventIterator(schema)` for streaming responses
|
|
63
|
+
- Define error objects with `status` + `message` and pass via `.errors()`
|
|
64
|
+
- Use `CommonPluginErrors` from `every-plugin/errors` for standard UNAUTHORIZED/FORBIDDEN/NOT_FOUND/BAD_REQUEST
|
|
65
|
+
|
|
66
|
+
## Step 2: Create the Service
|
|
67
|
+
|
|
68
|
+
Plain TypeScript class with Effect error handling:
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { Effect } from "every-plugin/effect";
|
|
72
|
+
|
|
73
|
+
export class MyService {
|
|
74
|
+
constructor(private baseUrl: string, private apiKey: string) {}
|
|
75
|
+
|
|
76
|
+
getById(id: string) {
|
|
77
|
+
return Effect.tryPromise({
|
|
78
|
+
try: async () => {
|
|
79
|
+
const res = await fetch(`${this.baseUrl}/items/${id}`);
|
|
80
|
+
if (!res.ok) throw new Error(`Fetch failed: ${res.status}`);
|
|
81
|
+
return res.json();
|
|
82
|
+
},
|
|
83
|
+
catch: (error: unknown) => new Error(`Failed to fetch item: ${error}`),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
ping() {
|
|
88
|
+
return Effect.succeed({ status: "ok" as const, timestamp: new Date().toISOString() });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Step 3: Wire with createPlugin
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
import { createPlugin } from "every-plugin";
|
|
97
|
+
import { Effect } from "every-plugin/effect";
|
|
98
|
+
import { z } from "every-plugin/zod";
|
|
99
|
+
import { contract } from "./contract";
|
|
100
|
+
import { MyService } from "./service";
|
|
101
|
+
|
|
102
|
+
export default createPlugin({
|
|
103
|
+
variables: z.object({
|
|
104
|
+
baseUrl: z.url().default("https://api.example.com"),
|
|
105
|
+
}),
|
|
106
|
+
secrets: z.object({
|
|
107
|
+
apiKey: z.string().min(1),
|
|
108
|
+
}),
|
|
109
|
+
contract,
|
|
110
|
+
|
|
111
|
+
initialize: (config) =>
|
|
112
|
+
Effect.gen(function* () {
|
|
113
|
+
const service = new MyService(config.variables.baseUrl, config.secrets.apiKey);
|
|
114
|
+
yield* service.ping();
|
|
115
|
+
return { service };
|
|
116
|
+
}),
|
|
117
|
+
|
|
118
|
+
shutdown: () => Effect.void,
|
|
119
|
+
|
|
120
|
+
createRouter: (context, builder) => {
|
|
121
|
+
const { service } = context;
|
|
122
|
+
|
|
123
|
+
const requireAuth = builder.middleware(async ({ context, next }) => {
|
|
124
|
+
if (!context.userId) throw new ORPCError("UNAUTHORIZED", { message: "Auth required" });
|
|
125
|
+
return next({ context: { ...context, userId: context.userId } });
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
getById: builder.getById.use(requireAuth).handler(async ({ input, context }) => {
|
|
130
|
+
return await Effect.runPromise(service.getById(input.id));
|
|
131
|
+
}),
|
|
132
|
+
ping: builder.ping.handler(async () => {
|
|
133
|
+
return await Effect.runPromise(service.ping());
|
|
134
|
+
}),
|
|
135
|
+
};
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Plugin Composition (withPlugins)
|
|
141
|
+
|
|
142
|
+
When an API plugin needs to call other plugins in-process:
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
import type { PluginsClient } from "./plugins-client.gen";
|
|
146
|
+
|
|
147
|
+
export default createPlugin.withPlugins<PluginsClient>()({
|
|
148
|
+
variables: z.object({ demoMessage: z.string().optional() }),
|
|
149
|
+
contract,
|
|
150
|
+
initialize: (config, plugins) =>
|
|
151
|
+
Effect.sync(() => ({ plugins, demoMessage: config.variables.demoMessage ?? "not configured" })),
|
|
152
|
+
createRouter: (services, builder) => ({
|
|
153
|
+
pluginDemo: builder.pluginDemo.handler(async () => {
|
|
154
|
+
const status = await services.plugins.registry().getRegistryStatus();
|
|
155
|
+
return { apiVariable: services.demoMessage, registryStatus: status };
|
|
156
|
+
}),
|
|
157
|
+
}),
|
|
158
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
- `pluginsClient` is a map of `createClient` factories, typed by the generated `PluginsClient`
|
|
162
|
+
- Call `services.plugins.{key}()` to execute plugin routers in-process — no HTTP roundtrip
|
|
163
|
+
- The host loads non-API plugins first (Phase 1), then loads the API with `pluginsClient` injected (Phase 2)
|
|
164
|
+
|
|
165
|
+
## Dev Server Config (plugin.dev.ts)
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
import type { PluginConfigInput } from "every-plugin";
|
|
169
|
+
import Plugin from "./src/index";
|
|
170
|
+
|
|
171
|
+
export default {
|
|
172
|
+
pluginId: "my-plugin",
|
|
173
|
+
port: 3010,
|
|
174
|
+
config: {
|
|
175
|
+
variables: {
|
|
176
|
+
baseUrl: "https://api.example.com",
|
|
177
|
+
},
|
|
178
|
+
secrets: {
|
|
179
|
+
apiKey: "dev-only-key",
|
|
180
|
+
},
|
|
181
|
+
} satisfies PluginConfigInput<typeof Plugin>,
|
|
182
|
+
};
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Port assignments: host=3000, api=3001, auth=3002, ui=3003, ui-ssr=3004, plugins=3010+.
|
|
186
|
+
|
|
187
|
+
## Build Config (rspack.config.js)
|
|
188
|
+
|
|
189
|
+
every-plugin provides rspack defaults via `EveryPluginDevServer`:
|
|
190
|
+
|
|
191
|
+
```javascript
|
|
192
|
+
const { EveryPluginDevServer } = require("every-plugin/build/rspack");
|
|
193
|
+
|
|
194
|
+
module.exports = EveryPluginDevServer.configure(__dirname, {
|
|
195
|
+
pluginId: "my-plugin",
|
|
196
|
+
port: 3010,
|
|
197
|
+
});
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
No manual rspack configuration needed — the helper handles Module Federation, shared deps, and dev server setup.
|
|
201
|
+
|
|
202
|
+
## Common Mistakes
|
|
203
|
+
|
|
204
|
+
- Importing `oc` from `@orpc/contract` instead of `every-plugin/orpc` — will cause version mismatches in Module Federation
|
|
205
|
+
- Importing `z` from `zod` instead of `every-plugin/zod` — same singleton issue
|
|
206
|
+
- Forgetting `.errors(Errors)` on routes that can throw ORPCError — untyped errors
|
|
207
|
+
- Using `Effect.runPromise` inside `Effect.gen` — use `yield*` instead for proper error channel
|
|
208
|
+
- Putting business logic in `createRouter` — keep it in the service class, router is just glue
|