@pikku/inspector 0.6.4 → 0.7.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/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 visit: (checker: ts.TypeChecker, node: ts.Node, state: InspectorState, filters: InspectorFilters) => void;
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 { addRoute } from './add-http-route.js';
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 visit = (checker, node, state, filters) => {
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
- addRoute(node, checker, state, filters);
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) => visit(checker, child, state, filters));
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.6.4",
3
+ "version": "0.7.0",
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.6.22",
20
+ "@pikku/core": "^0.7.0",
21
21
  "path-to-regexp": "^8.2.0",
22
22
  "typescript": "^5.6"
23
23
  },
@@ -1,180 +0,0 @@
1
- import * as ts from 'typescript'
2
- import { getPropertyValue } from './get-property-value.js'
3
- import { pathToRegexp } from 'path-to-regexp'
4
- import { APIDocs } from '@pikku/core'
5
- import { getInputTypes } from './add-http-route.js'
6
- import {
7
- getPropertyAssignment,
8
- getFunctionTypes,
9
- matchesFilters,
10
- } from './utils.js'
11
- import { ChannelMeta } from '@pikku/core/channel'
12
- import { TypesMap } from './types-map.js'
13
- import { InspectorFilters, InspectorState } from './types.js'
14
-
15
- const addMessagesRoutes = (
16
- obj: ts.ObjectLiteralExpression,
17
- checker: ts.TypeChecker,
18
- typesMap: TypesMap
19
- ) => {
20
- const messageTypes: ChannelMeta['messageRoutes'] = {}
21
-
22
- // Find the onMessageRoute property
23
- const messagesProperty = obj.properties.find(
24
- (p) =>
25
- ts.isPropertyAssignment(p) &&
26
- ts.isIdentifier(p.name) &&
27
- p.name.text === 'onMessageRoute'
28
- )
29
-
30
- if (!messagesProperty || !ts.isPropertyAssignment(messagesProperty)) {
31
- console.log(
32
- 'onMessageRoute property not found or is not a valid assignment.'
33
- )
34
- return {}
35
- }
36
-
37
- const initializer = messagesProperty.initializer
38
- // Ensure initializer is an object literal expression
39
- if (!ts.isObjectLiteralExpression(initializer)) {
40
- console.log('onMessageRoute is not an object literal.')
41
- return {}
42
- }
43
-
44
- // Iterate over the first level properties (like 'event')
45
- initializer.properties.forEach((property) => {
46
- const channel = property.name!.getText()
47
- messageTypes[channel] = {}
48
-
49
- if (ts.isPropertyAssignment(property)) {
50
- const nestedObject = property.initializer
51
- if (ts.isObjectLiteralExpression(nestedObject)) {
52
- const keys = nestedObject.properties.map((p) => p.name?.getText())
53
- for (const route of keys) {
54
- if (route) {
55
- const result = getFunctionTypes(checker, nestedObject, {
56
- funcName: route,
57
- inputIndex: 0,
58
- outputIndex: 1,
59
- typesMap,
60
- })
61
- const inputs = result?.inputs || null
62
- const outputs = result?.outputs || null
63
- const type = result?.type || null
64
- messageTypes[channel][route] = { inputs, outputs, type }
65
- }
66
- }
67
- } else {
68
- console.warn('Nested property is not an object literal:', nestedObject)
69
- }
70
- } else {
71
- console.warn(
72
- `Property "${property.getText()}" is a ${ts.SyntaxKind[property.kind]}`
73
- )
74
- }
75
- })
76
-
77
- return messageTypes
78
- }
79
-
80
- export const addChannel = (
81
- node: ts.Node,
82
- checker: ts.TypeChecker,
83
- state: InspectorState,
84
- filters: InspectorFilters
85
- ) => {
86
- if (!ts.isCallExpression(node)) {
87
- return
88
- }
89
-
90
- const args = node.arguments
91
- const firstArg = args[0]
92
- const expression = node.expression
93
-
94
- // Check if the call is to addRoute
95
- if (!ts.isIdentifier(expression) || expression.text !== 'addChannel') {
96
- return
97
- }
98
-
99
- if (!firstArg) {
100
- return
101
- }
102
-
103
- let docs: APIDocs | undefined
104
- let paramsValues: string[] | null = []
105
- let queryValues: string[] | [] = []
106
- let tags: string[] | undefined = undefined
107
- let inputType: string | null = null
108
- let route: string | null = null
109
- let name: string | null = null
110
-
111
- // Check if the first argument is an object literal
112
- if (ts.isObjectLiteralExpression(firstArg)) {
113
- const obj = firstArg
114
-
115
- name = getPropertyValue(obj, 'name') as string | null
116
- route = getPropertyValue(obj, 'route') as string | null
117
-
118
- if (!name) {
119
- console.error('Channel name is required')
120
- return
121
- }
122
-
123
- if (route) {
124
- const { keys } = pathToRegexp(route)
125
- paramsValues = keys.reduce((result, { type, name }) => {
126
- if (type === 'param') {
127
- result.push(name)
128
- }
129
- return result
130
- }, [] as string[])
131
- } else {
132
- route = ''
133
- }
134
-
135
- docs = (getPropertyValue(obj, 'docs') as APIDocs) || undefined
136
- queryValues = (getPropertyValue(obj, 'query') as string[]) || []
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
- }
157
-
158
- state.channels.files.add(node.getSourceFile().fileName)
159
- state.channels.meta.push({
160
- name,
161
- route,
162
- input: inputType,
163
- params: paramsValues.length > 0 ? paramsValues : undefined,
164
- query: queryValues.length > 0 ? queryValues : undefined,
165
- inputTypes: getInputTypes(
166
- state.channels.metaInputTypes,
167
- 'get',
168
- inputType,
169
- queryValues,
170
- paramsValues
171
- ),
172
- connect,
173
- disconnect,
174
- message: message || undefined,
175
- messageRoutes,
176
- docs,
177
- tags,
178
- })
179
- }
180
- }
@@ -1,50 +0,0 @@
1
- import * as ts from 'typescript'
2
- import { PathToNameAndType } from './types.js'
3
-
4
- // const VRAMEWORK_TYPES = ['CoreConfig', 'CoreService', 'CoreServices', 'CoreSingletonService', 'CoreSessionService']
5
-
6
- export const addFileExtendsCoreType = (
7
- node: ts.Node,
8
- checker: ts.TypeChecker,
9
- methods: PathToNameAndType,
10
- expectedTypeName: string
11
- ) => {
12
- if (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node)) {
13
- const fileName = node.getSourceFile().fileName
14
- const typeName = node.name?.getText()
15
-
16
- // Check if the class or interface extends the expected type
17
- if (node.heritageClauses) {
18
- for (const clause of node.heritageClauses) {
19
- if (clause.token === ts.SyntaxKind.ExtendsKeyword) {
20
- for (const type of clause.types) {
21
- const extendedTypeName = type.expression.getText()
22
- let extendedTypeDeclarationPath: string | null = null
23
-
24
- // Check if the extended type matches the expected type name
25
- if (extendedTypeName === expectedTypeName) {
26
- // Retrieve the symbol of the extended type
27
- const typeSymbol = checker.getSymbolAtLocation(type.expression)
28
- const declaration =
29
- typeSymbol &&
30
- typeSymbol.declarations &&
31
- typeSymbol.declarations[0]
32
- if (declaration) {
33
- const sourceFile = declaration.getSourceFile()
34
- extendedTypeDeclarationPath = sourceFile.fileName // Get the path of the file where the extended type was declared
35
- }
36
-
37
- const variables = methods[fileName] || []
38
- variables.push({
39
- variable: undefined,
40
- type: typeName,
41
- typePath: extendedTypeDeclarationPath,
42
- })
43
- methods[fileName] = variables
44
- }
45
- }
46
- }
47
- }
48
- }
49
- }
50
- }
@@ -1,45 +0,0 @@
1
- import * as ts from 'typescript'
2
- import { doesTypeExtendsCore } from './does-type-extend-core-type.js'
3
- import { PathToNameAndType } from './types.js'
4
-
5
- export const addFileWithConfig = (
6
- node: ts.Node,
7
- checker: ts.TypeChecker,
8
- configs: PathToNameAndType
9
- ) => {
10
- if (ts.isVariableDeclaration(node)) {
11
- const fileName = node.getSourceFile().fileName
12
- const variableSymbol = checker.getSymbolAtLocation(node.name)
13
-
14
- if (variableSymbol) {
15
- const variableType = checker.getTypeOfSymbolAtLocation(
16
- variableSymbol,
17
- node.name
18
- )
19
- const variableName = node.name.getText()
20
- const variableTypeText = node.type?.getText()
21
-
22
- // Check if the type extends CoreConfig
23
- if (doesTypeExtendsCore(variableType, checker, new Set(), 'CoreConfig')) {
24
- // Retrieve the symbol of the type (if it has one)
25
- const typeSymbol = variableType.symbol
26
- let typeDeclarationPath: string | null = null
27
- const declaration =
28
- typeSymbol && typeSymbol.declarations && typeSymbol.declarations[0]
29
-
30
- if (declaration) {
31
- const sourceFile = declaration.getSourceFile()
32
- typeDeclarationPath = sourceFile.fileName // Get the path of the file where the type was declared
33
- }
34
-
35
- const variables = configs[fileName] || []
36
- variables.push({
37
- variable: variableName,
38
- type: variableTypeText || null,
39
- typePath: typeDeclarationPath,
40
- })
41
- configs[fileName] = variables
42
- }
43
- }
44
- }
45
- }
@@ -1,65 +0,0 @@
1
- import * as ts from 'typescript'
2
- import { PathToNameAndType } from './types.js'
3
-
4
- export const addFileWithFactory = (
5
- node: ts.Node,
6
- checker: ts.TypeChecker,
7
- methods: PathToNameAndType = new Map(),
8
- expectedTypeName: string
9
- ) => {
10
- if (ts.isVariableDeclaration(node)) {
11
- const fileName = node.getSourceFile().fileName
12
- const variableTypeNode = node.type
13
- const variableName = node.name.getText()
14
-
15
- if (variableTypeNode && ts.isTypeReferenceNode(variableTypeNode)) {
16
- const typeNameNode = variableTypeNode.typeName || null
17
-
18
- let typeDeclarationPath: string | null = null
19
-
20
- // Check if the type name matches the expected type name
21
- if (
22
- ts.isIdentifier(typeNameNode) &&
23
- typeNameNode.text === expectedTypeName
24
- ) {
25
- const typeSymbol = checker.getSymbolAtLocation(typeNameNode)
26
- const declaration =
27
- typeSymbol && typeSymbol.declarations && typeSymbol.declarations[0]
28
- if (declaration) {
29
- const sourceFile = declaration.getSourceFile()
30
- typeDeclarationPath = sourceFile.fileName // Get the path of the file where the type was declared
31
- }
32
-
33
- const variables = methods[fileName] || []
34
- variables.push({
35
- variable: variableName,
36
- type: typeNameNode.getText(),
37
- typePath: typeDeclarationPath,
38
- })
39
- methods[fileName] = variables
40
- }
41
-
42
- // Handle qualified type names if necessary
43
- else if (ts.isQualifiedName(typeNameNode)) {
44
- const lastName = typeNameNode.right.text
45
- if (lastName === expectedTypeName) {
46
- const typeSymbol = checker.getSymbolAtLocation(typeNameNode.right)
47
- const declaration =
48
- typeSymbol && typeSymbol.declarations && typeSymbol.declarations[0]
49
- if (declaration) {
50
- const sourceFile = declaration.getSourceFile()
51
- typeDeclarationPath = sourceFile.fileName // Get the path of the file where the type was declared
52
- }
53
-
54
- const variables = methods[fileName] || []
55
- variables.push({
56
- variable: variableName,
57
- type: typeNameNode.getText(),
58
- typePath: typeDeclarationPath,
59
- })
60
- methods[fileName] = variables
61
- }
62
- }
63
- }
64
- }
65
- }
@@ -1,138 +0,0 @@
1
- import * as ts from 'typescript'
2
- import { getPropertyValue } from './get-property-value.js'
3
- import { pathToRegexp } from 'path-to-regexp'
4
- import { HTTPMethod } from '@pikku/core/http'
5
- import { APIDocs } from '@pikku/core'
6
- import { extractTypeKeys, getFunctionTypes, matchesFilters } from './utils.js'
7
- import { MetaInputTypes, InspectorState, InspectorFilters } from './types.js'
8
-
9
- export const getInputTypes = (
10
- metaTypes: MetaInputTypes,
11
- methodType: string,
12
- inputType: string | null,
13
- queryValues: string[],
14
- paramsValues: string[]
15
- ) => {
16
- if (!inputType) {
17
- return undefined
18
- }
19
-
20
- if (inputType) {
21
- metaTypes.set(inputType, {
22
- query: queryValues,
23
- params: paramsValues,
24
- body: ['post', 'put', 'patch'].includes(methodType)
25
- ? [...new Set([...queryValues, ...paramsValues])]
26
- : [],
27
- })
28
- }
29
-
30
- return undefined
31
- }
32
-
33
- export const addRoute = (
34
- node: ts.Node,
35
- checker: ts.TypeChecker,
36
- state: InspectorState,
37
- filters: InspectorFilters
38
- ) => {
39
- if (!ts.isCallExpression(node)) {
40
- return
41
- }
42
-
43
- const args = node.arguments
44
- const firstArg = args[0]
45
- const expression = node.expression
46
-
47
- // Check if the call is to addRoute
48
- if (!ts.isIdentifier(expression) || expression.text !== 'addRoute') {
49
- return
50
- }
51
-
52
- if (!firstArg) {
53
- return
54
- }
55
-
56
- let docs: APIDocs | undefined
57
- let methodValue: string | null = null
58
- let paramsValues: string[] | null = []
59
- let queryValues: string[] | [] = []
60
- let tags: string[] | [] = []
61
- let routeValue: string | null = null
62
-
63
- // Check if the first argument is an object literal
64
- if (ts.isObjectLiteralExpression(firstArg)) {
65
- const obj = firstArg
66
-
67
- routeValue = getPropertyValue(obj, 'route') as string | null
68
- if (!routeValue) {
69
- return
70
- }
71
-
72
- const { keys } = pathToRegexp(routeValue)
73
- paramsValues = keys.reduce((result, { type, name }) => {
74
- if (type === 'param') {
75
- result.push(name)
76
- }
77
- return result
78
- }, [] as string[])
79
-
80
- docs = (getPropertyValue(obj, 'docs') as APIDocs) || undefined
81
- methodValue = getPropertyValue(obj, 'method') as string
82
- queryValues = (getPropertyValue(obj, 'query') as string[]) || []
83
- tags = (getPropertyValue(obj, 'tags') as string[]) || undefined
84
-
85
- if (
86
- !matchesFilters(filters, { tags }, { type: 'http', name: routeValue })
87
- ) {
88
- return
89
- }
90
-
91
- let { inputs, outputs, inputTypes } = getFunctionTypes(checker, obj, {
92
- funcName: 'func',
93
- inputIndex: 0,
94
- outputIndex: 1,
95
- typesMap: state.http.typesMap,
96
- })
97
-
98
- const input = inputs ? inputs[0] || null : null
99
- const output = outputs ? outputs[0] || null : null
100
-
101
- if (inputs && inputs?.length > 1) {
102
- console.error(
103
- `Only one input type is currently allowed for method '${methodValue}' and route '${routeValue}': \n\t${inputs.join('\n\t')}`
104
- )
105
- }
106
-
107
- if (outputs && outputs?.length > 1) {
108
- console.error(
109
- `Only one output type is currently allowed for method '${methodValue}' and route '${routeValue}': \n\t${outputs.join('\n\t')}`
110
- )
111
- }
112
-
113
- if (inputTypes[0] && !['post', 'put', 'patch'].includes(methodValue)) {
114
- queryValues = [
115
- ...new Set([...queryValues, ...extractTypeKeys(inputTypes[0])]),
116
- ].filter((query) => !paramsValues?.includes(query))
117
- }
118
-
119
- state.http.files.add(node.getSourceFile().fileName)
120
- state.http.meta.push({
121
- route: routeValue,
122
- method: methodValue! as HTTPMethod,
123
- input,
124
- output,
125
- params: paramsValues.length > 0 ? paramsValues : undefined,
126
- query: queryValues.length > 0 ? queryValues : undefined,
127
- inputTypes: getInputTypes(
128
- state.http.metaInputTypes,
129
- methodValue,
130
- input,
131
- queryValues,
132
- paramsValues
133
- ),
134
- docs,
135
- tags,
136
- })
137
- }
138
- }
@@ -1,56 +0,0 @@
1
- import * as ts from 'typescript'
2
- import { getPropertyValue } from './get-property-value.js'
3
- import { APIDocs } from '@pikku/core'
4
- import { InspectorFilters, InspectorState } from './types.js'
5
- import { matchesFilters } from './utils.js'
6
-
7
- export const addSchedule = (
8
- node: ts.Node,
9
- _checker: ts.TypeChecker,
10
- state: InspectorState,
11
- filters: InspectorFilters
12
- ) => {
13
- if (!ts.isCallExpression(node)) {
14
- return
15
- }
16
-
17
- const args = node.arguments
18
- const firstArg = args[0]
19
- const expression = node.expression
20
-
21
- // Check if the call is to addScheduledTask
22
- if (!ts.isIdentifier(expression) || expression.text !== 'addScheduledTask') {
23
- return
24
- }
25
-
26
- if (!firstArg) {
27
- return
28
- }
29
-
30
- if (ts.isObjectLiteralExpression(firstArg)) {
31
- const obj = firstArg
32
-
33
- const nameValue = getPropertyValue(obj, 'name') as string | null
34
- const scheduleValue = getPropertyValue(obj, 'schedule') as string | null
35
- const docs = (getPropertyValue(obj, 'docs') as APIDocs) || undefined
36
- const tags = (getPropertyValue(obj, 'tags') as string[]) || undefined
37
-
38
- if (!nameValue || !scheduleValue) {
39
- return
40
- }
41
-
42
- if (
43
- !matchesFilters(filters, { tags }, { type: 'schedule', name: nameValue })
44
- ) {
45
- return
46
- }
47
-
48
- state.scheduledTasks.files.add(node.getSourceFile().fileName)
49
- state.scheduledTasks.meta.push({
50
- name: nameValue,
51
- schedule: scheduleValue,
52
- docs,
53
- tags,
54
- })
55
- }
56
- }
@@ -1,53 +0,0 @@
1
- import * as ts from 'typescript'
2
-
3
- export const doesTypeExtendsCore = (
4
- type: ts.Type,
5
- checker: ts.TypeChecker,
6
- visitedTypes: Set<ts.Type>,
7
- coreType: string
8
- ): boolean => {
9
- if (!type || !checker) return false
10
-
11
- // Avoid infinite recursion by checking if we've already visited this type
12
- if (visitedTypes.has(type)) {
13
- return false
14
- }
15
- visitedTypes.add(type)
16
-
17
- const typeSymbol = type.getSymbol()
18
- if (typeSymbol) {
19
- // Check if the type is the core type
20
- if (typeSymbol.getName() === coreType) {
21
- return true
22
- }
23
-
24
- // For interface and class types, check their base types
25
- if (type.isClassOrInterface()) {
26
- const baseTypes = type.getBaseTypes() || []
27
- for (const baseType of baseTypes) {
28
- if (doesTypeExtendsCore(baseType, checker, visitedTypes, coreType)) {
29
- return true
30
- }
31
- }
32
- }
33
- }
34
-
35
- // For type aliases, get the aliased type
36
- if (type.aliasSymbol) {
37
- const aliasedType = checker.getDeclaredTypeOfSymbol(type.aliasSymbol)
38
- if (doesTypeExtendsCore(aliasedType, checker, visitedTypes, coreType)) {
39
- return true
40
- }
41
- }
42
-
43
- // For union and intersection types, check all constituent types
44
- if (type.isUnionOrIntersection()) {
45
- for (const subType of type.types) {
46
- if (doesTypeExtendsCore(subType, checker, visitedTypes, coreType)) {
47
- return true
48
- }
49
- }
50
- }
51
-
52
- return false
53
- }