@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.
@@ -0,0 +1,260 @@
1
+ import * as ts from 'typescript';
2
+ import { extractFunctionName, getPropertyAssignmentInitializer, } from './utils.js';
3
+ const isValidVariableName = (name) => {
4
+ const regex = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
5
+ return regex.test(name);
6
+ };
7
+ const nullifyTypes = (type) => {
8
+ if (type === 'void' ||
9
+ type === 'undefined' ||
10
+ type === 'unknown' ||
11
+ type === 'any') {
12
+ return null;
13
+ }
14
+ return type;
15
+ };
16
+ const resolveTypeImports = (type, resolvedTypes, isCustom) => {
17
+ const types = [];
18
+ const visitType = (currentType) => {
19
+ const symbol = currentType.aliasSymbol || currentType.getSymbol();
20
+ if (symbol) {
21
+ const declarations = symbol.getDeclarations();
22
+ const declaration = declarations?.[0];
23
+ if (declaration) {
24
+ const sourceFile = declaration.getSourceFile();
25
+ const path = sourceFile.fileName;
26
+ // Skip built-in utility types or TypeScript lib types
27
+ if (!path.includes('node_modules/typescript') &&
28
+ symbol.getName() !== '__type' &&
29
+ !isPrimitiveType(currentType)) {
30
+ const originalName = symbol.getName();
31
+ // Check if the type is already in the map
32
+ let uniqueName = resolvedTypes.exists(originalName, path);
33
+ if (!uniqueName) {
34
+ if (isCustom) {
35
+ uniqueName = resolvedTypes.addUniqueType(originalName, path);
36
+ }
37
+ else {
38
+ resolvedTypes.addType(originalName, path);
39
+ uniqueName = originalName;
40
+ }
41
+ }
42
+ types.push(uniqueName);
43
+ }
44
+ }
45
+ }
46
+ if (isCustom) {
47
+ // Handle nested utility types like Partial, Pick, etc.
48
+ if (currentType.aliasTypeArguments) {
49
+ currentType.aliasTypeArguments.forEach(visitType);
50
+ }
51
+ // Handle intersections and unions
52
+ if (currentType.isUnionOrIntersection()) {
53
+ currentType.types.forEach(visitType);
54
+ }
55
+ // Handle object types with type arguments
56
+ if (currentType.flags & ts.TypeFlags.Object &&
57
+ currentType.objectFlags & ts.ObjectFlags.Reference) {
58
+ const typeRef = currentType;
59
+ typeRef.typeArguments?.forEach(visitType);
60
+ }
61
+ }
62
+ };
63
+ visitType(type);
64
+ return types;
65
+ };
66
+ const resolveUnionTypes = (checker, type) => {
67
+ const types = [];
68
+ const names = [];
69
+ // Check if it's a union type AND not part of an intersection
70
+ if (type.isUnion() && !(type.flags & ts.TypeFlags.Intersection)) {
71
+ for (const t of type.types) {
72
+ const name = nullifyTypes(checker.typeToString(t));
73
+ if (name) {
74
+ types.push(t);
75
+ names.push(name);
76
+ }
77
+ }
78
+ }
79
+ else {
80
+ const name = nullifyTypes(checker.typeToString(type));
81
+ if (name) {
82
+ types.push(type);
83
+ names.push(name);
84
+ }
85
+ }
86
+ return { types, names };
87
+ };
88
+ const getNamesAndTypes = (checker, typesMap, direction, funcName, type) => {
89
+ if (!type) {
90
+ return { names: [], types: [] };
91
+ }
92
+ // 1) Handle an explicit void (or undefined) type up front
93
+ if (type.flags & ts.TypeFlags.VoidLike) {
94
+ return {
95
+ names: ['void'],
96
+ types: [type],
97
+ };
98
+ }
99
+ // 2) For unions, resolve all member names/types
100
+ const { names: rawNames, types: rawTypes } = resolveUnionTypes(checker, type);
101
+ // If the union is exactly [void], we'd have caught it above.
102
+ // If it's e.g. [string, void], rawNames should already include 'void'.
103
+ // 3) If multiple names or the single name isn't a valid identifier,
104
+ // we emit an alias type.
105
+ const firstName = rawNames[0];
106
+ if (rawNames.length > 1 || (firstName && !isValidVariableName(firstName))) {
107
+ const aliasType = rawNames.join(' | ');
108
+ const aliasName = funcName.charAt(0).toUpperCase() + funcName.slice(1) + direction;
109
+ // record the alias in your TypesMap
110
+ const references = rawTypes
111
+ .map((t) => resolveTypeImports(t, typesMap, true))
112
+ .flat();
113
+ typesMap.addCustomType(aliasName, aliasType, references);
114
+ return {
115
+ names: [aliasName],
116
+ types: rawTypes,
117
+ };
118
+ }
119
+ // 4) Single, valid name → inline it
120
+ const uniqueNames = rawNames
121
+ .map((name, i) => {
122
+ const t = rawTypes[i];
123
+ if (!t) {
124
+ throw new Error(`Expected type for name "${name}" in ${funcName}`);
125
+ }
126
+ if (isPrimitiveType(t)) {
127
+ return name;
128
+ }
129
+ // non-primitive: import/alias it inline
130
+ return resolveTypeImports(t, typesMap, false);
131
+ })
132
+ .flat();
133
+ return {
134
+ names: uniqueNames,
135
+ types: rawTypes,
136
+ };
137
+ };
138
+ const isPrimitiveType = (type) => {
139
+ const primitiveFlags = ts.TypeFlags.Number |
140
+ ts.TypeFlags.String |
141
+ ts.TypeFlags.Boolean |
142
+ ts.TypeFlags.BigInt |
143
+ ts.TypeFlags.ESSymbol |
144
+ ts.TypeFlags.Void |
145
+ ts.TypeFlags.Undefined |
146
+ ts.TypeFlags.Null |
147
+ ts.TypeFlags.Any |
148
+ ts.TypeFlags.Unknown |
149
+ ts.TypeFlags.VoidLike;
150
+ return (type.flags & primitiveFlags) !== 0;
151
+ };
152
+ /**
153
+ * If `type` is a `Promise<T>`, return `T`, otherwise return `type` itself.
154
+ */
155
+ function unwrapPromise(checker, type) {
156
+ if (!type?.symbol)
157
+ return type;
158
+ const isPromise = type.symbol.name === 'Promise' &&
159
+ checker.getFullyQualifiedName(type.symbol).includes('Promise');
160
+ // aliasTypeArguments covers most Promise<T> cases
161
+ if (isPromise && type.aliasTypeArguments?.length === 1) {
162
+ return type.aliasTypeArguments[0];
163
+ }
164
+ // fallback for raw TypeReference
165
+ if (isPromise && type.typeArguments?.length === 1) {
166
+ return type.typeArguments[0];
167
+ }
168
+ return type;
169
+ }
170
+ /**
171
+ * Inspect pikkuFunc calls, extract input/output and first-arg destructuring,
172
+ * then push into state.functions.meta.
173
+ */
174
+ export function addFunctions(node, checker, state, filters) {
175
+ if (!ts.isCallExpression(node))
176
+ return;
177
+ const { expression, arguments: args, typeArguments } = node;
178
+ // only handle calls like pikkuFunc(...)
179
+ if (!ts.isIdentifier(expression) || !expression.text.startsWith('pikku')) {
180
+ return;
181
+ }
182
+ if (args.length === 0)
183
+ return;
184
+ const { pikkuFuncName, name } = extractFunctionName(node, checker);
185
+ // determine the actual handler expression:
186
+ // either the `func` prop or the first argument directly
187
+ let handlerNode = args[0];
188
+ if (ts.isObjectLiteralExpression(handlerNode)) {
189
+ const fnProp = getPropertyAssignmentInitializer(handlerNode, 'func', true, checker);
190
+ if (!fnProp ||
191
+ (!ts.isArrowFunction(fnProp) && !ts.isFunctionExpression(fnProp))) {
192
+ console.error(`• No valid 'func' property found for ${pikkuFuncName}.`);
193
+ return;
194
+ }
195
+ handlerNode = fnProp;
196
+ }
197
+ if (!ts.isArrowFunction(handlerNode) &&
198
+ !ts.isFunctionExpression(handlerNode)) {
199
+ console.error(`• Handler for TODO is not a function.`);
200
+ return;
201
+ }
202
+ const services = {
203
+ optimized: true,
204
+ services: [],
205
+ };
206
+ const firstParam = handlerNode.parameters[0];
207
+ if (firstParam) {
208
+ if (ts.isObjectBindingPattern(firstParam.name)) {
209
+ for (const elem of firstParam.name.elements) {
210
+ const original = elem.propertyName && ts.isIdentifier(elem.propertyName)
211
+ ? elem.propertyName.text
212
+ : ts.isIdentifier(elem.name)
213
+ ? elem.name.text
214
+ : undefined;
215
+ if (original) {
216
+ services.services.push(original);
217
+ }
218
+ }
219
+ }
220
+ else if (ts.isIdentifier(firstParam.name) &&
221
+ !firstParam.name.text.startsWith('_')) {
222
+ services.optimized = false;
223
+ }
224
+ }
225
+ // --- Generics → ts.Type[], unwrapped from Promise ---
226
+ const genericTypes = (typeArguments ?? [])
227
+ .map((tn) => checker.getTypeFromTypeNode(tn))
228
+ .map((t) => unwrapPromise(checker, t));
229
+ // --- Input Extraction ---
230
+ let { names: inputNames, types: inputTypes } = getNamesAndTypes(checker, state.functions.typesMap, 'Input', name, genericTypes[0]);
231
+ if (inputTypes.length === 0) {
232
+ console.warn(`\x1b[31m• Unknown input type for '${name}', assuming void.\x1b[0m`);
233
+ }
234
+ // --- Output Extraction ---
235
+ let outputNames = [];
236
+ if (genericTypes.length >= 2) {
237
+ outputNames = getNamesAndTypes(checker, state.functions.typesMap, 'Output', name, genericTypes[1]).names;
238
+ }
239
+ else {
240
+ const sig = checker.getSignatureFromDeclaration(handlerNode);
241
+ if (sig) {
242
+ const rawRet = checker.getReturnTypeOfSignature(sig);
243
+ const unwrapped = unwrapPromise(checker, rawRet);
244
+ outputNames = getNamesAndTypes(checker, state.functions.typesMap, 'Output', pikkuFuncName, unwrapped).names;
245
+ }
246
+ }
247
+ // --- Record metadata ---
248
+ state.functions.files.add(node.getSourceFile().fileName);
249
+ if (inputNames.length > 1) {
250
+ console.warn('More than one input type detected, only the first one will be used as a schema.');
251
+ }
252
+ state.functions.meta[pikkuFuncName] = {
253
+ pikkuFuncName,
254
+ name,
255
+ services,
256
+ schemaName: inputNames[0] ?? null,
257
+ inputs: inputNames.filter((n) => n !== 'void') ?? null,
258
+ outputs: outputNames.filter((n) => n !== 'void') ?? null,
259
+ };
260
+ }
@@ -1,4 +1,16 @@
1
1
  import * as ts from 'typescript';
