@pikku/inspector 0.9.5 → 0.10.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.
Files changed (133) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/add/add-channel.d.ts +17 -0
  3. package/dist/{add-channel.js → add/add-channel.js} +60 -34
  4. package/dist/add/add-cli.d.ts +9 -0
  5. package/dist/add/add-cli.js +566 -0
  6. package/dist/{add-file-extends-core-type.d.ts → add/add-file-extends-core-type.d.ts} +2 -2
  7. package/dist/{add-file-extends-core-type.js → add/add-file-extends-core-type.js} +17 -4
  8. package/dist/{add-file-with-config.d.ts → add/add-file-with-config.d.ts} +1 -1
  9. package/dist/{add-file-with-config.js → add/add-file-with-config.js} +1 -1
  10. package/dist/{add-file-with-factory.d.ts → add/add-file-with-factory.d.ts} +2 -2
  11. package/dist/{add-file-with-factory.js → add/add-file-with-factory.js} +38 -5
  12. package/dist/add/add-functions.d.ts +6 -0
  13. package/dist/{add-functions.js → add/add-functions.js} +77 -10
  14. package/dist/{add-http-route.d.ts → add/add-http-route.d.ts} +2 -3
  15. package/dist/{add-http-route.js → add/add-http-route.js} +26 -13
  16. package/dist/add/add-mcp-prompt.d.ts +2 -0
  17. package/dist/add/add-mcp-prompt.js +74 -0
  18. package/dist/add/add-mcp-resource.d.ts +2 -0
  19. package/dist/add/add-mcp-resource.js +84 -0
  20. package/dist/add/add-mcp-tool.d.ts +2 -0
  21. package/dist/add/add-mcp-tool.js +80 -0
  22. package/dist/add/add-middleware.d.ts +5 -0
  23. package/dist/add/add-middleware.js +290 -0
  24. package/dist/add/add-permission.d.ts +5 -0
  25. package/dist/add/add-permission.js +292 -0
  26. package/dist/add/add-queue-worker.d.ts +2 -0
  27. package/dist/add/add-queue-worker.js +52 -0
  28. package/dist/{add-rpc-invocations.d.ts → add/add-rpc-invocations.d.ts} +1 -1
  29. package/dist/add/add-schedule.d.ts +2 -0
  30. package/dist/{add-schedule.js → add/add-schedule.js} +16 -11
  31. package/dist/error-codes.d.ts +35 -0
  32. package/dist/error-codes.js +40 -0
  33. package/dist/index.d.ts +6 -0
  34. package/dist/index.js +4 -0
  35. package/dist/inspector.d.ts +2 -3
  36. package/dist/inspector.js +38 -8
  37. package/dist/types.d.ts +108 -1
  38. package/dist/utils/ensure-function-metadata.d.ts +6 -0
  39. package/dist/utils/ensure-function-metadata.js +18 -0
  40. package/dist/utils/extract-function-name.d.ts +31 -0
  41. package/dist/{utils.js → utils/extract-function-name.js} +35 -149
  42. package/dist/utils/extract-services.d.ts +6 -0
  43. package/dist/utils/extract-services.js +29 -0
  44. package/dist/utils/filter-inspector-state.d.ts +6 -0
  45. package/dist/utils/filter-inspector-state.js +382 -0
  46. package/dist/utils/filter-utils.d.ts +19 -0
  47. package/dist/utils/filter-utils.js +109 -0
  48. package/dist/utils/find-root-dir.d.ts +23 -0
  49. package/dist/utils/find-root-dir.js +55 -0
  50. package/dist/utils/get-files-and-methods.d.ts +22 -0
  51. package/dist/utils/get-files-and-methods.js +61 -0
  52. package/dist/utils/get-property-value.d.ts +12 -0
  53. package/dist/{get-property-value.js → utils/get-property-value.js} +20 -0
  54. package/dist/utils/middleware.d.ts +39 -0
  55. package/dist/utils/middleware.js +157 -0
  56. package/dist/utils/permissions.d.ts +43 -0
  57. package/dist/utils/permissions.js +178 -0
  58. package/dist/utils/post-process.d.ts +16 -0
  59. package/dist/utils/post-process.js +132 -0
  60. package/dist/utils/serialize-inspector-state.d.ts +179 -0
  61. package/dist/utils/serialize-inspector-state.js +170 -0
  62. package/dist/utils/type-utils.d.ts +3 -0
  63. package/dist/utils/type-utils.js +50 -0
  64. package/dist/visit.d.ts +3 -3
  65. package/dist/visit.js +35 -31
  66. package/package.json +5 -6
  67. package/src/{add-channel.ts → add/add-channel.ts} +108 -56
  68. package/src/add/add-cli.ts +822 -0
  69. package/src/{add-file-extends-core-type.ts → add/add-file-extends-core-type.ts} +23 -5
  70. package/src/{add-file-with-config.ts → add/add-file-with-config.ts} +2 -2
  71. package/src/{add-file-with-factory.ts → add/add-file-with-factory.ts} +49 -6
  72. package/src/{add-functions.ts → add/add-functions.ts} +89 -19
  73. package/src/{add-http-route.ts → add/add-http-route.ts} +66 -32
  74. package/src/add/add-mcp-prompt.ts +128 -0
  75. package/src/add/add-mcp-prompt.ts.tmp +0 -0
  76. package/src/add/add-mcp-resource.ts +145 -0
  77. package/src/add/add-mcp-resource.ts.tmp +0 -0
  78. package/src/add/add-mcp-tool.ts +137 -0
  79. package/src/add/add-middleware.ts +385 -0
  80. package/src/add/add-permission.ts +391 -0
  81. package/src/add/add-queue-worker.ts +92 -0
  82. package/src/{add-rpc-invocations.ts → add/add-rpc-invocations.ts} +1 -1
  83. package/src/{add-schedule.ts → add/add-schedule.ts} +30 -28
  84. package/src/error-codes.ts +43 -0
  85. package/src/index.ts +12 -0
  86. package/src/inspector.ts +41 -17
  87. package/src/types.ts +128 -1
  88. package/src/utils/ensure-function-metadata.ts +24 -0
  89. package/src/{utils.ts → utils/extract-function-name.ts} +44 -206
  90. package/src/utils/extract-services.ts +35 -0
  91. package/src/utils/filter-inspector-state.test.ts +1433 -0
  92. package/src/utils/filter-inspector-state.ts +526 -0
  93. package/src/{utils.test.ts → utils/filter-utils.test.ts} +351 -2
  94. package/src/utils/filter-utils.ts +152 -0
  95. package/src/utils/find-root-dir.ts +68 -0
  96. package/src/utils/get-files-and-methods.ts +151 -0
  97. package/src/{get-property-value.ts → utils/get-property-value.ts} +27 -0
  98. package/src/utils/middleware.ts +241 -0
  99. package/src/utils/permissions.test.ts +327 -0
  100. package/src/utils/permissions.ts +262 -0
  101. package/src/utils/post-process.ts +178 -0
  102. package/src/utils/serialize-inspector-state.ts +375 -0
  103. package/src/utils/test-data/inspector-state.json +1680 -0
  104. package/src/utils/type-utils.ts +74 -0
  105. package/src/visit.ts +50 -34
  106. package/tsconfig.tsbuildinfo +1 -1
  107. package/dist/add-channel.d.ts +0 -13
  108. package/dist/add-functions.d.ts +0 -7
  109. package/dist/add-mcp-prompt.d.ts +0 -3
  110. package/dist/add-mcp-prompt.js +0 -61
  111. package/dist/add-mcp-resource.d.ts +0 -3
  112. package/dist/add-mcp-resource.js +0 -68
  113. package/dist/add-mcp-tool.d.ts +0 -3
  114. package/dist/add-mcp-tool.js +0 -64
  115. package/dist/add-middleware.d.ts +0 -7
  116. package/dist/add-middleware.js +0 -35
  117. package/dist/add-permission.d.ts +0 -7
  118. package/dist/add-permission.js +0 -35
  119. package/dist/add-queue-worker.d.ts +0 -3
  120. package/dist/add-queue-worker.js +0 -48
  121. package/dist/add-schedule.d.ts +0 -3
  122. package/dist/get-property-value.d.ts +0 -3
  123. package/dist/utils.d.ts +0 -39
  124. package/src/add-mcp-prompt.ts +0 -104
  125. package/src/add-mcp-resource.ts +0 -116
  126. package/src/add-mcp-tool.ts +0 -107
  127. package/src/add-middleware.ts +0 -51
  128. package/src/add-permission.ts +0 -53
  129. package/src/add-queue-worker.ts +0 -92
  130. /package/dist/{add-rpc-invocations.js → add/add-rpc-invocations.js} +0 -0
  131. /package/dist/{does-type-extend-core-type.d.ts → utils/does-type-extend-core-type.d.ts} +0 -0
  132. /package/dist/{does-type-extend-core-type.js → utils/does-type-extend-core-type.js} +0 -0
  133. /package/src/{does-type-extend-core-type.ts → utils/does-type-extend-core-type.ts} +0 -0
