You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
> Service "`{package}`" is already installed — keeping the first installation and ignoring this one's options.
10
+
11
+
## Cause
12
+
13
+
Wire services are deduplicated by npm package name: the first installation wins, and later installs of the same package return the existing node API. Option sets from multiple installers only merge **before** the `ctx.services.ready()` barrier fires — an install that arrives after the service was constructed can no longer influence its configuration, so any options it carried are dropped with this warning.
14
+
15
+
## Example
16
+
17
+
```ts
18
+
awaitctx.services.ready()
19
+
20
+
// ✗ The service is already constructed; { themes } is ignored.
Install the service (or declare it in `DevframeDefinition.services`) before the barrier — a host's explicit installs during setup/`configure` naturally run before the adapter fires `ready()`, so its options join the merge:
-[`packages/devframe/src/node/host-services.ts`](https://github.com/devframes/devframe/blob/main/packages/devframe/src/node/host-services.ts) — `install()`/the barrier flush warn when an already-installed package is installed again.
> Failed to import the required service package "`{package}`": `{reason}`
10
+
11
+
## Cause
12
+
13
+
A service descriptor marked `required: true` names a package that could not be resolved and imported at the `ctx.services.ready()` barrier. Descriptors resolve against the declaring plugin's own dependencies first (then the workspace root), so this usually means the service package is missing from the declarer's `dependencies`, or isn't installed.
14
+
15
+
Descriptors without `required` degrade instead: the missing service is skipped and clients observe `services.has(pkg) === false`.
16
+
17
+
## Example
18
+
19
+
```ts
20
+
defineDevframe({
21
+
services: [
22
+
// ✗ Throws at the ready() barrier when the package isn't installed.
Install the service package next to whoever declares it — a plugin declaring it in `services` lists it in its own `dependencies` (or `peerDependencies`) — or drop `required: true` and let the consuming UI fall back when the service is absent.
31
+
32
+
## Source
33
+
34
+
-[`packages/devframe/src/node/host-services.ts`](https://github.com/devframes/devframe/blob/main/packages/devframe/src/node/host-services.ts) — the barrier flush throws when a `required` descriptor's package fails to import.
# DF0068: Required Service Version Range Not Satisfied
6
+
7
+
## Message
8
+
9
+
> The installed service "`{package}`@`{installed}`" does not satisfy the required range "`{required}`".
10
+
11
+
## Cause
12
+
13
+
A service descriptor marked `required: true` declares a `version` range, and the version of the service that actually resolved falls outside it. The range is checked at the `ctx.services.ready()` barrier against the resolved definition's own `version`.
14
+
15
+
Without `required`, the same mismatch installs the service anyway and warns with [`DF0069`](/errors/DF0069).
16
+
17
+
## Example
18
+
19
+
```ts
20
+
defineDevframe({
21
+
services: [
22
+
// ✗ Throws when @devframes/service-shiki@2.x is what's installed.
Align the installed service package with the declared range (update whichever side is stale), or drop `required: true` to downgrade the mismatch to a warning — the advertised meta carries the real version, so clients can gate on it.
31
+
32
+
## Source
33
+
34
+
-[`packages/devframe/src/node/host-services.ts`](https://github.com/devframes/devframe/blob/main/packages/devframe/src/node/host-services.ts) — the barrier flush checks each descriptor's `version` range against the resolved definition.
> The installed service "`{package}`@`{installed}`" does not satisfy the declared range "`{required}`" — installing it anyway.
10
+
11
+
## Cause
12
+
13
+
A service descriptor declares a `version` range, and the version of the service that actually resolved falls outside it. Since the descriptor isn't marked `required`, the service still installs — the range acts as a compatibility hint, and this warning surfaces the drift. The advertised meta carries the real version, so client UIs can gate features on it.
14
+
15
+
The `required: true` variant of the same mismatch throws [`DF0068`](/errors/DF0068) instead.
16
+
17
+
## Example
18
+
19
+
```ts
20
+
defineDevframe({
21
+
services: [
22
+
// Installed: @devframes/service-shiki@2.0.0 → warns, still installs.
Align the installed service package with the declared range to silence the warning, or widen the declared range when the newer service is actually fine.
31
+
32
+
## Source
33
+
34
+
-[`packages/devframe/src/node/host-services.ts`](https://github.com/devframes/devframe/blob/main/packages/devframe/src/node/host-services.ts) — the barrier flush checks each descriptor's `version` range against the resolved definition.
A wire service failed structural validation at install time. The `reason` names the specific gap:
14
+
15
+
- the install input has no `package` name,
16
+
- a definition is missing its `version` or its RPC `scope` namespace,
17
+
- an imported service package's default export is not a factory function,
18
+
- the factory didn't return a definition with a `setup` function.
19
+
20
+
## Example
21
+
22
+
```ts
23
+
// ✗ A pre-built instance as the default export — not a factory.
24
+
exportdefaultcreateShikiService()
25
+
26
+
// ✓ The factory itself.
27
+
exportdefaultcreateShikiService
28
+
```
29
+
30
+
## Fix
31
+
32
+
A service package's default export must be its `create<X>Service` factory, returning a `DevframeServiceDefinition` — an object with `package`, `version`, `scope`, and a `setup` function. See [Cross-Plugin Services](/guide/services#wire-services) for the full shape.
33
+
34
+
## Source
35
+
36
+
-[`packages/devframe/src/node/host-services.ts`](https://github.com/devframes/devframe/blob/main/packages/devframe/src/node/host-services.ts) — `install()` validates its input; the barrier flush validates imported factories and the definitions they return.
# DF0071: Deferred Service Installation Failed On Connect
6
+
7
+
## Message
8
+
9
+
> Deferred service installation failed while flushing on the first client connection: `{reason}`
10
+
11
+
## Cause
12
+
13
+
Queued wire-service installs are normally flushed by the host calling `ctx.services.ready()` once every devframe's setup has run — the first-party adapters (`initDevframe`, `createBuild`, `createCac`, `initHub`) all do. As a safety net, a host that never calls it still gets the flush right before the first client RPC connection is served. When that deferred flush fails (a `required` service missing, an unsatisfied version range, a throwing `setup`), the error can only be reported — a connection hook is no place to crash — so it surfaces as this diagnostic instead of a startup failure.
14
+
15
+
## Fix
16
+
17
+
Call `ctx.services.ready()` explicitly after every devframe's setup has run, so installation errors throw at startup where they can be acted on:
18
+
19
+
```ts
20
+
awaitdevframe.setup(ctx)
21
+
awaitctx.services.ready()
22
+
```
23
+
24
+
The `reason` carries the underlying error (typically [`DF0067`](/errors/DF0067), [`DF0068`](/errors/DF0068), or a service `setup` failure) — fix that root cause as its own page describes.
25
+
26
+
## Source
27
+
28
+
-[`packages/devframe/src/node/rpc-core.ts`](https://github.com/devframes/devframe/blob/main/packages/devframe/src/node/rpc-core.ts) — `createContextRpcServer()`'s connect hook reports a failing deferred flush.
See [Cross-Plugin Services](./services#wire-services).
219
+
209
220
## Settings
210
221
211
222
A scoped client also exposes a top-level persisted `settings` store, synced from the server. Read and write per-user (`global`) or per-workspace (`project`) values:
Copy file name to clipboardExpand all lines: docs/guide/devframe-definition.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -51,6 +51,7 @@ export default defineDevframe({
51
51
|`basePath`|`string`| Optional mount path override. Defaults depend on the adapter: `/` for standalone (`cli` / `build`), `/.<id>/` for hosted (`vite` / `embedded`). |
52
52
|`duplicationStrategy`|`'warn' \| 'silent' \| 'throw' \| 'duplicate'`| How a hub reacts when another devframe sharing this `id` is mounted onto the same hub. Defaults to `'warn'`. See [Hub](./hub). Hub adapters consult it; standalone adapters ignore it. |
53
53
|`capabilities`|`{ dev?, build? }`| Per-runtime feature flags. A `boolean` applies to the runtime as a whole; an object enables individual features. |
54
+
|`services`|`DevframeServiceInput[]`| Wire services this devframe consumes — descriptors (`{ package, version?, required?, options? }`) the adapter imports against the plugin's own dependencies, or ready definitions. See [Cross-Plugin Services](./services#wire-services). |
54
55
|`setup`|`(ctx, info?) => void \| Promise<void>`|**Required.** Server-side entry point. Runs in every runtime. The optional second argument carries runtime metadata — most notably the parsed CLI `flags` when running under `createCac`. |
55
56
|`cli`|`DevframeCliOptions`| Defaults for the CLI adapter. See [CLI options](#cli-options) below. |
Copy file name to clipboardExpand all lines: docs/guide/services.md
+78-1Lines changed: 78 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -8,6 +8,8 @@ outline: deep
8
8
9
9
Every devframe mounted into the same host shares one context, so services registered by one `setup(ctx)` are visible to every other.
10
10
11
+
The registry has two tiers: in-process services (`provide`/`get`, this page's first half) hand live objects between plugins on the node side, and [wire services](#wire-services) additionally register RPC functions and advertise themselves to browser clients, so UIs can feature-detect a capability and degrade when it is absent.
12
+
11
13
## Providing a service
12
14
13
15
Augment the `DevframeServicesRegistry` interface with your service's id and type, then provide the implementation at setup time:
A **wire service** is a shared server-side capability packaged as its own npm module — open-in-editor, syntax highlighting, anything several plugins would otherwise re-implement and re-bundle. A host installs it once; every plugin calls it in-process, every client calls it over RPC, and client UIs feature-detect it to fall back gracefully (hide the "open in editor" button, render un-highlighted code).
77
+
78
+
### Shipping one
79
+
80
+
A service package's default export is its factory, returning a `DevframeServiceDefinition`:
returnapi// the node API served from ctx.services.get(package)
97
+
},
98
+
}
99
+
}
100
+
```
101
+
102
+
Two declaration merges make it fully typed for consumers: the fully-qualified RPC ids go into `DevframeRpcServerFunctions`, and the package → scope mapping into `DevframeServicesScopeRegistry` (so a client's `services.get()` returns a scoped, typed RPC handle).
103
+
104
+
### Installing
105
+
106
+
A host with the factory at hand installs explicitly; a plugin declares what it consumes on its definition and the adapter resolves the package **against the plugin's own dependencies**:
Entries are optional by default — a package that isn't installed is skipped and clients see `has() === false`. Mark an entry `required: true` to fail hard instead ([`DF0067`](https://devfra.me/errors/DF0067) on a missing package, [`DF0068`](https://devfra.me/errors/DF0068) on an unsatisfied `version` range; without it a range mismatch only warns with [`DF0069`](https://devfra.me/errors/DF0069)).
122
+
123
+
Installs queue until the adapter fires the `ctx.services.ready()` barrier after every devframe's setup has run. There each service is constructed **once**, with the option sets from every declarer merged — through the definition's `mergeOptions` when it declares one, otherwise shallow-merged in declaration order, so a host installing last wins. After the barrier, installing an already-installed package returns the existing API and warns ([`DF0066`](https://devfra.me/errors/DF0066)) when its options had to be ignored.
124
+
125
+
Server-side consumers get the node API from the same registry — `ctx.services.get('@devframes/service-open')` or `whenAvailable` — with no RPC hop.
126
+
127
+
### Feature-detecting on the client
128
+
129
+
Installed services are advertised through the `devframe:services`[shared state](./shared-state); the client mirrors it on `rpc.services`:
130
+
131
+
```ts
132
+
const rpc =awaitconnectDevframe()
133
+
134
+
if (rpc.services.has('@devframes/service-open')) {
135
+
const open =rpc.services.get('@devframes/service-open')!
136
+
awaitopen.rpc.call('open-in-editor', { path })
66
137
}
138
+
139
+
// reactive UI: subscribe to the underlying shared state
140
+
const state =awaitrpc.services.state()
141
+
state.on('updated', render)
67
142
```
68
143
144
+
`has()`/`get()`/`keys()` are synchronous snapshots of the advertisement — before the first sync lands they read as empty, and `get()` returns `undefined` rather than throwing, so the natural shape of consuming code is "render the fallback until the service appears". Each handle carries the advertised `version` and `meta` for finer gating.
145
+
69
146
## Services, RPC, or shared state?
70
147
71
148
Each mechanism covers a different direction of travel:
@@ -74,4 +151,4 @@ Each mechanism covers a different direction of travel:
74
151
-**[RPC](./rpc)** — browser-to-node: a client invokes a named function over the connection.
75
152
-**[Shared state](./shared-state)** — data synchronized between node and every connected client; values must serialize.
76
153
77
-
A capability meant for *other plugins* belongs in a service; a capability meant for *UIs or agents* belongs in RPC.
154
+
A capability meant for *other plugins* belongs in a service; a capability meant for *UIs or agents* belongs in RPC. A capability meant for both — and shared across many plugins — is a [wire service](#wire-services), which combines all three: a node API for plugins, scoped RPC for clients, and a shared-state advertisement for feature-detection.
0 commit comments