@pikku/inspector 0.11.1 → 0.12.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.
- package/CHANGELOG.md +26 -1
- package/OPTIMIZATION-PLAN.md +195 -0
- package/dist/add/add-ai-agent.d.ts +2 -0
- package/dist/add/add-ai-agent.js +314 -0
- package/dist/add/add-channel.js +69 -61
- package/dist/add/add-cli.js +36 -18
- package/dist/add/add-file-with-factory.js +2 -0
- package/dist/add/add-functions.js +327 -59
- package/dist/add/add-http-route.d.ts +19 -10
- package/dist/add/add-http-route.js +153 -44
- package/dist/add/add-http-routes.d.ts +5 -0
- package/dist/add/add-http-routes.js +159 -0
- package/dist/add/add-keyed-wiring.d.ts +12 -0
- package/dist/add/add-keyed-wiring.js +97 -0
- package/dist/add/add-mcp-prompt.js +14 -9
- package/dist/add/add-mcp-resource.js +14 -9
- package/dist/add/add-middleware.d.ts +1 -4
- package/dist/add/add-middleware.js +364 -79
- package/dist/add/add-permission.d.ts +1 -1
- package/dist/add/add-permission.js +152 -40
- package/dist/add/add-queue-worker.js +18 -12
- package/dist/add/add-rpc-invocations.d.ts +3 -0
- package/dist/add/add-rpc-invocations.js +65 -25
- package/dist/add/add-schedule.js +11 -5
- package/dist/add/add-secret.d.ts +3 -0
- package/dist/add/add-secret.js +82 -0
- package/dist/add/add-trigger.d.ts +2 -0
- package/dist/add/add-trigger.js +87 -0
- package/dist/add/add-variable.d.ts +1 -0
- package/dist/add/add-variable.js +8 -0
- package/dist/add/add-workflow-graph.d.ts +7 -0
- package/dist/add/add-workflow-graph.js +396 -0
- package/dist/add/add-workflow.js +124 -26
- package/dist/error-codes.d.ts +16 -1
- package/dist/error-codes.js +21 -1
- package/dist/index.d.ts +9 -5
- package/dist/index.js +5 -2
- package/dist/inspector.d.ts +1 -1
- package/dist/inspector.js +106 -13
- package/dist/schema-generator.d.ts +1 -0
- package/dist/schema-generator.js +1 -0
- package/dist/types-map.js +10 -1
- package/dist/types.d.ts +180 -30
- package/dist/utils/compute-required-schemas.d.ts +4 -0
- package/dist/utils/compute-required-schemas.js +41 -0
- package/dist/utils/contract-hashes.d.ts +35 -0
- package/dist/utils/contract-hashes.js +202 -0
- package/dist/utils/custom-types-generator.d.ts +9 -0
- package/dist/utils/custom-types-generator.js +71 -0
- package/dist/utils/detect-schema-vendor.d.ts +22 -0
- package/dist/utils/detect-schema-vendor.js +76 -0
- package/dist/utils/ensure-function-metadata.d.ts +5 -2
- package/dist/utils/ensure-function-metadata.js +220 -6
- package/dist/utils/extract-function-name.d.ts +5 -16
- package/dist/utils/extract-function-name.js +93 -298
- package/dist/utils/extract-services.d.ts +2 -1
- package/dist/utils/extract-services.js +25 -1
- package/dist/utils/filter-inspector-state.js +107 -23
- package/dist/utils/get-property-value.d.ts +8 -2
- package/dist/utils/get-property-value.js +33 -4
- package/dist/utils/hash.d.ts +2 -0
- package/dist/utils/hash.js +23 -0
- package/dist/utils/middleware.d.ts +7 -30
- package/dist/utils/middleware.js +80 -66
- package/dist/utils/permissions.d.ts +2 -2
- package/dist/utils/permissions.js +10 -10
- package/dist/utils/post-process.d.ts +9 -10
- package/dist/utils/post-process.js +231 -24
- package/dist/utils/resolve-external-package.d.ts +12 -0
- package/dist/utils/resolve-external-package.js +34 -0
- package/dist/utils/resolve-function-types.d.ts +6 -0
- package/dist/utils/resolve-function-types.js +29 -0
- package/dist/utils/resolve-identifier.d.ts +10 -0
- package/dist/utils/resolve-identifier.js +36 -0
- package/dist/utils/resolve-versions.d.ts +2 -0
- package/dist/utils/resolve-versions.js +78 -0
- package/dist/utils/schema-generator.d.ts +9 -0
- package/dist/utils/schema-generator.js +209 -0
- package/dist/utils/serialize-inspector-state.d.ts +73 -13
- package/dist/utils/serialize-inspector-state.js +102 -6
- package/dist/utils/serialize-mcp-json.d.ts +2 -0
- package/dist/utils/serialize-mcp-json.js +99 -0
- package/dist/utils/serialize-middleware-groups-meta.d.ts +12 -0
- package/dist/utils/serialize-middleware-groups-meta.js +28 -0
- package/dist/utils/serialize-openapi-json.d.ts +85 -0
- package/dist/utils/serialize-openapi-json.js +151 -0
- package/dist/utils/serialize-permissions-groups-meta.d.ts +6 -0
- package/dist/utils/serialize-permissions-groups-meta.js +31 -0
- package/dist/utils/workflow/dsl/deserialize-dsl-workflow.d.ts +24 -0
- package/dist/utils/workflow/dsl/deserialize-dsl-workflow.js +830 -0
- package/dist/{workflow/extract-simple-workflow.d.ts → utils/workflow/dsl/extract-dsl-workflow.d.ts} +4 -2
- package/dist/{workflow/extract-simple-workflow.js → utils/workflow/dsl/extract-dsl-workflow.js} +572 -72
- package/dist/utils/workflow/dsl/index.d.ts +7 -0
- package/dist/utils/workflow/dsl/index.js +7 -0
- package/dist/{workflow → utils/workflow/dsl}/patterns.d.ts +21 -0
- package/dist/{workflow → utils/workflow/dsl}/patterns.js +90 -10
- package/dist/{workflow → utils/workflow/dsl}/validation.d.ts +2 -0
- package/dist/{workflow → utils/workflow/dsl}/validation.js +25 -7
- package/dist/utils/workflow/graph/convert-dsl-to-graph.d.ts +13 -0
- package/dist/utils/workflow/graph/convert-dsl-to-graph.js +318 -0
- package/dist/utils/workflow/graph/finalize-workflow-wires.d.ts +3 -0
- package/dist/utils/workflow/graph/finalize-workflow-wires.js +276 -0
- package/dist/utils/workflow/graph/finalize-workflows.d.ts +2 -0
- package/dist/utils/workflow/graph/finalize-workflows.js +75 -0
- package/dist/utils/workflow/graph/index.d.ts +8 -0
- package/dist/utils/workflow/graph/index.js +8 -0
- package/dist/utils/workflow/graph/serialize-workflow-graph.d.ts +35 -0
- package/dist/utils/workflow/graph/serialize-workflow-graph.js +150 -0
- package/dist/utils/workflow/graph/workflow-graph.types.d.ts +203 -0
- package/dist/utils/workflow/graph/workflow-graph.types.js +38 -0
- package/dist/visit.js +13 -2
- package/package.json +26 -4
- package/src/add/add-ai-agent.ts +468 -0
- package/src/add/add-channel.ts +82 -79
- package/src/add/add-cli.ts +49 -20
- package/src/add/add-file-with-factory.ts +2 -0
- package/src/add/add-functions.ts +429 -71
- package/src/add/add-http-route.ts +246 -65
- package/src/add/add-http-routes.ts +228 -0
- package/src/add/add-keyed-wiring.ts +151 -0
- package/src/add/add-mcp-prompt.ts +26 -15
- package/src/add/add-mcp-resource.ts +27 -15
- package/src/add/add-middleware.ts +482 -80
- package/src/add/add-permission.ts +199 -40
- package/src/add/add-queue-worker.ts +24 -19
- package/src/add/add-rpc-invocations.ts +78 -31
- package/src/add/add-schedule.ts +16 -11
- package/src/add/add-secret.ts +140 -0
- package/src/add/add-trigger.ts +154 -0
- package/src/add/add-variable.ts +9 -0
- package/src/add/add-workflow-graph.ts +522 -0
- package/src/add/add-workflow.ts +117 -30
- package/src/error-codes.ts +26 -1
- package/src/index.ts +27 -8
- package/src/inspector.ts +145 -17
- package/src/schema-generator.ts +1 -0
- package/src/types-map.ts +12 -1
- package/src/types.ts +192 -51
- package/src/utils/compute-required-schemas.ts +49 -0
- package/src/utils/contract-hashes.test.ts +528 -0
- package/src/utils/contract-hashes.ts +290 -0
- package/src/utils/custom-types-generator.ts +88 -0
- package/src/utils/detect-schema-vendor.ts +90 -0
- package/src/utils/ensure-function-metadata.ts +324 -7
- package/src/utils/extract-function-name.ts +108 -358
- package/src/utils/extract-services.ts +35 -2
- package/src/utils/filter-inspector-state.test.ts +34 -20
- package/src/utils/filter-inspector-state.ts +140 -31
- package/src/utils/get-property-value.ts +50 -5
- package/src/utils/hash.ts +26 -0
- package/src/utils/middleware.test.ts +204 -0
- package/src/utils/middleware.ts +129 -67
- package/src/utils/permissions.test.ts +35 -12
- package/src/utils/permissions.ts +10 -10
- package/src/utils/post-process.ts +283 -43
- package/src/utils/resolve-external-package.ts +42 -0
- package/src/utils/resolve-function-types.ts +42 -0
- package/src/utils/resolve-identifier.ts +46 -0
- package/src/utils/resolve-versions.test.ts +249 -0
- package/src/utils/resolve-versions.ts +105 -0
- package/src/utils/schema-generator.ts +329 -0
- package/src/utils/serialize-inspector-state.ts +181 -20
- package/src/utils/serialize-mcp-json.ts +145 -0
- package/src/utils/serialize-middleware-groups-meta.ts +33 -0
- package/src/utils/serialize-openapi-json.ts +277 -0
- package/src/utils/serialize-permissions-groups-meta.ts +35 -0
- package/src/utils/test-data/inspector-state.json +69 -66
- package/src/utils/workflow/dsl/deserialize-dsl-workflow.ts +1104 -0
- package/src/{workflow/extract-simple-workflow.ts → utils/workflow/dsl/extract-dsl-workflow.ts} +678 -85
- package/src/utils/workflow/dsl/index.ts +11 -0
- package/src/{workflow → utils/workflow/dsl}/patterns.ts +108 -11
- package/src/{workflow → utils/workflow/dsl}/validation.ts +34 -7
- package/src/utils/workflow/graph/convert-dsl-to-graph.ts +422 -0
- package/src/utils/workflow/graph/finalize-workflow-wires.ts +310 -0
- package/src/utils/workflow/graph/finalize-workflows.ts +100 -0
- package/src/utils/workflow/graph/index.ts +11 -0
- package/src/utils/workflow/graph/serialize-workflow-graph.ts +216 -0
- package/src/utils/workflow/graph/workflow-graph.types.ts +231 -0
- package/src/visit.ts +14 -2
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/add/add-mcp-tool.d.ts +0 -2
- package/dist/add/add-mcp-tool.js +0 -81
- package/dist/utils/extract-service-metadata.d.ts +0 -19
- package/dist/utils/extract-service-metadata.js +0 -244
- package/dist/utils/write-service-metadata.d.ts +0 -13
- package/dist/utils/write-service-metadata.js +0 -37
- package/src/add/add-mcp-tool.ts +0 -141
- package/src/utils/extract-service-metadata.ts +0 -353
- package/src/utils/write-service-metadata.ts +0 -51
|
@@ -5,20 +5,110 @@ import {
|
|
|
5
5
|
} from '../utils/get-property-value.js'
|
|
6
6
|
import { pathToRegexp } from 'path-to-regexp'
|
|
7
7
|
import { HTTPMethod } from '@pikku/core/http'
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
import {
|
|
9
|
+
extractFunctionName,
|
|
10
|
+
makeContextBasedId,
|
|
11
|
+
} from '../utils/extract-function-name.js'
|
|
12
|
+
import {
|
|
13
|
+
getPropertyAssignmentInitializer,
|
|
14
|
+
extractTypeKeys,
|
|
15
|
+
} from '../utils/type-utils.js'
|
|
16
|
+
import { AddWiring, InspectorState } from '../types.js'
|
|
11
17
|
import { resolveHTTPMiddlewareFromObject } from '../utils/middleware.js'
|
|
12
18
|
import { resolveHTTPPermissionsFromObject } from '../utils/permissions.js'
|
|
13
19
|
import { extractWireNames } from '../utils/post-process.js'
|
|
14
20
|
import { ensureFunctionMetadata } from '../utils/ensure-function-metadata.js'
|
|
15
21
|
import { ErrorCode } from '../error-codes.js'
|
|
22
|
+
import { detectSchemaVendorOrError } from '../utils/detect-schema-vendor.js'
|
|
23
|
+
|
|
24
|
+
import type { InspectorLogger } from '../types.js'
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Parameters for registering an HTTP route
|
|
28
|
+
*/
|
|
29
|
+
export interface RegisterHTTPRouteParams {
|
|
30
|
+
obj: ts.ObjectLiteralExpression
|
|
31
|
+
state: InspectorState
|
|
32
|
+
checker: ts.TypeChecker
|
|
33
|
+
logger: InspectorLogger
|
|
34
|
+
sourceFile: ts.SourceFile
|
|
35
|
+
basePath?: string
|
|
36
|
+
inheritedTags?: string[]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Extract header schema reference from headers property
|
|
41
|
+
*/
|
|
42
|
+
const extractHeadersSchema = (
|
|
43
|
+
obj: ts.ObjectLiteralExpression,
|
|
44
|
+
routeName: string,
|
|
45
|
+
method: string,
|
|
46
|
+
state: InspectorState,
|
|
47
|
+
checker: ts.TypeChecker,
|
|
48
|
+
logger: InspectorLogger
|
|
49
|
+
): string | undefined => {
|
|
50
|
+
const headersNode = getPropertyAssignmentInitializer(
|
|
51
|
+
obj,
|
|
52
|
+
'headers',
|
|
53
|
+
true,
|
|
54
|
+
checker
|
|
55
|
+
)
|
|
56
|
+
if (!headersNode || !ts.isIdentifier(headersNode)) return undefined
|
|
57
|
+
|
|
58
|
+
// Resolve the schema reference
|
|
59
|
+
const symbol = checker.getSymbolAtLocation(headersNode)
|
|
60
|
+
if (!symbol) return undefined
|
|
61
|
+
|
|
62
|
+
const decl = symbol.valueDeclaration || symbol.declarations?.[0]
|
|
63
|
+
if (!decl) return undefined
|
|
64
|
+
|
|
65
|
+
let sourceFile: string
|
|
66
|
+
if (ts.isImportSpecifier(decl)) {
|
|
67
|
+
const aliasedSymbol = checker.getAliasedSymbol(symbol)
|
|
68
|
+
if (aliasedSymbol) {
|
|
69
|
+
const aliasedDecl =
|
|
70
|
+
aliasedSymbol.valueDeclaration || aliasedSymbol.declarations?.[0]
|
|
71
|
+
if (aliasedDecl) {
|
|
72
|
+
sourceFile = aliasedDecl.getSourceFile().fileName
|
|
73
|
+
} else {
|
|
74
|
+
return undefined
|
|
75
|
+
}
|
|
76
|
+
} else {
|
|
77
|
+
return undefined
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
sourceFile = decl.getSourceFile().fileName
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const vendor = detectSchemaVendorOrError(
|
|
84
|
+
headersNode,
|
|
85
|
+
checker,
|
|
86
|
+
logger,
|
|
87
|
+
`Route '${routeName}' header`,
|
|
88
|
+
sourceFile
|
|
89
|
+
)
|
|
90
|
+
if (!vendor) return undefined
|
|
91
|
+
|
|
92
|
+
// Create a sanitized schema name from route and method to avoid collisions
|
|
93
|
+
const sanitizedRoute = routeName
|
|
94
|
+
.replace(/[^a-zA-Z0-9]/g, '_')
|
|
95
|
+
.replace(/^_+|_+$/g, '')
|
|
96
|
+
const schemaName = `${method.toUpperCase()}_${sanitizedRoute}_Headers`
|
|
97
|
+
|
|
98
|
+
state.schemaLookup.set(schemaName, {
|
|
99
|
+
variableName: headersNode.text,
|
|
100
|
+
sourceFile,
|
|
101
|
+
vendor,
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
return schemaName
|
|
105
|
+
}
|
|
16
106
|
|
|
17
107
|
/**
|
|
18
108
|
* Populate metaInputTypes for a given route based on method, input type,
|
|
19
|
-
* query and params.
|
|
109
|
+
* query and params.
|
|
20
110
|
*/
|
|
21
|
-
|
|
111
|
+
const computeInputTypes = (
|
|
22
112
|
metaTypes: Map<
|
|
23
113
|
string,
|
|
24
114
|
{ query?: string[]; params?: string[]; body?: string[] }
|
|
@@ -27,7 +117,7 @@ export const getInputTypes = (
|
|
|
27
117
|
inputType: string | null,
|
|
28
118
|
queryValues: string[],
|
|
29
119
|
paramsValues: string[]
|
|
30
|
-
):
|
|
120
|
+
): void => {
|
|
31
121
|
if (!inputType) return
|
|
32
122
|
metaTypes.set(inputType, {
|
|
33
123
|
query: queryValues,
|
|
@@ -36,49 +126,60 @@ export const getInputTypes = (
|
|
|
36
126
|
? [...new Set([...queryValues, ...paramsValues])]
|
|
37
127
|
: [],
|
|
38
128
|
})
|
|
39
|
-
return
|
|
40
129
|
}
|
|
41
130
|
|
|
42
131
|
/**
|
|
43
|
-
*
|
|
44
|
-
*
|
|
132
|
+
* Shared function to register an HTTP route in the inspector state.
|
|
133
|
+
* Used by both wireHTTP and wireHTTPRoutes.
|
|
45
134
|
*/
|
|
46
|
-
export
|
|
47
|
-
|
|
48
|
-
node,
|
|
49
|
-
checker,
|
|
135
|
+
export function registerHTTPRoute({
|
|
136
|
+
obj,
|
|
50
137
|
state,
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
138
|
+
checker,
|
|
139
|
+
logger,
|
|
140
|
+
sourceFile,
|
|
141
|
+
basePath = '',
|
|
142
|
+
inheritedTags = [],
|
|
143
|
+
}: RegisterHTTPRouteParams): void {
|
|
144
|
+
// Extract route path
|
|
145
|
+
const routePath = getPropertyValue(obj, 'route') as string | null
|
|
146
|
+
if (!routePath) return
|
|
55
147
|
|
|
56
|
-
const
|
|
57
|
-
|
|
148
|
+
const method = (
|
|
149
|
+
(getPropertyValue(obj, 'method') as string) || 'get'
|
|
150
|
+
).toLowerCase()
|
|
151
|
+
const fullRoute = basePath + routePath
|
|
58
152
|
|
|
59
|
-
//
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
153
|
+
// Extract params from route path
|
|
154
|
+
let params: string[] = []
|
|
155
|
+
try {
|
|
156
|
+
const keys = pathToRegexp(fullRoute).keys
|
|
157
|
+
params = keys.filter((k) => k.type === 'param').map((k) => k.name)
|
|
158
|
+
} catch (e) {
|
|
159
|
+
logger.error(
|
|
160
|
+
`Failed to parse route '${fullRoute}': ${e instanceof Error ? e.message : e}`
|
|
161
|
+
)
|
|
162
|
+
return
|
|
163
|
+
}
|
|
63
164
|
|
|
64
|
-
//
|
|
65
|
-
const
|
|
66
|
-
|
|
165
|
+
// Get common metadata
|
|
166
|
+
const {
|
|
167
|
+
disabled,
|
|
168
|
+
title,
|
|
169
|
+
tags: routeTags,
|
|
170
|
+
summary,
|
|
171
|
+
description,
|
|
172
|
+
errors,
|
|
173
|
+
} = getCommonWireMetaData(obj, 'HTTP route', fullRoute, logger)
|
|
67
174
|
|
|
68
|
-
|
|
69
|
-
|
|
175
|
+
if (disabled) return
|
|
176
|
+
|
|
177
|
+
// Merge inherited tags with route tags
|
|
178
|
+
const tags = [...inheritedTags, ...(routeTags || [])]
|
|
70
179
|
|
|
71
|
-
const method =
|
|
72
|
-
(getPropertyValue(obj, 'method') as string)?.toLowerCase() || 'get'
|
|
73
|
-
const { tags, summary, description, errors } = getCommonWireMetaData(
|
|
74
|
-
obj,
|
|
75
|
-
'HTTP route',
|
|
76
|
-
route,
|
|
77
|
-
logger
|
|
78
|
-
)
|
|
79
180
|
const query = (getPropertyValue(obj, 'query') as string[]) || []
|
|
80
181
|
|
|
81
|
-
//
|
|
182
|
+
// Get function reference
|
|
82
183
|
const funcInitializer = getPropertyAssignmentInitializer(
|
|
83
184
|
obj,
|
|
84
185
|
'func',
|
|
@@ -88,21 +189,27 @@ export const addHTTPRoute: AddWiring = (
|
|
|
88
189
|
if (!funcInitializer) {
|
|
89
190
|
logger.critical(
|
|
90
191
|
ErrorCode.MISSING_FUNC,
|
|
91
|
-
`No valid 'func' property for route '${
|
|
192
|
+
`No valid 'func' property for route '${fullRoute}'.`
|
|
92
193
|
)
|
|
93
194
|
return
|
|
94
195
|
}
|
|
95
196
|
|
|
96
|
-
const
|
|
197
|
+
const extracted = extractFunctionName(funcInitializer, checker, state.rootDir)
|
|
198
|
+
let funcName = extracted.pikkuFuncId
|
|
199
|
+
if (funcName.startsWith('__temp_')) {
|
|
200
|
+
funcName = makeContextBasedId('http', method, fullRoute)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
ensureFunctionMetadata(
|
|
204
|
+
state,
|
|
205
|
+
funcName,
|
|
206
|
+
fullRoute,
|
|
97
207
|
funcInitializer,
|
|
98
208
|
checker,
|
|
99
|
-
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
// Ensure function metadata exists (creates stub for inline functions)
|
|
103
|
-
ensureFunctionMetadata(state, funcName, route)
|
|
209
|
+
extracted.isHelper
|
|
210
|
+
)
|
|
104
211
|
|
|
105
|
-
//
|
|
212
|
+
// Lookup existing function metadata
|
|
106
213
|
const fnMeta = state.functions.meta[funcName]
|
|
107
214
|
if (!fnMeta) {
|
|
108
215
|
logger.critical(
|
|
@@ -113,34 +220,64 @@ export const addHTTPRoute: AddWiring = (
|
|
|
113
220
|
}
|
|
114
221
|
const input = fnMeta.inputs?.[0] || null
|
|
115
222
|
|
|
116
|
-
//
|
|
117
|
-
|
|
118
|
-
state.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
223
|
+
// Validate that route params and query params exist in function input type
|
|
224
|
+
if (params.length > 0 || query.length > 0) {
|
|
225
|
+
const inputTypes = state.typesLookup.get(funcName)
|
|
226
|
+
if (inputTypes && inputTypes.length > 0) {
|
|
227
|
+
const inputKeys = extractTypeKeys(inputTypes[0])
|
|
228
|
+
|
|
229
|
+
// Check path params
|
|
230
|
+
if (params.length > 0) {
|
|
231
|
+
const missingParams = params.filter((p) => !inputKeys.includes(p))
|
|
232
|
+
if (missingParams.length > 0) {
|
|
233
|
+
logger.critical(
|
|
234
|
+
ErrorCode.ROUTE_PARAM_MISMATCH,
|
|
235
|
+
`Route '${fullRoute}' has path parameter(s) [${missingParams.join(', ')}] ` +
|
|
236
|
+
`not found in function '${funcName}' input type. ` +
|
|
237
|
+
`Input type has: [${inputKeys.join(', ')}]`
|
|
238
|
+
)
|
|
239
|
+
return
|
|
240
|
+
}
|
|
241
|
+
}
|
|
124
242
|
|
|
125
|
-
|
|
243
|
+
// Check query params
|
|
244
|
+
if (query.length > 0) {
|
|
245
|
+
const missingQuery = query.filter((q) => !inputKeys.includes(q))
|
|
246
|
+
if (missingQuery.length > 0) {
|
|
247
|
+
logger.critical(
|
|
248
|
+
ErrorCode.ROUTE_QUERY_MISMATCH,
|
|
249
|
+
`Route '${fullRoute}' has query parameter(s) [${missingQuery.join(', ')}] ` +
|
|
250
|
+
`not found in function '${funcName}' input type. ` +
|
|
251
|
+
`Input type has: [${inputKeys.join(', ')}]`
|
|
252
|
+
)
|
|
253
|
+
return
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Compute inputTypes (body/query/params)
|
|
260
|
+
computeInputTypes(state.http.metaInputTypes, method, input, query, params)
|
|
261
|
+
|
|
262
|
+
// Resolve middleware
|
|
126
263
|
const middleware = resolveHTTPMiddlewareFromObject(
|
|
127
264
|
state,
|
|
128
|
-
|
|
265
|
+
fullRoute,
|
|
129
266
|
obj,
|
|
130
267
|
tags,
|
|
131
268
|
checker
|
|
132
269
|
)
|
|
133
270
|
|
|
134
|
-
//
|
|
271
|
+
// Resolve permissions
|
|
135
272
|
const permissions = resolveHTTPPermissionsFromObject(
|
|
136
273
|
state,
|
|
137
|
-
|
|
274
|
+
fullRoute,
|
|
138
275
|
obj,
|
|
139
276
|
tags,
|
|
140
277
|
checker
|
|
141
278
|
)
|
|
142
279
|
|
|
143
|
-
//
|
|
280
|
+
// Track used functions/middleware/permissions for service aggregation
|
|
144
281
|
state.serviceAggregation.usedFunctions.add(funcName)
|
|
145
282
|
extractWireNames(middleware).forEach((name) =>
|
|
146
283
|
state.serviceAggregation.usedMiddleware.add(name)
|
|
@@ -149,20 +286,64 @@ export const addHTTPRoute: AddWiring = (
|
|
|
149
286
|
state.serviceAggregation.usedPermissions.add(name)
|
|
150
287
|
)
|
|
151
288
|
|
|
152
|
-
//
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
289
|
+
// Check for SSE
|
|
290
|
+
const sse = getPropertyValue(obj, 'sse') === true
|
|
291
|
+
|
|
292
|
+
// Extract header schema
|
|
293
|
+
const headersSchemaName = extractHeadersSchema(
|
|
294
|
+
obj,
|
|
295
|
+
fullRoute,
|
|
296
|
+
method,
|
|
297
|
+
state,
|
|
298
|
+
checker,
|
|
299
|
+
logger
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
// Record route
|
|
303
|
+
state.http.files.add(sourceFile.fileName)
|
|
304
|
+
state.http.meta[method][fullRoute] = {
|
|
305
|
+
pikkuFuncId: funcName,
|
|
306
|
+
route: fullRoute,
|
|
157
307
|
method: method as HTTPMethod,
|
|
158
308
|
params: params.length > 0 ? params : undefined,
|
|
159
309
|
query: query.length > 0 ? query : undefined,
|
|
160
|
-
inputTypes,
|
|
310
|
+
inputTypes: undefined,
|
|
311
|
+
title,
|
|
161
312
|
summary,
|
|
162
313
|
description,
|
|
163
314
|
errors,
|
|
164
|
-
tags,
|
|
315
|
+
tags: tags.length > 0 ? tags : undefined,
|
|
165
316
|
middleware,
|
|
166
317
|
permissions,
|
|
318
|
+
sse: sse ? true : undefined,
|
|
319
|
+
headersSchemaName,
|
|
320
|
+
groupBasePath: basePath || undefined,
|
|
167
321
|
}
|
|
168
322
|
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Process wireHTTP calls
|
|
326
|
+
*/
|
|
327
|
+
export const addHTTPRoute: AddWiring = (
|
|
328
|
+
logger,
|
|
329
|
+
node,
|
|
330
|
+
checker,
|
|
331
|
+
state,
|
|
332
|
+
_options
|
|
333
|
+
) => {
|
|
334
|
+
if (!ts.isCallExpression(node)) return
|
|
335
|
+
|
|
336
|
+
const { expression, arguments: args } = node
|
|
337
|
+
if (!ts.isIdentifier(expression) || expression.text !== 'wireHTTP') return
|
|
338
|
+
|
|
339
|
+
const firstArg = args[0]
|
|
340
|
+
if (!firstArg || !ts.isObjectLiteralExpression(firstArg)) return
|
|
341
|
+
|
|
342
|
+
registerHTTPRoute({
|
|
343
|
+
obj: firstArg,
|
|
344
|
+
state,
|
|
345
|
+
checker,
|
|
346
|
+
logger,
|
|
347
|
+
sourceFile: node.getSourceFile(),
|
|
348
|
+
})
|
|
349
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import * as ts from 'typescript'
|
|
2
|
+
import { getPropertyValue } from '../utils/get-property-value.js'
|
|
3
|
+
import { AddWiring, InspectorState, InspectorLogger } from '../types.js'
|
|
4
|
+
import { registerHTTPRoute } from './add-http-route.js'
|
|
5
|
+
import { resolveIdentifier } from '../utils/resolve-identifier.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Group configuration extracted from wireHTTPRoutes or defineHTTPRoutes
|
|
9
|
+
*/
|
|
10
|
+
interface GroupConfig {
|
|
11
|
+
basePath: string
|
|
12
|
+
tags: string[]
|
|
13
|
+
auth?: boolean
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Process wireHTTPRoutes calls
|
|
18
|
+
*/
|
|
19
|
+
export const addHTTPRoutes: AddWiring = (
|
|
20
|
+
logger,
|
|
21
|
+
node,
|
|
22
|
+
checker,
|
|
23
|
+
state,
|
|
24
|
+
_options
|
|
25
|
+
) => {
|
|
26
|
+
if (!ts.isCallExpression(node)) return
|
|
27
|
+
|
|
28
|
+
const { expression, arguments: args } = node
|
|
29
|
+
if (!ts.isIdentifier(expression) || expression.text !== 'wireHTTPRoutes')
|
|
30
|
+
return
|
|
31
|
+
|
|
32
|
+
const firstArg = args[0]
|
|
33
|
+
if (!firstArg || !ts.isObjectLiteralExpression(firstArg)) return
|
|
34
|
+
|
|
35
|
+
// Extract group config
|
|
36
|
+
const groupConfig = extractGroupConfig(firstArg)
|
|
37
|
+
|
|
38
|
+
// Get routes property
|
|
39
|
+
const routesProp = getPropertyAssignment(firstArg, 'routes')
|
|
40
|
+
if (!routesProp) return
|
|
41
|
+
|
|
42
|
+
// Process routes recursively
|
|
43
|
+
processRoutes(
|
|
44
|
+
routesProp.initializer,
|
|
45
|
+
groupConfig,
|
|
46
|
+
state,
|
|
47
|
+
checker,
|
|
48
|
+
logger,
|
|
49
|
+
node.getSourceFile()
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Get a property assignment from an object literal
|
|
55
|
+
*/
|
|
56
|
+
function getPropertyAssignment(
|
|
57
|
+
obj: ts.ObjectLiteralExpression,
|
|
58
|
+
propName: string
|
|
59
|
+
): ts.PropertyAssignment | undefined {
|
|
60
|
+
for (const prop of obj.properties) {
|
|
61
|
+
if (
|
|
62
|
+
ts.isPropertyAssignment(prop) &&
|
|
63
|
+
ts.isIdentifier(prop.name) &&
|
|
64
|
+
prop.name.text === propName
|
|
65
|
+
) {
|
|
66
|
+
return prop
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return undefined
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Extract group configuration from an object literal
|
|
74
|
+
*/
|
|
75
|
+
function extractGroupConfig(obj: ts.ObjectLiteralExpression): GroupConfig {
|
|
76
|
+
const basePath = (getPropertyValue(obj, 'basePath') as string) || ''
|
|
77
|
+
const tags = (getPropertyValue(obj, 'tags') as string[]) || []
|
|
78
|
+
const auth = getPropertyValue(obj, 'auth')
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
basePath,
|
|
82
|
+
tags,
|
|
83
|
+
auth: auth === true ? true : auth === false ? false : undefined,
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Merge two group configs following cascading rules
|
|
89
|
+
*/
|
|
90
|
+
function mergeConfigs(parent: GroupConfig, child: GroupConfig): GroupConfig {
|
|
91
|
+
return {
|
|
92
|
+
basePath: parent.basePath + child.basePath,
|
|
93
|
+
tags: [...parent.tags, ...child.tags],
|
|
94
|
+
auth: child.auth ?? parent.auth,
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Check if a value is a route config (has method, func, and route)
|
|
100
|
+
*/
|
|
101
|
+
function isRouteConfig(obj: ts.ObjectLiteralExpression): boolean {
|
|
102
|
+
let hasMethod = false
|
|
103
|
+
let hasFunc = false
|
|
104
|
+
let hasRoute = false
|
|
105
|
+
|
|
106
|
+
for (const prop of obj.properties) {
|
|
107
|
+
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
|
|
108
|
+
if (prop.name.text === 'method') hasMethod = true
|
|
109
|
+
if (prop.name.text === 'func') hasFunc = true
|
|
110
|
+
if (prop.name.text === 'route') hasRoute = true
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return hasMethod && hasFunc && hasRoute
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Check if a value is a route contract (has routes property but no method/func)
|
|
119
|
+
*/
|
|
120
|
+
function isRouteContract(obj: ts.ObjectLiteralExpression): boolean {
|
|
121
|
+
let hasRoutes = false
|
|
122
|
+
let hasMethod = false
|
|
123
|
+
let hasFunc = false
|
|
124
|
+
|
|
125
|
+
for (const prop of obj.properties) {
|
|
126
|
+
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
|
|
127
|
+
if (prop.name.text === 'routes') hasRoutes = true
|
|
128
|
+
if (prop.name.text === 'method') hasMethod = true
|
|
129
|
+
if (prop.name.text === 'func') hasFunc = true
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return hasRoutes && !hasMethod && !hasFunc
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Recursively process routes - handles nested maps, contracts, and identifiers
|
|
138
|
+
*/
|
|
139
|
+
function processRoutes(
|
|
140
|
+
node: ts.Node,
|
|
141
|
+
parentConfig: GroupConfig,
|
|
142
|
+
state: InspectorState,
|
|
143
|
+
checker: ts.TypeChecker,
|
|
144
|
+
logger: InspectorLogger,
|
|
145
|
+
sourceFile: ts.SourceFile
|
|
146
|
+
): void {
|
|
147
|
+
// Handle array of routes
|
|
148
|
+
if (ts.isArrayLiteralExpression(node)) {
|
|
149
|
+
for (const element of node.elements) {
|
|
150
|
+
if (ts.isObjectLiteralExpression(element) && isRouteConfig(element)) {
|
|
151
|
+
processRoute(element, parentConfig, state, checker, logger, sourceFile)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Handle object literal
|
|
158
|
+
if (ts.isObjectLiteralExpression(node)) {
|
|
159
|
+
// Check if this is a route config
|
|
160
|
+
if (isRouteConfig(node)) {
|
|
161
|
+
processRoute(node, parentConfig, state, checker, logger, sourceFile)
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Check if this is a route contract
|
|
166
|
+
if (isRouteContract(node)) {
|
|
167
|
+
const contractConfig = extractGroupConfig(node)
|
|
168
|
+
const mergedConfig = mergeConfigs(parentConfig, contractConfig)
|
|
169
|
+
const routesProp = getPropertyAssignment(node, 'routes')
|
|
170
|
+
if (routesProp) {
|
|
171
|
+
processRoutes(
|
|
172
|
+
routesProp.initializer,
|
|
173
|
+
mergedConfig,
|
|
174
|
+
state,
|
|
175
|
+
checker,
|
|
176
|
+
logger,
|
|
177
|
+
sourceFile
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
return
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Otherwise it's a nested map - process each property
|
|
184
|
+
for (const prop of node.properties) {
|
|
185
|
+
if (ts.isPropertyAssignment(prop)) {
|
|
186
|
+
processRoutes(
|
|
187
|
+
prop.initializer,
|
|
188
|
+
parentConfig,
|
|
189
|
+
state,
|
|
190
|
+
checker,
|
|
191
|
+
logger,
|
|
192
|
+
sourceFile
|
|
193
|
+
)
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Handle identifier - resolve to its definition
|
|
200
|
+
if (ts.isIdentifier(node)) {
|
|
201
|
+
const resolved = resolveIdentifier(node, checker, ['defineHTTPRoutes'])
|
|
202
|
+
if (resolved) {
|
|
203
|
+
processRoutes(resolved, parentConfig, state, checker, logger, sourceFile)
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Register a single route using the shared registerHTTPRoute function
|
|
210
|
+
*/
|
|
211
|
+
function processRoute(
|
|
212
|
+
obj: ts.ObjectLiteralExpression,
|
|
213
|
+
groupConfig: GroupConfig,
|
|
214
|
+
state: InspectorState,
|
|
215
|
+
checker: ts.TypeChecker,
|
|
216
|
+
logger: InspectorLogger,
|
|
217
|
+
sourceFile: ts.SourceFile
|
|
218
|
+
): void {
|
|
219
|
+
registerHTTPRoute({
|
|
220
|
+
obj,
|
|
221
|
+
state,
|
|
222
|
+
checker,
|
|
223
|
+
logger,
|
|
224
|
+
sourceFile,
|
|
225
|
+
basePath: groupConfig.basePath,
|
|
226
|
+
inheritedTags: groupConfig.tags,
|
|
227
|
+
})
|
|
228
|
+
}
|