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.
Files changed (43) hide show
  1. package/dist/build/rspack/dev-server-middleware.cjs +4 -2
  2. package/dist/build/rspack/dev-server-middleware.cjs.map +1 -1
  3. package/dist/build/rspack/dev-server-middleware.mjs +4 -2
  4. package/dist/build/rspack/dev-server-middleware.mjs.map +1 -1
  5. package/dist/plugin.cjs +2 -1
  6. package/dist/plugin.cjs.map +1 -1
  7. package/dist/plugin.d.cts.map +1 -1
  8. package/dist/plugin.d.mts.map +1 -1
  9. package/dist/plugin.mjs +2 -1
  10. package/dist/plugin.mjs.map +1 -1
  11. package/dist/runtime/errors.cjs +3 -42
  12. package/dist/runtime/errors.cjs.map +1 -1
  13. package/dist/runtime/errors.d.cts +1 -1
  14. package/dist/runtime/errors.d.cts.map +1 -1
  15. package/dist/runtime/errors.d.mts +1 -1
  16. package/dist/runtime/errors.d.mts.map +1 -1
  17. package/dist/runtime/errors.mjs +3 -42
  18. package/dist/runtime/errors.mjs.map +1 -1
  19. package/dist/runtime/index.cjs +4 -4
  20. package/dist/runtime/index.cjs.map +1 -1
  21. package/dist/runtime/index.mjs +4 -4
  22. package/dist/runtime/index.mjs.map +1 -1
  23. package/dist/runtime/mf-config.cjs +1 -1
  24. package/dist/runtime/mf-config.mjs +1 -1
  25. package/dist/runtime/services/module-federation.service.cjs +6 -5
  26. package/dist/runtime/services/module-federation.service.cjs.map +1 -1
  27. package/dist/runtime/services/module-federation.service.mjs +6 -5
  28. package/dist/runtime/services/module-federation.service.mjs.map +1 -1
  29. package/dist/runtime/services/plugin-loader.service.cjs +4 -4
  30. package/dist/runtime/services/plugin-loader.service.cjs.map +1 -1
  31. package/dist/runtime/services/plugin-loader.service.d.cts.map +1 -1
  32. package/dist/runtime/services/plugin-loader.service.d.mts.map +1 -1
  33. package/dist/runtime/services/plugin-loader.service.mjs +4 -4
  34. package/dist/runtime/services/plugin-loader.service.mjs.map +1 -1
  35. package/package.json +2 -2
  36. package/skills/plugin-client/SKILL.md +450 -0
  37. package/skills/plugin-client/references/api-keys.md +145 -0
  38. package/src/build/rspack/dev-server-middleware.ts +4 -2
  39. package/src/plugin.ts +2 -1
  40. package/src/runtime/errors.ts +4 -60
  41. package/src/runtime/index.ts +4 -4
  42. package/src/runtime/services/module-federation.service.ts +7 -5
  43. 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.7.1",
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.0.40",
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.