every-plugin 2.4.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/plugin.cjs +1 -1
- package/dist/build/rspack/plugin.cjs.map +1 -1
- package/dist/build/rspack/plugin.mjs +1 -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/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/package.json +7 -3
- package/skills/plugin-development/SKILL.md +208 -0
- package/skills/plugin-testing/SKILL.md +152 -0
- package/src/build/rspack/plugin.ts +1 -1
- package/src/build/shared-deps.ts +16 -0
- package/src/runtime/mf-config.ts +16 -0
- package/src/runtime/services/module-federation.service.ts +37 -24
|
@@ -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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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "every-plugin",
|
|
3
|
-
"version": "2.4.
|
|
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
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: plugin-testing
|
|
3
|
+
description: Test every-plugin modules with vitest and the plugin runtime. Use when writing or modifying plugin tests under plugins/*/src/__tests__/ or plugins/*/tests/.
|
|
4
|
+
metadata:
|
|
5
|
+
sources: "src/testing/index.ts,src/runtime/index.ts"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# every-plugin Testing
|
|
9
|
+
|
|
10
|
+
## Test Structure
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
plugins/your-plugin/
|
|
14
|
+
├── src/__tests__/
|
|
15
|
+
│ ├── integration.test.ts # Full plugin lifecycle via runtime
|
|
16
|
+
│ └── unit.test.ts # Service class in isolation
|
|
17
|
+
└── vitest.config.ts
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Unit Tests (Service Only)
|
|
21
|
+
|
|
22
|
+
Test the service class directly without the plugin runtime:
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { describe, expect, it } from "vitest";
|
|
26
|
+
import { Effect } from "effect";
|
|
27
|
+
import { MyService } from "../service";
|
|
28
|
+
|
|
29
|
+
describe("MyService", () => {
|
|
30
|
+
const service = new MyService("https://api.example.com", "test-key");
|
|
31
|
+
|
|
32
|
+
it("ping returns ok", async () => {
|
|
33
|
+
const result = await Effect.runPromise(service.ping());
|
|
34
|
+
expect(result.status).toBe("ok");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("getById returns item", async () => {
|
|
38
|
+
const result = await Effect.runPromise(service.getById("item-1"));
|
|
39
|
+
expect(result.item.id).toBe("item-1");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("getById throws on missing item", async () => {
|
|
43
|
+
await expect(Effect.runPromise(service.getById("missing"))).rejects.toThrow();
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Integration Tests (Plugin Runtime)
|
|
49
|
+
|
|
50
|
+
Test the full plugin lifecycle — initialization, router execution, shutdown:
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { describe, expect, it, beforeAll, afterAll } from "vitest";
|
|
54
|
+
import { createPluginRuntime } from "every-plugin/runtime";
|
|
55
|
+
import Plugin from "../index";
|
|
56
|
+
|
|
57
|
+
describe("MyPlugin integration", () => {
|
|
58
|
+
let runtime: Awaited<ReturnType<typeof createPluginRuntime>>;
|
|
59
|
+
let client: any;
|
|
60
|
+
|
|
61
|
+
beforeAll(async () => {
|
|
62
|
+
runtime = createPluginRuntime();
|
|
63
|
+
const result = await runtime.usePlugin("my-plugin", {
|
|
64
|
+
variables: { baseUrl: "https://api.example.com" },
|
|
65
|
+
secrets: { apiKey: "test-key" },
|
|
66
|
+
});
|
|
67
|
+
client = result.createClient();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
afterAll(async () => {
|
|
71
|
+
await runtime.dispose();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("ping responds", async () => {
|
|
75
|
+
const result = await client.ping();
|
|
76
|
+
expect(result.status).toBe("ok");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("getById returns item", async () => {
|
|
80
|
+
const result = await client.getById({ id: "item-1" });
|
|
81
|
+
expect(result.item.id).toBe("item-1");
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("getById without auth throws UNAUTHORIZED", async () => {
|
|
85
|
+
await expect(client.getById({ id: "item-1" })).rejects.toThrow();
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Testing Plugin Composition
|
|
91
|
+
|
|
92
|
+
When testing an API plugin that uses `withPlugins`, mock the `pluginsClient`:
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
import { createPluginRuntime } from "every-plugin/runtime";
|
|
96
|
+
|
|
97
|
+
const mockRegistryClient = {
|
|
98
|
+
getRegistryStatus: vi.fn().mockResolvedValue({ status: "ok" }),
|
|
99
|
+
listRegistryApps: vi.fn().mockResolvedValue({ apps: [] }),
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
describe("API with mock registry", () => {
|
|
103
|
+
it("pluginDemo returns composed data", async () => {
|
|
104
|
+
const runtime = createPluginRuntime();
|
|
105
|
+
const result = await runtime.usePlugin(
|
|
106
|
+
"api",
|
|
107
|
+
{ variables: {}, secrets: {} },
|
|
108
|
+
{ registry: () => mockRegistryClient },
|
|
109
|
+
);
|
|
110
|
+
const client = result.createClient();
|
|
111
|
+
const data = await client.pluginDemo();
|
|
112
|
+
expect(data.registryStatus.status).toBe("ok");
|
|
113
|
+
await runtime.dispose();
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Testing Streaming (eventIterator)
|
|
119
|
+
|
|
120
|
+
Use `for await` to collect streaming results:
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
it("search streams results", async () => {
|
|
124
|
+
const chunks: any[] = [];
|
|
125
|
+
const stream = await client.search({ query: "test", limit: 5 });
|
|
126
|
+
for await (const chunk of stream) {
|
|
127
|
+
chunks.push(chunk);
|
|
128
|
+
}
|
|
129
|
+
expect(chunks.length).toBeGreaterThan(0);
|
|
130
|
+
});
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Vitest Config
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
import { defineConfig } from "vitest/config";
|
|
137
|
+
import tsconfigPaths from "vite-tsconfig-paths";
|
|
138
|
+
|
|
139
|
+
export default defineConfig({
|
|
140
|
+
plugins: [tsconfigPaths()],
|
|
141
|
+
test: {
|
|
142
|
+
include: ["src/__tests__/**/*.test.ts"],
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Common Mistakes
|
|
148
|
+
|
|
149
|
+
- Not calling `runtime.dispose()` in `afterAll` — leaves processes/resources running
|
|
150
|
+
- Using `vi.fn()` without `.mockResolvedValue()` — unhandled promise rejections
|
|
151
|
+
- Forgetting `vite-tsconfig-paths` plugin — path aliases like `every-plugin/orpc` won't resolve
|
|
152
|
+
- Testing only happy paths — always test error channels (Effect failures, ORPCError throws)
|
|
@@ -205,7 +205,7 @@ export class EveryPluginDevServer implements RspackPluginInstance {
|
|
|
205
205
|
compiler.options.mode = process.env.NODE_ENV === "development" ? "development" : "production";
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
-
if (
|
|
208
|
+
if (compiler.options.devtool === undefined) {
|
|
209
209
|
compiler.options.devtool = "source-map";
|
|
210
210
|
}
|
|
211
211
|
|
package/src/build/shared-deps.ts
CHANGED
|
@@ -54,6 +54,22 @@ export const pluginSharedDependencies = {
|
|
|
54
54
|
version: getInstalledPackageVersion("zod", pkg.peerDependencies.zod),
|
|
55
55
|
...DEFAULT_SHARE_CONFIG,
|
|
56
56
|
},
|
|
57
|
+
"better-auth": {
|
|
58
|
+
version: getInstalledPackageVersion("better-auth", "^1.6.9"),
|
|
59
|
+
...DEFAULT_SHARE_CONFIG,
|
|
60
|
+
},
|
|
61
|
+
"drizzle-orm": {
|
|
62
|
+
version: getInstalledPackageVersion("drizzle-orm", "^0.45.1"),
|
|
63
|
+
...DEFAULT_SHARE_CONFIG,
|
|
64
|
+
},
|
|
65
|
+
"@orpc/contract": {
|
|
66
|
+
version: getInstalledPackageVersion("@orpc/contract", pkg.peerDependencies["@orpc/contract"]),
|
|
67
|
+
...DEFAULT_SHARE_CONFIG,
|
|
68
|
+
},
|
|
69
|
+
"@orpc/server": {
|
|
70
|
+
version: getInstalledPackageVersion("@orpc/server", pkg.peerDependencies["@orpc/server"]),
|
|
71
|
+
...DEFAULT_SHARE_CONFIG,
|
|
72
|
+
},
|
|
57
73
|
} satisfies SharedDependencies;
|
|
58
74
|
|
|
59
75
|
export type PluginSharedDependencyName = keyof typeof pluginSharedDependencies;
|
package/src/runtime/mf-config.ts
CHANGED
|
@@ -43,6 +43,22 @@ export const MF_SHARED_DEPS = {
|
|
|
43
43
|
version: getInstalledPackageVersion("zod", "^4.3.6"),
|
|
44
44
|
shareConfig: SHARE_CONFIG,
|
|
45
45
|
},
|
|
46
|
+
"better-auth": {
|
|
47
|
+
version: getInstalledPackageVersion("better-auth", "^1.6.9"),
|
|
48
|
+
shareConfig: SHARE_CONFIG,
|
|
49
|
+
},
|
|
50
|
+
"drizzle-orm": {
|
|
51
|
+
version: getInstalledPackageVersion("drizzle-orm", "^0.45.1"),
|
|
52
|
+
shareConfig: SHARE_CONFIG,
|
|
53
|
+
},
|
|
54
|
+
"@orpc/contract": {
|
|
55
|
+
version: getInstalledPackageVersion("@orpc/contract", "^1.13.4"),
|
|
56
|
+
shareConfig: SHARE_CONFIG,
|
|
57
|
+
},
|
|
58
|
+
"@orpc/server": {
|
|
59
|
+
version: getInstalledPackageVersion("@orpc/server", "^1.13.4"),
|
|
60
|
+
shareConfig: SHARE_CONFIG,
|
|
61
|
+
},
|
|
46
62
|
} as const;
|
|
47
63
|
|
|
48
64
|
export type SharedDepName = keyof typeof MF_SHARED_DEPS;
|