2
- import { MetaInputTypes, InspectorState, InspectorFilters } from './types.js';
3
- export declare const getInputTypes: (metaTypes: MetaInputTypes, methodType: string, inputType: string | null, queryValues: string[], paramsValues: string[]) => undefined;
4
- export declare const addRoute: (node: ts.Node, checker: ts.TypeChecker, state: InspectorState, filters: InspectorFilters) => void;
2
+ import { InspectorState, InspectorFilters } from './types.js';
3
+ /**
4
+ * Populate metaInputTypes for a given route based on method, input type,
5
+ * query and params. Returns undefined (we only mutate metaTypes).
6
+ */
7
+ export declare const getInputTypes: (metaTypes: Map<string, {
8
+ query?: string[];
9
+ params?: string[];
10
+ body?: string[];
11
+ }>, methodType: string, inputType: string | null, queryValues: string[], paramsValues: string[]) => undefined;
12
+ /**
13
+ * Simplified addHTTPRoute: re-uses function metadata from state.functions.meta
14
+ * instead of re-inferring types here.
15
+ */
16
+ export declare const addHTTPRoute: (node: ts.Node, checker: ts.TypeChecker, state: InspectorState, filters: InspectorFilters) => void;
@@ -1,93 +1,82 @@
1
1
  import * as ts from 'typescript';
