Skip to content

Commit 558e28c

Browse files
committed
refactor: eliminate duplicated logic in inspect, data-inspector, and simple-schema
Extract shared helpers for copy-pasted logic surfaced by jscpd: - inspect RPC functions share one toInvokeResult() envelope helper - data-inspector runQuery/runQueryAtPath share executeQuery() - simple-schema record/array/object share pushFieldIssues() - AgentSmart onInvoke/onRead share runAction()
1 parent f2632b2 commit 558e28c

8 files changed

Lines changed: 82 additions & 127 deletions

File tree

‎packages/devframe/src/utils/simple-schema.ts‎

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,15 @@ function runSync<T extends StandardSchemaV1>(
7979
return result
8080
}
8181

82+
/** Validate a single field, appending any issues to `issues` prefixed by `key`. */
83+
function pushFieldIssues(issues: Issue[], key: PropertyKey, schema: StandardSchemaV1, value: unknown): void {
84+
const result = runSync(schema, value)
85+
if (result.issues) {
86+
for (const issue of result.issues)
87+
issues.push({ message: issue.message, path: [key, ...(issue.path ?? [])] })
88+
}
89+
}
90+
8291
/** Any string. */
8392
export function string(): SimpleSchema<string> {
8493
return make('string', v => (typeof v === 'string' ? ok(v) : fail('Expected a string')))
@@ -147,13 +156,8 @@ export function record<V extends StandardSchemaV1>(
147156
return fail('Expected an object')
148157
const obj = v as Record<string, unknown>
149158
const issues: Issue[] = []
150-
for (const key of Object.keys(obj)) {
151-
const result = runSync(value, obj[key])
152-
if (result.issues) {
153-
for (const issue of result.issues)
154-
issues.push({ message: issue.message, path: [key, ...(issue.path ?? [])] })
155-
}
156-
}
159+
for (const key of Object.keys(obj))
160+
pushFieldIssues(issues, key, value, obj[key])
157161
return issues.length ? { issues } : ok(v as any)
158162
})
159163
}
@@ -166,13 +170,8 @@ export function array<T extends StandardSchemaV1>(
166170
if (!Array.isArray(v))
167171
return fail('Expected an array')
168172
const issues: Issue[] = []
169-
for (let i = 0; i < v.length; i++) {
170-
const result = runSync(item, v[i])
171-
if (result.issues) {
172-
for (const issue of result.issues)
173-
issues.push({ message: issue.message, path: [i, ...(issue.path ?? [])] })
174-
}
175-
}
173+
for (let i = 0; i < v.length; i++)
174+
pushFieldIssues(issues, i, item, v[i])
176175
return issues.length ? { issues } : ok(v as any)
177176
})
178177
}
@@ -203,13 +202,8 @@ export function object<T extends Record<string, StandardSchemaV1>>(
203202
return fail('Expected an object')
204203
const obj = v as Record<string, unknown>
205204
const issues: Issue[] = []
206-
for (const [key, schema] of entries) {
207-
const result = runSync(schema, obj[key])
208-
if (result.issues) {
209-
for (const issue of result.issues)
210-
issues.push({ message: issue.message, path: [key, ...(issue.path ?? [])] })
211-
}
212-
}
205+
for (const [key, schema] of entries)
206+
pushFieldIssues(issues, key, schema, obj[key])
213207
// Guard-only: return the original object so extra keys survive.
214208
return issues.length ? { issues } : ok(v as any)
215209
})

‎plugins/data-inspector/src/engine/query-engine.ts‎

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -107,13 +107,25 @@ function getCreateQuery(): Promise<CreateQuery> {
107107
}))
108108
}
109109

