@pikku/inspector 0.10.0 → 0.10.2
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 +54 -0
- package/dist/add/add-channel.js +68 -14
- package/dist/add/add-file-with-factory.js +53 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/inspector.d.ts +6 -0
- package/dist/inspector.js +49 -15
- package/dist/types.d.ts +3 -0
- package/dist/utils/get-files-and-methods.js +2 -2
- package/dist/utils/post-process.d.ts +1 -1
- package/dist/utils/post-process.js +30 -0
- package/dist/utils/serialize-inspector-state.d.ts +2 -0
- package/dist/utils/serialize-inspector-state.js +4 -0
- package/dist/utils/type-utils.js +5 -3
- package/package.json +2 -2
- package/src/add/add-channel.ts +94 -19
- package/src/add/add-file-with-factory.ts +69 -0
- package/src/index.ts +1 -1
- package/src/inspector.ts +73 -22
- package/src/types.ts +3 -0
- package/src/utils/get-files-and-methods.ts +2 -2
- package/src/utils/post-process.ts +40 -2
- package/src/utils/serialize-inspector-state.ts +6 -0
- package/src/utils/type-utils.ts +5 -3
- package/src/visit.ts +1 -1
- package/tsconfig.tsbuildinfo +1 -1
package/src/add/add-channel.ts
CHANGED
|
@@ -136,7 +136,15 @@ export function addMessagesRoutes(
|
|
|
136
136
|
const init = getInitializerOf(routeElem)
|
|
137
137
|
if (!init) continue
|
|
138
138
|
|
|
139
|
-
|
|
139
|
+
// Get the route key, stripping quotes if it's a string literal
|
|
140
|
+
const routeName = routeElem.name
|
|
141
|
+
if (!routeName) continue
|
|
142
|
+
|
|
143
|
+
let routeKey = routeName.getText()
|
|
144
|
+
// For string literals like 'greet' or "greet", strip the quotes
|
|
145
|
+
if (ts.isStringLiteral(routeName)) {
|
|
146
|
+
routeKey = routeName.text
|
|
147
|
+
}
|
|
140
148
|
|
|
141
149
|
// For shorthand properties, we need to resolve the identifier to its declaration
|
|
142
150
|
if (ts.isShorthandPropertyAssignment(routeElem)) {
|
|
@@ -196,8 +204,17 @@ export function addMessagesRoutes(
|
|
|
196
204
|
// Look up in the registry
|
|
197
205
|
const fnMeta = state.functions.meta[handlerName]
|
|
198
206
|
if (fnMeta) {
|
|
207
|
+
// Resolve middleware for this route
|
|
208
|
+
const routeTags = ts.isObjectLiteralExpression(init)
|
|
209
|
+
? getPropertyTags(init, 'channel', channelKey, logger)
|
|
210
|
+
: undefined
|
|
211
|
+
const routeMiddleware = ts.isObjectLiteralExpression(init)
|
|
212
|
+
? resolveMiddleware(state, init, routeTags, checker)
|
|
213
|
+
: undefined
|
|
214
|
+
|
|
199
215
|
result[channelKey]![routeKey] = {
|
|
200
216
|
pikkuFuncName: handlerName,
|
|
217
|
+
middleware: routeMiddleware,
|
|
201
218
|
}
|
|
202
219
|
continue
|
|
203
220
|
}
|
|
@@ -214,8 +231,17 @@ export function addMessagesRoutes(
|
|
|
214
231
|
// Look up in the registry
|
|
215
232
|
const fnMeta = state.functions.meta[handlerName]
|
|
216
233
|
if (fnMeta) {
|
|
234
|
+
// Resolve middleware for this route
|
|
235
|
+
const routeTags = ts.isObjectLiteralExpression(init)
|
|
236
|
+
? getPropertyTags(init, 'channel', channelKey, logger)
|
|
237
|
+
: undefined
|
|
238
|
+
const routeMiddleware = ts.isObjectLiteralExpression(init)
|
|
239
|
+
? resolveMiddleware(state, init, routeTags, checker)
|
|
240
|
+
: undefined
|
|
241
|
+
|
|
217
242
|
result[channelKey]![routeKey] = {
|
|
218
243
|
pikkuFuncName: handlerName,
|
|
244
|
+
middleware: routeMiddleware,
|
|
219
245
|
}
|
|
220
246
|
continue
|
|
221
247
|
}
|
|
@@ -249,8 +275,24 @@ export function addMessagesRoutes(
|
|
|
249
275
|
|
|
250
276
|
const fnMeta = state.functions.meta[handlerName]
|
|
251
277
|
if (fnMeta) {
|
|
278
|
+
// Resolve middleware for this route
|
|
279
|
+
const routeTags = ts.isObjectLiteralExpression(init)
|
|
280
|
+
? getPropertyTags(
|
|
281
|
+
init,
|
|
282
|
+
'channel',
|
|
283
|
+
channelKey,
|
|
284
|
+
logger
|
|
285
|
+
)
|
|
286
|
+
: undefined
|
|
287
|
+
const routeMiddleware = ts.isObjectLiteralExpression(
|
|
288
|
+
init
|
|
289
|
+
)
|
|
290
|
+
? resolveMiddleware(state, init, routeTags, checker)
|
|
291
|
+
: undefined
|
|
292
|
+
|
|
252
293
|
result[channelKey]![routeKey] = {
|
|
253
294
|
pikkuFuncName: handlerName,
|
|
295
|
+
middleware: routeMiddleware,
|
|
254
296
|
}
|
|
255
297
|
continue
|
|
256
298
|
}
|
|
@@ -264,8 +306,24 @@ export function addMessagesRoutes(
|
|
|
264
306
|
|
|
265
307
|
const fnMeta = state.functions.meta[handlerName]
|
|
266
308
|
if (fnMeta) {
|
|
309
|
+
// Resolve middleware for this route
|
|
310
|
+
const routeTags = ts.isObjectLiteralExpression(init)
|
|
311
|
+
? getPropertyTags(
|
|
312
|
+
init,
|
|
313
|
+
'channel',
|
|
314
|
+
channelKey,
|
|
315
|
+
logger
|
|
316
|
+
)
|
|
317
|
+
: undefined
|
|
318
|
+
const routeMiddleware = ts.isObjectLiteralExpression(
|
|
319
|
+
init
|
|
320
|
+
)
|
|
321
|
+
? resolveMiddleware(state, init, routeTags, checker)
|
|
322
|
+
: undefined
|
|
323
|
+
|
|
267
324
|
result[channelKey]![routeKey] = {
|
|
268
325
|
pikkuFuncName: handlerName,
|
|
326
|
+
middleware: routeMiddleware,
|
|
269
327
|
}
|
|
270
328
|
continue
|
|
271
329
|
}
|
|
@@ -336,8 +394,17 @@ export function addMessagesRoutes(
|
|
|
336
394
|
const fnMeta = state.functions.meta[handlerName]
|
|
337
395
|
|
|
338
396
|
if (fnMeta) {
|
|
397
|
+
// Resolve middleware for this route
|
|
398
|
+
const routeTags = ts.isObjectLiteralExpression(init)
|
|
399
|
+
? getPropertyTags(init, 'channel', channelKey, logger)
|
|
400
|
+
: undefined
|
|
401
|
+
const routeMiddleware = ts.isObjectLiteralExpression(init)
|
|
402
|
+
? resolveMiddleware(state, init, routeTags, checker)
|
|
403
|
+
: undefined
|
|
404
|
+
|
|
339
405
|
result[channelKey]![routeKey] = {
|
|
340
406
|
pikkuFuncName: handlerName,
|
|
407
|
+
middleware: routeMiddleware,
|
|
341
408
|
}
|
|
342
409
|
continue // Skip the normal processing below
|
|
343
410
|
}
|
|
@@ -368,8 +435,18 @@ export function addMessagesRoutes(
|
|
|
368
435
|
continue
|
|
369
436
|
}
|
|
370
437
|
|
|
438
|
+
// Resolve middleware and permissions for this route
|
|
439
|
+
// Check if the route config is an object literal with middleware/permissions
|
|
440
|
+
const routeTags = ts.isObjectLiteralExpression(init)
|
|
441
|
+
? getPropertyTags(init, 'channel', channelKey, logger)
|
|
442
|
+
: undefined
|
|
443
|
+
const routeMiddleware = ts.isObjectLiteralExpression(init)
|
|
444
|
+
? resolveMiddleware(state, init, routeTags, checker)
|
|
445
|
+
: undefined
|
|
446
|
+
|
|
371
447
|
result[channelKey]![routeKey] = {
|
|
372
448
|
pikkuFuncName: handlerName,
|
|
449
|
+
middleware: routeMiddleware,
|
|
373
450
|
}
|
|
374
451
|
}
|
|
375
452
|
}
|
|
@@ -417,13 +494,13 @@ export const addChannel: AddWiring = (
|
|
|
417
494
|
const connect = getPropertyAssignmentInitializer(
|
|
418
495
|
obj,
|
|
419
496
|
'onConnect',
|
|
420
|
-
|
|
497
|
+
true,
|
|
421
498
|
checker
|
|
422
499
|
)
|
|
423
500
|
const disconnect = getPropertyAssignmentInitializer(
|
|
424
501
|
obj,
|
|
425
502
|
'onDisconnect',
|
|
426
|
-
|
|
503
|
+
true,
|
|
427
504
|
checker
|
|
428
505
|
)
|
|
429
506
|
|
|
@@ -432,28 +509,26 @@ export const addChannel: AddWiring = (
|
|
|
432
509
|
const onMsgProp = getPropertyAssignmentInitializer(
|
|
433
510
|
obj,
|
|
434
511
|
'onMessage',
|
|
435
|
-
|
|
512
|
+
true,
|
|
436
513
|
checker
|
|
437
514
|
)
|
|
438
515
|
|
|
439
516
|
if (onMsgProp) {
|
|
440
|
-
const
|
|
441
|
-
onMsgProp
|
|
442
|
-
|
|
443
|
-
|
|
517
|
+
const { pikkuFuncName } = extractFunctionName(
|
|
518
|
+
onMsgProp,
|
|
519
|
+
checker,
|
|
520
|
+
state.rootDir
|
|
521
|
+
)
|
|
522
|
+
const fnMeta = state.functions.meta[pikkuFuncName]
|
|
444
523
|
if (!fnMeta) {
|
|
445
|
-
|
|
446
|
-
|
|
524
|
+
logger.critical(
|
|
525
|
+
ErrorCode.FUNCTION_METADATA_NOT_FOUND,
|
|
526
|
+
`No function metadata found for onMessage handler '${pikkuFuncName}'`
|
|
447
527
|
)
|
|
448
|
-
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
onMsgProp as any,
|
|
453
|
-
checker,
|
|
454
|
-
state.rootDir
|
|
455
|
-
).pikkuFuncName,
|
|
456
|
-
}
|
|
528
|
+
return
|
|
529
|
+
}
|
|
530
|
+
message = {
|
|
531
|
+
pikkuFuncName,
|
|
457
532
|
}
|
|
458
533
|
}
|
|
459
534
|
|
|
@@ -2,6 +2,13 @@ import * as ts from 'typescript'
|
|
|
2
2
|
import { PathToNameAndType, InspectorState } from '../types.js'
|
|
3
3
|
import { extractServicesFromFunction } from '../utils/extract-services.js'
|
|
4
4
|
|
|
5
|
+
// Mapping of wrapper function names to their corresponding types
|
|
6
|
+
const wrapperFunctionMap: Record<string, string> = {
|
|
7
|
+
pikkuConfig: 'CreateConfig',
|
|
8
|
+
pikkuServices: 'CreateSingletonServices',
|
|
9
|
+
pikkuSessionServices: 'CreateSessionServices',
|
|
10
|
+
}
|
|
11
|
+
|
|
5
12
|
export const addFileWithFactory = (
|
|
6
13
|
node: ts.Node,
|
|
7
14
|
checker: ts.TypeChecker,
|
|
@@ -14,6 +21,68 @@ export const addFileWithFactory = (
|
|
|
14
21
|
const variableTypeNode = node.type
|
|
15
22
|
const variableName = node.name.getText()
|
|
16
23
|
|
|
24
|
+
// Check for wrapper function calls FIRST (e.g., pikkuConfig(...), pikkuServices(...))
|
|
25
|
+
// This handles both cases: with and without explicit type annotations
|
|
26
|
+
if (node.initializer && ts.isCallExpression(node.initializer)) {
|
|
27
|
+
const callExpression = node.initializer
|
|
28
|
+
const expression = callExpression.expression
|
|
29
|
+
|
|
30
|
+
if (ts.isIdentifier(expression)) {
|
|
31
|
+
const wrapperFunctionName = expression.text
|
|
32
|
+
const inferredType = wrapperFunctionMap[wrapperFunctionName]
|
|
33
|
+
|
|
34
|
+
if (inferredType === expectedTypeName) {
|
|
35
|
+
// Get the type declaration path from the wrapper function
|
|
36
|
+
const typeSymbol = checker.getSymbolAtLocation(expression)
|
|
37
|
+
let typeDeclarationPath: string | null = null
|
|
38
|
+
|
|
39
|
+
if (
|
|
40
|
+
typeSymbol &&
|
|
41
|
+
typeSymbol.declarations &&
|
|
42
|
+
typeSymbol.declarations[0]
|
|
43
|
+
) {
|
|
44
|
+
const declaration = typeSymbol.declarations[0]
|
|
45
|
+
const sourceFile = declaration.getSourceFile()
|
|
46
|
+
typeDeclarationPath = sourceFile.fileName
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const variables = methods.get(fileName) || []
|
|
50
|
+
variables.push({
|
|
51
|
+
variable: variableName,
|
|
52
|
+
type: inferredType,
|
|
53
|
+
typePath: typeDeclarationPath,
|
|
54
|
+
})
|
|
55
|
+
methods.set(fileName, variables)
|
|
56
|
+
|
|
57
|
+
// Extract singleton services for CreateSessionServices factories
|
|
58
|
+
if (
|
|
59
|
+
expectedTypeName === 'CreateSessionServices' &&
|
|
60
|
+
state &&
|
|
61
|
+
callExpression.arguments.length > 0
|
|
62
|
+
) {
|
|
63
|
+
const firstArg = callExpression.arguments[0]
|
|
64
|
+
let functionNode:
|
|
65
|
+
| ts.ArrowFunction
|
|
66
|
+
| ts.FunctionExpression
|
|
67
|
+
| undefined
|
|
68
|
+
|
|
69
|
+
if (ts.isArrowFunction(firstArg)) {
|
|
70
|
+
functionNode = firstArg
|
|
71
|
+
} else if (ts.isFunctionExpression(firstArg)) {
|
|
72
|
+
functionNode = firstArg
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (functionNode) {
|
|
76
|
+
const servicesMeta = extractServicesFromFunction(functionNode)
|
|
77
|
+
state.sessionServicesMeta.set(variableName, servicesMeta.services)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return // Early return since we found a match
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
17
86
|
if (variableTypeNode && ts.isTypeReferenceNode(variableTypeNode)) {
|
|
18
87
|
const typeNameNode = variableTypeNode.typeName || null
|
|
19
88
|
|
package/src/index.ts
CHANGED
package/src/inspector.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as ts from 'typescript'
|
|
2
|
+
import { performance } from 'perf_hooks'
|
|
2
3
|
import { visitSetup, visitRoutes } from './visit.js'
|
|
3
4
|
import { TypesMap } from './types-map.js'
|
|
4
5
|
import { InspectorState, InspectorLogger, InspectorOptions } from './types.js'
|
|
@@ -6,22 +7,13 @@ import { getFilesAndMethods } from './utils/get-files-and-methods.js'
|
|
|
6
7
|
import { findCommonAncestor } from './utils/find-root-dir.js'
|
|
7
8
|
import { aggregateRequiredServices } from './utils/post-process.js'
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
module: ts.ModuleKind.CommonJS,
|
|
17
|
-
})
|
|
18
|
-
const checker = program.getTypeChecker()
|
|
19
|
-
const sourceFiles = program.getSourceFiles()
|
|
20
|
-
|
|
21
|
-
// Infer root directory from source files
|
|
22
|
-
const rootDir = findCommonAncestor(routeFiles)
|
|
23
|
-
|
|
24
|
-
const state: InspectorState = {
|
|
10
|
+
/**
|
|
11
|
+
* Creates an initial/empty inspector state with all required properties initialized
|
|
12
|
+
* @param rootDir - The root directory for the project
|
|
13
|
+
* @returns A fresh InspectorState with empty collections
|
|
14
|
+
*/
|
|
15
|
+
export function getInitialInspectorState(rootDir: string): InspectorState {
|
|
16
|
+
return {
|
|
25
17
|
rootDir,
|
|
26
18
|
singletonServicesTypeImportMap: new Map(),
|
|
27
19
|
sessionServicesTypeImportMap: new Map(),
|
|
@@ -99,30 +91,89 @@ export const inspect = (
|
|
|
99
91
|
usedFunctions: new Set(),
|
|
100
92
|
usedMiddleware: new Set(),
|
|
101
93
|
usedPermissions: new Set(),
|
|
94
|
+
allSingletonServices: [],
|
|
95
|
+
allSessionServices: [],
|
|
102
96
|
},
|
|
103
97
|
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export const inspect = (
|
|
101
|
+
logger: InspectorLogger,
|
|
102
|
+
routeFiles: string[],
|
|
103
|
+
options: InspectorOptions = {}
|
|
104
|
+
): InspectorState => {
|
|
105
|
+
const startProgram = performance.now()
|
|
106
|
+
const program = ts.createProgram(routeFiles, {
|
|
107
|
+
target: ts.ScriptTarget.ESNext,
|
|
108
|
+
module: ts.ModuleKind.CommonJS,
|
|
109
|
+
skipLibCheck: true,
|
|
110
|
+
skipDefaultLibCheck: true,
|
|
111
|
+
moduleResolution: ts.ModuleResolutionKind.Node10,
|
|
112
|
+
types: [],
|
|
113
|
+
allowJs: false,
|
|
114
|
+
checkJs: false,
|
|
115
|
+
})
|
|
116
|
+
logger.debug(
|
|
117
|
+
`Created program in ${(performance.now() - startProgram).toFixed(2)}ms`
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
const startChecker = performance.now()
|
|
121
|
+
const checker = program.getTypeChecker()
|
|
122
|
+
logger.debug(
|
|
123
|
+
`Got type checker in ${(performance.now() - startChecker).toFixed(2)}ms`
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
const startSourceFiles = performance.now()
|
|
127
|
+
const sourceFiles = program.getSourceFiles()
|
|
128
|
+
logger.debug(
|
|
129
|
+
`Got source files in ${(performance.now() - startSourceFiles).toFixed(2)}ms`
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
// Infer root directory from source files
|
|
133
|
+
const rootDir = findCommonAncestor(routeFiles)
|
|
134
|
+
|
|
135
|
+
const state = getInitialInspectorState(rootDir)
|
|
104
136
|
|
|
105
137
|
// First sweep: add all functions
|
|
138
|
+
const startSetup = performance.now()
|
|
106
139
|
for (const sourceFile of sourceFiles) {
|
|
107
140
|
ts.forEachChild(sourceFile, (child) =>
|
|
108
141
|
visitSetup(logger, checker, child, state, options)
|
|
109
142
|
)
|
|
110
143
|
}
|
|
144
|
+
logger.debug(
|
|
145
|
+
`Visit setup phase completed in ${(performance.now() - startSetup).toFixed(2)}ms`
|
|
146
|
+
)
|
|
111
147
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
148
|
+
if (!options.setupOnly) {
|
|
149
|
+
// Second sweep: add all transports
|
|
150
|
+
const startRoutes = performance.now()
|
|
151
|
+
for (const sourceFile of sourceFiles) {
|
|
152
|
+
ts.forEachChild(sourceFile, (child) =>
|
|
153
|
+
visitRoutes(logger, checker, child, state, options)
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
logger.debug(
|
|
157
|
+
`Visit routes phase completed in ${(performance.now() - startRoutes).toFixed(2)}ms`
|
|
116
158
|
)
|
|
117
159
|
}
|
|
118
160
|
|
|
119
161
|
// Populate filesAndMethods
|
|
162
|
+
const startFilesAndMethods = performance.now()
|
|
120
163
|
const { result, errors } = getFilesAndMethods(state, options.types)
|
|
121
164
|
state.filesAndMethods = result
|
|
122
165
|
state.filesAndMethodsErrors = errors
|
|
166
|
+
logger.debug(
|
|
167
|
+
`Get files and methods completed in ${(performance.now() - startFilesAndMethods).toFixed(2)}ms`
|
|
168
|
+
)
|
|
123
169
|
|
|
124
|
-
|
|
125
|
-
|
|
170
|
+
if (!options.setupOnly) {
|
|
171
|
+
const startAggregate = performance.now()
|
|
172
|
+
aggregateRequiredServices(state)
|
|
173
|
+
logger.debug(
|
|
174
|
+
`Aggregate required services completed in ${(performance.now() - startAggregate).toFixed(2)}ms`
|
|
175
|
+
)
|
|
176
|
+
}
|
|
126
177
|
|
|
127
178
|
return state
|
|
128
179
|
}
|
package/src/types.ts
CHANGED
|
@@ -114,6 +114,7 @@ export type InspectorFilters = {
|
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
export type InspectorOptions = Partial<{
|
|
117
|
+
setupOnly: boolean
|
|
117
118
|
types: Partial<{
|
|
118
119
|
configFileType: string
|
|
119
120
|
userSessionType: string
|
|
@@ -231,5 +232,7 @@ export interface InspectorState {
|
|
|
231
232
|
usedFunctions: Set<string> // Function names actually wired/exposed
|
|
232
233
|
usedMiddleware: Set<string> // Middleware names used by wired functions
|
|
233
234
|
usedPermissions: Set<string> // Permission names used by wired functions
|
|
235
|
+
allSingletonServices: string[] // All services available in SingletonServices type
|
|
236
|
+
allSessionServices: string[] // All services available in Services type (excluding SingletonServices)
|
|
234
237
|
}
|
|
235
238
|
}
|
|
@@ -54,9 +54,9 @@ const getMetaTypes = (
|
|
|
54
54
|
const helpMessage =
|
|
55
55
|
type === 'CoreConfig'
|
|
56
56
|
? `No ${type} found. Make sure you have exported a createConfig function in your codebase:\n\n` +
|
|
57
|
-
`export const createConfig
|
|
57
|
+
`export const createConfig = pikkuConfig(async () => {\n` +
|
|
58
58
|
` return {}\n` +
|
|
59
|
-
`}\n\n` +
|
|
59
|
+
`})\n\n` +
|
|
60
60
|
`Possible issues:\n` +
|
|
61
61
|
`- srcDirectories in pikku.config.json doesn't include the file with the createConfig method`
|
|
62
62
|
: `No ${type} found`
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
MiddlewareMetadata,
|
|
5
5
|
PermissionMetadata,
|
|
6
6
|
} from '@pikku/core'
|
|
7
|
+
import { extractTypeKeys } from './type-utils.js'
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Helper to extract wire-level middleware/permission names from metadata.
|
|
@@ -28,7 +29,7 @@ export function extractWireNames(
|
|
|
28
29
|
*/
|
|
29
30
|
function expandAndAddGroupServices(
|
|
30
31
|
list: MiddlewareMetadata[] | PermissionMetadata[] | undefined,
|
|
31
|
-
state: InspectorState,
|
|
32
|
+
state: InspectorState | Omit<InspectorState, 'typesLookup'>,
|
|
32
33
|
addServices: (services: FunctionServicesMeta | undefined) => void,
|
|
33
34
|
isMiddleware: boolean
|
|
34
35
|
): void {
|
|
@@ -57,6 +58,38 @@ function expandAndAddGroupServices(
|
|
|
57
58
|
}
|
|
58
59
|
}
|
|
59
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Extracts all service names from SingletonServices and Services types.
|
|
63
|
+
* This provides the complete list of available services for code generation.
|
|
64
|
+
* Only runs if typesLookup is available (omitted in deserialized states).
|
|
65
|
+
*/
|
|
66
|
+
function extractAllServices(
|
|
67
|
+
state: InspectorState | Omit<InspectorState, 'typesLookup'>
|
|
68
|
+
): void {
|
|
69
|
+
// Skip if typesLookup is not available (e.g., deserialized state)
|
|
70
|
+
if (!('typesLookup' in state)) {
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Extract all singleton services from the SingletonServices type
|
|
75
|
+
const singletonServicesTypes = state.typesLookup.get('SingletonServices')
|
|
76
|
+
if (singletonServicesTypes && singletonServicesTypes.length > 0) {
|
|
77
|
+
const singletonServiceNames = extractTypeKeys(singletonServicesTypes[0])
|
|
78
|
+
state.serviceAggregation.allSingletonServices = singletonServiceNames.sort()
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Extract all services from the Services type
|
|
82
|
+
const servicesTypes = state.typesLookup.get('Services')
|
|
83
|
+
if (servicesTypes && servicesTypes.length > 0) {
|
|
84
|
+
const allServiceNames = extractTypeKeys(servicesTypes[0])
|
|
85
|
+
// Session services are those in Services but not in SingletonServices
|
|
86
|
+
const singletonSet = new Set(state.serviceAggregation.allSingletonServices)
|
|
87
|
+
state.serviceAggregation.allSessionServices = allServiceNames
|
|
88
|
+
.filter((name) => !singletonSet.has(name))
|
|
89
|
+
.sort()
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
60
93
|
/**
|
|
61
94
|
* Aggregates all required services from wired functions, middleware, and permissions.
|
|
62
95
|
* Must be called after AST traversal completes.
|
|
@@ -64,7 +97,12 @@ function expandAndAddGroupServices(
|
|
|
64
97
|
* Note: usedFunctions, usedMiddleware, and usedPermissions are tracked directly
|
|
65
98
|
* in the add-* methods during AST traversal for efficiency.
|
|
66
99
|
*/
|
|
67
|
-
export function aggregateRequiredServices(
|
|
100
|
+
export function aggregateRequiredServices(
|
|
101
|
+
state: InspectorState | Omit<InspectorState, 'typesLookup'>
|
|
102
|
+
): void {
|
|
103
|
+
// First, extract all available services from types
|
|
104
|
+
extractAllServices(state)
|
|
105
|
+
|
|
68
106
|
const { requiredServices, usedFunctions, usedMiddleware, usedPermissions } =
|
|
69
107
|
state.serviceAggregation
|
|
70
108
|
|
|
@@ -162,6 +162,8 @@ export interface SerializableInspectorState {
|
|
|
162
162
|
usedFunctions: string[]
|
|
163
163
|
usedMiddleware: string[]
|
|
164
164
|
usedPermissions: string[]
|
|
165
|
+
allSingletonServices: string[]
|
|
166
|
+
allSessionServices: string[]
|
|
165
167
|
}
|
|
166
168
|
}
|
|
167
169
|
|
|
@@ -271,6 +273,8 @@ export function serializeInspectorState(
|
|
|
271
273
|
usedFunctions: Array.from(state.serviceAggregation.usedFunctions),
|
|
272
274
|
usedMiddleware: Array.from(state.serviceAggregation.usedMiddleware),
|
|
273
275
|
usedPermissions: Array.from(state.serviceAggregation.usedPermissions),
|
|
276
|
+
allSingletonServices: state.serviceAggregation.allSingletonServices,
|
|
277
|
+
allSessionServices: state.serviceAggregation.allSessionServices,
|
|
274
278
|
},
|
|
275
279
|
}
|
|
276
280
|
}
|
|
@@ -370,6 +374,8 @@ export function deserializeInspectorState(
|
|
|
370
374
|
usedFunctions: new Set(data.serviceAggregation.usedFunctions),
|
|
371
375
|
usedMiddleware: new Set(data.serviceAggregation.usedMiddleware),
|
|
372
376
|
usedPermissions: new Set(data.serviceAggregation.usedPermissions),
|
|
377
|
+
allSingletonServices: data.serviceAggregation.allSingletonServices,
|
|
378
|
+
allSessionServices: data.serviceAggregation.allSessionServices,
|
|
373
379
|
},
|
|
374
380
|
}
|
|
375
381
|
}
|
package/src/utils/type-utils.ts
CHANGED
|
@@ -37,20 +37,22 @@ export function getPropertyAssignmentInitializer(
|
|
|
37
37
|
) {
|
|
38
38
|
if (!checker) return prop.name // best effort without a checker
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
// Use the proper TypeScript API for shorthand property resolution
|
|
41
|
+
let sym = checker.getShorthandAssignmentValueSymbol(prop)
|
|
41
42
|
if (sym && sym.flags & ts.SymbolFlags.Alias) {
|
|
42
43
|
sym = checker.getAliasedSymbol(sym)
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
const decl = sym?.declarations?.[0]
|
|
46
47
|
|
|
47
|
-
// const foo = () => {}
|
|
48
|
+
// const foo = () => {} or const foo = pikkuFunc(...)
|
|
48
49
|
if (
|
|
49
50
|
decl &&
|
|
50
51
|
ts.isVariableDeclaration(decl) &&
|
|
51
52
|
decl.initializer &&
|
|
52
53
|
(ts.isArrowFunction(decl.initializer) ||
|
|
53
|
-
ts.isFunctionExpression(decl.initializer)
|
|
54
|
+
ts.isFunctionExpression(decl.initializer) ||
|
|
55
|
+
ts.isCallExpression(decl.initializer))
|
|
54
56
|
) {
|
|
55
57
|
return decl.initializer
|
|
56
58
|
}
|
package/src/visit.ts
CHANGED
|
@@ -70,8 +70,8 @@ export const visitSetup = (
|
|
|
70
70
|
)
|
|
71
71
|
|
|
72
72
|
addFileWithFactory(node, checker, state.configFactories, 'CreateConfig')
|
|
73
|
-
addRPCInvocations(node, state, logger)
|
|
74
73
|
|
|
74
|
+
addRPCInvocations(node, state, logger)
|
|
75
75
|
addMiddleware(logger, node, checker, state, options)
|
|
76
76
|
addPermission(logger, node, checker, state, options)
|
|
77
77
|
|