2
2
  import { getPropertyValue } from './get-property-value.js';
3
3
  import { pathToRegexp } from 'path-to-regexp';
4
- import { extractTypeKeys, getFunctionTypes, matchesFilters } from './utils.js';
4
+ import { extractFunctionName, getPropertyAssignmentInitializer, matchesFilters, } from './utils.js';
5
+ /**
6
+ * Populate metaInputTypes for a given route based on method, input type,
7
+ * query and params. Returns undefined (we only mutate metaTypes).
8
+ */
5
9
  export const getInputTypes = (metaTypes, methodType, inputType, queryValues, paramsValues) => {
6
- if (!inputType) {
7
- return undefined;
8
- }
9
- if (inputType) {
10
- metaTypes.set(inputType, {
11
- query: queryValues,
12
- params: paramsValues,
13
- body: ['post', 'put', 'patch'].includes(methodType)
14
- ? [...new Set([...queryValues, ...paramsValues])]
15
- : [],
16
- });
17
- }
18
- return undefined;
10
+ if (!inputType)
11
+ return;
12
+ metaTypes.set(inputType, {
13
+ query: queryValues,
14
+ params: paramsValues,
15
+ body: ['post', 'put', 'patch'].includes(methodType)
16
+ ? [...new Set([...queryValues, ...paramsValues])]
17
+ : [],
18
+ });
19
+ return;
19
20
  };
