@pikku/inspector 0.10.1 → 0.11.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/CHANGELOG.md +54 -0
- package/dist/add/add-channel.js +68 -14
- package/dist/add/add-functions.js +9 -2
- package/dist/add/add-workflow.d.ts +6 -0
- package/dist/add/add-workflow.js +152 -0
- package/dist/error-codes.d.ts +4 -1
- package/dist/error-codes.js +4 -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 +53 -15
- package/dist/types.d.ts +10 -2
- package/dist/utils/extract-node-value.d.ts +24 -0
- package/dist/utils/extract-node-value.js +79 -0
- 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 +6 -0
- package/dist/utils/serialize-inspector-state.js +12 -0
- package/dist/utils/type-utils.d.ts +4 -0
- package/dist/utils/type-utils.js +60 -3
- package/dist/visit.js +2 -0
- package/package.json +2 -2
- package/src/add/add-channel.ts +94 -19
- package/src/add/add-functions.ts +10 -2
- package/src/add/add-workflow.ts +231 -0
- package/src/error-codes.ts +5 -0
- package/src/index.ts +1 -1
- package/src/inspector.ts +77 -22
- package/src/types.ts +10 -2
- package/src/utils/extract-node-value.ts +101 -0
- package/src/utils/post-process.ts +40 -2
- package/src/utils/serialize-inspector-state.ts +18 -0
- package/src/utils/test-data/inspector-state.json +4 -0
- package/src/utils/type-utils.ts +74 -3
- package/src/visit.ts +3 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/src/add/add-mcp-prompt.ts.tmp +0 -0
- package/src/add/add-mcp-resource.ts.tmp +0 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import * as ts from 'typescript';
|
|
2
|
+
/**
|
|
3
|
+
* Extract string literal value from a TypeScript node.
|
|
4
|
+
* Handles string literals, template literals (including placeholders),
|
|
5
|
+
* and constant variable references.
|
|
6
|
+
*/
|
|
7
|
+
export function extractStringLiteral(node, checker) {
|
|
8
|
+
if (ts.isStringLiteral(node)) {
|
|
9
|
+
return node.text;
|
|
10
|
+
}
|
|
11
|
+
if (ts.isNoSubstitutionTemplateLiteral(node)) {
|
|
12
|
+
return node.text;
|
|
13
|
+
}
|
|
14
|
+
if (ts.isTemplateExpression(node)) {
|
|
15
|
+
// reconstruct: `head + ${expr} + middle + ${expr} + tail`
|
|
16
|
+
let result = node.head.text;
|
|
17
|
+
for (const span of node.templateSpans) {
|
|
18
|
+
const exprText = span.expression.getText();
|
|
19
|
+
result += '${' + exprText + '}' + span.literal.text;
|
|
20
|
+
}
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
// Try to evaluate constant identifiers
|
|
24
|
+
if (ts.isIdentifier(node)) {
|
|
25
|
+
const symbol = checker.getSymbolAtLocation(node);
|
|
26
|
+
if (symbol?.valueDeclaration &&
|
|
27
|
+
ts.isVariableDeclaration(symbol.valueDeclaration)) {
|
|
28
|
+
const init = symbol.valueDeclaration.initializer;
|
|
29
|
+
if (init) {
|
|
30
|
+
return extractStringLiteral(init, checker);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
throw new Error('Unable to extract string literal from node');
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Check if node is string-like (string literal or template expression)
|
|
38
|
+
*/
|
|
39
|
+
export function isStringLike(node, _checker) {
|
|
40
|
+
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
// Check if it's a template string with substitutions
|
|
44
|
+
if (ts.isTemplateExpression(node)) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Check if node is function-like (arrow, function expression, or function declaration)
|
|
51
|
+
*/
|
|
52
|
+
export function isFunctionLike(node) {
|
|
53
|
+
return (ts.isArrowFunction(node) ||
|
|
54
|
+
ts.isFunctionExpression(node) ||
|
|
55
|
+
ts.isFunctionDeclaration(node));
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Extract number literal value from a node
|
|
59
|
+
*/
|
|
60
|
+
export function extractNumberLiteral(node) {
|
|
61
|
+
if (ts.isNumericLiteral(node)) {
|
|
62
|
+
return Number(node.text);
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Extract a property value from an object literal expression
|
|
68
|
+
* Returns the extracted value or null if not found/cannot extract
|
|
69
|
+
*/
|
|
70
|
+
export function extractPropertyString(objNode, propertyName, checker) {
|
|
71
|
+
for (const prop of objNode.properties) {
|
|
72
|
+
if (ts.isPropertyAssignment(prop) &&
|
|
73
|
+
ts.isIdentifier(prop.name) &&
|
|
74
|
+
prop.name.text === propertyName) {
|
|
75
|
+
return extractStringLiteral(prop.initializer, checker);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
@@ -13,4 +13,4 @@ export declare function extractWireNames(list?: MiddlewareMetadata[] | Permissio
|
|
|
13
13
|
* Note: usedFunctions, usedMiddleware, and usedPermissions are tracked directly
|
|
14
14
|
* in the add-* methods during AST traversal for efficiency.
|
|
15
15
|
*/
|
|
16
|
-
export declare function aggregateRequiredServices(state: InspectorState): void;
|
|
16
|
+
export declare function aggregateRequiredServices(state: InspectorState | Omit<InspectorState, 'typesLookup'>): void;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { extractTypeKeys } from './type-utils.js';
|
|
1
2
|
/**
|
|
2
3
|
* Helper to extract wire-level middleware/permission names from metadata.
|
|
3
4
|
* Only extracts type:'wire' variants (individual middleware/permissions).
|
|
@@ -39,6 +40,33 @@ function expandAndAddGroupServices(list, state, addServices, isMiddleware) {
|
|
|
39
40
|
}
|
|
40
41
|
}
|
|
41
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Extracts all service names from SingletonServices and Services types.
|
|
45
|
+
* This provides the complete list of available services for code generation.
|
|
46
|
+
* Only runs if typesLookup is available (omitted in deserialized states).
|
|
47
|
+
*/
|
|
48
|
+
function extractAllServices(state) {
|
|
49
|
+
// Skip if typesLookup is not available (e.g., deserialized state)
|
|
50
|
+
if (!('typesLookup' in state)) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
// Extract all singleton services from the SingletonServices type
|
|
54
|
+
const singletonServicesTypes = state.typesLookup.get('SingletonServices');
|
|
55
|
+
if (singletonServicesTypes && singletonServicesTypes.length > 0) {
|
|
56
|
+
const singletonServiceNames = extractTypeKeys(singletonServicesTypes[0]);
|
|
57
|
+
state.serviceAggregation.allSingletonServices = singletonServiceNames.sort();
|
|
58
|
+
}
|
|
59
|
+
// Extract all services from the Services type
|
|
60
|
+
const servicesTypes = state.typesLookup.get('Services');
|
|
61
|
+
if (servicesTypes && servicesTypes.length > 0) {
|
|
62
|
+
const allServiceNames = extractTypeKeys(servicesTypes[0]);
|
|
63
|
+
// Session services are those in Services but not in SingletonServices
|
|
64
|
+
const singletonSet = new Set(state.serviceAggregation.allSingletonServices);
|
|
65
|
+
state.serviceAggregation.allSessionServices = allServiceNames
|
|
66
|
+
.filter((name) => !singletonSet.has(name))
|
|
67
|
+
.sort();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
42
70
|
/**
|
|
43
71
|
* Aggregates all required services from wired functions, middleware, and permissions.
|
|
44
72
|
* Must be called after AST traversal completes.
|
|
@@ -47,6 +75,8 @@ function expandAndAddGroupServices(list, state, addServices, isMiddleware) {
|
|
|
47
75
|
* in the add-* methods during AST traversal for efficiency.
|
|
48
76
|
*/
|
|
49
77
|
export function aggregateRequiredServices(state) {
|
|
78
|
+
// First, extract all available services from types
|
|
79
|
+
extractAllServices(state);
|
|
50
80
|
const { requiredServices, usedFunctions, usedMiddleware, usedPermissions } = state.serviceAggregation;
|
|
51
81
|
// Internal services (always excluded from tree-shaking)
|
|
52
82
|
const internalServices = new Set(['rpc', 'mcp', 'channel', 'userSession']);
|
|
@@ -123,6 +123,10 @@ export interface SerializableInspectorState {
|
|
|
123
123
|
meta: InspectorState['queueWorkers']['meta'];
|
|
124
124
|
files: string[];
|
|
125
125
|
};
|
|
126
|
+
workflows: {
|
|
127
|
+
meta: InspectorState['workflows']['meta'];
|
|
128
|
+
files: string[];
|
|
129
|
+
};
|
|
126
130
|
rpc: {
|
|
127
131
|
internalMeta: InspectorState['rpc']['internalMeta'];
|
|
128
132
|
internalFiles: Array<[string, {
|
|
@@ -165,6 +169,8 @@ export interface SerializableInspectorState {
|
|
|
165
169
|
usedFunctions: string[];
|
|
166
170
|
usedMiddleware: string[];
|
|
167
171
|
usedPermissions: string[];
|
|
172
|
+
allSingletonServices: string[];
|
|
173
|
+
allSessionServices: string[];
|
|
168
174
|
};
|
|
169
175
|
}
|
|
170
176
|
/**
|
|
@@ -50,6 +50,10 @@ export function serializeInspectorState(state) {
|
|
|
50
50
|
meta: state.queueWorkers.meta,
|
|
51
51
|
files: Array.from(state.queueWorkers.files),
|
|
52
52
|
},
|
|
53
|
+
workflows: {
|
|
54
|
+
meta: state.workflows.meta,
|
|
55
|
+
files: Array.from(state.workflows.files),
|
|
56
|
+
},
|
|
53
57
|
rpc: {
|
|
54
58
|
internalMeta: state.rpc.internalMeta,
|
|
55
59
|
internalFiles: Array.from(state.rpc.internalFiles.entries()),
|
|
@@ -80,6 +84,8 @@ export function serializeInspectorState(state) {
|
|
|
80
84
|
usedFunctions: Array.from(state.serviceAggregation.usedFunctions),
|
|
81
85
|
usedMiddleware: Array.from(state.serviceAggregation.usedMiddleware),
|
|
82
86
|
usedPermissions: Array.from(state.serviceAggregation.usedPermissions),
|
|
87
|
+
allSingletonServices: state.serviceAggregation.allSingletonServices,
|
|
88
|
+
allSessionServices: state.serviceAggregation.allSessionServices,
|
|
83
89
|
},
|
|
84
90
|
};
|
|
85
91
|
}
|
|
@@ -135,6 +141,10 @@ export function deserializeInspectorState(data) {
|
|
|
135
141
|
meta: data.queueWorkers.meta,
|
|
136
142
|
files: new Set(data.queueWorkers.files),
|
|
137
143
|
},
|
|
144
|
+
workflows: {
|
|
145
|
+
meta: data.workflows.meta,
|
|
146
|
+
files: new Set(data.workflows.files),
|
|
147
|
+
},
|
|
138
148
|
rpc: {
|
|
139
149
|
internalMeta: data.rpc.internalMeta,
|
|
140
150
|
internalFiles: new Map(data.rpc.internalFiles),
|
|
@@ -165,6 +175,8 @@ export function deserializeInspectorState(data) {
|
|
|
165
175
|
usedFunctions: new Set(data.serviceAggregation.usedFunctions),
|
|
166
176
|
usedMiddleware: new Set(data.serviceAggregation.usedMiddleware),
|
|
167
177
|
usedPermissions: new Set(data.serviceAggregation.usedPermissions),
|
|
178
|
+
allSingletonServices: data.serviceAggregation.allSingletonServices,
|
|
179
|
+
allSessionServices: data.serviceAggregation.allSessionServices,
|
|
168
180
|
},
|
|
169
181
|
};
|
|
170
182
|
}
|
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
2
|
export declare const extractTypeKeys: (type: ts.Type) => string[];
|
|
3
|
+
/**
|
|
4
|
+
* Resolve an identifier or call expression to the actual function declaration
|
|
5
|
+
*/
|
|
6
|
+
export declare function resolveFunctionDeclaration(node: ts.Node, checker: ts.TypeChecker): ts.Node | null;
|
|
3
7
|
export declare function getPropertyAssignmentInitializer(obj: ts.ObjectLiteralExpression, propName: string, followShorthand?: boolean, checker?: ts.TypeChecker): ts.Expression | undefined;
|
package/dist/utils/type-utils.js
CHANGED
|
@@ -2,6 +2,61 @@ import * as ts from 'typescript';
|
|
|
2
2
|
export const extractTypeKeys = (type) => {
|
|
3
3
|
return type.getProperties().map((symbol) => symbol.getName());
|
|
4
4
|
};
|
|
5
|
+
/**
|
|
6
|
+
* Resolve an identifier or call expression to the actual function declaration
|
|
7
|
+
*/
|
|
8
|
+
export function resolveFunctionDeclaration(node, checker) {
|
|
9
|
+
// If it's already a function-like node, return it
|
|
10
|
+
if (ts.isFunctionDeclaration(node) ||
|
|
11
|
+
ts.isFunctionExpression(node) ||
|
|
12
|
+
ts.isArrowFunction(node)) {
|
|
13
|
+
return node;
|
|
14
|
+
}
|
|
15
|
+
// If it's a call expression (e.g., pikkuWorkflowFunc(...)), get its first argument
|
|
16
|
+
if (ts.isCallExpression(node) && node.arguments.length > 0) {
|
|
17
|
+
const firstArg = node.arguments[0];
|
|
18
|
+
if (ts.isFunctionExpression(firstArg) || ts.isArrowFunction(firstArg)) {
|
|
19
|
+
return firstArg;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
// If it's an identifier, resolve to declaration
|
|
23
|
+
if (ts.isIdentifier(node)) {
|
|
24
|
+
const symbol = checker.getSymbolAtLocation(node);
|
|
25
|
+
if (!symbol)
|
|
26
|
+
return null;
|
|
27
|
+
// Try valueDeclaration first, then fallback to declarations[0]
|
|
28
|
+
const decl = symbol.valueDeclaration || symbol.declarations?.[0];
|
|
29
|
+
if (!decl)
|
|
30
|
+
return null;
|
|
31
|
+
// If it's an import specifier, resolve the aliased symbol
|
|
32
|
+
if (ts.isImportSpecifier(decl)) {
|
|
33
|
+
const aliasedSymbol = checker.getAliasedSymbol(symbol);
|
|
34
|
+
if (aliasedSymbol) {
|
|
35
|
+
const aliasedDecl = aliasedSymbol.valueDeclaration || aliasedSymbol.declarations?.[0];
|
|
36
|
+
if (aliasedDecl) {
|
|
37
|
+
// For variable declarations, get the initializer
|
|
38
|
+
if (ts.isVariableDeclaration(aliasedDecl) &&
|
|
39
|
+
aliasedDecl.initializer) {
|
|
40
|
+
return resolveFunctionDeclaration(aliasedDecl.initializer, checker);
|
|
41
|
+
}
|
|
42
|
+
// For function declarations, return directly
|
|
43
|
+
if (ts.isFunctionDeclaration(aliasedDecl)) {
|
|
44
|
+
return aliasedDecl;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// If it's a variable declaration, get the initializer
|
|
50
|
+
if (ts.isVariableDeclaration(decl) && decl.initializer) {
|
|
51
|
+
return resolveFunctionDeclaration(decl.initializer, checker);
|
|
52
|
+
}
|
|
53
|
+
// If it's a function declaration
|
|
54
|
+
if (ts.isFunctionDeclaration(decl)) {
|
|
55
|
+
return decl;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
5
60
|
export function getPropertyAssignmentInitializer(obj, propName, followShorthand = false, checker) {
|
|
6
61
|
for (const prop of obj.properties) {
|
|
7
62
|
// ① foo: () => {}
|
|
@@ -22,17 +77,19 @@ export function getPropertyAssignmentInitializer(obj, propName, followShorthand
|
|
|
22
77
|
prop.name.text === propName) {
|
|
23
78
|
if (!checker)
|
|
24
79
|
return prop.name; // best effort without a checker
|
|
25
|
-
|
|
80
|
+
// Use the proper TypeScript API for shorthand property resolution
|
|
81
|
+
let sym = checker.getShorthandAssignmentValueSymbol(prop);
|
|
26
82
|
if (sym && sym.flags & ts.SymbolFlags.Alias) {
|
|
27
83
|
sym = checker.getAliasedSymbol(sym);
|
|
28
84
|
}
|
|
29
85
|
const decl = sym?.declarations?.[0];
|
|
30
|
-
// const foo = () => {}
|
|
86
|
+
// const foo = () => {} or const foo = pikkuFunc(...)
|
|
31
87
|
if (decl &&
|
|
32
88
|
ts.isVariableDeclaration(decl) &&
|
|
33
89
|
decl.initializer &&
|
|
34
90
|
(ts.isArrowFunction(decl.initializer) ||
|
|
35
|
-
ts.isFunctionExpression(decl.initializer)
|
|
91
|
+
ts.isFunctionExpression(decl.initializer) ||
|
|
92
|
+
ts.isCallExpression(decl.initializer))) {
|
|
36
93
|
return decl.initializer;
|
|
37
94
|
}
|
|
38
95
|
// function foo() {}
|
package/dist/visit.js
CHANGED
|
@@ -4,6 +4,7 @@ import { addFileExtendsCoreType } from './add/add-file-extends-core-type.js';
|
|
|
4
4
|
import { addHTTPRoute } from './add/add-http-route.js';
|
|
5
5
|
import { addSchedule } from './add/add-schedule.js';
|
|
6
6
|
import { addQueueWorker } from './add/add-queue-worker.js';
|
|
7
|
+
import { addWorkflow } from './add/add-workflow.js';
|
|
7
8
|
import { addMCPResource } from './add/add-mcp-resource.js';
|
|
8
9
|
import { addMCPTool } from './add/add-mcp-tool.js';
|
|
9
10
|
import { addMCPPrompt } from './add/add-mcp-prompt.js';
|
|
@@ -24,6 +25,7 @@ export const visitSetup = (logger, checker, node, state, options) => {
|
|
|
24
25
|
addRPCInvocations(node, state, logger);
|
|
25
26
|
addMiddleware(logger, node, checker, state, options);
|
|
26
27
|
addPermission(logger, node, checker, state, options);
|
|
28
|
+
addWorkflow(logger, node, checker, state, options);
|
|
27
29
|
ts.forEachChild(node, (child) => visitSetup(logger, checker, child, state, options));
|
|
28
30
|
};
|
|
29
31
|
export const visitRoutes = (logger, checker, node, state, options) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pikku/inspector",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"author": "yasser.fadl@gmail.com",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"test:coverage": "bash run-tests.sh --coverage"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@pikku/core": "^0.
|
|
19
|
+
"@pikku/core": "^0.11.0",
|
|
20
20
|
"path-to-regexp": "^8.3.0",
|
|
21
21
|
"typescript": "^5.9"
|
|
22
22
|
},
|
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
|
|
package/src/add/add-functions.ts
CHANGED
|
@@ -305,6 +305,7 @@ export const addFunctions: AddWiring = (logger, node, checker, state) => {
|
|
|
305
305
|
|
|
306
306
|
let tags: string[] | undefined
|
|
307
307
|
let expose: boolean | undefined
|
|
308
|
+
let internal: boolean | undefined
|
|
308
309
|
let docs: PikkuDocs | undefined
|
|
309
310
|
let objectNode: ts.ObjectLiteralExpression | undefined
|
|
310
311
|
|
|
@@ -318,6 +319,7 @@ export const addFunctions: AddWiring = (logger, node, checker, state) => {
|
|
|
318
319
|
objectNode = handlerNode
|
|
319
320
|
tags = (getPropertyValue(handlerNode, 'tags') as string[]) || undefined
|
|
320
321
|
expose = getPropertyValue(handlerNode, 'expose') as boolean | undefined
|
|
322
|
+
internal = getPropertyValue(handlerNode, 'internal') as boolean | undefined
|
|
321
323
|
docs = getPropertyValue(handlerNode, 'docs') as PikkuDocs | undefined
|
|
322
324
|
|
|
323
325
|
const fnProp = getPropertyAssignmentInitializer(
|
|
@@ -454,6 +456,7 @@ export const addFunctions: AddWiring = (logger, node, checker, state) => {
|
|
|
454
456
|
inputs: inputNames.filter((n) => n !== 'void') ?? null,
|
|
455
457
|
outputs: outputNames.filter((n) => n !== 'void') ?? null,
|
|
456
458
|
expose: expose || undefined,
|
|
459
|
+
internal: internal || undefined,
|
|
457
460
|
tags: tags || undefined,
|
|
458
461
|
docs: docs || undefined,
|
|
459
462
|
isDirectFunction,
|
|
@@ -481,6 +484,11 @@ export const addFunctions: AddWiring = (logger, node, checker, state) => {
|
|
|
481
484
|
return
|
|
482
485
|
}
|
|
483
486
|
|
|
487
|
+
// Mark internal functions as invoked to force bundling
|
|
488
|
+
if (internal) {
|
|
489
|
+
state.rpc.invokedFunctions.add(pikkuFuncName)
|
|
490
|
+
}
|
|
491
|
+
|
|
484
492
|
if (expose) {
|
|
485
493
|
state.rpc.exposedMeta[name] = pikkuFuncName
|
|
486
494
|
state.rpc.exposedFiles.set(name, {
|
|
@@ -496,8 +504,8 @@ export const addFunctions: AddWiring = (logger, node, checker, state) => {
|
|
|
496
504
|
|
|
497
505
|
// But we only import the actual function if it's actually invoked to keep
|
|
498
506
|
// bundle size down
|
|
499
|
-
if (state.rpc.invokedFunctions.has(pikkuFuncName) || expose) {
|
|
500
|
-
state.rpc.internalFiles.set(
|
|
507
|
+
if (state.rpc.invokedFunctions.has(pikkuFuncName) || expose || internal) {
|
|
508
|
+
state.rpc.internalFiles.set(pikkuFuncName, {
|
|
501
509
|
path: node.getSourceFile().fileName,
|
|
502
510
|
exportedName,
|
|
503
511
|
})
|