Skip to content

Commit bdd357b

Browse files
committed
refactor: move snapshotRpc under rpc.snapshot on DevframeDefinition
Group the RPC static-build opt-in under a new `rpc` config object: `DevframeDefinition.rpc.snapshot` (new `DevframeRpcOptions`) replaces the top-level `snapshotRpc`. Entry types and the `applySnapshotRpc` build helper are unchanged; the git plugin, build adapter, DF0072 message, tests, and docs are updated to the new shape.
1 parent fd01bb7 commit bdd357b

11 files changed

Lines changed: 56 additions & 43 deletions

File tree

‎docs/guide/devframe-definition.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export default defineDevframe({
5454
| `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. |
5555
| `capabilities` | `{ dev?, build? }` | Per-runtime feature flags. A `boolean` applies to the runtime as a whole; an object enables individual features. |
5656
| `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). |
57-
| `snapshotRpc` | `(string \| { method, inputs })[]` | Opt an RPC function this devframe doesn't own (e.g. a wire service's) into the static build's dump. A bare method id bakes the no-argument call; `{ method, inputs }` bakes one record per argument-tuple, where `inputs` is a list of tuples or an async `(ctx) => tuples` provider (so it can enumerate at build time via the service's node API). The first tuple's result becomes the fallback. |
57+
| `rpc` | `{ snapshot?: (string \| { method, inputs })[] }` | RPC-level config. `rpc.snapshot` opts an RPC function this devframe doesn't own (e.g. a wire service's) into the static build's dump. A bare method id bakes the no-argument call; `{ method, inputs }` bakes one record per argument-tuple, where `inputs` is a list of tuples or an async `(ctx) => tuples` provider (so it can enumerate at build time via the service's node API). The first tuple's result becomes the fallback. |
5858
| `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`. |
5959
| `cli` | `DevframeCliOptions` | Defaults for the CLI adapter. See [CLI options](#cli-options) below. |
6060

‎docs/guide/services.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ state.on('updated', render)
155155

156156
**`@devframes/service-open`** (`devframes:service:open`) opens files in the user's editor (`open-in-editor`, with optional `line`/`column`) or reveals them in the OS file explorer (`open-in-finder`). Paths may be absolute or relative to the workspace root (so a client with only a workspace-relative path — a message's file position, say — calls it directly); the service refuses anything outside the workspace root and the configured extra `roots` (`DS_OPEN_0002`), and gates editor commands to the `KNOWN_EDITORS` picklist. Options: `{ editor?, roots? }` — the preferred editor (later installer wins) and additional openable directories (merged as a union). It supersedes the per-plugin `devframe/recipes/common-rpc-functions` registrations, now deprecated.
157157

158-
**`@devframes/service-git`** (`devframes:service:git`) runs read/write git operations over RPC — `status`, `log`, `show`, `diff`, `branches`, `stage`, `unstage`, `commit` — with parsed, typed results, so a devframe (the git plugin, or any tool) consumes git without shelling out itself. It operates on a single repo fixed at install (`{ cwd? }`, defaulting to the context cwd; root discovered once). Write ops are always exposed — authorization is the host's connection-trust boundary. The service defines no `dump`/`snapshot`; a devframe bakes the read ops it wants into a static build via [`snapshotRpc`](./devframe-definition). Client-supplied revisions are guarded against option injection.
158+
**`@devframes/service-git`** (`devframes:service:git`) runs read/write git operations over RPC — `status`, `log`, `show`, `diff`, `branches`, `stage`, `unstage`, `commit` — with parsed, typed results, so a devframe (the git plugin, or any tool) consumes git without shelling out itself. It operates on a single repo fixed at install (`{ cwd? }`, defaulting to the context cwd; root discovered once). Write ops are always exposed — authorization is the host's connection-trust boundary. The service defines no `dump`/`snapshot`; a devframe bakes the read ops it wants into a static build via [`rpc.snapshot`](./devframe-definition). Client-supplied revisions are guarded against option injection.
159159

