@pikku/inspector 0.6.4 → 0.7.1
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 +16 -0
- package/dist/add-channel.d.ts +12 -2
- package/dist/add-channel.js +336 -109
- package/dist/add-functions.d.ts +7 -0
- package/dist/add-functions.js +269 -0
- package/dist/add-http-route.d.ts +15 -3
- package/dist/add-http-route.js +69 -80
- package/dist/add-schedule.d.ts +1 -1
- package/dist/add-schedule.js +14 -4
- package/dist/inspector.js +14 -4
- package/dist/types.d.ts +7 -10
- package/dist/utils.d.ts +21 -27
- package/dist/utils.js +631 -211
- package/dist/visit.d.ts +2 -1
- package/dist/visit.js +9 -4
- package/package.json +2 -2
- package/src/add-channel.ts +442 -140
- package/src/add-functions.ts +376 -0
- package/src/add-http-route.ts +94 -109
- package/src/add-schedule.ts +24 -4
- package/src/inspector.ts +17 -5
- package/src/types.ts +8 -12
- package/src/utils.ts +778 -286
- package/src/visit.ts +16 -6
- package/tsconfig.tsbuildinfo +1 -1
package/dist/visit.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
2
|
import { InspectorFilters, InspectorState } from './types.js';
|
|
3
|
-
export declare const
|
|
3
|
+
export declare const visitSetup: (checker: ts.TypeChecker, node: ts.Node, state: InspectorState, filters: InspectorFilters) => void;
|
|
4
|
+
export declare const visitRoutes: (checker: ts.TypeChecker, node: ts.Node, state: InspectorState, filters: InspectorFilters) => void;
|
package/dist/visit.js
CHANGED
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
2
|
import { addFileWithFactory } from './add-file-with-factory.js';
|
|
3
3
|
import { addFileExtendsCoreType } from './add-file-extends-core-type.js';
|
|
4
|
-
import {
|
|
4
|
+
import { addHTTPRoute } from './add-http-route.js';
|
|
5
5
|
import { addSchedule } from './add-schedule.js';
|
|
6
|
+
import { addFunctions } from './add-functions.js';
|
|
6
7
|
import { addChannel } from './add-channel.js';
|
|
7
|
-
export const
|
|
8
|
+
export const visitSetup = (checker, node, state, filters) => {
|
|
8
9
|
addFileExtendsCoreType(node, checker, state.singletonServicesTypeImportMap, 'CoreSingletonServices');
|
|
9
10
|
addFileExtendsCoreType(node, checker, state.sessionServicesTypeImportMap, 'CoreServices');
|
|
10
11
|
addFileExtendsCoreType(node, checker, state.userSessionTypeImportMap, 'CoreUserSession');
|
|
11
12
|
addFileWithFactory(node, checker, state.singletonServicesFactories, 'CreateSingletonServices');
|
|
12
13
|
addFileWithFactory(node, checker, state.sessionServicesFactories, 'CreateSessionServices');
|
|
13
14
|
addFileWithFactory(node, checker, state.configFactories, 'CreateConfig');
|
|
14
|
-
|
|
15
|
+
addFunctions(node, checker, state, filters);
|
|
16
|
+
ts.forEachChild(node, (child) => visitSetup(checker, child, state, filters));
|
|
17
|
+
};
|
|
18
|
+
export const visitRoutes = (checker, node, state, filters) => {
|
|
19
|
+
addHTTPRoute(node, checker, state, filters);
|
|
15
20
|
addSchedule(node, checker, state, filters);
|
|
16
21
|
addChannel(node, checker, state, filters);
|
|
17
|
-
ts.forEachChild(node, (child) =>
|
|
22
|
+
ts.forEachChild(node, (child) => visitRoutes(checker, child, state, filters));
|
|
18
23
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pikku/inspector",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"author": "yasser.fadl@gmail.com",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"test:coverage": "bash run-tests.sh --coverage"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@pikku/core": "^0.
|
|
20
|
+
"@pikku/core": "^0.7.0",
|
|
21
21
|
"path-to-regexp": "^8.2.0",
|
|
22
22
|
"typescript": "^5.6"
|
|
23
23
|
},
|
package/src/add-channel.ts
CHANGED
|
@@ -4,177 +4,479 @@ import { pathToRegexp } from 'path-to-regexp'
|
|
|
4
4
|
import { APIDocs } from '@pikku/core'
|
|
5
5
|
import { getInputTypes } from './add-http-route.js'
|
|
6
6
|
import {
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
extractFunctionName,
|
|
8
|
+
getPropertyAssignmentInitializer,
|
|
9
9
|
matchesFilters,
|
|
10
10
|
} from './utils.js'
|
|
11
|
-
import { ChannelMeta } from '@pikku/core/channel'
|
|
12
|
-
import {
|
|
13
|
-
import { InspectorFilters, InspectorState } from './types.js'
|
|
11
|
+
import type { ChannelMessageMeta, ChannelMeta } from '@pikku/core/channel'
|
|
12
|
+
import type { InspectorFilters, InspectorState } from './types.js'
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Safely get the “initializer” expression of a property-like AST node:
|
|
16
|
+
* - for `foo: expr`, returns `expr`
|
|
17
|
+
* - for `{ foo }` shorthand, returns the identifier `foo`
|
|
18
|
+
* - otherwise, returns undefined
|
|
19
|
+
*/
|
|
20
|
+
function getInitializerOf(
|
|
21
|
+
elem: ts.ObjectLiteralElementLike
|
|
22
|
+
): ts.Expression | undefined {
|
|
23
|
+
if (ts.isPropertyAssignment(elem)) {
|
|
24
|
+
return elem.initializer
|
|
25
|
+
}
|
|
26
|
+
if (ts.isShorthandPropertyAssignment(elem)) {
|
|
27
|
+
return elem.name
|
|
28
|
+
}
|
|
29
|
+
return undefined
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Resolve a handler expression (Identifier, CallExpression, or { func })
|
|
34
|
+
* into its underlying function name.
|
|
35
|
+
*/
|
|
36
|
+
function getHandlerNameFromExpression(
|
|
37
|
+
expr: ts.Expression,
|
|
38
|
+
checker: ts.TypeChecker
|
|
39
|
+
): string | null {
|
|
40
|
+
// Handle direct identifier case (which includes shorthand properties)
|
|
41
|
+
if (ts.isIdentifier(expr)) {
|
|
42
|
+
const sym = checker.getSymbolAtLocation(expr)
|
|
43
|
+
if (sym) {
|
|
44
|
+
let resolvedSym = sym
|
|
45
|
+
if (resolvedSym.flags & ts.SymbolFlags.Alias) {
|
|
46
|
+
resolvedSym = checker.getAliasedSymbol(resolvedSym) ?? resolvedSym
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Try to get declarations
|
|
50
|
+
const decls = resolvedSym.declarations ?? []
|
|
51
|
+
if (decls.length > 0) {
|
|
52
|
+
const decl = decls[0]!
|
|
29
53
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
54
|
+
// For variable declarations, look at the initializer
|
|
55
|
+
if (ts.isVariableDeclaration(decl) && decl.initializer) {
|
|
56
|
+
if (
|
|
57
|
+
ts.isCallExpression(decl.initializer) ||
|
|
58
|
+
ts.isArrowFunction(decl.initializer) ||
|
|
59
|
+
ts.isFunctionExpression(decl.initializer)
|
|
60
|
+
) {
|
|
61
|
+
// Extract function name from the declaration's initializer
|
|
62
|
+
const { pikkuFuncName } = extractFunctionName(
|
|
63
|
+
decl.initializer,
|
|
64
|
+
checker
|
|
65
|
+
)
|
|
66
|
+
return pikkuFuncName
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// For function declarations, use directly
|
|
70
|
+
else if (ts.isFunctionDeclaration(decl)) {
|
|
71
|
+
const { pikkuFuncName } = extractFunctionName(decl, checker)
|
|
72
|
+
return pikkuFuncName
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Fallback: try to extract directly from the identifier
|
|
78
|
+
const { pikkuFuncName } = extractFunctionName(expr, checker)
|
|
79
|
+
return pikkuFuncName
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Handle call expressions
|
|
83
|
+
if (ts.isCallExpression(expr)) {
|
|
84
|
+
const { pikkuFuncName } = extractFunctionName(expr, checker)
|
|
85
|
+
return pikkuFuncName
|
|
35
86
|
}
|
|
36
87
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
88
|
+
// Handle object literals with 'func' property
|
|
89
|
+
if (ts.isObjectLiteralExpression(expr)) {
|
|
90
|
+
const fnProp = getPropertyAssignmentInitializer(expr, 'func', true, checker)
|
|
91
|
+
if (fnProp) {
|
|
92
|
+
return getHandlerNameFromExpression(fnProp, checker)
|
|
93
|
+
}
|
|
42
94
|
}
|
|
43
95
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
96
|
+
return null
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Build out the nested message-routes by looking up each handler
|
|
101
|
+
* in state.functions.meta instead of re-inferring it here.
|
|
102
|
+
*/
|
|
103
|
+
export function addMessagesRoutes(
|
|
104
|
+
obj: ts.ObjectLiteralExpression,
|
|
105
|
+
state: InspectorState,
|
|
106
|
+
checker: ts.TypeChecker
|
|
107
|
+
): ChannelMeta['messageRoutes'] {
|
|
108
|
+
const result: ChannelMeta['messageRoutes'] = {}
|
|
109
|
+
const onMsgRouteProp = getPropertyAssignmentInitializer(
|
|
110
|
+
obj,
|
|
111
|
+
'onMessageRoute',
|
|
112
|
+
true,
|
|
113
|
+
checker
|
|
114
|
+
)
|
|
115
|
+
if (!onMsgRouteProp) return result
|
|
116
|
+
|
|
117
|
+
if (!onMsgRouteProp || !ts.isObjectLiteralExpression(onMsgRouteProp))
|
|
118
|
+
return result
|
|
119
|
+
|
|
120
|
+
for (const chanElem of onMsgRouteProp.properties) {
|
|
121
|
+
const chanInit = getInitializerOf(chanElem)
|
|
122
|
+
if (!chanInit || !ts.isObjectLiteralExpression(chanInit)) continue
|
|
123
|
+
|
|
124
|
+
const channelKey = chanElem.name!.getText()
|
|
125
|
+
result[channelKey] = {}
|
|
126
|
+
|
|
127
|
+
for (const routeElem of chanInit.properties) {
|
|
128
|
+
const init = getInitializerOf(routeElem)
|
|
129
|
+
if (!init) continue
|
|
130
|
+
|
|
131
|
+
const routeKey = routeElem.name!.getText()
|
|
132
|
+
|
|
133
|
+
// For shorthand properties, we need to resolve the identifier to its declaration
|
|
134
|
+
if (ts.isShorthandPropertyAssignment(routeElem)) {
|
|
135
|
+
// Get the symbol for the shorthand property
|
|
136
|
+
const shorthandSym =
|
|
137
|
+
checker.getShorthandAssignmentValueSymbol(routeElem)
|
|
138
|
+
|
|
139
|
+
if (
|
|
140
|
+
shorthandSym &&
|
|
141
|
+
shorthandSym.declarations &&
|
|
142
|
+
shorthandSym.declarations.length > 0
|
|
143
|
+
) {
|
|
144
|
+
const shorthandDecl = shorthandSym.declarations[0]
|
|
145
|
+
if (!shorthandDecl) {
|
|
146
|
+
throw new Error(
|
|
147
|
+
`No declaration found for shorthand property '${routeKey}'`
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Handle import specifiers
|
|
152
|
+
if (ts.isImportSpecifier(shorthandDecl)) {
|
|
153
|
+
// Get the imported symbol
|
|
154
|
+
const importedSymbol = checker.getSymbolAtLocation(
|
|
155
|
+
shorthandDecl.name
|
|
156
|
+
)
|
|
157
|
+
if (importedSymbol) {
|
|
158
|
+
// Try to resolve the alias to get the original symbol
|
|
159
|
+
let resolvedSymbol = importedSymbol
|
|
160
|
+
if (resolvedSymbol.flags & ts.SymbolFlags.Alias) {
|
|
161
|
+
resolvedSymbol =
|
|
162
|
+
checker.getAliasedSymbol(resolvedSymbol) ?? resolvedSymbol
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Try to get the declarations of the resolved symbol
|
|
166
|
+
const importDecls = resolvedSymbol.declarations ?? []
|
|
167
|
+
if (importDecls.length > 0) {
|
|
168
|
+
const importDecl = importDecls[0]!
|
|
169
|
+
|
|
170
|
+
// Handle different kinds of declarations
|
|
171
|
+
if (
|
|
172
|
+
ts.isVariableDeclaration(importDecl) &&
|
|
173
|
+
importDecl.initializer
|
|
174
|
+
) {
|
|
175
|
+
// Extract from the initializer if it's a function
|
|
176
|
+
if (
|
|
177
|
+
ts.isArrowFunction(importDecl.initializer) ||
|
|
178
|
+
ts.isFunctionExpression(importDecl.initializer) ||
|
|
179
|
+
ts.isCallExpression(importDecl.initializer)
|
|
180
|
+
) {
|
|
181
|
+
const { pikkuFuncName } = extractFunctionName(
|
|
182
|
+
importDecl.initializer,
|
|
183
|
+
checker
|
|
184
|
+
)
|
|
185
|
+
const handlerName = pikkuFuncName
|
|
186
|
+
|
|
187
|
+
// Look up in the registry
|
|
188
|
+
const fnMeta = state.functions.meta[handlerName]
|
|
189
|
+
if (fnMeta) {
|
|
190
|
+
result[channelKey]![routeKey] = {
|
|
191
|
+
pikkuFuncName: handlerName,
|
|
192
|
+
inputs: fnMeta.inputs ?? null,
|
|
193
|
+
outputs: fnMeta.outputs ?? null,
|
|
194
|
+
}
|
|
195
|
+
continue
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
} else if (ts.isFunctionDeclaration(importDecl)) {
|
|
199
|
+
// Extract from the function declaration
|
|
200
|
+
const { pikkuFuncName } = extractFunctionName(
|
|
201
|
+
importDecl,
|
|
202
|
+
checker
|
|
203
|
+
)
|
|
204
|
+
const handlerName = pikkuFuncName
|
|
205
|
+
|
|
206
|
+
// Look up in the registry
|
|
207
|
+
const fnMeta = state.functions.meta[handlerName]
|
|
208
|
+
if (fnMeta) {
|
|
209
|
+
result[channelKey]![routeKey] = {
|
|
210
|
+
pikkuFuncName: handlerName,
|
|
211
|
+
inputs: fnMeta.inputs ?? null,
|
|
212
|
+
outputs: fnMeta.outputs ?? null,
|
|
213
|
+
}
|
|
214
|
+
continue
|
|
215
|
+
}
|
|
216
|
+
} else if (ts.isExportSpecifier(importDecl)) {
|
|
217
|
+
// For re-exports, we need to follow another level of indirection
|
|
218
|
+
const exportSymbol = checker.getSymbolAtLocation(
|
|
219
|
+
importDecl.name
|
|
220
|
+
)
|
|
221
|
+
if (exportSymbol) {
|
|
222
|
+
let resolvedExportSymbol = exportSymbol
|
|
223
|
+
if (resolvedExportSymbol.flags & ts.SymbolFlags.Alias) {
|
|
224
|
+
resolvedExportSymbol =
|
|
225
|
+
checker.getAliasedSymbol(resolvedExportSymbol) ??
|
|
226
|
+
resolvedExportSymbol
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const exportDecls = resolvedExportSymbol.declarations ?? []
|
|
230
|
+
if (exportDecls.length > 0) {
|
|
231
|
+
const exportDecl = exportDecls[0]!
|
|
232
|
+
|
|
233
|
+
if (
|
|
234
|
+
ts.isVariableDeclaration(exportDecl) &&
|
|
235
|
+
exportDecl.initializer
|
|
236
|
+
) {
|
|
237
|
+
const { pikkuFuncName } = extractFunctionName(
|
|
238
|
+
exportDecl.initializer,
|
|
239
|
+
checker
|
|
240
|
+
)
|
|
241
|
+
const handlerName = pikkuFuncName
|
|
242
|
+
|
|
243
|
+
const fnMeta = state.functions.meta[handlerName]
|
|
244
|
+
if (fnMeta) {
|
|
245
|
+
result[channelKey]![routeKey] = {
|
|
246
|
+
pikkuFuncName: handlerName,
|
|
247
|
+
inputs: fnMeta.inputs ?? null,
|
|
248
|
+
outputs: fnMeta.outputs ?? null,
|
|
249
|
+
}
|
|
250
|
+
continue
|
|
251
|
+
}
|
|
252
|
+
} else if (ts.isFunctionDeclaration(exportDecl)) {
|
|
253
|
+
const { pikkuFuncName } = extractFunctionName(
|
|
254
|
+
exportDecl,
|
|
255
|
+
checker
|
|
256
|
+
)
|
|
257
|
+
const handlerName = pikkuFuncName
|
|
258
|
+
|
|
259
|
+
const fnMeta = state.functions.meta[handlerName]
|
|
260
|
+
if (fnMeta) {
|
|
261
|
+
result[channelKey]![routeKey] = {
|
|
262
|
+
pikkuFuncName: handlerName,
|
|
263
|
+
inputs: fnMeta.inputs ?? null,
|
|
264
|
+
outputs: fnMeta.outputs ?? null,
|
|
265
|
+
}
|
|
266
|
+
continue
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// As a fallback, try to look up by name
|
|
276
|
+
const funcName = shorthandDecl.name.getText()
|
|
277
|
+
|
|
278
|
+
// Look for any function in the registry that ends with this name
|
|
279
|
+
const possibleMatch = Object.keys(state.functions.meta).find(
|
|
280
|
+
(key) => {
|
|
281
|
+
const parts = key.split('_')
|
|
282
|
+
const filename = parts[parts.length - 3] || ''
|
|
283
|
+
return filename.endsWith(funcName)
|
|
284
|
+
}
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
if (possibleMatch) {
|
|
288
|
+
const fnMeta = state.functions.meta[possibleMatch]
|
|
289
|
+
if (!fnMeta) {
|
|
290
|
+
console.error(
|
|
291
|
+
`No function metadata found for handler '${possibleMatch}'`
|
|
292
|
+
)
|
|
293
|
+
continue
|
|
294
|
+
}
|
|
295
|
+
result[channelKey]![routeKey] = {
|
|
296
|
+
pikkuFuncName: possibleMatch,
|
|
297
|
+
inputs: fnMeta.inputs ?? null,
|
|
298
|
+
outputs: fnMeta.outputs ?? null,
|
|
299
|
+
}
|
|
300
|
+
continue
|
|
301
|
+
}
|
|
302
|
+
} else {
|
|
303
|
+
// Handle other declaration types (variable, function, etc.)
|
|
304
|
+
let actualFunction: ts.Node | undefined = undefined
|
|
305
|
+
|
|
306
|
+
if (ts.isVariableDeclaration(shorthandDecl)) {
|
|
307
|
+
// Check if it has an initializer
|
|
308
|
+
if (shorthandDecl.initializer) {
|
|
309
|
+
// If it's a function expression or similar, use that
|
|
310
|
+
if (
|
|
311
|
+
ts.isArrowFunction(shorthandDecl.initializer) ||
|
|
312
|
+
ts.isFunctionExpression(shorthandDecl.initializer) ||
|
|
313
|
+
ts.isCallExpression(shorthandDecl.initializer)
|
|
314
|
+
) {
|
|
315
|
+
actualFunction = shorthandDecl.initializer
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
} else if (ts.isFunctionDeclaration(shorthandDecl)) {
|
|
319
|
+
actualFunction = shorthandDecl
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// If we found the actual function, extract its name
|
|
323
|
+
if (actualFunction) {
|
|
324
|
+
// Extract the function name directly from the actual function
|
|
325
|
+
const { pikkuFuncName } = extractFunctionName(
|
|
326
|
+
actualFunction,
|
|
327
|
+
checker
|
|
328
|
+
)
|
|
329
|
+
const handlerName = pikkuFuncName
|
|
330
|
+
|
|
331
|
+
// Now use this handlerName to look up in the registry
|
|
332
|
+
const fnMeta = state.functions.meta[handlerName]
|
|
333
|
+
|
|
334
|
+
if (fnMeta) {
|
|
335
|
+
result[channelKey]![routeKey] = {
|
|
336
|
+
pikkuFuncName: handlerName,
|
|
337
|
+
inputs: fnMeta.inputs ?? null,
|
|
338
|
+
outputs: fnMeta.outputs ?? null,
|
|
339
|
+
}
|
|
340
|
+
continue // Skip the normal processing below
|
|
341
|
+
}
|
|
342
|
+
}
|
|
65
343
|
}
|
|
66
344
|
}
|
|
67
|
-
} else {
|
|
68
|
-
console.warn('Nested property is not an object literal:', nestedObject)
|
|
69
345
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
)
|
|
346
|
+
|
|
347
|
+
// Normal processing for non-shorthand properties
|
|
348
|
+
const handlerName = getHandlerNameFromExpression(init, checker)
|
|
349
|
+
if (!handlerName) {
|
|
350
|
+
console.error(
|
|
351
|
+
`Could not resolve handler for message route '${routeKey}'`
|
|
352
|
+
)
|
|
353
|
+
continue
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const fnMeta = state.functions.meta[handlerName]
|
|
357
|
+
if (!fnMeta) {
|
|
358
|
+
console.error(`No function metadata found for handler '${handlerName}'`)
|
|
359
|
+
continue
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
result[channelKey]![routeKey] = {
|
|
363
|
+
pikkuFuncName: handlerName,
|
|
364
|
+
inputs: fnMeta.inputs ?? null,
|
|
365
|
+
outputs: fnMeta.outputs ?? null,
|
|
366
|
+
}
|
|
74
367
|
}
|
|
75
|
-
}
|
|
368
|
+
}
|
|
76
369
|
|
|
77
|
-
return
|
|
370
|
+
return result
|
|
78
371
|
}
|
|
79
372
|
|
|
80
|
-
|
|
373
|
+
/**
|
|
374
|
+
* Inspect addChannel calls, look up all handlers in state.functions.meta,
|
|
375
|
+
* and emit one entry into state.channels.meta.
|
|
376
|
+
*/
|
|
377
|
+
export function addChannel(
|
|
81
378
|
node: ts.Node,
|
|
82
379
|
checker: ts.TypeChecker,
|
|
83
380
|
state: InspectorState,
|
|
84
381
|
filters: InspectorFilters
|
|
85
|
-
)
|
|
86
|
-
if (!ts.isCallExpression(node))
|
|
87
|
-
|
|
88
|
-
|
|
382
|
+
) {
|
|
383
|
+
if (!ts.isCallExpression(node)) return
|
|
384
|
+
const { expression, arguments: args } = node
|
|
385
|
+
if (!ts.isIdentifier(expression) || expression.text !== 'addChannel') return
|
|
386
|
+
const first = args[0]
|
|
387
|
+
if (!first || !ts.isObjectLiteralExpression(first)) return
|
|
89
388
|
|
|
90
|
-
const
|
|
91
|
-
const
|
|
92
|
-
const
|
|
389
|
+
const obj = first
|
|
390
|
+
const name = getPropertyValue(obj, 'name') as string | undefined
|
|
391
|
+
const route = (getPropertyValue(obj, 'route') as string) ?? ''
|
|
93
392
|
|
|
94
|
-
|
|
95
|
-
|
|
393
|
+
if (!name) {
|
|
394
|
+
console.error('Channel name is required')
|
|
96
395
|
return
|
|
97
396
|
}
|
|
98
397
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
398
|
+
// path parameters
|
|
399
|
+
const params = route
|
|
400
|
+
? pathToRegexp(route)
|
|
401
|
+
.keys.filter((k) => k.type === 'param')
|
|
402
|
+
.map((k) => k.name)
|
|
403
|
+
: []
|
|
102
404
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
let tags: string[] | undefined = undefined
|
|
107
|
-
let inputType: string | null = null
|
|
108
|
-
let route: string | null = null
|
|
109
|
-
let name: string | null = null
|
|
405
|
+
const docs = getPropertyValue(obj, 'docs') as APIDocs | undefined
|
|
406
|
+
const tags = getPropertyValue(obj, 'tags') as string[] | undefined
|
|
407
|
+
const query = getPropertyValue(obj, 'query') as string[] | []
|
|
110
408
|
|
|
111
|
-
|
|
112
|
-
if (ts.isObjectLiteralExpression(firstArg)) {
|
|
113
|
-
const obj = firstArg
|
|
409
|
+
if (!matchesFilters(filters, { tags }, { type: 'channel', name })) return
|
|
114
410
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
411
|
+
const connect = getPropertyAssignmentInitializer(
|
|
412
|
+
obj,
|
|
413
|
+
'onConnect',
|
|
414
|
+
false,
|
|
415
|
+
checker
|
|
416
|
+
)
|
|
417
|
+
const disconnect = getPropertyAssignmentInitializer(
|
|
418
|
+
obj,
|
|
419
|
+
'onDisconnect',
|
|
420
|
+
false,
|
|
421
|
+
checker
|
|
422
|
+
)
|
|
122
423
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
424
|
+
// default onMessage handler
|
|
425
|
+
let message: ChannelMessageMeta | null = null
|
|
426
|
+
const onMsgProp = getPropertyAssignmentInitializer(
|
|
427
|
+
obj,
|
|
428
|
+
'onMessage',
|
|
429
|
+
false,
|
|
430
|
+
checker
|
|
431
|
+
)
|
|
432
|
+
if (onMsgProp) {
|
|
433
|
+
const handlerName =
|
|
434
|
+
onMsgProp && getHandlerNameFromExpression(onMsgProp, checker)
|
|
435
|
+
const fnMeta = handlerName && state.functions.meta[handlerName]
|
|
436
|
+
if (!fnMeta) {
|
|
437
|
+
console.error(
|
|
438
|
+
`No function metadata for onMessage handler '${handlerName}'`
|
|
439
|
+
)
|
|
440
|
+
throw new Error()
|
|
131
441
|
} else {
|
|
132
|
-
|
|
442
|
+
message = {
|
|
443
|
+
pikkuFuncName: extractFunctionName(onMsgProp as any, checker)
|
|
444
|
+
.pikkuFuncName,
|
|
445
|
+
inputs: fnMeta.inputs ?? null,
|
|
446
|
+
outputs: fnMeta.outputs ?? null,
|
|
447
|
+
}
|
|
133
448
|
}
|
|
449
|
+
}
|
|
134
450
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
tags = (getPropertyValue(obj, 'tags') as string[]) || undefined
|
|
138
|
-
|
|
139
|
-
const connect = !!getPropertyAssignment(obj, 'onConnect')
|
|
140
|
-
const disconnect = !!getPropertyAssignment(obj, 'onDisconnect')
|
|
141
|
-
const { inputs, outputs, type } = getFunctionTypes(checker, obj, {
|
|
142
|
-
funcName: 'onMessage',
|
|
143
|
-
inputIndex: 0,
|
|
144
|
-
outputIndex: 1,
|
|
145
|
-
typesMap: state.channels.typesMap,
|
|
146
|
-
})
|
|
147
|
-
const message = { inputs, outputs, type }
|
|
148
|
-
const messageRoutes = addMessagesRoutes(
|
|
149
|
-
obj,
|
|
150
|
-
checker,
|
|
151
|
-
state.channels.typesMap
|
|
152
|
-
)
|
|
153
|
-
|
|
154
|
-
if (!matchesFilters(filters, { tags }, { type: 'channel', name })) {
|
|
155
|
-
return
|
|
156
|
-
}
|
|
451
|
+
// nested message-routes
|
|
452
|
+
const messageRoutes = addMessagesRoutes(obj, state, checker)
|
|
157
453
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
454
|
+
// record into state
|
|
455
|
+
state.channels.files.add(node.getSourceFile().fileName)
|
|
456
|
+
state.channels.meta[name] = {
|
|
457
|
+
name,
|
|
458
|
+
route,
|
|
459
|
+
input: null,
|
|
460
|
+
params: params.length ? params : undefined,
|
|
461
|
+
query: query?.length ? query : undefined,
|
|
462
|
+
inputTypes: getInputTypes(
|
|
463
|
+
state.channels.metaInputTypes,
|
|
464
|
+
'get',
|
|
465
|
+
message?.inputs?.[0] ?? null,
|
|
466
|
+
query,
|
|
467
|
+
params
|
|
468
|
+
),
|
|
469
|
+
connectPikkuFuncName: connect
|
|
470
|
+
? extractFunctionName(connect, checker).pikkuFuncName
|
|
471
|
+
: null,
|
|
472
|
+
connect: !!connect,
|
|
473
|
+
disconnectPikkuFuncName: disconnect
|
|
474
|
+
? extractFunctionName(disconnect as any, checker).pikkuFuncName
|
|
475
|
+
: null,
|
|
476
|
+
disconnect: !!disconnect,
|
|
477
|
+
message,
|
|
478
|
+
messageRoutes,
|
|
479
|
+
docs: docs ?? undefined,
|
|
480
|
+
tags: tags ?? undefined,
|
|
179
481
|
}
|
|
180
482
|
}
|