Skip to content

Commit 0147cc5

Browse files
committed
feat(data-inspector): full dev server, /inject rename, richer source select, polished save/list UI
- pnpm dev now runs a full devframe dev server: the SPA vite config mounts dataInspectorVitePlugin({ devMiddleware: true }), so the HMR frontend has a live RPC + WS backend (with the example source) instead of a backend-less SPA - rename the /agent subpath export to /inject (dir, exports, tsdown, alias, imports, docs); the API is unchanged - new DataSourceSelect component replaces the generic FormSelect for the source picker: renders each source's icon (fallback i-ph:database-duotone) and a one-line description in the dropdown - Save-query dialog uses ActionToggleGroup for the save location (Just me / Shared with team) with a contextual hint - SavedQueriesPanel renders filter markers as pretty pill badges and drops the per-row suggested/scope badge - example source gains a filter-carrying suggested query to exercise it
1 parent cad3bcf commit 0147cc5

16 files changed

Lines changed: 145 additions & 62 deletions

File tree

alias.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ export const alias = {
7070
'@devframes/plugin-data-inspector/node': p('data-inspector/src/node/index.ts'),
7171
'@devframes/plugin-data-inspector/registry': p('data-inspector/src/registry/index.ts'),
7272
'@devframes/plugin-data-inspector/engine': p('data-inspector/src/engine/index.ts'),
73-
'@devframes/plugin-data-inspector/agent': p('data-inspector/src/agent/index.ts'),
73+
'@devframes/plugin-data-inspector/inject': p('data-inspector/src/inject/index.ts'),
7474
'@devframes/plugin-data-inspector/cli': p('data-inspector/src/cli.ts'),
7575
'@devframes/plugin-data-inspector/vite': p('data-inspector/src/vite.ts'),
7676
'@devframes/plugin-data-inspector': p('data-inspector/src/index.ts'),

docs/plugins/data-inspector.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,15 +112,15 @@ devframe-data-inspector attach # attach to a process running the a
112112
The target process opts in by starting the agent:
113113

114114
```ts
115-
import { exposeDataInspector } from '@devframes/plugin-data-inspector/agent'
115+
import { exposeDataInspector } from '@devframes/plugin-data-inspector/inject'
116116

117117
await exposeDataInspector()
118118
```
119119

120120
or with zero code changes:
121121

122122
```sh
123-
DEVFRAME_DATA_INSPECTOR=1 node --import @devframes/plugin-data-inspector/agent server.js
123+
DEVFRAME_DATA_INSPECTOR=1 node --import @devframes/plugin-data-inspector/inject server.js
124124
```
125125

126126
The agent binds `127.0.0.1`, requires devframe's trust handshake with a per-run pre-shared token, and advertises its endpoint in `node_modules/.data-inspector/agent.json``devframe-data-inspector attach` consumes it automatically (or pass `ws://…` and `--token` explicitly). Queries execute inside the target process, where the live objects are. Treat the endpoint like a debugger port.

plugins/data-inspector/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,15 @@ Static exports embed the dataset and run the same query engine client-side, so s
4848
## Attach to another Node process
4949

5050
```ts
51-
import { exposeDataInspector } from '@devframes/plugin-data-inspector/agent'
51+
import { exposeDataInspector } from '@devframes/plugin-data-inspector/inject'
5252

5353
await exposeDataInspector()
5454
```
5555

5656
or with zero code changes:
5757

5858
```sh
59-
DEVFRAME_DATA_INSPECTOR=1 node --import @devframes/plugin-data-inspector/agent server.js
59+
DEVFRAME_DATA_INSPECTOR=1 node --import @devframes/plugin-data-inspector/inject server.js
6060
```
6161

6262
The agent binds `127.0.0.1`, requires devframe's trust handshake with a per-run token by default, and advertises its endpoint in `node_modules/.data-inspector/agent.json`, which `devframe-data-inspector attach` picks up automatically.

plugins/data-inspector/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@
2222
"sideEffects": false,
2323
"exports": {
2424
".": "./dist/index.mjs",
25-
"./agent": "./dist/agent/index.mjs",
2625
"./client": "./dist/client/index.mjs",
26+
"./inject": "./dist/inject/index.mjs",
2727
"./cli": "./dist/cli.mjs",
2828
"./engine": "./dist/engine/index.mjs",
2929
"./node": "./dist/node/index.mjs",

plugins/data-inspector/src/cli.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
* devframe-data-inspector build stats.json # self-contained static export
99
* ```
1010
*/
11-
import type { AgentDiscovery } from './agent/index'
11+
import type { AgentDiscovery } from './inject/index'
1212
import { existsSync, readFileSync } from 'node:fs'
1313
import { cp, mkdir, writeFile } from 'node:fs/promises'
1414
import { createServer } from 'node:http'
@@ -21,8 +21,8 @@ import { DEVFRAME_CONNECTION_META_FILENAME } from 'devframe/constants'
2121
import { serveStaticNodeMiddleware } from 'devframe/utils/serve-static'
2222
import { getPort } from 'get-port-please'
2323
import pkg from '../package.json' with { type: 'json' }
24-
import { AGENT_DISCOVERY_FILE } from './agent/index'
2524
import { createDataInspectorDevframe } from './index'
25+
import { AGENT_DISCOVERY_FILE } from './inject/index'
2626
import { createFileDataSource, loadDataFile } from './node/files'
2727
import { listDataSources, registerDataSource } from './registry/index'
2828

@@ -77,7 +77,7 @@ export function createDataInspectorCli() {
7777
const discoveryPath = resolve(process.cwd(), AGENT_DISCOVERY_FILE)
7878
if (!existsSync(discoveryPath)) {
7979
console.error(`No agent endpoint given and no discovery file at ${discoveryPath}.`)
80-
console.error('Start the target with the agent (see @devframes/plugin-data-inspector/agent) or pass a ws:// endpoint.')
80+
console.error('Start the target with the agent (see @devframes/plugin-data-inspector/inject) or pass a ws:// endpoint.')
8181
process.exitCode = 1
8282
return
8383
}

plugins/data-inspector/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export interface DataInspectorDevframeOptions {
3333
/**
3434
* Require the trust handshake on the standalone server. Defaults to
3535
* `false` (auto-trust) for the single-user localhost CLI. The in-process
36-
* agent (`@devframes/plugin-data-inspector/agent`) defaults to `true`.
36+
* agent (`@devframes/plugin-data-inspector/inject`) defaults to `true`.
3737
*/
3838
auth?: boolean
3939
/**

plugins/data-inspector/src/agent/index.ts renamed to plugins/data-inspector/src/inject/index.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
/**
2-
* In-process agent — expose this process's registered data sources to an
3-
* external data-inspector UI.
2+
* Inject the data inspector into a running process — expose that process's
3+
* registered data sources to an external data-inspector UI.
44
*
55
* Two ways in:
66
*
77
* ```ts
88
* // 1. explicit, from the target's code
9-
* import { exposeDataInspector } from '@devframes/plugin-data-inspector/agent'
9+
* import { exposeDataInspector } from '@devframes/plugin-data-inspector/inject'
1010
* import { registerDataSource } from '@devframes/plugin-data-inspector/registry'
1111
*
1212
* registerDataSource({ id: 'app:store', title: 'App store', data: () => store })
1313
* await exposeDataInspector()
1414
* ```
1515
*
1616
* ```sh
17-
* # 2. zero code change — preload the agent into any Node process
18-
* DEVFRAME_DATA_INSPECTOR=1 node --import @devframes/plugin-data-inspector/agent server.js
17+
* # 2. zero code change — preload the inject entry into any Node process
18+
* DEVFRAME_DATA_INSPECTOR=1 node --import @devframes/plugin-data-inspector/inject server.js
1919
* ```
2020
*
21-
* The agent binds `127.0.0.1` and requires devframe's trust handshake by
21+
* It binds `127.0.0.1` and requires devframe's trust handshake by
2222
* default: a random pre-shared token is minted per run, printed to stderr,
2323
* and written (with the endpoint) to the discovery file
2424
* `<cwd>/node_modules/.data-inspector/agent.json`, which
@@ -146,7 +146,7 @@ export async function exposeDataInspector(options: ExposeDataInspectorOptions =
146146
}
147147
}
148148

149-
// `node --import @devframes/plugin-data-inspector/agent` path: opt in via env
149+
// `node --import @devframes/plugin-data-inspector/inject` path: opt in via env
150150
// so merely importing the module never opens a port.
151151
if (process.env.DEVFRAME_DATA_INSPECTOR === '1' || process.env.DEVFRAME_DATA_INSPECTOR === 'true') {
152152
void exposeDataInspector({

plugins/data-inspector/src/node/example-source.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,12 @@ export function createExampleDataSource(ctx?: DevframeNodeContext): DataSourceEn
132132
description: 'Filter and project an array',
133133
query: 'playground.build.modules.[sizeKb > 80].({ id, sizeKb })',
134134
},
135+
{
136+
title: 'Middlewares (data only)',
137+
description: 'The exclude-functions filter strips the handlers',
138+
query: 'playground.middlewares',
139+
excludeFunctions: true,
140+
},
135141
{ title: 'Everything', query: '' },
136142
],
137143
}

plugins/data-inspector/src/spa/App.vue

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ import type { Query, SavedQueryScope } from '../engine'
33
import Button from '@antfu/design/components/Action/ActionButton.vue'
44
import ActionDarkToggle from '@antfu/design/components/Action/ActionDarkToggle.vue'
55
import DisplayBadge from '@antfu/design/components/Display/DisplayBadge.vue'
6-
import FormSelect from '@antfu/design/components/Form/FormSelect.vue'
76
import LayoutSplitPane from '@antfu/design/components/Layout/LayoutSplitPane.vue'
87
import { Pane } from 'splitpanes'
9-
import { computed, onMounted, ref } from 'vue'
8+
import { onMounted, ref } from 'vue'
109
import DataShapePanel from './components/DataShapePanel.vue'
10+
import DataSourceSelect from './components/DataSourceSelect.vue'
1111
import QueryEditor from './components/QueryEditor.vue'
1212
import QuerySettings from './components/QuerySettings.vue'
1313
import ResultViewer from './components/ResultViewer.vue'
@@ -21,10 +21,6 @@ import '@antfu/design/styles.css'
2121
const wb = useWorkbench()
2222
const savedApi = useSavedQueries()
2323
24-
const sourceOptions = computed(() =>
25-
wb.sources.value.map(s => ({ value: s.id, label: s.title })),
26-
)
27-
2824
const showDataSourceDetails = ref(false)
2925
3026
onMounted(async () => {
@@ -120,8 +116,7 @@ function queryAppend(path: string): void {
120116
<div class="font-semibold text-xs op-fade uppercase tracking-wide select-none">
121117
Data Source
122118
</div>
123-
<FormSelect v-model="wb.sourceId.value" :options="sourceOptions" placeholder="Data source" class="text-sm font-semibold text-primary" />
124-
<DisplayBadge v-if="wb.activeSource.value?.static" text="static" :color="false" />
119+
<DataSourceSelect v-model="wb.sourceId.value" :sources="wb.sources.value" placeholder="Data source" />
125120
<div v-if="wb.activeSource.value?.description && !showDataSourceDetails" class="text-xs op-fade ws-nowrap of-hidden shrink" :title="wb.activeSource.value?.description">
126121
{{ wb.activeSource.value?.description }}
127122
</div>
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<script setup lang="ts">
2+
import type { DataSourceMeta } from '../../engine'
3+
import { SelectContent, SelectIcon, SelectItem, SelectItemIndicator, SelectPortal, SelectRoot, SelectTrigger, SelectViewport } from 'reka-ui'
4+
import { computed } from 'vue'
5+
6+
const props = defineProps<{
7+
sources: DataSourceMeta[]
8+
placeholder?: string
9+
}>()
10+
11+
const model = defineModel<string>()
12+
13+
/** Fallback icon when a source doesn't declare one. */
14+
const FALLBACK_ICON = 'i-ph:database-duotone'
15+
16+
const active = computed(() => props.sources.find(s => s.id === model.value))
17+
18+
function iconOf(source: DataSourceMeta | undefined): string {
19+
return source?.icon || FALLBACK_ICON
20+
}
21+
</script>
22+
23+
<template>
24+
<SelectRoot v-model="model">
25+
<SelectTrigger
26+
class="text-sm px-2.5 outline-none border border-base rounded bg-base inline-flex gap-2 h-9 min-w-52 max-w-80 transition items-center justify-between data-[disabled]:op50 focus-visible:ring-2 focus-visible:ring-primary-500/40"
27+
>
28+
<span class="inline-flex items-center gap-2 min-w-0">
29+
<span class="shrink-0 color-active" :class="iconOf(active)" aria-hidden="true" />
30+
<span class="truncate font-semibold text-primary">{{ active?.title ?? placeholder ?? 'Data source' }}</span>
31+
</span>
32+
<SelectIcon class="op-fade shrink-0">
33+
<svg width="1em" height="1em" viewBox="0 0 24 24" aria-hidden="true">
34+
<path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" d="m6 9l6 6l6-6" />
35+
</svg>
36+
</SelectIcon>
37+
</SelectTrigger>
38+
<SelectPortal>
39+
<SelectContent
40+
position="popper"
41+
:side-offset="6"
42+
class="border border-base rounded-lg bg-base min-w-[--reka-select-trigger-width] max-w-100 shadow-lg z-dropdown overflow-hidden"
43+
>
44+
<SelectViewport class="p-1">
45+
<SelectItem
46+
v-for="source in sources"
47+
:key="source.id"
48+
:value="source.id"
49+
class="text-sm color-base py-1.5 pl-2 pr-2 outline-none rounded-md flex gap-2 cursor-pointer select-none transition items-start relative data-[highlighted]:bg-active"
50+
>
51+
<span class="shrink-0 mt-0.5 color-active" :class="iconOf(source)" aria-hidden="true" />
52+
<span class="flex flex-col min-w-0 flex-1">
53+
<span class="flex items-center gap-1.5">
54+
<span class="truncate font-medium">{{ source.title }}</span>
55+
<span v-if="source.static" class="shrink-0 text-10px op-fade border border-base rounded px-1">static</span>
56+
</span>
57+
<span v-if="source.description" class="text-11px color-faint truncate">{{ source.description }}</span>
58+
</span>
59+
<SelectItemIndicator class="color-active inline-flex items-center shrink-0 mt-0.5">
60+
<svg width="0.85em" height="0.85em" viewBox="0 0 24 24" aria-hidden="true">
61+
<path fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" d="M20 6L9 17l-5-5" />
62+
</svg>
63+
</SelectItemIndicator>
64+
</SelectItem>
65+
</SelectViewport>
66+
</SelectContent>
67+
</SelectPortal>
68+
</SelectRoot>
69+
</template>

0 commit comments

Comments
 (0)