110-
export async function runQuery(target: unknown, query: string, options?: NormalizeOptions): Promise<QueryOutcome> {
110+
/**
111+
* Run the query against the target, select the node to return from its raw
112+
* result, and normalize that node into a wire-safe {@link QueryOutcome}.
113+
* The `select` step runs inside the timed section so re-descent costs (used
114+
* by {@link runQueryAtPath}) are reflected in `queryMs`.
115+
*/
116+
async function executeQuery(
117+
target: unknown,
118+
query: string,
119+
options: NormalizeOptions | undefined,
120+
select: (raw: unknown) => unknown,
121+
): Promise<QueryOutcome> {
111122
try {
112123
const started = performance.now()
113124
const createQuery = await getCreateQuery()
114125
const raw = createQuery(query)(target)
126+
const node = select(raw)
115127
const queryMs = Math.round((performance.now() - started) * 100) / 100
116-
const { data, stats } = normalize(raw, options)
128+
const { data, stats } = normalize(node, options)
117129
// The normalizer guarantees plain JSON, so this measures the actual wire payload.
118130
const payloadBytes = new TextEncoder().encode(JSON.stringify(data) ?? '').length
119131
return { ok: true, result: data, stats: { queryMs, normalize: stats, payloadBytes } }
@@ -124,28 +136,19 @@ export async function runQuery(target: unknown, query: string, options?: Normali
124136
}
125137
}
126138

139+
export function runQuery(target: unknown, query: string, options?: NormalizeOptions): Promise<QueryOutcome> {
140+
return executeQuery(target, query, options, raw => raw)
141+
}
142+
127143
/**
128144
* Lazy-expand a depth-truncated node: re-run the base query against the live
129145
* object, re-descend to the node the `NodePath` addresses, and normalize just
130146
* that subtree with a fresh depth budget. The path comes from a `$truncated:
131147
* 'depth'` marker the client is expanding, so the same filter options must be
132148
* threaded through (they shift array indices and drop keys).
133149
*/
134-
export async function runQueryAtPath(target: unknown, query: string, path: NodePath, options?: NormalizeOptions): Promise<QueryOutcome> {
135-
try {
136-
const started = performance.now()
137-
const createQuery = await getCreateQuery()
138-
const raw = createQuery(query)(target)
139-
const node = navigate(raw, path, options)
140-
const queryMs = Math.round((performance.now() - started) * 100) / 100
141-
const { data, stats } = normalize(node, options)
142-
const payloadBytes = new TextEncoder().encode(JSON.stringify(data) ?? '').length
143-
return { ok: true, result: data, stats: { queryMs, normalize: stats, payloadBytes } }
144-
}
145-
catch (error) {
146-
const e = error instanceof Error ? error : new Error(String(error))
147-
return { ok: false, error: { message: e.message, name: e.name } }
148-
}
150+
export function runQueryAtPath(target: unknown, query: string, path: NodePath, options?: NormalizeOptions): Promise<QueryOutcome> {
151+
return executeQuery(target, query, options, raw => navigate(raw, path, options))
149152
}
150153

151154
interface JoraStatEntry {
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import type { InvokeResult } from '../../types'
2+
3+
/**
4+
* Run an async operation and normalize it into an {@link InvokeResult}
5+
* envelope: time the call, and capture a thrown error into a serializable
6+
* shape rather than propagating it, so the inspector UI can render failures
7+
* inline alongside successes.
8+
*/
9+
export async function toInvokeResult(run: () => Promise<unknown>): Promise<InvokeResult> {
10+
const start = Date.now()
11+
try {
12+
const result = await run()
13+
return { ok: true, result, durationMs: Date.now() - start }
14+
}
15+
catch (error) {
16+
const e = error as Error
17+
return {
18+
ok: false,
19+
error: {
20+
name: e?.name ?? 'Error',
21+
message: e?.message ?? String(error),
22+
stack: e?.stack,
23+
},
24+
durationMs: Date.now() - start,
25+
}
26+
}
27+
}

‎plugins/inspect/src/rpc/functions/execute-command.ts‎

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { InvokeResult } from '../../types'
22
import { diagnostics } from '../../diagnostics'
33
import { defineInspectRpc } from './_define'
44
import { resolveHubCommands } from './_hub-commands'
5+
import { toInvokeResult } from './_invoke-result'
56

67
/**
78
* Execute a hub command by id and return a result envelope, mirroring
@@ -24,23 +25,7 @@ export const executeCommand = defineInspectRpc({
2425
if (!host)
2526
throw diagnostics.DP_INSPECT_0003({ id })
2627

27-
const start = Date.now()
28-
try {
29-
const result = await host.execute(id, ...args)
30-
return { ok: true, result, durationMs: Date.now() - start }
31-
}
32-
catch (error) {
33-
const e = error as Error
34-
return {
35-
ok: false,
36-
error: {
37-
name: e?.name ?? 'Error',
38-
message: e?.message ?? String(error),
39-
stack: e?.stack,
40-
},
41-
durationMs: Date.now() - start,
42-
}
43-
}
28+
return toInvokeResult(() => host.execute(id, ...args))
4429
},
4530
}),
4631
})
Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,12 @@
11
import type { InvokeResult } from '../../types'
22
import { defineInspectRpc } from './_define'
3+
import { toInvokeResult } from './_invoke-result'
34

45
export const invokeAgentTool = defineInspectRpc({
56
name: 'devframes:plugin:inspect:invoke-agent-tool',
67
type: 'action',
78
setup: ctx => ({
8-
handler: async (id: string, args: unknown): Promise<InvokeResult> => {
9-
const start = Date.now()
10-
try {
11-
const result = await ctx.agent.invoke(id, args)
12-
return { ok: true, result, durationMs: Date.now() - start }
13-
}
14-
catch (error) {
15-
const e = error as Error
16-
return {
17-
ok: false,
18-
error: {
19-
name: e?.name ?? 'Error',
20-
message: e?.message ?? String(error),
21-
stack: e?.stack,
22-
},
23-
durationMs: Date.now() - start,
24-
}
25-
}
26-
},
9+
handler: (id: string, args: unknown): Promise<InvokeResult> =>
10+
toInvokeResult(() => ctx.agent.invoke(id, args)),
2711
}),
2812
})

‎plugins/inspect/src/rpc/functions/invoke.ts‎

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { InvokeResult } from '../../types'
22
import { diagnostics } from '../../diagnostics'
33
import { defineInspectRpc } from './_define'
4+
import { toInvokeResult } from './_invoke-result'
45

56
const INVOKABLE_TYPES = new Set(['query', 'static'])
67

@@ -26,23 +27,7 @@ export const invoke = defineInspectRpc({
2627
if (!INVOKABLE_TYPES.has(type))
2728
throw diagnostics.DP_INSPECT_0002({ name, type })
2829

29-
const start = Date.now()
30-
try {
31-
const result = await ctx.rpc.invokeLocal(name as any, ...(args as any))
32-
return { ok: true, result, durationMs: Date.now() - start }
33-
}
34-
catch (error) {
35-
const e = error as Error
36-
return {
37-
ok: false,
38-
error: {
39-
name: e?.name ?? 'Error',
40-
message: e?.message ?? String(error),
41-
stack: e?.stack,
42-
},
43-
durationMs: Date.now() - start,
44-
}
45-
}
30+
return toInvokeResult(() => ctx.rpc.invokeLocal(name as any, ...(args as any)))
4631
},
4732
}),
4833
})
Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,12 @@
11
import type { InvokeResult } from '../../types'
22
import { defineInspectRpc } from './_define'
3+
import { toInvokeResult } from './_invoke-result'
34

45
export const readAgentResource = defineInspectRpc({
56
name: 'devframes:plugin:inspect:read-agent-resource',
67
type: 'action',
78
setup: ctx => ({
8-
handler: async (id: string): Promise<InvokeResult> => {
9-
const start = Date.now()
10-
try {
11-
const result = await ctx.agent.read(id)
12-
return { ok: true, result, durationMs: Date.now() - start }
13-
}
14-
catch (error) {
15-
const e = error as Error
16-
return {
17-
ok: false,
18-
error: {
19-
name: e?.name ?? 'Error',
20-
message: e?.message ?? String(error),
21-
stack: e?.stack,
22-
},
23-
durationMs: Date.now() - start,
24-
}
25-
}
26-
},
9+
handler: (id: string): Promise<InvokeResult> =>
10+
toInvokeResult(() => ctx.agent.read(id)),
2711
}),
2812
})

‎plugins/inspect/src/spa/components/AgentSmart.vue‎

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,13 @@ async function fetchData(): Promise<void> {
1919
useRefreshProvider(fetchData)
2020
onMounted(fetchData)
2121
22-
async function onInvoke(id: string, parsedArgs: unknown) {
23-
if (!rpc.value)
22+
async function runAction(id: string, call: (rpc: NonNullable<typeof rpc.value>) => Promise<InvokeResult>) {
23+
const client = rpc.value
24+
if (!client)
2425
return
2526
pending[id] = true
2627
try {
27-
results[id] = await rpc.value.call('devframes:plugin:inspect:invoke-agent-tool', id, parsedArgs)
28+
results[id] = await call(client)
2829
}
2930
catch (e) {
3031
const err = e as Error
@@ -35,20 +36,12 @@ async function onInvoke(id: string, parsedArgs: unknown) {
3536
}
3637
}
3738
38-
async function onRead(id: string) {
39-
if (!rpc.value)
40-
return
41-
pending[id] = true
42-
try {
43-
results[id] = await rpc.value.call('devframes:plugin:inspect:read-agent-resource', id)
44-
}
45-
catch (e) {
46-
const err = e as Error
47-
results[id] = { ok: false, error: { name: err?.name ?? 'Error', message: err?.message ?? String(e) } }
48-
}
49-
finally {
50-
pending[id] = false
51-
}
39+
function onInvoke(id: string, parsedArgs: unknown) {
40+
return runAction(id, client => client.call('devframes:plugin:inspect:invoke-agent-tool', id, parsedArgs))
41+
}
42+
43+
function onRead(id: string) {
44+
return runAction(id, client => client.call('devframes:plugin:inspect:read-agent-resource', id))
5245
}
5346
</script>
5447

0 commit comments

Comments
 (0)