@@ -0,0 +1,12 @@
1
+ import { PikkuDocs } from '@pikku/core';
2
+ import * as ts from 'typescript';
3
+ import { ErrorCode } from '../error-codes.js';
4
+ export declare const getPropertyValue: (obj: ts.ObjectLiteralExpression, propertyName: string) => string | string[] | null | PikkuDocs | boolean;
5
+ /**
6
+ * Gets the 'tags' property from an object and validates it's an array.
7
+ * Logs a critical error if tags is not an array but still returns the value.
8
+ * @param logger - Optional logger instance; if not provided, uses console.error
9
+ */
10
+ export declare const getPropertyTags: (obj: ts.ObjectLiteralExpression, wiringType: string, wiringName: string | null, logger?: {
11
+ critical: (code: ErrorCode, message: string) => void;
12
+ }) => string[] | undefined;
@@ -1,4 +1,5 @@
1
1
  import * as ts from 'typescript';
2
+ import { ErrorCode } from '../error-codes.js';
2
3
  export const getPropertyValue = (obj, propertyName) => {
3
4
  const property = obj.properties.find((p) => ts.isPropertyAssignment(p) &&
4
5
  ts.isIdentifier(p.name) &&
@@ -64,3 +65,22 @@ export const getPropertyValue = (obj, propertyName) => {
64
65
  }
65
66
  return null;
66
67
  };
68
+ /**
69
+ * Gets the 'tags' property from an object and validates it's an array.
70
+ * Logs a critical error if tags is not an array but still returns the value.
71
+ * @param logger - Optional logger instance; if not provided, uses console.error
72
+ */
73
+ export const getPropertyTags = (obj, wiringType, wiringName, logger) => {
74
+ const tagsValue = getPropertyValue(obj, 'tags');
75
+ if (tagsValue !== null && !Array.isArray(tagsValue)) {
76
+ const errorMsg = `${wiringType} '${wiringName}' has invalid 'tags' property - must be an array of strings.`;
77
+ if (logger) {
78
+ logger.critical(ErrorCode.INVALID_TAGS_TYPE, errorMsg);
79
+ }
80
+ else {
81
+ console.error(errorMsg);
82
+ }
83
+ // Return undefined but don't stop processing - error will be caught by the exit handler
84
+ }
85
+ return Array.isArray(tagsValue) ? tagsValue : undefined;
86
+ };
@@ -0,0 +1,39 @@
1
+ import * as ts from 'typescript';
2
+ import { MiddlewareMetadata } from '@pikku/core';
3
+ import { InspectorState } from '../types.js';
4
+ /**
5
+ * Extract middleware pikkuFuncNames from an array literal expression
6
+ * Resolves each identifier to its pikkuFuncName using extractFunctionName
7
+ * Also handles call expressions (like logCommandInfoAndTime({...}))
8
+ */
9
+ export declare function extractMiddlewarePikkuNames(arrayNode: ts.Expression, checker: ts.TypeChecker, rootDir: string): string[];
10
+ /**
11
+ * Get middleware array from an object literal expression property
12
+ * Returns the initializer node for the 'middleware' property if it exists
13
+ */
14
+ export declare function getMiddlewareNode(obj: ts.ObjectLiteralExpression): ts.Expression | undefined;
15
+ /**
16
+ * Check if a route matches a pattern with wildcards
17
+ * Pattern can be exact match or use * as wildcard
18
+ * e.g., '/api/*' matches '/api/users', '/api/posts/123', etc.
19
+ */
20
+ export declare function routeMatchesPattern(route: string, pattern: string): boolean;
21
+ /**
22
+ * Resolve middleware for an HTTP wiring based on:
23
+ * 1. Global HTTP middleware (addd([...]))
24
+ * 2. Route-specific HTTP middleware (addHTTPMiddleware('/pattern', [...]))
25
+ * 3. Tag-based middleware (addMiddleware('tag', [...]))
26
+ * 4. Explicit wiring middleware (wireHTTP({ middleware: [...] }))
27
+ * Returns undefined if no middleware is found, otherwise returns array with at least one item
28
+ */
29
+ export declare function resolveHTTPMiddleware(state: InspectorState, route: string, tags: string[] | undefined, explicitMiddlewareNode: ts.Expression | undefined, checker: ts.TypeChecker): MiddlewareMetadata[] | undefined;
30
+ /**
31
+ * Convenience wrapper: Extract middleware node from object and resolve
32
+ * Use this in add-* files for cleaner code
33
+ */
34
+ export declare function resolveMiddleware(state: InspectorState, obj: ts.ObjectLiteralExpression, tags: string[] | undefined, checker: ts.TypeChecker): MiddlewareMetadata[] | undefined;
35
+ /**
36
+ * Convenience wrapper for HTTP: Extract middleware and resolve with HTTP-specific logic
37
+ * Use this in add-http-route.ts for cleaner code
38
+ */
39
+ export declare function resolveHTTPMiddlewareFromObject(state: InspectorState, route: string, obj: ts.ObjectLiteralExpression, tags: string[] | undefined, checker: ts.TypeChecker): MiddlewareMetadata[] | undefined;
@@ -0,0 +1,157 @@
1
+ import * as ts from 'typescript';
2
+ import { extractFunctionName } from './extract-function-name.js';
3
+ /**
4
+ * Extract middleware pikkuFuncNames from an array literal expression
5
+ * Resolves each identifier to its pikkuFuncName using extractFunctionName
6
+ * Also handles call expressions (like logCommandInfoAndTime({...}))
7
+ */
8
+ export function extractMiddlewarePikkuNames(arrayNode, checker, rootDir) {
9
+ if (!ts.isArrayLiteralExpression(arrayNode)) {
10
+ return [];
11
+ }
12
+ const names = [];
13
+ for (const element of arrayNode.elements) {
14
+ if (ts.isIdentifier(element)) {
15
+ // Resolve the identifier to its pikkuFuncName
16
+ const { pikkuFuncName } = extractFunctionName(element, checker, rootDir);
17
+ names.push(pikkuFuncName);
18
+ }
19
+ else if (ts.isCallExpression(element)) {
20
+ // Handle call expressions like rateLimiter(10) or logCommandInfoAndTime({...})
21
+ // Extract the function being called (e.g., 'rateLimiter' from 'rateLimiter(10)')
22
+ const { pikkuFuncName } = extractFunctionName(element.expression, checker, rootDir);
23
+ names.push(pikkuFuncName);
24
+ }
25
+ }
26
+ return names;
27
+ }
28
+ /**
29
+ * Get middleware array from an object literal expression property
30
+ * Returns the initializer node for the 'middleware' property if it exists
31
+ */
32
+ export function getMiddlewareNode(obj) {
33
+ const middlewareProp = obj.properties.find((p) => ts.isPropertyAssignment(p) &&
34
+ ts.isIdentifier(p.name) &&
35
+ p.name.text === 'middleware');
36
+ return middlewareProp?.initializer;
37
+ }
38
+ /**
39
+ * Check if a route matches a pattern with wildcards
40
+ * Pattern can be exact match or use * as wildcard
41
+ * e.g., '/api/*' matches '/api/users', '/api/posts/123', etc.
42
+ */
43
+ export function routeMatchesPattern(route, pattern) {
44
+ if (route === pattern)
45
+ return true;
46
+ // Convert pattern to regex: replace * with .*
47
+ const regexPattern = pattern
48
+ .replace(/[.+?^${}()|[\]\\]/g, '\\$&') // Escape regex special chars except *
49
+ .replace(/\*/g, '.*'); // Replace * with .*
50
+ const regex = new RegExp(`^${regexPattern}$`);
51
+ return regex.test(route);
52
+ }
53
+ /**
54
+ * Resolve middleware for an HTTP wiring based on:
55
+ * 1. Global HTTP middleware (addd([...]))
56
+ * 2. Route-specific HTTP middleware (addHTTPMiddleware('/pattern', [...]))
57
+ * 3. Tag-based middleware (addMiddleware('tag', [...]))
58
+ * 4. Explicit wiring middleware (wireHTTP({ middleware: [...] }))
59
+ * Returns undefined if no middleware is found, otherwise returns array with at least one item
60
+ */
61
+ export function resolveHTTPMiddleware(state, route, tags, explicitMiddlewareNode, checker) {
62
+ const resolved = [];
63
+ // 1. HTTP route middleware groups (includes '*' for global)
64
+ for (const [pattern, _groupMeta] of state.http.routeMiddleware.entries()) {
65
+ if (routeMatchesPattern(route, pattern)) {
66
+ // Just reference the group by route pattern
67
+ resolved.push({
68
+ type: 'http',
69
+ route: pattern,
70
+ });
71
+ }
72
+ }
73
+ // 2. Tag-based middleware groups
74
+ if (tags && tags.length > 0) {
75
+ for (const tag of tags) {
76
+ if (state.middleware.tagMiddleware.has(tag)) {
77
+ // Just reference the group by tag
78
+ resolved.push({
79
+ type: 'tag',
80
+ tag,
81
+ });
82
+ }
83
+ }
84
+ }
85
+ // 3. Explicit wire middleware (inline is OK here)
86
+ if (explicitMiddlewareNode) {
87
+ const middlewareNames = extractMiddlewarePikkuNames(explicitMiddlewareNode, checker, state.rootDir);
88
+ for (const name of middlewareNames) {
89
+ const meta = state.middleware.meta[name];
90
+ resolved.push({
91
+ type: 'wire',
92
+ name,
93
+ inline: meta?.exportedName === null,
94
+ });
95
+ }
96
+ }
97
+ return resolved.length > 0 ? resolved : undefined;
98
+ }
99
+ /**
100
+ * Resolve tag-based and explicit middleware (common logic for wires and functions)
101
+ * 1. Tag-based middleware (addMiddleware('tag', [...]))
102
+ * 2. Explicit middleware (wireHTTP/pikkuFunc({ middleware: [...] }))
103
+ */
104
+ function resolveTagAndExplicitMiddleware(state, tags, explicitMiddlewareNode, checker) {
105
+ const resolved = [];
106
+ // 1. Tag-based middleware groups
107
+ if (tags && tags.length > 0) {
108
+ for (const tag of tags) {
109
+ if (state.middleware.tagMiddleware.has(tag)) {
110
+ // Just reference the group by tag
111
+ resolved.push({
112
+ type: 'tag',
113
+ tag,
114
+ });
115
+ }
116
+ }
117
+ }
118
+ // 2. Explicit middleware (inline is OK here - used directly in wire/function)
119
+ if (explicitMiddlewareNode) {
120
+ const middlewareNames = extractMiddlewarePikkuNames(explicitMiddlewareNode, checker, state.rootDir);
121
+ for (const name of middlewareNames) {
122
+ const meta = state.middleware.meta[name];
123
+ resolved.push({
124
+ type: 'wire',
125
+ name,
126
+ inline: meta?.exportedName === null,
127
+ });
128
+ }
129
+ }
130
+ return resolved;
131
+ }
132
+ /**
133
+ * Resolve middleware for a function based on:
134
+ * 1. Tag-based middleware (addMiddleware('tag', [...]))
135
+ * 2. Explicit function middleware (pikkuFunc({ middleware: [...] }))
136
+ * Returns undefined if no middleware is found, otherwise returns array with at least one item
137
+ */
138
+ function resolveFunctionMiddlewareInternal(state, tags, explicitMiddlewareNode, checker) {
139
+ const resolved = resolveTagAndExplicitMiddleware(state, tags, explicitMiddlewareNode, checker);
140
+ return resolved.length > 0 ? resolved : undefined;
141
+ }
142
+ /**
143
+ * Convenience wrapper: Extract middleware node from object and resolve
144
+ * Use this in add-* files for cleaner code
145
+ */
146
+ export function resolveMiddleware(state, obj, tags, checker) {
147
+ const explicitMiddlewareNode = getMiddlewareNode(obj);
148
+ return resolveFunctionMiddlewareInternal(state, tags, explicitMiddlewareNode, checker);
149
+ }
150
+ /**
151
+ * Convenience wrapper for HTTP: Extract middleware and resolve with HTTP-specific logic
152
+ * Use this in add-http-route.ts for cleaner code
153
+ */
154
+ export function resolveHTTPMiddlewareFromObject(state, route, obj, tags, checker) {
155
+ const explicitMiddlewareNode = getMiddlewareNode(obj);
156
+ return resolveHTTPMiddleware(state, route, tags, explicitMiddlewareNode, checker);
157
+ }
@@ -0,0 +1,43 @@
1
+ import * as ts from 'typescript';
2
+ import { PermissionMetadata } from '@pikku/core';
3
+ import { InspectorState } from '../types.js';
4
+ /**
5
+ * Extract permission pikkuFuncNames from an expression (array or object literal)
6
+ * Resolves each identifier to its pikkuFuncName using extractFunctionName
7
+ * Also handles call expressions (like rolePermission({...}))
8
+ *
9
+ * Supports both formats:
10
+ * - Array: [permission1, permission2]
11
+ * - Record: { groupName: permission1, anotherGroup: [permission2, permission3] }
12
+ */
13
+ export declare function extractPermissionPikkuNames(node: ts.Expression, checker: ts.TypeChecker, rootDir: string): string[];
14
+ /**
15
+ * Get permissions array from an object literal expression property
16
+ * Returns the initializer node for the 'permissions' property if it exists
17
+ */
18
+ export declare function getPermissionsNode(obj: ts.ObjectLiteralExpression): ts.Expression | undefined;
19
+ /**
20
+ * Check if a route matches a pattern with wildcards
21
+ * Pattern can be exact match or use * as wildcard
22
+ * e.g., '/api/*' matches '/api/users', '/api/posts/123', etc.
23
+ */
24
+ export declare function routeMatchesPattern(route: string, pattern: string): boolean;
25
+ /**
26
+ * Resolve permissions for an HTTP wiring based on:
27
+ * 1. Global HTTP permissions (addHTTPPermission('*', [...]))
28
+ * 2. Route-specific HTTP permissions (addHTTPPermission('/pattern', [...]))
29
+ * 3. Tag-based permissions (addPermission('tag', [...]))
30
+ * 4. Explicit wiring permissions (wireHTTP({ permissions: [...] }))
31
+ * Returns undefined if no permissions are found, otherwise returns array with at least one item
32
+ */
33
+ export declare function resolveHTTPPermissions(state: InspectorState, route: string, tags: string[] | undefined, explicitPermissionsNode: ts.Expression | undefined, checker: ts.TypeChecker): PermissionMetadata[] | undefined;
34
+ /**
35
+ * Convenience wrapper: Extract permissions node from object and resolve
36
+ * Use this in add-* files for cleaner code
37
+ */
38
+ export declare function resolvePermissions(state: InspectorState, obj: ts.ObjectLiteralExpression, tags: string[] | undefined, checker: ts.TypeChecker): PermissionMetadata[] | undefined;
39
+ /**
40
+ * Convenience wrapper for HTTP: Extract permissions and resolve with HTTP-specific logic
41
+ * Use this in add-http-route.ts for cleaner code
42
+ */
43
+ export declare function resolveHTTPPermissionsFromObject(state: InspectorState, route: string, obj: ts.ObjectLiteralExpression, tags: string[] | undefined, checker: ts.TypeChecker): PermissionMetadata[] | undefined;
@@ -0,0 +1,178 @@
1
+ import * as ts from 'typescript';
2
+ import { extractFunctionName } from './extract-function-name.js';
3
+ /**
4
+ * Extract permission pikkuFuncNames from an expression (array or object literal)
5
+ * Resolves each identifier to its pikkuFuncName using extractFunctionName
6
+ * Also handles call expressions (like rolePermission({...}))
7
+ *
8
+ * Supports both formats:
9
+ * - Array: [permission1, permission2]
10
+ * - Record: { groupName: permission1, anotherGroup: [permission2, permission3] }
11
+ */
12
+ export function extractPermissionPikkuNames(node, checker, rootDir) {
13
+ const names = [];
14
+ // Helper to extract from a single element
15
+ const extractFromElement = (element) => {
16
+ if (ts.isIdentifier(element)) {
17
+ const { pikkuFuncName } = extractFunctionName(element, checker, rootDir);
18
+ names.push(pikkuFuncName);
19
+ }
20
+ else if (ts.isCallExpression(element)) {
21
+ // Handle call expressions like hasEmailQuota(100) or rolePermission({...})
22
+ // Extract the function being called (e.g., 'hasEmailQuota' from 'hasEmailQuota(100)')
23
+ const { pikkuFuncName } = extractFunctionName(element.expression, checker, rootDir);
24
+ names.push(pikkuFuncName);
25
+ }
26
+ else if (ts.isArrayLiteralExpression(element)) {
27
+ // Nested array (for Record values that are arrays)
28
+ for (const nestedElement of element.elements) {
29
+ extractFromElement(nestedElement);
30
+ }
31
+ }
32
+ };
33
+ if (ts.isArrayLiteralExpression(node)) {
34
+ // Array format: [permission1, permission2]
35
+ for (const element of node.elements) {
36
+ extractFromElement(element);
37
+ }
38
+ }
39
+ else if (ts.isObjectLiteralExpression(node)) {
40
+ // Record format: { groupName: permission1, anotherGroup: [permission2] }
41
+ for (const prop of node.properties) {
42
+ if (ts.isPropertyAssignment(prop)) {
43
+ extractFromElement(prop.initializer);
44
+ }
45
+ }
46
+ }
47
+ return names;
48
+ }
49
+ /**
50
+ * Get permissions array from an object literal expression property
51
+ * Returns the initializer node for the 'permissions' property if it exists
52
+ */
53
+ export function getPermissionsNode(obj) {
54
+ const permissionsProp = obj.properties.find((p) => ts.isPropertyAssignment(p) &&
55
+ ts.isIdentifier(p.name) &&
56
+ p.name.text === 'permissions');
57
+ return permissionsProp?.initializer;
58
+ }
59
+ /**
60
+ * Check if a route matches a pattern with wildcards
61
+ * Pattern can be exact match or use * as wildcard
62
+ * e.g., '/api/*' matches '/api/users', '/api/posts/123', etc.
63
+ */
64
+ export function routeMatchesPattern(route, pattern) {
65
+ if (route === pattern)
66
+ return true;
67
+ // Convert pattern to regex: replace * with .*
68
+ const regexPattern = pattern
69
+ .replace(/[.+?^${}()|[\]\\]/g, '\\$&') // Escape regex special chars except *
70
+ .replace(/\*/g, '.*'); // Replace * with .*
71
+ const regex = new RegExp(`^${regexPattern}$`);
72
+ return regex.test(route);
73
+ }
74
+ /**
75
+ * Resolve permissions for an HTTP wiring based on:
76
+ * 1. Global HTTP permissions (addHTTPPermission('*', [...]))
77
+ * 2. Route-specific HTTP permissions (addHTTPPermission('/pattern', [...]))
78
+ * 3. Tag-based permissions (addPermission('tag', [...]))
79
+ * 4. Explicit wiring permissions (wireHTTP({ permissions: [...] }))
80
+ * Returns undefined if no permissions are found, otherwise returns array with at least one item
81
+ */
82
+ export function resolveHTTPPermissions(state, route, tags, explicitPermissionsNode, checker) {
83
+ const resolved = [];
84
+ // 1. HTTP route permission groups (includes '*' for global)
85
+ for (const [pattern, _groupMeta] of state.http.routePermissions.entries()) {
86
+ if (routeMatchesPattern(route, pattern)) {
87
+ // Just reference the group by route pattern
88
+ resolved.push({
89
+ type: 'http',
90
+ route: pattern,
91
+ });
92
+ }
93
+ }
94
+ // 2. Tag-based permission groups
95
+ if (tags && tags.length > 0) {
96
+ for (const tag of tags) {
97
+ if (state.permissions.tagPermissions.has(tag)) {
98
+ // Just reference the group by tag
99
+ resolved.push({
100
+ type: 'tag',
101
+ tag,
102
+ });
103
+ }
104
+ }
105
+ }
106
+ // 3. Explicit wire permissions (inline is OK here)
107
+ if (explicitPermissionsNode) {
108
+ const permissionNames = extractPermissionPikkuNames(explicitPermissionsNode, checker, state.rootDir);
109
+ for (const name of permissionNames) {
110
+ const meta = state.permissions.meta[name];
111
+ resolved.push({
112
+ type: 'wire',
113
+ name,
114
+ inline: meta?.exportedName === null,
115
+ });
116
+ }
117
+ }
118
+ return resolved.length > 0 ? resolved : undefined;
119
+ }
120
+ /**
121
+ * Resolve tag-based and explicit permissions (common logic for wires and functions)
122
+ * 1. Tag-based permissions (addPermission('tag', [...]))
123
+ * 2. Explicit permissions (wireHTTP/pikkuFunc({ permissions: [...] }))
124
+ */
125
+ function resolveTagAndExplicitPermissions(state, tags, explicitPermissionsNode, checker) {
126
+ const resolved = [];
127
+ // 1. Tag-based permission groups
128
+ if (tags && tags.length > 0) {
129
+ for (const tag of tags) {
130
+ if (state.permissions.tagPermissions.has(tag)) {
131
+ // Just reference the group by tag
132
+ resolved.push({
133
+ type: 'tag',
134
+ tag,
135
+ });
136
+ }
137
+ }
138
+ }
139
+ // 2. Explicit permissions (inline is OK here - used directly in wire/function)
140
+ if (explicitPermissionsNode) {
141
+ const permissionNames = extractPermissionPikkuNames(explicitPermissionsNode, checker, state.rootDir);
142
+ for (const name of permissionNames) {
143
+ const meta = state.permissions.meta[name];
144
+ resolved.push({
145
+ type: 'wire',
146
+ name,
147
+ inline: meta?.exportedName === null,
148
+ });
149
+ }
150
+ }
151
+ return resolved;
152
+ }
153
+ /**
154
+ * Resolve permissions for a function based on:
155
+ * 1. Tag-based permissions (addPermission('tag', [...]))
156
+ * 2. Explicit function permissions (pikkuFunc({ permissions: [...] }))
157
+ * Returns undefined if no permissions are found, otherwise returns array with at least one item
158
+ */
159
+ function resolveFunctionPermissionsInternal(state, tags, explicitPermissionsNode, checker) {
160
+ const resolved = resolveTagAndExplicitPermissions(state, tags, explicitPermissionsNode, checker);
161
+ return resolved.length > 0 ? resolved : undefined;
162
+ }
163
+ /**
164
+ * Convenience wrapper: Extract permissions node from object and resolve
165
+ * Use this in add-* files for cleaner code
166
+ */
167
+ export function resolvePermissions(state, obj, tags, checker) {
168
+ const explicitPermissionsNode = getPermissionsNode(obj);
169
+ return resolveFunctionPermissionsInternal(state, tags, explicitPermissionsNode, checker);
170
+ }
171
+ /**
172
+ * Convenience wrapper for HTTP: Extract permissions and resolve with HTTP-specific logic
173
+ * Use this in add-http-route.ts for cleaner code
174
+ */
175
+ export function resolveHTTPPermissionsFromObject(state, route, obj, tags, checker) {
176
+ const explicitPermissionsNode = getPermissionsNode(obj);
177
+ return resolveHTTPPermissions(state, route, tags, explicitPermissionsNode, checker);
178
+ }
@@ -0,0 +1,16 @@
1
+ import { InspectorState } from '../types.js';
2
+ import { MiddlewareMetadata, PermissionMetadata } from '@pikku/core';
3
+ /**
4
+ * Helper to extract wire-level middleware/permission names from metadata.
5
+ * Only extracts type:'wire' variants (individual middleware/permissions).
6
+ * Skips type:'http' and type:'tag' (reference groups, not individuals).
7
+ */
8
+ export declare function extractWireNames(list?: MiddlewareMetadata[] | PermissionMetadata[]): string[];
9
+ /**
10
+ * Aggregates all required services from wired functions, middleware, and permissions.
11
+ * Must be called after AST traversal completes.
12
+ *
13
+ * Note: usedFunctions, usedMiddleware, and usedPermissions are tracked directly
14
+ * in the add-* methods during AST traversal for efficiency.
15
+ */
16
+ export declare function aggregateRequiredServices(state: InspectorState): void;
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Helper to extract wire-level middleware/permission names from metadata.
3
+ * Only extracts type:'wire' variants (individual middleware/permissions).
4
+ * Skips type:'http' and type:'tag' (reference groups, not individuals).
5
+ */
6
+ export function extractWireNames(list) {
7
+ if (!list)
8
+ return [];
9
+ return list
10
+ .filter((item) => item.type === 'wire')
11
+ .map((item) => item.name);
12
+ }
13
+ /**
14
+ * Helper to expand middleware/permission groups into individual names
15
+ * and add their services to the aggregation.
16
+ * This handles tag-based and HTTP-pattern-based middleware/permission groups.
17
+ */
18
+ function expandAndAddGroupServices(list, state, addServices, isMiddleware) {
19
+ if (!list)
20
+ return;
21
+ for (const item of list) {
22
+ if (item.type === 'tag') {
23
+ // Expand tag-based group
24
+ const groupMeta = isMiddleware
25
+ ? state.middleware.tagMiddleware.get(item.tag)
26
+ : state.permissions.tagPermissions.get(item.tag);
27
+ if (groupMeta?.services) {
28
+ addServices(groupMeta.services);
29
+ }
30
+ }
31
+ else if (item.type === 'http' && 'route' in item) {
32
+ // Expand HTTP-pattern-based group
33
+ const groupMeta = isMiddleware
34
+ ? state.http.routeMiddleware.get(item.route)
35
+ : state.http.routePermissions.get(item.route);
36
+ if (groupMeta?.services) {
37
+ addServices(groupMeta.services);
38
+ }
39
+ }
40
+ }
41
+ }
42
+ /**
43
+ * Aggregates all required services from wired functions, middleware, and permissions.
44
+ * Must be called after AST traversal completes.
45
+ *
46
+ * Note: usedFunctions, usedMiddleware, and usedPermissions are tracked directly
47
+ * in the add-* methods during AST traversal for efficiency.
48
+ */
49
+ export function aggregateRequiredServices(state) {
50
+ const { requiredServices, usedFunctions, usedMiddleware, usedPermissions } = state.serviceAggregation;
51
+ // Internal services (always excluded from tree-shaking)
52
+ const internalServices = new Set(['rpc', 'mcp', 'channel', 'userSession']);
53
+ const addServices = (services) => {
54
+ if (!services || !services.services)
55
+ return;
56
+ services.services.forEach((service) => {
57
+ if (!internalServices.has(service)) {
58
+ requiredServices.add(service);
59
+ }
60
+ });
61
+ };
62
+ // 1. Services from used functions
63
+ usedFunctions.forEach((funcName) => {
64
+ const funcMeta = state.functions.meta[funcName];
65
+ if (funcMeta?.services) {
66
+ addServices(funcMeta.services);
67
+ }
68
+ });
69
+ // 2. Services from used middleware (individual + groups)
70
+ usedMiddleware.forEach((middlewareName) => {
71
+ const middlewareMeta = state.middleware.meta[middlewareName];
72
+ if (middlewareMeta?.services) {
73
+ addServices(middlewareMeta.services);
74
+ }
75
+ });
76
+ // 3. Services from used permissions (individual + groups)
77
+ usedPermissions.forEach((permissionName) => {
78
+ const permissionMeta = state.permissions.meta[permissionName];
79
+ if (permissionMeta?.services) {
80
+ addServices(permissionMeta.services);
81
+ }
82
+ });
83
+ // 4. Services from middleware/permission groups used in wirings
84
+ // We need to check all wirings and expand any tag/HTTP-pattern groups they use
85
+ for (const method of [
86
+ 'get',
87
+ 'post',
88
+ 'put',
89
+ 'patch',
90
+ 'delete',
91
+ 'head',
92
+ 'options',
93
+ ]) {
94
+ for (const routeMeta of Object.values(state.http.meta[method])) {
95
+ expandAndAddGroupServices(routeMeta.middleware, state, addServices, true);
96
+ expandAndAddGroupServices(routeMeta.permissions, state, addServices, false);
97
+ }
98
+ }
99
+ // Also check other wiring types (channels, queues, schedulers, MCP)
100
+ for (const channelMeta of Object.values(state.channels.meta)) {
101
+ expandAndAddGroupServices(channelMeta.middleware, state, addServices, true);
102
+ expandAndAddGroupServices(channelMeta.permissions, state, addServices, false);
103
+ }
104
+ for (const queueMeta of Object.values(state.queueWorkers.meta)) {
105
+ expandAndAddGroupServices(queueMeta.middleware, state, addServices, true);
106
+ // expandAndAddGroupServices(queueMeta.permissions, state, addServices, false)
107
+ }
108
+ for (const scheduleMeta of Object.values(state.scheduledTasks.meta)) {
109
+ expandAndAddGroupServices(scheduleMeta.middleware, state, addServices, true);
110
+ // expandAndAddGroupServices(scheduleMeta.permissions, state, addServices, false)
111
+ }
112
+ for (const toolMeta of Object.values(state.mcpEndpoints.toolsMeta)) {
113
+ expandAndAddGroupServices(toolMeta.middleware, state, addServices, true);
114
+ expandAndAddGroupServices(toolMeta.permissions, state, addServices, false);
115
+ }
116
+ for (const promptMeta of Object.values(state.mcpEndpoints.promptsMeta)) {
117
+ expandAndAddGroupServices(promptMeta.middleware, state, addServices, true);
118
+ expandAndAddGroupServices(promptMeta.permissions, state, addServices, false);
119
+ }
120
+ for (const resourceMeta of Object.values(state.mcpEndpoints.resourcesMeta)) {
121
+ expandAndAddGroupServices(resourceMeta.middleware, state, addServices, true);
122
+ expandAndAddGroupServices(resourceMeta.permissions, state, addServices, false);
123
+ }
124
+ // 5. Services from session service factories
125
+ for (const singletonServices of state.sessionServicesMeta.values()) {
126
+ singletonServices.forEach((service) => {
127
+ if (!internalServices.has(service)) {
128
+ requiredServices.add(service);
129
+ }
130
+ });
131
+ }
132
+ }