160160
**`@devframes/service-shiki`** (`devframes:service:shiki`) renders [Shiki](https://shiki.style) syntax highlighting on the server, so plugin bundles stop shipping grammars and themes. Three RPC queries — `highlight` (dual-theme HTML), `code-to-hast`, and `code-to-tokens` (for renderers that own their DOM, e.g. diff views) — all client-`cacheable` and LRU-cached server-side per `(code, lang, themes)`. Unknown languages degrade to plain text. Options: `{ themes?, langs? }` — the default light/dark pair (defaults `vitesse-light`/`vitesse-dark`, matching the design system; later installer wins) and languages to eagerly load (merged as a union).
161161

‎packages/devframe/src/adapters/__tests__/build.test.ts‎

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,10 @@ describe('adapters/build', () => {
6262
}
6363
})
6464

65-
it('bakes snapshotRpc methods a devframe does not own into the dump', async () => {
65+
it('bakes rpc.snapshot methods a devframe does not own into the dump', async () => {
6666
const outDir = mkdtempSync(join(tmpdir(), 'devframe-build-test-out-'))
6767
// A dump-less query RPC (as a wire service would register) that the
68-
// devframe opts into baking via `snapshotRpc` — string (no-arg), static
68+
// devframe opts into baking via `rpc.snapshot` — string (no-arg), static
6969
// inputs, and an async provider.
7070
const def = baseDevframe({
7171
setup: (ctx) => {
@@ -79,11 +79,13 @@ describe('adapters/build', () => {
7979
handler: (input: { value: string }) => input,
8080
})
8181
},
82-
snapshotRpc: [
83-
'demo:ping',
84-
{ method: 'demo:echo', inputs: [[{ value: 'a' }]] },
85-
{ method: 'demo:echo', inputs: async () => [[{ value: 'b' }]] },
86-
],
82+
rpc: {
83+
snapshot: [
84+
'demo:ping',
85+
{ method: 'demo:echo', inputs: [[{ value: 'a' }]] },
86+
{ method: 'demo:echo', inputs: async () => [[{ value: 'b' }]] },
87+
],
88+
},
8789
})
8890
try {
8991
await createBuild(def, { outDir })
@@ -92,7 +94,7 @@ describe('adapters/build', () => {
9294
expect(manifest['demo:ping']?.type).toBe('query')
9395
expect(manifest['demo:ping'].fallback).toBeTruthy()
9496
// `demo:echo` baked a record per provided tuple (static + provider merged
95-
// — the last snapshotRpc entry for a method wins).
97+
// — the last rpc.snapshot entry for a method wins).
9698
expect(manifest['demo:echo']?.type).toBe('query')
9799
expect(Object.keys(manifest['demo:echo'].records).length).toBeGreaterThanOrEqual(1)
98100
}
@@ -101,12 +103,12 @@ describe('adapters/build', () => {
101103
}
102104
})
103105

104-
it('warns (DF0072) when snapshotRpc names an unregistered method', async () => {
106+
it('warns (DF0072) when rpc.snapshot names an unregistered method', async () => {
105107
const outDir = mkdtempSync(join(tmpdir(), 'devframe-build-test-out-'))
106108
try {
107109
// Does not throw — a missing target is a warning, the build proceeds.
108110
await expect(
109-
createBuild(baseDevframe({ snapshotRpc: ['does:not:exist'] }), { outDir }),
111+
createBuild(baseDevframe({ rpc: { snapshot: ['does:not:exist'] } }), { outDir }),
110112
).resolves.toBeUndefined()
111113
}
112114
finally {

‎packages/devframe/src/adapters/build.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,10 @@ export async function createBuild(d: DevframeDefinition, options: CreateBuildOpt
9696
await ctx.services.ready()
9797
await d.setup(ctx)
9898

99-
// Bake declared `snapshotRpc` methods (typically a wire service's RPC the
99+
// Bake declared `rpc.snapshot` methods (typically a wire service's RPC the
100100
// devframe doesn't own) into the static dump by attaching a `dump` to their
101101
// registered definitions — the service itself defines none.
102-
applySnapshotRpc(ctx, d.snapshotRpc)
102+
applySnapshotRpc(ctx, d.rpc?.snapshot)
103103

104104
await fs.mkdir(resolve(outDir, DEVFRAME_RPC_DUMP_DIRNAME), { recursive: true })
105105

@@ -142,7 +142,7 @@ export async function createBuild(d: DevframeDefinition, options: CreateBuildOpt
142142
}
143143

144144
/**
145-
* Attach a `dump` to each {@link DevframeDefinition.snapshotRpc} target so the
145+
* Attach a `dump` to each {@link DevframeRpcOptions.snapshot} target so the
146146
* static collector bakes it, even though the (service-owned) definition
147147
* declares no dump of its own. A bare method id becomes `snapshot: true`
148148
* (bakes the no-arg call); `{ method, inputs }` bakes one record per resolved

‎packages/devframe/src/node/diagnostics.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ export const diagnostics = defineDiagnostics({
196196
},
197197
DF0072: {
198198
why: (p: { method: string }) =>
199-
`\`snapshotRpc\` names "${p.method}", but no RPC function is registered under that id — nothing to bake into the static build.`,
199+
`\`rpc.snapshot\` names "${p.method}", but no RPC function is registered under that id — nothing to bake into the static build.`,
200200
fix: 'Check the method id, and ensure the service/plugin that registers it is installed (e.g. declared in `services`) before the build collects the dump.',
201201
},
202202
},

‎packages/devframe/src/types/devframe.ts‎

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,14 @@ export interface DevframeDefinition {
363363
* `client.services.has(pkg)` and degrade.
364364
*/
365365
services?: DevframeServiceInput[]
366+
/** RPC-level configuration for this devframe (see {@link DevframeRpcOptions}). */
367+
rpc?: DevframeRpcOptions
368+
/** Server-side setup — the primary entrypoint. Runs in every runtime. */
369+
setup: (ctx: DevframeNodeContext, info?: DevframeSetupInfo) => void | Promise<void>
370+
cli?: DevframeCliOptions
371+
}
372+
373+
export interface DevframeRpcOptions {
366374
/**
367375
* Opt an RPC function into the static-build snapshot **without owning its
368376
* definition** — the mechanism a devframe uses to bake a wire service's
@@ -377,10 +385,7 @@ export interface DevframeDefinition {
377385
* executes the target's own handler per tuple; the first tuple's result
378386
* becomes the fallback so any call variant resolves to a baked value.
379387
*/
380-
snapshotRpc?: DevframeSnapshotRpcEntry[]
381-
/** Server-side setup — the primary entrypoint. Runs in every runtime. */
382-
setup: (ctx: DevframeNodeContext, info?: DevframeSetupInfo) => void | Promise<void>
383-
cli?: DevframeCliOptions
388+
snapshot?: DevframeSnapshotRpcEntry[]
384389
}
385390

386391
/** Argument-tuples to bake for a {@link DevframeSnapshotRpcEntry}, or a provider that computes them at build time. */
@@ -389,7 +394,7 @@ export type DevframeSnapshotRpcInputs
389394
| ((ctx: DevframeNodeContext) => readonly (readonly unknown[])[] | Promise<readonly (readonly unknown[])[]>)
390395

391396
/**
392-
* One {@link DevframeDefinition.snapshotRpc} entry: a bare method id (bakes
397+
* One {@link DevframeRpcOptions.snapshot} entry: a bare method id (bakes
393398
* the no-argument call) or a method plus the argument-tuples to bake.
394399
*/
395400
export type DevframeSnapshotRpcEntry

‎plugins/git/README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ All git work runs through the [`@devframes/service-git`](../../services/git)
5555
wire service, which this devframe declares (`services`) and its SPA calls
5656
directly over `devframes:service:git:*`. The read functions are `query`
5757
functions that degrade to an empty, `isRepo: false` result outside a git
58-
repository; the definition opts them into the static build via `snapshotRpc`
58+
repository; the definition opts them into the static build via `rpc.snapshot`
5959
(resolved live over WebSocket in dev, served from a build-time snapshot for
6060
static deploys).
6161

‎plugins/git/src/index.ts‎

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Types-only: loads service-git's registry augmentation so
2-
// `ctx.services.get('@devframes/service-git')` is typed in `snapshotRpc`.
2+
// `ctx.services.get('@devframes/service-git')` is typed in `rpc.snapshot`.
33
import type {} from '@devframes/service-git'
44
import type { DevframeDefinition, RemoteAssets } from 'devframe'
55
import process from 'node:process'
@@ -82,22 +82,24 @@ export function createGitDevframe(options: GitDevframeOptions = {}): DevframeDef
8282
// their no-arg call; log bakes the 200-commit head; show bakes one
8383
// (patch-less) record per commit, enumerated at build time via the
8484
// service's node API.
85-
snapshotRpc: [
86-
'devframes:service:git:status',
87-
'devframes:service:git:branches',
88-
'devframes:service:git:diff',
89-
{ method: 'devframes:service:git:log', inputs: [[{ limit: 200 }]] },
90-
{
91-
method: 'devframes:service:git:show',
92-
inputs: async (ctx) => {
93-
const git = ctx.services.get(GIT_SERVICE)
94-
if (!git)
95-
return []
96-
const { commits } = await git.log({ limit: 200 })
97-
return commits.map(commit => [{ hash: commit.hash, patch: false }])
85+
rpc: {
86+
snapshot: [
87+
'devframes:service:git:status',
88+
'devframes:service:git:branches',
89+
'devframes:service:git:diff',
90+
{ method: 'devframes:service:git:log', inputs: [[{ limit: 200 }]] },
91+
{
92+
method: 'devframes:service:git:show',
93+
inputs: async (ctx) => {
94+
const git = ctx.services.get(GIT_SERVICE)
95+
if (!git)
96+
return []
97+
const { commits } = await git.log({ limit: 200 })
98+
return commits.map(commit => [{ hash: commit.hash, patch: false }])
99+
},
98100
},
99-
},
100-
],
101+
],
102+
},
101103
setup() {},
102104
})
103105
}

‎plugins/git/test/git.test.ts‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,13 @@ describe('@devframes/plugin-git', () => {
6969
})
7070
})
7171

72-
describe('@devframes/plugin-git (snapshotRpc build baking)', () => {
73-
it('bakes the git service read ops declared in snapshotRpc', async () => {
72+
describe('@devframes/plugin-git (rpc.snapshot build baking)', () => {
73+
it('bakes the git service read ops declared in rpc.snapshot', async () => {
7474
const repo = createTempRepo()
7575
try {
7676
const ctx = await createDashboardContext(repo.dir, 'build')
77-
// Mirror `createBuild`: honor the definition's `snapshotRpc` before collecting.
78-
applySnapshotRpc(ctx, createGitDevframe().snapshotRpc)
77+
// Mirror `createBuild`: honor the definition's `rpc.snapshot` before collecting.
78+
applySnapshotRpc(ctx, createGitDevframe().rpc?.snapshot)
7979
const dump = await collectStaticRpcDump(ctx.rpc.definitions.values(), ctx)
8080

8181
const status = dump.manifest['devframes:service:git:status']

‎tests/__snapshots__/tsnapi/devframe/index.snapshot.d.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ export interface DevframeDefinition {
147147
build?: boolean;
148148
};
149149
services?: DevframeServiceInput[];
150-
snapshotRpc?: DevframeSnapshotRpcEntry[];
150+
rpc?: DevframeRpcOptions;
151151
setup: (_: DevframeNodeContext, _?: DevframeSetupInfo) => void | Promise<void>;
152152
cli?: DevframeCliOptions;
153153
}
@@ -229,6 +229,9 @@ export interface DevframeRpcConnectionRequest {
229229
get: (_: string) => string | null | undefined;
230230
};
231231
}
232+
export interface DevframeRpcOptions {
233+
snapshot?: DevframeSnapshotRpcEntry[];
234+
}
232235
export interface DevframeRpcServerFunctions {
233236
'anonymous:devframe:auth': (_: {
234237
authToken: string;

0 commit comments

Comments
 (0)