20
- export const addRoute = (node, checker, state, filters) => {
21
- if (!ts.isCallExpression(node)) {
21
+ /**
22
+ * Simplified addHTTPRoute: re-uses function metadata from state.functions.meta
23
+ * instead of re-inferring types here.
24
+ */
25
+ export const addHTTPRoute = (node, checker, state, filters) => {
26
+ // only look at calls
27
+ if (!ts.isCallExpression(node))
22
28
  return;
23
- }
24
- const args = node.arguments;
29
+ const { expression, arguments: args } = node;
30
+ if (!ts.isIdentifier(expression) || expression.text !== 'addHTTPRoute')
31
+ return;
32
+ // must pass an object literal
25
33
  const firstArg = args[0];
26
- const expression = node.expression;
27
- // Check if the call is to addRoute
28
- if (!ts.isIdentifier(expression) || expression.text !== 'addRoute') {
34
+ if (!firstArg || !ts.isObjectLiteralExpression(firstArg))
35
+ return;
36
+ const obj = firstArg;
37
+ // --- extract HTTP metadata ---
38
+ const route = getPropertyValue(obj, 'route');
39
+ if (!route)
40
+ return;
41
+ const keys = pathToRegexp(route).keys;
42
+ const params = keys.filter((k) => k.type === 'param').map((k) => k.name);
43
+ const method = getPropertyValue(obj, 'method')?.toLowerCase() || 'get';
44
+ const docs = getPropertyValue(obj, 'docs') || undefined;
45
+ const tags = getPropertyValue(obj, 'tags') || undefined;
46
+ const query = getPropertyValue(obj, 'query') || [];
47
+ if (!matchesFilters(filters, { tags }, { type: 'http', name: route })) {
29
48
  return;
30
49
  }
31
- if (!firstArg) {
50
+ // --- find the referenced function ---
51
+ const funcInitializer = getPropertyAssignmentInitializer(obj, 'func', true, checker);
52
+ if (!funcInitializer) {
53
+ console.error(`• No valid 'func' property for route '${route}'.`);
32
54
  return;
33
55
  }
34
- let docs;
35
- let methodValue = null;
36
- let paramsValues = [];
37
- let queryValues = [];
38
- let tags = [];
39
- let routeValue = null;
40
- // Check if the first argument is an object literal
41
- if (ts.isObjectLiteralExpression(firstArg)) {
42
- const obj = firstArg;
43
- routeValue = getPropertyValue(obj, 'route');
44
- if (!routeValue) {
45
- return;
46
- }
47
- const { keys } = pathToRegexp(routeValue);
48
- paramsValues = keys.reduce((result, { type, name }) => {
49
- if (type === 'param') {
50
- result.push(name);
51
- }
52
- return result;
53
- }, []);
54
- docs = getPropertyValue(obj, 'docs') || undefined;
55
- methodValue = getPropertyValue(obj, 'method');
56
- queryValues = getPropertyValue(obj, 'query') || [];
57
- tags = getPropertyValue(obj, 'tags') || undefined;
58
- if (!matchesFilters(filters, { tags }, { type: 'http', name: routeValue })) {
59
- return;
60
- }
61
- let { inputs, outputs, inputTypes } = getFunctionTypes(checker, obj, {
62
- funcName: 'func',
63
- inputIndex: 0,
64
- outputIndex: 1,
65
- typesMap: state.http.typesMap,
66
- });
67
- const input = inputs ? inputs[0] || null : null;
68
- const output = outputs ? outputs[0] || null : null;
69
- if (inputs && inputs?.length > 1) {
70
- console.error(`Only one input type is currently allowed for method '${methodValue}' and route '${routeValue}': \n\t${inputs.join('\n\t')}`);
71
- }
72
- if (outputs && outputs?.length > 1) {
73
- console.error(`Only one output type is currently allowed for method '${methodValue}' and route '${routeValue}': \n\t${outputs.join('\n\t')}`);
74
- }
75
- if (inputTypes[0] && !['post', 'put', 'patch'].includes(methodValue)) {
76
- queryValues = [
77
- ...new Set([...queryValues, ...extractTypeKeys(inputTypes[0])]),
78
- ].filter((query) => !paramsValues?.includes(query));
79
- }
80
- state.http.files.add(node.getSourceFile().fileName);
81
- state.http.meta.push({
82
- route: routeValue,
83
- method: methodValue,
84
- input,
85
- output,
86
- params: paramsValues.length > 0 ? paramsValues : undefined,
87
- query: queryValues.length > 0 ? queryValues : undefined,
88
- inputTypes: getInputTypes(state.http.metaInputTypes, methodValue, input, queryValues, paramsValues),
89
- docs,
90
- tags,
91
- });
56
+ const funcName = extractFunctionName(funcInitializer, checker).pikkuFuncName;
57
+ // lookup existing function metadata
58
+ const fnMeta = state.functions.meta[funcName];
59
+ if (!fnMeta) {
60
+ console.log(Object.keys(state.functions.meta));
61
+ console.error(`• No function metadata found for '${funcName}'.`);
62
+ return;
92
63
  }
64
+ const input = fnMeta.inputs?.[0] || null;
65
+ const output = fnMeta.outputs?.[0] || null;
66
+ // --- compute inputTypes (body/query/params) ---
67
+ const inputTypes = getInputTypes(state.http.metaInputTypes, method, input, query, params);
68
+ // --- record route ---
69
+ state.http.files.add(node.getSourceFile().fileName);
70
+ state.http.meta.push({
71
+ pikkuFuncName: funcName,
72
+ route,
73
+ method: method,
74
+ input,
75
+ output,
76
+ params: params.length > 0 ? params : undefined,
77
+ query: query.length > 0 ? query : undefined,
78
+ inputTypes,
79
+ docs,
80
+ tags,
81
+ });
93
82
  };
@@ -1,3 +1,3 @@
1
1
  import * as ts from 'typescript';
2
2
  import { InspectorFilters, InspectorState } from './types.js';
3
- export declare const addSchedule: (node: ts.Node, _checker: ts.TypeChecker, state: InspectorState, filters: InspectorFilters) => void;
3
+ export declare const addSchedule: (node: ts.Node, checker: ts.TypeChecker, state: InspectorState, filters: InspectorFilters) => void;
@@ -1,7 +1,7 @@
1
1
  import * as ts from 'typescript';
2
2
  import { getPropertyValue } from './get-property-value.js';
3
- import { matchesFilters } from './utils.js';
4
- export const addSchedule = (node, _checker, state, filters) => {
3
+ import { extractFunctionName, matchesFilters } from './utils.js';
4
+ export const addSchedule = (node, checker, state, filters) => {
5
5
  if (!ts.isCallExpression(node)) {
6
6
  return;
7
7
  }
@@ -21,6 +21,15 @@ export const addSchedule = (node, _checker, state, filters) => {
21
21
  const scheduleValue = getPropertyValue(obj, 'schedule');
22
22
  const docs = getPropertyValue(obj, 'docs') || undefined;
23
23
  const tags = getPropertyValue(obj, 'tags') || undefined;
24
+ // --- find the referenced function ---
25
+ const funcProp = obj.properties.find((p) => ts.isPropertyAssignment(p) &&
26
+ ts.isIdentifier(p.name) &&
27
+ p.name.text === 'func');
28
+ if (!funcProp || !ts.isIdentifier(funcProp.initializer)) {
29
+ console.error(`• No valid 'func' property for scheduled task '${nameValue}'.`);
30
+ return;
31
+ }
32
+ const pikkuFuncName = extractFunctionName(funcProp.initializer, checker).pikkuFuncName;
24
33
  if (!nameValue || !scheduleValue) {
25
34
  return;
26
35
  }
@@ -28,11 +37,12 @@ export const addSchedule = (node, _checker, state, filters) => {
28
37
  return;
29
38
  }
30
39
  state.scheduledTasks.files.add(node.getSourceFile().fileName);
31
- state.scheduledTasks.meta.push({
40
+ state.scheduledTasks.meta[nameValue] = {
41
+ pikkuFuncName,
32
42
  name: nameValue,
33
43
  schedule: scheduleValue,
34
44
  docs,
35
45
  tags,
36
- });
46
+ };
37
47
  }
38
48
  };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,170 @@
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
+ // matchesFilters,
9
+ // } from './utils.js'
10
+ // import { ChannelMeta } from '@pikku/core/channel'
11
+ // import { TypesMap } from './types-map.js'
12
+ // import { InspectorFilters, InspectorState } from './types.js'
13
+ export {};
14
+ // const addMessagesRoutes = (
15
+ // obj: ts.ObjectLiteralExpression,
16
+ // checker: ts.TypeChecker,
17
+ // typesMap: TypesMap
18
+ // ) => {
19
+ // const messageTypes: ChannelMeta['messageRoutes'] = {}
20
+ // // Find the onMessageRoute property
21
+ // const messagesProperty = obj.properties.find(
22
+ // (p) =>
23
+ // ts.isPropertyAssignment(p) &&
24
+ // ts.isIdentifier(p.name) &&
25
+ // p.name.text === 'onMessageRoute'
26
+ // )
27
+ // if (!messagesProperty || !ts.isPropertyAssignment(messagesProperty)) {
28
+ // console.log(
29
+ // 'onMessageRoute property not found or is not a valid assignment.'
30
+ // )
31
+ // return {}
32
+ // }
33
+ // const initializer = messagesProperty.initializer
34
+ // // Ensure initializer is an object literal expression
35
+ // if (!ts.isObjectLiteralExpression(initializer)) {
36
+ // console.log('onMessageRoute is not an object literal.')
37
+ // return {}
38
+ // }
39
+ // // Iterate over the first level properties (like 'event')
40
+ // initializer.properties.forEach((property) => {
41
+ // const channel = property.name!.getText()
42
+ // messageTypes[channel] = {}
43
+ // if (ts.isPropertyAssignment(property)) {
44
+ // const nestedObject = property.initializer
45
+ // if (ts.isObjectLiteralExpression(nestedObject)) {
46
+ // const keys = nestedObject.properties.map((p) => p.name?.getText())
47
+ // for (const route of keys) {
48
+ // if (route) {
49
+ // const result = getFunctionTypesFromObject(
50
+ // checker,
51
+ // nestedObject,
52
+ // false,
53
+ // {
54
+ // funcName: route,
55
+ // inputIndex: 0,
56
+ // outputIndex: 1,
57
+ // typesMap,
58
+ // }
59
+ // )
60
+ // const inputs = result?.inputs || null
61
+ // const outputs = result?.outputs || null
62
+ // const type = result?.type || null
63
+ // messageTypes[channel][route] = { inputs, outputs, type }
64
+ // }
65
+ // }
66
+ // } else {
67
+ // console.warn('Nested property is not an object literal:', nestedObject)
68
+ // }
69
+ // } else {
70
+ // console.warn(
71
+ // `Property "${property.getText()}" is a ${ts.SyntaxKind[property.kind]}`
72
+ // )
73
+ // }
74
+ // })
75
+ // return messageTypes
76
+ // }
77
+ // export const addChannel = (
78
+ // node: ts.Node,
79
+ // checker: ts.TypeChecker,
80
+ // state: InspectorState,
81
+ // filters: InspectorFilters
82
+ // ) => {
83
+ // if (!ts.isCallExpression(node)) {
84
+ // return
85
+ // }
86
+ // const args = node.arguments
87
+ // const firstArg = args[0]
88
+ // const expression = node.expression
89
+ // // Check if the call is to addRoute
90
+ // if (!ts.isIdentifier(expression) || expression.text !== 'addChannel') {
91
+ // return
92
+ // }
93
+ // if (!firstArg) {
94
+ // return
95
+ // }
96
+ // let docs: APIDocs | undefined
97
+ // let paramsValues: string[] | null = []
98
+ // let queryValues: string[] | [] = []
99
+ // let tags: string[] | undefined = undefined
100
+ // let inputType: string | null = null
101
+ // let route: string | null = null
102
+ // let name: string | null = null
103
+ // // Check if the first argument is an object literal
104
+ // if (ts.isObjectLiteralExpression(firstArg)) {
105
+ // const obj = firstArg
106
+ // name = getPropertyValue(obj, 'name') as string | null
107
+ // route = getPropertyValue(obj, 'route') as string | null
108
+ // if (!name) {
109
+ // console.error('Channel name is required')
110
+ // return
111
+ // }
112
+ // if (route) {
113
+ // const { keys } = pathToRegexp(route)
114
+ // paramsValues = keys.reduce((result, { type, name }) => {
115
+ // if (type === 'param') {
116
+ // result.push(name)
117
+ // }
118
+ // return result
119
+ // }, [] as string[])
120
+ // } else {
121
+ // route = ''
122
+ // }
123
+ // docs = (getPropertyValue(obj, 'docs') as APIDocs) || undefined
124
+ // queryValues = (getPropertyValue(obj, 'query') as string[]) || []
125
+ // tags = (getPropertyValue(obj, 'tags') as string[]) || undefined
126
+ // const connect = !!getPropertyAssignment(obj, 'onConnect', false)
127
+ // const disconnect = !!getPropertyAssignment(obj, 'onDisconnect', false)
128
+ // const { inputs, outputs, type } = getFunctionTypesFromObject(
129
+ // checker,
130
+ // obj,
131
+ // false,
132
+ // {
133
+ // funcName: 'onMessage',
134
+ // inputIndex: 0,
135
+ // outputIndex: 1,
136
+ // typesMap: state.channels.typesMap,
137
+ // }
138
+ // )
139
+ // const message = { inputs, outputs, type }
140
+ // const messageRoutes = addMessagesRoutes(
141
+ // obj,
142
+ // checker,
143
+ // state.channels.typesMap
144
+ // )
145
+ // if (!matchesFilters(filters, { tags }, { type: 'channel', name })) {
146
+ // return
147
+ // }
148
+ // state.channels.files.add(node.getSourceFile().fileName)
149
+ // state.channels.meta.push({
150
+ // name,
151
+ // route,
152
+ // input: inputType,
153
+ // params: paramsValues.length > 0 ? paramsValues : undefined,
154
+ // query: queryValues.length > 0 ? queryValues : undefined,
155
+ // inputTypes: getInputTypes(
156
+ // state.channels.metaInputTypes,
157
+ // 'get',
158
+ // inputType,
159
+ // queryValues,
160
+ // paramsValues
161
+ // ),
162
+ // connect,
163
+ // disconnect,
164
+ // message: message || undefined,
165
+ // messageRoutes,
166
+ // docs,
167
+ // tags,
168
+ // })
169
+ // }
170
+ // }
@@ -0,0 +1,16 @@
1
+ import * as ts from 'typescript';
2
+ import { InspectorState, InspectorFilters } from '../types.js';
3
+ /**
4
+ * Populate metaInputTypes for a given route based on method, input type,
5
+ * query and params. Returns undefined (we only mutate metaTypes).
6
+ */
7
+ export declare const getInputTypes: (metaTypes: Map<string, {
8
+ query?: string[];
9
+ params?: string[];
10
+ body?: string[];
11
+ }>, methodType: string, inputType: string | null, queryValues: string[], paramsValues: string[]) => undefined;
12
+ /**
13
+ * Simplified addRoute: re-uses function metadata from state.functions.meta
14
+ * instead of re-inferring types here.
15
+ */
16
+ export declare const addRoute: (node: ts.Node, checker: ts.TypeChecker, state: InspectorState, filters: InspectorFilters) => void;