every-plugin 2.7.1 → 2.9.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.
- package/dist/build/rspack/dev-server-middleware.cjs +4 -2
- package/dist/build/rspack/dev-server-middleware.cjs.map +1 -1
- package/dist/build/rspack/dev-server-middleware.mjs +4 -2
- package/dist/build/rspack/dev-server-middleware.mjs.map +1 -1
- 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/errors.cjs +3 -42
- package/dist/runtime/errors.cjs.map +1 -1
- package/dist/runtime/errors.d.cts +1 -1
- package/dist/runtime/errors.d.cts.map +1 -1
- package/dist/runtime/errors.d.mts +1 -1
- package/dist/runtime/errors.d.mts.map +1 -1
- package/dist/runtime/errors.mjs +3 -42
- package/dist/runtime/errors.mjs.map +1 -1
- package/dist/runtime/index.cjs +4 -4
- package/dist/runtime/index.cjs.map +1 -1
- package/dist/runtime/index.mjs +4 -4
- package/dist/runtime/index.mjs.map +1 -1
- package/dist/runtime/mf-config.cjs +1 -1
- package/dist/runtime/mf-config.mjs +1 -1
- package/dist/runtime/services/module-federation.service.cjs +6 -5
- package/dist/runtime/services/module-federation.service.cjs.map +1 -1
- package/dist/runtime/services/module-federation.service.mjs +6 -5
- package/dist/runtime/services/module-federation.service.mjs.map +1 -1
- package/dist/runtime/services/plugin-loader.service.cjs +4 -4
- package/dist/runtime/services/plugin-loader.service.cjs.map +1 -1
- package/dist/runtime/services/plugin-loader.service.d.cts.map +1 -1
- package/dist/runtime/services/plugin-loader.service.d.mts.map +1 -1
- package/dist/runtime/services/plugin-loader.service.mjs +4 -4
- package/dist/runtime/services/plugin-loader.service.mjs.map +1 -1
- package/package.json +2 -2
- package/skills/plugin-client/SKILL.md +450 -0
- package/skills/plugin-client/references/api-keys.md +145 -0
- package/src/build/rspack/dev-server-middleware.ts +4 -2
- package/src/plugin.ts +2 -1
- package/src/runtime/errors.ts +4 -60
- package/src/runtime/index.ts +4 -4
- package/src/runtime/services/module-federation.service.ts +7 -5
- package/src/runtime/services/plugin-loader.service.ts +17 -7
|
@@ -62,18 +62,18 @@ var PluginLoaderService = class extends Effect.Service()("PluginLoaderService",
|
|
|
62
62
|
retryable: false
|
|
63
63
|
}));
|
|
64
64
|
const resolvedUrl = resolveUrl(url);
|
|
65
|
-
yield* moduleFederationService.registerRemote(pluginId, resolvedUrl).pipe(Effect.mapError((error) => toPluginRuntimeError(error, pluginId, void 0, "register-remote", true)));
|
|
65
|
+
yield* moduleFederationService.registerRemote(pluginId, resolvedUrl).pipe(Effect.tapError((_error) => Effect.logError(`Plugin ${pluginId} failed during register-remote`)), Effect.mapError((error) => toPluginRuntimeError(error, pluginId, void 0, "register-remote", true)));
|
|
66
66
|
yield* Effect.logDebug("Loading plugin from remote", {
|
|
67
67
|
pluginId,
|
|
68
68
|
url: resolvedUrl
|
|
69
69
|
});
|
|
70
70
|
return {
|
|
71
|
-
ctor: yield* moduleFederationService.loadRemoteConstructor(pluginId, resolvedUrl).pipe(Effect.mapError((error) => toPluginRuntimeError(error, pluginId, void 0, "load-remote", false))),
|
|
71
|
+
ctor: yield* moduleFederationService.loadRemoteConstructor(pluginId, resolvedUrl).pipe(Effect.tapError((_error) => Effect.logError(`Plugin ${pluginId} failed during load-remote`)), Effect.mapError((error) => toPluginRuntimeError(error, pluginId, void 0, "load-remote", false))),
|
|
72
72
|
metadata: entry.metadata
|
|
73
73
|
};
|
|
74
74
|
}),
|
|
75
75
|
instantiatePlugin: (pluginId, loadedPlugin) => Effect.gen(function* () {
|
|
76
|
-
const instance = yield* Effect.try(() => new loadedPlugin.ctor()).pipe(Effect.mapError((error) => toPluginRuntimeError(error, pluginId, void 0, "instantiate-plugin", false)));
|
|
76
|
+
const instance = yield* Effect.try(() => new loadedPlugin.ctor()).pipe(Effect.tapError((_error) => Effect.logError(`Plugin ${pluginId} failed during instantiate-plugin`)), Effect.mapError((error) => toPluginRuntimeError(error, pluginId, void 0, "instantiate-plugin", false)));
|
|
77
77
|
instance.id = pluginId;
|
|
78
78
|
return {
|
|
79
79
|
plugin: instance,
|
|
@@ -108,7 +108,7 @@ var PluginLoaderService = class extends Effect.Service()("PluginLoaderService",
|
|
|
108
108
|
const context = yield* plugin.initialize({
|
|
109
109
|
variables: _variables,
|
|
110
110
|
secrets: hydratedConfig.secrets
|
|
111
|
-
}, plugins ?? {}).pipe(Effect.provideService(Scope.Scope, scope), Effect.mapError((error) => toPluginRuntimeError(error, plugin.id, void 0, "initialize-plugin", false)));
|
|
111
|
+
}, plugins ?? {}).pipe(Effect.provideService(Scope.Scope, scope), Effect.tapError((_error) => Effect.logError(`Plugin ${plugin.id} failed during initialize-plugin`)), Effect.mapError((error) => toPluginRuntimeError(error, plugin.id, void 0, "initialize-plugin", false)));
|
|
112
112
|
return {
|
|
113
113
|
plugin,
|
|
114
114
|
metadata: pluginInstance.metadata,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin-loader.service.mjs","names":[],"sources":["../../../src/runtime/services/plugin-loader.service.ts"],"sourcesContent":["import type { InferSchemaInput, InferSchemaOutput } from \"@orpc/contract\";\nimport { Context, Effect, Scope } from \"effect\";\nimport type { z } from \"zod\";\nimport type {\n AnyPlugin,\n AnyPluginConstructor,\n InitializedPlugin,\n LoadedPlugin,\n PluginInstance,\n PluginMetadata,\n PluginRegistry,\n} from \"../../types\";\nimport { PluginRuntimeError, toPluginRuntimeError } from \"../errors\";\nimport { validate } from \"../validation\";\nimport { ModuleFederationService } from \"./module-federation.service\";\nimport { SecretsService } from \"./secrets.service\";\n\nexport class PluginRegistryTag extends Context.Tag(\"PluginRegistry\")<\n PluginRegistryTag,\n PluginRegistry\n>() {}\n\nexport class PluginMapTag extends Context.Tag(\"PluginMap\")<\n PluginMapTag,\n Record<string, AnyPluginConstructor>\n>() {}\n\nexport class RegistryService extends Effect.Service<RegistryService>()(\"RegistryService\", {\n effect: Effect.gen(function* () {\n const registry = yield* PluginRegistryTag;\n const pluginMap = yield* PluginMapTag;\n\n return {\n get: (pluginId: string) =>\n Effect.gen(function* () {\n const entry = registry[pluginId];\n\n if (!entry) {\n return yield* Effect.fail(\n new PluginRuntimeError({\n pluginId,\n operation: \"validate-plugin-id\",\n cause: new Error(`Plugin ${pluginId} not found in registry`),\n retryable: false,\n }),\n );\n }\n\n if (\"module\" in entry) {\n return {\n constructor: entry.module,\n metadata: {\n remoteUrl: entry.remote || \"\",\n version: entry.version,\n description: entry.description,\n } as PluginMetadata,\n };\n }\n\n return {\n constructor: null,\n metadata: {\n remoteUrl: entry.remote,\n version: entry.version,\n description: entry.description,\n } as PluginMetadata,\n };\n }),\n\n getModule: (pluginId: string) => Effect.succeed(pluginMap[pluginId] || null),\n };\n }),\n}) {}\n\nexport class PluginLoaderService extends Effect.Service<PluginLoaderService>()(\n \"PluginLoaderService\",\n {\n effect: Effect.gen(function* () {\n const moduleFederationService = yield* ModuleFederationService;\n const secretsService = yield* SecretsService;\n const registryService = yield* RegistryService;\n\n const resolveUrl = (baseUrl: string, version?: string): string =>\n version && version !== \"latest\" ? baseUrl.replace(\"@latest\", `@${version}`) : baseUrl;\n\n return {\n loadPlugin: (pluginId: string) =>\n Effect.gen(function* () {\n const entry = yield* registryService.get(pluginId);\n\n if (entry.constructor) {\n yield* Effect.logDebug(\"Loading plugin from direct module\", { pluginId });\n\n return {\n ctor: entry.constructor,\n metadata: entry.metadata,\n } satisfies LoadedPlugin;\n }\n\n const url = entry.metadata.remoteUrl;\n if (!url) {\n return yield* Effect.fail(\n new PluginRuntimeError({\n pluginId,\n operation: \"load-plugin\",\n cause: new Error(`Plugin ${pluginId} has no module or remote URL configured`),\n retryable: false,\n }),\n );\n }\n\n const resolvedUrl = resolveUrl(url);\n\n yield* moduleFederationService\n .registerRemote(pluginId, resolvedUrl)\n .pipe(\n Effect.mapError((error) =>\n toPluginRuntimeError(error, pluginId, undefined, \"register-remote\", true),\n ),\n );\n\n yield* Effect.logDebug(\"Loading plugin from remote\", { pluginId, url: resolvedUrl });\n\n const ctor = yield* moduleFederationService\n .loadRemoteConstructor(pluginId, resolvedUrl)\n .pipe(\n Effect.mapError((error) =>\n toPluginRuntimeError(error, pluginId, undefined, \"load-remote\", false),\n ),\n );\n\n return {\n ctor,\n metadata: entry.metadata,\n } satisfies LoadedPlugin;\n }),\n\n instantiatePlugin: <T extends AnyPlugin>(pluginId: string, loadedPlugin: LoadedPlugin<T>) =>\n Effect.gen(function* () {\n const instance = yield* Effect.try(() => new loadedPlugin.ctor()).pipe(\n Effect.mapError((error) =>\n toPluginRuntimeError(error, pluginId, undefined, \"instantiate-plugin\", false),\n ),\n );\n\n (instance.id as string) = pluginId;\n\n return {\n plugin: instance,\n metadata: loadedPlugin.metadata,\n } satisfies PluginInstance<T>;\n }),\n\n initializePlugin: <T extends AnyPlugin>(\n pluginInstance: PluginInstance<T>,\n config: {\n variables: InferSchemaInput<T[\"configSchema\"][\"variables\"]>;\n secrets: InferSchemaInput<T[\"configSchema\"][\"secrets\"]>;\n },\n plugins?: Record<string, unknown>,\n ) =>\n Effect.gen(function* () {\n const { plugin } = pluginInstance;\n\n // Validate and hydrate config\n const validatedVariables = yield* validate(\n plugin.configSchema.variables as z.ZodSchema<\n InferSchemaOutput<T[\"configSchema\"][\"variables\"]>\n >,\n config.variables,\n plugin.id,\n \"config\",\n ).pipe(\n Effect.mapError(\n (validationError) =>\n new PluginRuntimeError({\n pluginId: plugin.id,\n operation: \"validate-config\",\n cause: validationError.zodError,\n retryable: false,\n }),\n ),\n );\n\n // Validate secrets\n const validatedSecrets = yield* validate(\n plugin.configSchema.secrets as z.ZodSchema<\n InferSchemaOutput<T[\"configSchema\"][\"secrets\"]>\n >,\n config.secrets,\n plugin.id,\n \"config\",\n ).pipe(\n Effect.mapError(\n (validationError) =>\n new PluginRuntimeError({\n pluginId: plugin.id,\n operation: \"validate-secrets\",\n cause: validationError.zodError,\n retryable: false,\n }),\n ),\n );\n\n // Hydrate secrets in variables\n const hydratedConfig = yield* secretsService.hydrateSecrets({\n variables: validatedVariables,\n secrets: validatedSecrets,\n });\n\n const _variables = yield* validate(\n plugin.configSchema.variables as z.ZodSchema<\n InferSchemaOutput<T[\"configSchema\"][\"variables\"]>\n >,\n hydratedConfig.variables,\n plugin.id,\n \"config\",\n ).pipe(\n Effect.mapError(\n (validationError) =>\n new PluginRuntimeError({\n pluginId: plugin.id,\n operation: \"validate-hydrated-config\",\n cause: validationError.zodError,\n retryable: false,\n }),\n ),\n );\n\n // Create a long-lived scope for this plugin instance\n const scope = yield* Scope.make();\n\n // Initialize plugin within the scope\n const context = yield* plugin\n .initialize({ variables: _variables, secrets: hydratedConfig.secrets }, plugins ?? {})\n .pipe(\n Effect.provideService(Scope.Scope, scope),\n Effect.mapError((error) =>\n toPluginRuntimeError(error, plugin.id, undefined, \"initialize-plugin\", false),\n ),\n );\n\n return {\n plugin,\n metadata: pluginInstance.metadata,\n config: { variables: _variables, secrets: hydratedConfig.secrets },\n context,\n scope,\n } satisfies InitializedPlugin<T>;\n }),\n };\n }),\n },\n) {}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"plugin-loader.service.mjs","names":[],"sources":["../../../src/runtime/services/plugin-loader.service.ts"],"sourcesContent":["import type { InferSchemaInput, InferSchemaOutput } from \"@orpc/contract\";\nimport { Context, Effect, Scope } from \"effect\";\nimport type { z } from \"zod\";\nimport type {\n AnyPlugin,\n AnyPluginConstructor,\n InitializedPlugin,\n LoadedPlugin,\n PluginInstance,\n PluginMetadata,\n PluginRegistry,\n} from \"../../types\";\nimport { PluginRuntimeError, toPluginRuntimeError } from \"../errors\";\nimport { validate } from \"../validation\";\nimport { ModuleFederationService } from \"./module-federation.service\";\nimport { SecretsService } from \"./secrets.service\";\n\nexport class PluginRegistryTag extends Context.Tag(\"PluginRegistry\")<\n PluginRegistryTag,\n PluginRegistry\n>() {}\n\nexport class PluginMapTag extends Context.Tag(\"PluginMap\")<\n PluginMapTag,\n Record<string, AnyPluginConstructor>\n>() {}\n\nexport class RegistryService extends Effect.Service<RegistryService>()(\"RegistryService\", {\n effect: Effect.gen(function* () {\n const registry = yield* PluginRegistryTag;\n const pluginMap = yield* PluginMapTag;\n\n return {\n get: (pluginId: string) =>\n Effect.gen(function* () {\n const entry = registry[pluginId];\n\n if (!entry) {\n return yield* Effect.fail(\n new PluginRuntimeError({\n pluginId,\n operation: \"validate-plugin-id\",\n cause: new Error(`Plugin ${pluginId} not found in registry`),\n retryable: false,\n }),\n );\n }\n\n if (\"module\" in entry) {\n return {\n constructor: entry.module,\n metadata: {\n remoteUrl: entry.remote || \"\",\n version: entry.version,\n description: entry.description,\n } as PluginMetadata,\n };\n }\n\n return {\n constructor: null,\n metadata: {\n remoteUrl: entry.remote,\n version: entry.version,\n description: entry.description,\n } as PluginMetadata,\n };\n }),\n\n getModule: (pluginId: string) => Effect.succeed(pluginMap[pluginId] || null),\n };\n }),\n}) {}\n\nexport class PluginLoaderService extends Effect.Service<PluginLoaderService>()(\n \"PluginLoaderService\",\n {\n effect: Effect.gen(function* () {\n const moduleFederationService = yield* ModuleFederationService;\n const secretsService = yield* SecretsService;\n const registryService = yield* RegistryService;\n\n const resolveUrl = (baseUrl: string, version?: string): string =>\n version && version !== \"latest\" ? baseUrl.replace(\"@latest\", `@${version}`) : baseUrl;\n\n return {\n loadPlugin: (pluginId: string) =>\n Effect.gen(function* () {\n const entry = yield* registryService.get(pluginId);\n\n if (entry.constructor) {\n yield* Effect.logDebug(\"Loading plugin from direct module\", { pluginId });\n\n return {\n ctor: entry.constructor,\n metadata: entry.metadata,\n } satisfies LoadedPlugin;\n }\n\n const url = entry.metadata.remoteUrl;\n if (!url) {\n return yield* Effect.fail(\n new PluginRuntimeError({\n pluginId,\n operation: \"load-plugin\",\n cause: new Error(`Plugin ${pluginId} has no module or remote URL configured`),\n retryable: false,\n }),\n );\n }\n\n const resolvedUrl = resolveUrl(url);\n\n yield* moduleFederationService.registerRemote(pluginId, resolvedUrl).pipe(\n Effect.tapError((_error) =>\n Effect.logError(`Plugin ${pluginId} failed during register-remote`),\n ),\n Effect.mapError((error) =>\n toPluginRuntimeError(error, pluginId, undefined, \"register-remote\", true),\n ),\n );\n\n yield* Effect.logDebug(\"Loading plugin from remote\", { pluginId, url: resolvedUrl });\n\n const ctor = yield* moduleFederationService\n .loadRemoteConstructor(pluginId, resolvedUrl)\n .pipe(\n Effect.tapError((_error) =>\n Effect.logError(`Plugin ${pluginId} failed during load-remote`),\n ),\n Effect.mapError((error) =>\n toPluginRuntimeError(error, pluginId, undefined, \"load-remote\", false),\n ),\n );\n\n return {\n ctor,\n metadata: entry.metadata,\n } satisfies LoadedPlugin;\n }),\n\n instantiatePlugin: <T extends AnyPlugin>(pluginId: string, loadedPlugin: LoadedPlugin<T>) =>\n Effect.gen(function* () {\n const instance = yield* Effect.try(() => new loadedPlugin.ctor()).pipe(\n Effect.tapError((_error) =>\n Effect.logError(`Plugin ${pluginId} failed during instantiate-plugin`),\n ),\n Effect.mapError((error) =>\n toPluginRuntimeError(error, pluginId, undefined, \"instantiate-plugin\", false),\n ),\n );\n\n (instance.id as string) = pluginId;\n\n return {\n plugin: instance,\n metadata: loadedPlugin.metadata,\n } satisfies PluginInstance<T>;\n }),\n\n initializePlugin: <T extends AnyPlugin>(\n pluginInstance: PluginInstance<T>,\n config: {\n variables: InferSchemaInput<T[\"configSchema\"][\"variables\"]>;\n secrets: InferSchemaInput<T[\"configSchema\"][\"secrets\"]>;\n },\n plugins?: Record<string, unknown>,\n ) =>\n Effect.gen(function* () {\n const { plugin } = pluginInstance;\n\n // Validate and hydrate config\n const validatedVariables = yield* validate(\n plugin.configSchema.variables as z.ZodSchema<\n InferSchemaOutput<T[\"configSchema\"][\"variables\"]>\n >,\n config.variables,\n plugin.id,\n \"config\",\n ).pipe(\n Effect.mapError(\n (validationError) =>\n new PluginRuntimeError({\n pluginId: plugin.id,\n operation: \"validate-config\",\n cause: validationError.zodError,\n retryable: false,\n }),\n ),\n );\n\n // Validate secrets\n const validatedSecrets = yield* validate(\n plugin.configSchema.secrets as z.ZodSchema<\n InferSchemaOutput<T[\"configSchema\"][\"secrets\"]>\n >,\n config.secrets,\n plugin.id,\n \"config\",\n ).pipe(\n Effect.mapError(\n (validationError) =>\n new PluginRuntimeError({\n pluginId: plugin.id,\n operation: \"validate-secrets\",\n cause: validationError.zodError,\n retryable: false,\n }),\n ),\n );\n\n // Hydrate secrets in variables\n const hydratedConfig = yield* secretsService.hydrateSecrets({\n variables: validatedVariables,\n secrets: validatedSecrets,\n });\n\n const _variables = yield* validate(\n plugin.configSchema.variables as z.ZodSchema<\n InferSchemaOutput<T[\"configSchema\"][\"variables\"]>\n >,\n hydratedConfig.variables,\n plugin.id,\n \"config\",\n ).pipe(\n Effect.mapError(\n (validationError) =>\n new PluginRuntimeError({\n pluginId: plugin.id,\n operation: \"validate-hydrated-config\",\n cause: validationError.zodError,\n retryable: false,\n }),\n ),\n );\n\n // Create a long-lived scope for this plugin instance\n const scope = yield* Scope.make();\n\n // Initialize plugin within the scope\n const context = yield* plugin\n .initialize({ variables: _variables, secrets: hydratedConfig.secrets }, plugins ?? {})\n .pipe(\n Effect.provideService(Scope.Scope, scope),\n Effect.tapError((_error) =>\n Effect.logError(`Plugin ${plugin.id} failed during initialize-plugin`),\n ),\n Effect.mapError((error) =>\n toPluginRuntimeError(error, plugin.id, undefined, \"initialize-plugin\", false),\n ),\n );\n\n return {\n plugin,\n metadata: pluginInstance.metadata,\n config: { variables: _variables, secrets: hydratedConfig.secrets },\n context,\n scope,\n } satisfies InitializedPlugin<T>;\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.
|
|
3
|
+
"version": "2.9.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -108,7 +108,7 @@
|
|
|
108
108
|
"@orpc/client": "^1.13.4",
|
|
109
109
|
"@orpc/openapi": "^1.13.4",
|
|
110
110
|
"@rspack/cli": "1.7.11",
|
|
111
|
-
"@tanstack/intent": "^0.
|
|
111
|
+
"@tanstack/intent": "^0.3.5",
|
|
112
112
|
"@types/bun": "^1.3.7",
|
|
113
113
|
"@types/node": "^25.1.0",
|
|
114
114
|
"@vitest/ui": "4.1.5",
|
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: plugin-client
|
|
3
|
+
description: Connect to and consume deployed everything.dev plugins from an external app, child project, or script. Use when creating API/auth clients, reading runtime config, authenticating with API keys or sessions, or calling plugin routes programmatically.
|
|
4
|
+
metadata:
|
|
5
|
+
sources: "ui/src/lib/api.ts,ui/src/lib/auth.ts,ui/src/app.ts,everything-dev/src/ui/runtime.ts,everything-dev/src/types.ts,host/src/program.ts,host/src/services/auth.ts,api/src/lib/auth.ts"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Plugin Client — Consuming everything.dev APIs
|
|
9
|
+
|
|
10
|
+
## Minimal Connection Config
|
|
11
|
+
|
|
12
|
+
Every everything.dev host exposes two surfaces:
|
|
13
|
+
|
|
14
|
+
| Surface | URL | Purpose |
|
|
15
|
+
|---------|-----|---------|
|
|
16
|
+
| RPC | `{hostUrl}/api/rpc` | Typed oRPC procedure calls (POST) |
|
|
17
|
+
| OpenAPI / REST | `{hostUrl}/api` | Scalar docs + OpenAPI spec + REST routes |
|
|
18
|
+
|
|
19
|
+
The host injects a `ClientRuntimeConfig` into `window.__RUNTIME_CONFIG__` via an inline script during SSR. The key fields for client connection:
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
{
|
|
23
|
+
hostUrl: "https://dev.everything.dev", // the host server origin
|
|
24
|
+
apiBase: "/api",
|
|
25
|
+
rpcBase: "/api/rpc",
|
|
26
|
+
authAvailable: true,
|
|
27
|
+
account: "dev.everything.near",
|
|
28
|
+
networkId: "mainnet",
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Read it with `getRuntimeConfig()` from `everything-dev/ui/runtime`:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { getRuntimeConfig } from "everything-dev/ui/runtime";
|
|
36
|
+
|
|
37
|
+
const config = getRuntimeConfig(); // throws if window.__RUNTIME_CONFIG__ is missing
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Or use the `@/app` helpers (in-repo UI only):
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { getAccount, getActiveRuntime, getAppName, getRepository } from "@/app";
|
|
44
|
+
|
|
45
|
+
const account = getAccount(); // "dev.everything.near"
|
|
46
|
+
const runtime = getActiveRuntime(); // { accountId, gatewayId, title, ... }
|
|
47
|
+
const appName = getAppName(); // runtime title or account
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
All helpers accept an optional config argument for SSR — pass `runtimeConfig` from loader data instead of reading `window`.
|
|
51
|
+
|
|
52
|
+
## External / Standalone API Client
|
|
53
|
+
|
|
54
|
+
An external app, script, or child project can connect with just `@orpc/client` and `@orpc/client/fetch`:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { createORPCClient } from "@orpc/client";
|
|
58
|
+
import { RPCLink } from "@orpc/client/fetch";
|
|
59
|
+
|
|
60
|
+
const HOST = "https://dev.everything.dev";
|
|
61
|
+
|
|
62
|
+
const link = new RPCLink({
|
|
63
|
+
url: `${HOST}/api/rpc`,
|
|
64
|
+
fetch: globalThis.fetch,
|
|
65
|
+
headers: {
|
|
66
|
+
"x-api-key": process.env.EVERYTHING_DEV_API_KEY!,
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const client = createORPCClient(link);
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### URL Structure
|
|
74
|
+
|
|
75
|
+
oRPC's RPC protocol encodes the procedure path in the URL segment after the RPC base:
|
|
76
|
+
|
|
77
|
+
| Call | HTTP Request |
|
|
78
|
+
|------|-------------|
|
|
79
|
+
| `client.ping()` | `POST {hostUrl}/api/rpc/ping` |
|
|
80
|
+
| `client.apps.listRegistryApps({ limit: 24 })` | `POST {hostUrl}/api/rpc/apps/listRegistryApps` |
|
|
81
|
+
| `client.auth.getSession()` | `POST {hostUrl}/api/rpc/auth/getSession` |
|
|
82
|
+
|
|
83
|
+
The host matches `/api/rpc/{pluginKey}/*` prefixes and dispatches to each plugin's `RPCHandler`. Base API procedures (not namespaced) go to the main API handler at `/api/rpc`.
|
|
84
|
+
|
|
85
|
+
### Plugin Namespacing
|
|
86
|
+
|
|
87
|
+
Plugins are namespaced by their `bos.config.json` key. If the config has:
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
{
|
|
91
|
+
"plugins": {
|
|
92
|
+
"apps": { "production": "https://..." },
|
|
93
|
+
"template": { "production": "https://..." }
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Then the client surface is:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
client.ping(); // base API
|
|
102
|
+
client.apps.listRegistryApps(); // "apps" plugin
|
|
103
|
+
client.template.someProcedure(); // "template" plugin
|
|
104
|
+
client.auth.getSession(); // auth plugin (mounted at /api/rpc/auth)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Without the generated `ApiContract` type, the client is untyped — but the runtime behavior is identical. Use OpenAPI codegen (see below) for typed clients in other languages.
|
|
108
|
+
|
|
109
|
+
### Auth for External Clients
|
|
110
|
+
|
|
111
|
+
Two credential mechanisms:
|
|
112
|
+
|
|
113
|
+
**API key** — pass `x-api-key` header on every request:
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
const link = new RPCLink({
|
|
117
|
+
url: `${HOST}/api/rpc`,
|
|
118
|
+
fetch: globalThis.fetch,
|
|
119
|
+
headers: { "x-api-key": "edk_xxxxxxxxxxxxxxx" },
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Session cookies** — use `credentials: "include"` (browser only, requires CORS origin allowlist):
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
const link = new RPCLink({
|
|
127
|
+
url: `${HOST}/api/rpc`,
|
|
128
|
+
fetch: (url, options) => fetch(url, { ...options, credentials: "include" }),
|
|
129
|
+
});
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
The host's session middleware resolves auth on every `/api/*` request by calling Better-Auth's `getContext()`, which reads session cookies and the `x-api-key` header. The resulting context (user, apiKey, organization) is forwarded into every plugin's oRPC handler.
|
|
133
|
+
|
|
134
|
+
## In-Repo API Client
|
|
135
|
+
|
|
136
|
+
Within the everything.dev UI, use `createApiClient` from `@/app`:
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
import { createApiClient, useApiClient, useOrpc } from "@/app";
|
|
140
|
+
|
|
141
|
+
// In hydration (once):
|
|
142
|
+
const apiClient = createApiClient({
|
|
143
|
+
hostUrl: runtimeConfig.hostUrl,
|
|
144
|
+
rpcBase: runtimeConfig.rpcBase,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// In route components:
|
|
148
|
+
const apiClient = useApiClient();
|
|
149
|
+
const result = await apiClient.apps.listRegistryApps({ limit: 24 });
|
|
150
|
+
|
|
151
|
+
// With TanStack Query utils:
|
|
152
|
+
const orpc = useOrpc();
|
|
153
|
+
const { data } = orpc.apps.listRegistryApps.useQuery({ limit: 24 });
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
`createApiClient` caches a singleton in the browser (when no SSR headers are passed). SSR builds a fresh client per request with forwarded cookies. See the `ui-integration` skill for route-level patterns, loaders, and error handling.
|
|
157
|
+
|
|
158
|
+
## Auth Client (Session-Based)
|
|
159
|
+
|
|
160
|
+
The auth client uses Better-Auth with SIWN (NEAR wallet sign-in). Create it with `createAuthClient` from `@/app`:
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
import { createAuthClient, sessionQueryOptions, useAuthClient } from "@/app";
|
|
164
|
+
|
|
165
|
+
// In hydration:
|
|
166
|
+
const authClient = createAuthClient({ runtimeConfig, cspNonce });
|
|
167
|
+
|
|
168
|
+
// In route components:
|
|
169
|
+
const authClient = useAuthClient();
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### SIWN Configuration
|
|
173
|
+
|
|
174
|
+
The auth client reads NEAR recipient config from `runtimeConfig.auth.variables.siwn`:
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
// runtimeConfig.auth.variables.siwn:
|
|
178
|
+
{
|
|
179
|
+
recipient: "dev.everything.near", // single network
|
|
180
|
+
// OR:
|
|
181
|
+
recipients: {
|
|
182
|
+
mainnet: "dev.everything.near",
|
|
183
|
+
testnet: "dev.testnet.near",
|
|
184
|
+
},
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
The client resolves `mainnetRecipient` from `recipients.mainnet ?? recipient` and infers `networkId` from the runtime config or the recipient suffix.
|
|
189
|
+
|
|
190
|
+
### Sign-In Flows
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
// NEAR wallet (SIWN)
|
|
194
|
+
await authClient.signIn.siwn({ networkId: "mainnet" });
|
|
195
|
+
|
|
196
|
+
// Email/password
|
|
197
|
+
await authClient.signIn.email({ email, password });
|
|
198
|
+
|
|
199
|
+
// Passkey
|
|
200
|
+
await authClient.signIn.passkey();
|
|
201
|
+
|
|
202
|
+
// Sign out
|
|
203
|
+
await authClient.signOut();
|
|
204
|
+
|
|
205
|
+
// Organization switching
|
|
206
|
+
await authClient.organization.setActive({ organizationId: "org_123" });
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Session Query
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
import { sessionQueryOptions } from "@/app";
|
|
213
|
+
|
|
214
|
+
// In a route loader (SSR-safe with initialData):
|
|
215
|
+
const session = await queryClient.ensureQueryData(
|
|
216
|
+
sessionQueryOptions(authClient, context.session),
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
// In a component:
|
|
220
|
+
const { data: session } = useQuery(sessionQueryOptions(authClient));
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Session query key is `["session"]` with 1-min stale time and 10-min GC time.
|
|
224
|
+
|
|
225
|
+
### SSR Cookie Forwarding
|
|
226
|
+
|
|
227
|
+
On the server, cookies can't be read from `window`. Pass request headers:
|
|
228
|
+
|
|
229
|
+
```ts
|
|
230
|
+
const authClient = createAuthClient({
|
|
231
|
+
runtimeConfig,
|
|
232
|
+
headers: request.headers, // forwards session cookies
|
|
233
|
+
cspNonce,
|
|
234
|
+
});
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Both API and auth clients use `credentials: "include"` unconditionally. The host's CORS allows any HTTPS origin in production (configurable via `CORS_ORIGIN`).
|
|
238
|
+
|
|
239
|
+
## API Key Auth
|
|
240
|
+
|
|
241
|
+
For programmatic access (scripts, backend services, CI), use API keys instead of sessions.
|
|
242
|
+
|
|
243
|
+
### Creating API Keys
|
|
244
|
+
|
|
245
|
+
API keys are created via the Better-Auth `apiKeyClient()` plugin (exposed as `authClient.apiKey.*`):
|
|
246
|
+
|
|
247
|
+
```ts
|
|
248
|
+
const authClient = useAuthClient();
|
|
249
|
+
|
|
250
|
+
// Create a key (secret returned once!)
|
|
251
|
+
const { data, error } = await authClient.apiKey.create({
|
|
252
|
+
name: "my-bot-key",
|
|
253
|
+
configId: "org-keys", // optional: scope to a key config
|
|
254
|
+
organizationId: "org_123", // optional: scope to an org
|
|
255
|
+
expiresIn: 60 * 60 * 24 * 30, // optional: 30 days (seconds)
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
if (data) {
|
|
259
|
+
console.log(data.apiKey.key); // "edk_xxxxxxxxx" — save this now
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
The full key secret is only returned at creation time. Store it immediately.
|
|
264
|
+
|
|
265
|
+
### Using API Keys
|
|
266
|
+
|
|
267
|
+
Pass the key in the `x-api-key` header:
|
|
268
|
+
|
|
269
|
+
```ts
|
|
270
|
+
const link = new RPCLink({
|
|
271
|
+
url: `${HOST}/api/rpc`,
|
|
272
|
+
fetch: globalThis.fetch,
|
|
273
|
+
headers: { "x-api-key": "edk_xxxxxxxxxxxxxxx" },
|
|
274
|
+
});
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Or with plain fetch to REST endpoints:
|
|
278
|
+
|
|
279
|
+
```ts
|
|
280
|
+
const res = await fetch(`${HOST}/api/v1/registry/apps`, {
|
|
281
|
+
headers: { "x-api-key": "edk_xxxxxxxxxxxxxxx" },
|
|
282
|
+
});
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### Permission Scopes
|
|
286
|
+
|
|
287
|
+
Keys have a `permissions` map (`Record<string, string[]>` — resource to allowed actions). Routes protected by `requireApiKey(requiredPermissions)` check that the key's permissions cover all required resource/action pairs:
|
|
288
|
+
|
|
289
|
+
```ts
|
|
290
|
+
// Server-side (plugin route):
|
|
291
|
+
builder.deleteThing
|
|
292
|
+
.use(requireApiKey({ things: ["delete"] }))
|
|
293
|
+
.handler(async ({ context }) => {
|
|
294
|
+
context.apiKey.permissions; // { things: ["delete", "create"], ... }
|
|
295
|
+
});
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
If the key lacks a required permission, the server returns `FORBIDDEN` with `{ requiredPermissions, keyPermissions }` in the error data.
|
|
299
|
+
|
|
300
|
+
### Dual-Mode Auth
|
|
301
|
+
|
|
302
|
+
`requireAuthOrApiKey` accepts either a session or an API key — useful for routes that serve both interactive UI and programmatic clients:
|
|
303
|
+
|
|
304
|
+
```ts
|
|
305
|
+
builder.createThing
|
|
306
|
+
.use(requireAuthOrApiKey)
|
|
307
|
+
.handler(async ({ input, context }) => {
|
|
308
|
+
if (context.apiKey) {
|
|
309
|
+
// API key path
|
|
310
|
+
} else if (context.user) {
|
|
311
|
+
// Session path
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
See `references/api-keys.md` for the full API key route table, permission schema, and management examples.
|
|
317
|
+
|
|
318
|
+
## OpenAPI / REST
|
|
319
|
+
|
|
320
|
+
The host serves Scalar API reference docs and an OpenAPI spec:
|
|
321
|
+
|
|
322
|
+
| Endpoint | Content |
|
|
323
|
+
|----------|---------|
|
|
324
|
+
| `GET {hostUrl}/api` | Scalar HTML docs UI |
|
|
325
|
+
| `GET {hostUrl}/api` (Accept: application/json) | OpenAPI JSON spec |
|
|
326
|
+
|
|
327
|
+
Use the OpenAPI spec to generate typed clients in any language:
|
|
328
|
+
|
|
329
|
+
```bash
|
|
330
|
+
# Generate a TypeScript client
|
|
331
|
+
npx openapi-typescript-codegen --input https://dev.everything.dev/api --output ./generated-api
|
|
332
|
+
|
|
333
|
+
# Generate a Python client
|
|
334
|
+
openapi-generator-cli generate -i https://dev.everything.dev/api -g python -o ./api-client
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
REST routes follow the `oc.route({ method, path })` declarations in each plugin's contract. For example, `GET {hostUrl}/api/v1/registry/apps` maps to the `apps` plugin's `listRegistryApps` procedure.
|
|
338
|
+
|
|
339
|
+
### Plugin Manifests
|
|
340
|
+
|
|
341
|
+
Each deployed plugin exposes a manifest at `{pluginUrl}/plugin.manifest.json`:
|
|
342
|
+
|
|
343
|
+
```json
|
|
344
|
+
{
|
|
345
|
+
"schemaVersion": 1,
|
|
346
|
+
"kind": "every-plugin/manifest",
|
|
347
|
+
"plugin": { "name": "apps", "version": "1.0.0" },
|
|
348
|
+
"runtime": { "remoteEntry": "./remoteEntry.js" },
|
|
349
|
+
"contract": {
|
|
350
|
+
"kind": "orpc",
|
|
351
|
+
"types": {
|
|
352
|
+
"path": "./types/contract.d.ts",
|
|
353
|
+
"exportName": "contract",
|
|
354
|
+
"typeName": "ContractType",
|
|
355
|
+
"sha256": "abc123..."
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
The manifest provides the contract type file path and SHA-256 checksum for verified type generation.
|
|
362
|
+
|
|
363
|
+
## Typed Clients via `bos types gen`
|
|
364
|
+
|
|
365
|
+
Child projects and the in-repo UI get typed API clients through generated contract bridge files.
|
|
366
|
+
|
|
367
|
+
### What It Generates
|
|
368
|
+
|
|
369
|
+
| File | Contents |
|
|
370
|
+
|------|----------|
|
|
371
|
+
| `ui/src/lib/api-types.gen.ts` | `ApiContract` — merged type of base API + all plugin contracts |
|
|
372
|
+
| `api/src/lib/plugins-types.gen.ts` | `PluginsClient` — factory types for in-process plugin composition |
|
|
373
|
+
| `*/src/lib/auth-types.gen.ts` | Auth plugin session/user/organization types |
|
|
374
|
+
|
|
375
|
+
### The Merged Contract
|
|
376
|
+
|
|
377
|
+
`api-types.gen.ts` intersects the base API contract with each plugin's contract as a namespaced key:
|
|
378
|
+
|
|
379
|
+
```ts
|
|
380
|
+
import type { ContractType as BaseApiContract } from "../../../api/src/contract.ts";
|
|
381
|
+
import type { ContractType as appsContract } from "../../../plugins/apps/src/contract.ts";
|
|
382
|
+
|
|
383
|
+
export type ApiContract = BaseApiContract & {
|
|
384
|
+
auth: authContract;
|
|
385
|
+
apps: appsContract;
|
|
386
|
+
template: templateContract;
|
|
387
|
+
};
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
`ContractRouterClient<ApiContract>` turns this into a fully typed callable proxy — `apiClient.apps.listRegistryApps({ limit: 24 })` has complete input/output types.
|
|
391
|
+
|
|
392
|
+
### Regeneration Triggers
|
|
393
|
+
|
|
394
|
+
Generated files are gitignored and auto-regenerated on:
|
|
395
|
+
- `bun install` (via postinstall `bos types gen`)
|
|
396
|
+
- `bun typecheck`
|
|
397
|
+
- `bos dev` / `bos build`
|
|
398
|
+
- `bos plugin add` / `bos plugin remove`
|
|
399
|
+
|
|
400
|
+
After hand-editing `bos.config.json`, run `bos types gen` or restart `bos dev`.
|
|
401
|
+
|
|
402
|
+
### Type Resolution
|
|
403
|
+
|
|
404
|
+
- `local:plugins/<name>` → reads `src/contract.ts` directly from disk
|
|
405
|
+
- Remote URL → fetches `contract.d.ts` from the deployed plugin's manifest (with SHA-256 verification)
|
|
406
|
+
- Missing local path with no URL → skipped with a warning
|
|
407
|
+
|
|
408
|
+
## Child Project Config
|
|
409
|
+
|
|
410
|
+
A child project inherits the parent's host, API, auth, and plugins via the `extends` field in `bos.config.json`:
|
|
411
|
+
|
|
412
|
+
```json
|
|
413
|
+
{
|
|
414
|
+
"extends": "bos://dev.everything.near/dev.everything.dev",
|
|
415
|
+
"account": "myapp.near",
|
|
416
|
+
"domain": "myapp.everything.dev"
|
|
417
|
+
}
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
This inherits:
|
|
421
|
+
- `app.host` — the parent's host server URL
|
|
422
|
+
- `app.ui` — the parent's UI CDN (overridable)
|
|
423
|
+
- `app.api` — the parent's API
|
|
424
|
+
- `app.auth` — the parent's auth plugin (via nested `extends` ref)
|
|
425
|
+
- `plugins` — all parent plugins (overridable per-key)
|
|
426
|
+
|
|
427
|
+
### Scaffolding with `bos init`
|
|
428
|
+
|
|
429
|
+
```bash
|
|
430
|
+
bos init myapp.everything.dev \
|
|
431
|
+
--extends dev.everything.near/dev.everything.dev \
|
|
432
|
+
--overrides ui,plugins \
|
|
433
|
+
--plugins apps,template
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
This writes `bos.config.json` with `extends`, scaffolds the project, and generates types. The child's `postinstall` hook runs `bos types gen` automatically.
|
|
437
|
+
|
|
438
|
+
### What Can Be Overridden
|
|
439
|
+
|
|
440
|
+
Child configs can override `app.ui` (custom UI CDN) and `plugins.<key>` (custom plugin URLs). The host's `ALLOW_OVERRIDE` env var controls which sections tenants can customize (`ui`, `plugins`, `plugins.<key>`).
|
|
441
|
+
|
|
442
|
+
See the `extends-config` skill for deep merge semantics, per-environment extends, and canonical field ordering.
|
|
443
|
+
|
|
444
|
+
## Common Mistakes
|
|
445
|
+
|
|
446
|
+
- **Wrong header for API keys** — use `x-api-key`, not `Authorization: Bearer`. The `Authorization` header is only listed in CORS allowed headers for third-party integrations, not for everything.dev auth.
|
|
447
|
+
- **Missing `credentials: "include"`** — session cookies won't be sent without it. Both `createApiClient` and `createAuthClient` set this automatically; external clients must set it manually.
|
|
448
|
+
- **Not saving the API key secret** — `authClient.apiKey.create()` returns the full key string (`edk_...`) only once. Subsequent calls to `list` or `update` return only the prefix and metadata.
|
|
449
|
+
- **Plugin key vs plugin name confusion** — the client namespace is the `bos.config.json` `plugins` key (e.g., `"apps"`), not the Module Federation `name` field. They often match but are not guaranteed to.
|
|
450
|
+
- **Forgetting to regenerate types** — after adding/removing plugins in `bos.config.json`, run `bos types gen` or restart `bos dev`. Stale `api-types.gen.ts` will have missing or wrong namespace keys.
|