@pikku/inspector 0.12.60 → 0.12.61
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 +301 -0
- package/OPTIMIZATION-PLAN.md +19 -19
- package/dist/add/add-auth.js +1 -1
- package/dist/add/add-channel.d.ts +1 -1
- package/dist/add/add-cli.js +5 -0
- package/dist/add/add-functions.js +10 -1
- package/dist/add/add-http-route.js +18 -18
- package/dist/add/add-keyed-wiring.d.ts +2 -0
- package/dist/add/add-keyed-wiring.js +3 -1
- package/dist/add/add-personas.js +4 -1
- package/dist/add/add-secret.js +1 -0
- package/dist/add/add-variable.js +1 -0
- package/dist/add/add-wire-remote-addon.js +2 -1
- package/dist/add/add-workflow-graph.js +63 -6
- package/dist/add/add-workflow.d.ts +1 -1
- package/dist/error-codes.d.ts +3 -1
- package/dist/error-codes.js +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -0
- package/dist/inspector.js +12 -2
- package/dist/types.d.ts +25 -21
- package/dist/utils/annotate-http-route-auth.js +2 -1
- package/dist/utils/check-secret-sinks.js +2 -1
- package/dist/utils/compute-required-schemas.d.ts +1 -1
- package/dist/utils/contract-hashes.d.ts +2 -2
- package/dist/utils/contract-hashes.js +1 -1
- package/dist/utils/extract-function-name.js +1 -1
- package/dist/utils/extract-services.d.ts +1 -1
- package/dist/utils/extract-surface-usage.d.ts +29 -0
- package/dist/utils/extract-surface-usage.js +97 -0
- package/dist/utils/filter-inspector-state.js +53 -16
- package/dist/utils/filter-utils.d.ts +1 -1
- package/dist/utils/get-property-value.js +42 -3
- package/dist/utils/load-addon-functions-meta.js +31 -0
- package/dist/utils/middleware.d.ts +1 -1
- package/dist/utils/permissions.d.ts +1 -1
- package/dist/utils/post-process.d.ts +27 -7
- package/dist/utils/post-process.js +79 -14
- package/dist/utils/resolve-deploy-target.d.ts +1 -1
- package/dist/utils/resolve-function-meta.d.ts +1 -1
- package/dist/utils/resolve-function-types.d.ts +1 -1
- package/dist/utils/resolve-versions.js +1 -1
- package/dist/utils/schema-generator.d.ts +1 -1
- package/dist/utils/serialize-inspector-state.d.ts +2 -1
- package/dist/utils/serialize-inspector-state.js +2 -0
- package/dist/utils/serialize-openapi-json.d.ts +2 -2
- package/dist/utils/validate-auth-sessionless.js +2 -1
- package/dist/utils/workflow/derive-workflow-plan.d.ts +1 -1
- package/dist/utils/workflow/dsl/extract-dsl-workflow.d.ts +1 -1
- package/dist/utils/workflow/graph/convert-dsl-to-graph.d.ts +1 -1
- package/dist/utils/workflow/graph/finalize-workflows.js +1 -1
- package/dist/utils/workflow/graph/serialize-workflow-graph.d.ts +6 -1
- package/dist/utils/workflow/graph/serialize-workflow-graph.js +26 -2
- package/dist/utils/workflow/graph/workflow-graph.types.d.ts +11 -2
- package/package.json +10 -10
- package/run-tests.sh +0 -0
- package/src/add/add-agent.ts +2 -4
- package/src/add/add-auth.ts +2 -2
- package/src/add/add-channel.ts +1 -4
- package/src/add/add-cli.ts +7 -1
- package/src/add/add-credential.ts +3 -6
- package/src/add/add-file-with-factory.ts +2 -6
- package/src/add/add-functions.ts +24 -31
- package/src/add/add-gateway.ts +1 -1
- package/src/add/add-http-route-ref.test.ts +149 -0
- package/src/add/add-http-route.ts +26 -29
- package/src/add/add-keyed-wiring.ts +8 -7
- package/src/add/add-permission.ts +1 -1
- package/src/add/add-personas.test.ts +26 -2
- package/src/add/add-personas.ts +4 -3
- package/src/add/add-scenario-step.test.ts +37 -2
- package/src/add/add-scope.test.ts +15 -15
- package/src/add/add-scope.ts +5 -9
- package/src/add/add-secret.ts +1 -0
- package/src/add/add-system-role.test.ts +4 -4
- package/src/add/add-system-role.ts +2 -4
- package/src/add/add-variable.ts +1 -0
- package/src/add/add-wire-remote-addon.ts +4 -1
- package/src/add/add-workflow-graph-foreach.test.ts +174 -0
- package/src/add/add-workflow-graph-input.test.ts +1 -1
- package/src/add/add-workflow-graph.ts +78 -6
- package/src/add/add-workflow-sleep-expression.test.ts +3 -4
- package/src/add/add-workflow.ts +1 -4
- package/src/add/addon-contracts.test.ts +1 -1
- package/src/add/config-value-metadata.test.ts +94 -0
- package/src/add/secret-sink-check.test.ts +4 -1
- package/src/error-codes.ts +3 -1
- package/src/index.ts +3 -4
- package/src/inspector.ts +19 -2
- package/src/types.ts +25 -28
- package/src/utils/annotate-http-route-auth.test.ts +5 -7
- package/src/utils/annotate-http-route-auth.ts +2 -1
- package/src/utils/check-secret-sinks.ts +4 -1
- package/src/utils/compute-required-schemas.ts +1 -1
- package/src/utils/contract-hashes.test.ts +1 -1
- package/src/utils/contract-hashes.ts +3 -3
- package/src/utils/extract-function-name.ts +1 -1
- package/src/utils/extract-services.ts +1 -2
- package/src/utils/extract-surface-usage.test.ts +147 -0
- package/src/utils/extract-surface-usage.ts +121 -0
- package/src/utils/filter-inspector-state.test.ts +138 -12
- package/src/utils/filter-inspector-state.ts +82 -21
- package/src/utils/filter-utils.ts +1 -1
- package/src/utils/get-property-value.ts +43 -5
- package/src/utils/load-addon-functions-meta.ts +40 -0
- package/src/utils/middleware.ts +1 -1
- package/src/utils/permissions.ts +1 -1
- package/src/utils/post-process.test.ts +59 -2
- package/src/utils/post-process.ts +88 -25
- package/src/utils/resolve-deploy-target.ts +1 -1
- package/src/utils/resolve-function-meta.ts +1 -4
- package/src/utils/resolve-function-types.ts +1 -1
- package/src/utils/resolve-versions.ts +2 -5
- package/src/utils/schema-generator.ts +3 -3
- package/src/utils/serialize-inspector-state.ts +4 -1
- package/src/utils/serialize-mcp-json.ts +1 -1
- package/src/utils/serialize-openapi-json.ts +2 -2
- package/src/utils/validate-auth-sessionless.ts +2 -1
- package/src/utils/validate-tags-resolve-to-middleware.test.ts +4 -1
- package/src/utils/workflow/derive-workflow-plan.test.ts +1 -1
- package/src/utils/workflow/derive-workflow-plan.ts +1 -1
- package/src/utils/workflow/dsl/extract-approval-policy.test.ts +1 -1
- package/src/utils/workflow/dsl/extract-dsl-workflow.ts +2 -2
- package/src/utils/workflow/graph/convert-dsl-to-graph-skip.test.ts +1 -1
- package/src/utils/workflow/graph/convert-dsl-to-graph.ts +1 -4
- package/src/utils/workflow/graph/finalize-workflow-wires.ts +1 -1
- package/src/utils/workflow/graph/finalize-workflows.ts +2 -2
- package/src/utils/workflow/graph/serialize-workflow-graph.ts +34 -3
- package/src/utils/workflow/graph/workflow-graph.types.ts +12 -2
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/add/add-ai-agent.d.ts +0 -4
- package/dist/add/add-ai-agent.js +0 -442
- package/dist/add/add-ai-scorer.d.ts +0 -2
- package/dist/add/add-ai-scorer.js +0 -61
- package/dist/utils/secret-brokers.d.ts +0 -2
- package/dist/utils/secret-brokers.js +0 -9
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isRef } from '@pikku/core/
|
|
1
|
+
import { isRef, template } from '@pikku/core/workflow';
|
|
2
2
|
/**
|
|
3
3
|
* Convert a RefValue (from runtime) to DataRef (serialized)
|
|
4
4
|
*/
|
|
@@ -23,6 +23,22 @@ function serializeInputMapping(input) {
|
|
|
23
23
|
}
|
|
24
24
|
return result;
|
|
25
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* Convert a `forEach` source — a bare node id, a ref, or a `(ref) => ref(...)`
|
|
28
|
+
* callback — to the serialized DataRef form
|
|
29
|
+
*/
|
|
30
|
+
function serializeForEach(forEach, createRef) {
|
|
31
|
+
if (forEach === undefined)
|
|
32
|
+
return undefined;
|
|
33
|
+
if (typeof forEach === 'string')
|
|
34
|
+
return { $ref: forEach };
|
|
35
|
+
if (typeof forEach === 'function') {
|
|
36
|
+
return serializeForEach(forEach(createRef), createRef);
|
|
37
|
+
}
|
|
38
|
+
if (isRef(forEach))
|
|
39
|
+
return convertRef(forEach);
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
26
42
|
/**
|
|
27
43
|
* Convert next config from runtime format to serialized format
|
|
28
44
|
* Runtime uses Record<string, string | string[]> for branching with graph.branch()
|
|
@@ -59,6 +75,7 @@ export function serializeWorkflowGraph(definition, options) {
|
|
|
59
75
|
nodeId,
|
|
60
76
|
path,
|
|
61
77
|
});
|
|
78
|
+
const createItemRef = (path) => createRef('$item', path);
|
|
62
79
|
// Track which nodes have incoming edges
|
|
63
80
|
const hasIncomingEdge = new Set();
|
|
64
81
|
// First pass: identify nodes with incoming edges
|
|
@@ -88,7 +105,7 @@ export function serializeWorkflowGraph(definition, options) {
|
|
|
88
105
|
// Evaluate input callback to get the mapping
|
|
89
106
|
let input = {};
|
|
90
107
|
if (node.input) {
|
|
91
|
-
const rawInput = node.input(createRef);
|
|
108
|
+
const rawInput = node.input(createRef, template, createItemRef);
|
|
92
109
|
input = serializeInputMapping(rawInput);
|
|
93
110
|
}
|
|
94
111
|
// Get RPC name from func
|
|
@@ -100,6 +117,13 @@ export function serializeWorkflowGraph(definition, options) {
|
|
|
100
117
|
next: serializeNext(node.next),
|
|
101
118
|
onError: node.onError,
|
|
102
119
|
};
|
|
120
|
+
const forEach = serializeForEach(node.forEach, createRef);
|
|
121
|
+
if (forEach) {
|
|
122
|
+
funcNode.forEach = forEach;
|
|
123
|
+
if (node.mode !== undefined) {
|
|
124
|
+
funcNode.mode = node.mode;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
103
127
|
if (node.notes !== undefined) {
|
|
104
128
|
funcNode.notes = node.notes;
|
|
105
129
|
}
|
|
@@ -67,8 +67,8 @@ export interface NodeOptions {
|
|
|
67
67
|
* Flow node types for control flow (no RPC call)
|
|
68
68
|
*/
|
|
69
69
|
export type FlowType = 'sleep' | 'suspend' | 'approval' | 'branch' | 'parallel' | 'fanout' | 'inline' | 'switch' | 'filter' | 'arrayPredicate' | 'return' | 'cancel' | 'set';
|
|
70
|
-
import type { ContextVariable, WorkflowContext, WorkflowPlannedStep } from '@pikku/core/
|
|
71
|
-
import type { ScenarioStepPhase } from '@pikku/core/
|
|
70
|
+
import type { ContextVariable, WorkflowContext, WorkflowPlannedStep } from '@pikku/core/workflow';
|
|
71
|
+
import type { ScenarioStepPhase } from '@pikku/core/scenario';
|
|
72
72
|
export type { ContextVariable, WorkflowContext };
|
|
73
73
|
/**
|
|
74
74
|
* Base node properties shared by all node types
|
|
@@ -90,11 +90,20 @@ interface BaseNode {
|
|
|
90
90
|
/**
|
|
91
91
|
* Function node - calls an RPC
|
|
92
92
|
*/
|
|
93
|
+
/** How the per-item instances of a `forEach` node run */
|
|
94
|
+
export type ForEachMode = 'parallel' | 'sequential';
|
|
93
95
|
export interface FunctionNode extends BaseNode {
|
|
94
96
|
/** RPC function name */
|
|
95
97
|
rpcName: string;
|
|
96
98
|
/** Input mapping - values can be literals or DataRefs */
|
|
97
99
|
input?: Record<string, unknown | DataRef>;
|
|
100
|
+
/**
|
|
101
|
+
* Run this node once per element of the referenced array. The node's own
|
|
102
|
+
* result becomes the ordered array of per-item results.
|
|
103
|
+
*/
|
|
104
|
+
forEach?: DataRef;
|
|
105
|
+
/** How the per-item instances run. Defaults to 'parallel'. */
|
|
106
|
+
mode?: ForEachMode;
|
|
98
107
|
/** Output variable name for storing result */
|
|
99
108
|
outputVar?: string;
|
|
100
109
|
/** Hash of nodeId + RPC input/output schemas for version detection */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pikku/inspector",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.61",
|
|
4
4
|
"description": "TypeScript inspector for Pikku — discovers functions and wirings to drive code generation",
|
|
5
5
|
"author": "yasser.fadl@gmail.com",
|
|
6
6
|
"license": "BUSL-1.1",
|
|
@@ -35,23 +35,23 @@
|
|
|
35
35
|
"prepublishOnly": "yarn build"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@openapi-contrib/json-schema-to-openapi-schema": "^4.3.
|
|
38
|
+
"@openapi-contrib/json-schema-to-openapi-schema": "^4.3.4",
|
|
39
39
|
"openapi-types": "^12.1.3",
|
|
40
|
-
"path-to-regexp": "^8.
|
|
41
|
-
"ts-json-schema-generator": "2.9.1-next.
|
|
42
|
-
"tsx": "^4.
|
|
40
|
+
"path-to-regexp": "^8.4.2",
|
|
41
|
+
"ts-json-schema-generator": "2.9.1-next.18",
|
|
42
|
+
"tsx": "^4.23.12",
|
|
43
43
|
"typescript": "^6.0.3",
|
|
44
|
-
"zod": "^4.3
|
|
44
|
+
"zod": "^4.4.3",
|
|
45
45
|
"zod-to-ts": "^2.1.0"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
|
-
"@pikku/core": "^0.12.
|
|
48
|
+
"@pikku/core": "^0.12.85"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@pikku/core": "^0.12.
|
|
52
|
-
"@types/node": "^24.
|
|
51
|
+
"@pikku/core": "^0.12.85",
|
|
52
|
+
"@types/node": "^24.13.3"
|
|
53
53
|
},
|
|
54
54
|
"engines": {
|
|
55
55
|
"node": ">=24"
|
|
56
56
|
}
|
|
57
|
-
}
|
|
57
|
+
}
|
package/run-tests.sh
CHANGED
|
File without changes
|
package/src/add/add-agent.ts
CHANGED
|
@@ -395,14 +395,12 @@ export const addAgent: AddWiring = (logger, node, checker, state, options) => {
|
|
|
395
395
|
|
|
396
396
|
const roleValue = getPropertyValue(obj, 'role') as string | null
|
|
397
397
|
const personalityValue = getPropertyValue(obj, 'personality') as
|
|
398
|
-
|
|
|
399
|
-
| null
|
|
398
|
+
string | null
|
|
400
399
|
const goalValue = getPropertyValue(obj, 'goal') as string | null
|
|
401
400
|
|
|
402
401
|
const maxStepsValue = getPropertyValue(obj, 'maxSteps') as number | null
|
|
403
402
|
const temperatureValue = getPropertyValue(obj, 'temperature') as
|
|
404
|
-
|
|
|
405
|
-
| null
|
|
403
|
+
number | null
|
|
406
404
|
const toolChoiceValue = getPropertyValue(obj, 'toolChoice') as string | null
|
|
407
405
|
const scorersValue = resolveScorerNames(obj, nameValue || '', logger)
|
|
408
406
|
const toolsValue = resolveToolReferences(
|
package/src/add/add-auth.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as ts from 'typescript'
|
|
2
|
-
import type { FunctionServicesMeta } from '@pikku/core/
|
|
2
|
+
import type { FunctionServicesMeta } from '@pikku/core/function'
|
|
3
3
|
import type { AddWiring } from '../types.js'
|
|
4
4
|
import { ErrorCode } from '../error-codes.js'
|
|
5
5
|
import { extractServicesFromFunction } from '../utils/extract-services.js'
|
|
@@ -89,7 +89,7 @@ const readArrayProp = (
|
|
|
89
89
|
|
|
90
90
|
/**
|
|
91
91
|
* Read the callee name of a `plugins: [...]` entry. better-auth plugins are
|
|
92
|
-
* factory calls (`bearer()`, `twoFactor({ ... })`, `
|
|
92
|
+
* factory calls (`bearer()`, `twoFactor({ ... })`, `ban()`); the entry's id
|
|
93
93
|
* is the called function's name. Member-expression callees (`foo.bar()`) and
|
|
94
94
|
* non-call entries are ignored.
|
|
95
95
|
*/
|
package/src/add/add-channel.ts
CHANGED
|
@@ -10,10 +10,7 @@ import {
|
|
|
10
10
|
makeContextBasedId,
|
|
11
11
|
} from '../utils/extract-function-name.js'
|
|
12
12
|
import { getPropertyAssignmentInitializer } from '../utils/type-utils.js'
|
|
13
|
-
import type {
|
|
14
|
-
ChannelMessageMeta,
|
|
15
|
-
ChannelMeta,
|
|
16
|
-
} from '@pikku/core/ecosystem/channel'
|
|
13
|
+
import type { ChannelMessageMeta, ChannelMeta } from '@pikku/core/channel'
|
|
17
14
|
import type { InspectorState, AddWiring } from '../types.js'
|
|
18
15
|
import {
|
|
19
16
|
resolveMiddleware,
|
package/src/add/add-cli.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type {
|
|
|
6
6
|
InspectorOptions,
|
|
7
7
|
InspectorState,
|
|
8
8
|
} from '../types.js'
|
|
9
|
-
import type { CLIProgramMeta, CLICommandMeta } from '@pikku/core/
|
|
9
|
+
import type { CLIProgramMeta, CLICommandMeta } from '@pikku/core/cli'
|
|
10
10
|
import {
|
|
11
11
|
extractFunctionName,
|
|
12
12
|
makeContextBasedId,
|
|
@@ -630,6 +630,12 @@ function processOptions(
|
|
|
630
630
|
}
|
|
631
631
|
break
|
|
632
632
|
|
|
633
|
+
case 'type':
|
|
634
|
+
if (ts.isStringLiteral(optProp.initializer)) {
|
|
635
|
+
option.type = optProp.initializer.text
|
|
636
|
+
}
|
|
637
|
+
break
|
|
638
|
+
|
|
633
639
|
case 'default':
|
|
634
640
|
// Extract default value from expression
|
|
635
641
|
if (ts.isStringLiteral(optProp.initializer)) {
|
|
@@ -39,11 +39,9 @@ export const addCredential: AddWiring = (
|
|
|
39
39
|
|
|
40
40
|
const nameValue = getPropertyValue(obj, 'name') as string | null
|
|
41
41
|
const displayNameValue = getPropertyValue(obj, 'displayName') as
|
|
42
|
-
|
|
|
43
|
-
| null
|
|
42
|
+
string | null
|
|
44
43
|
const descriptionValue = getPropertyValue(obj, 'description') as
|
|
45
|
-
|
|
|
46
|
-
| null
|
|
44
|
+
string | null
|
|
47
45
|
const typeValue = getPropertyValue(obj, 'type') as string | null
|
|
48
46
|
const docsUrlValue = getPropertyValue(obj, 'docsUrl') as string | null
|
|
49
47
|
|
|
@@ -126,8 +124,7 @@ export const addCredential: AddWiring = (
|
|
|
126
124
|
'appCredentialSecretId'
|
|
127
125
|
) as string | null
|
|
128
126
|
const tokenSecretId = getPropertyValue(oauth2Obj, 'tokenSecretId') as
|
|
129
|
-
|
|
|
130
|
-
| null
|
|
127
|
+
string | null
|
|
131
128
|
const authorizationUrl = getPropertyValue(
|
|
132
129
|
oauth2Obj,
|
|
133
130
|
'authorizationUrl'
|
|
@@ -61,9 +61,7 @@ export const addFileWithFactory = (
|
|
|
61
61
|
if (state && callExpression.arguments.length > 0) {
|
|
62
62
|
const firstArg = callExpression.arguments[0]
|
|
63
63
|
let functionNode:
|
|
64
|
-
| ts.
|
|
65
|
-
| ts.FunctionExpression
|
|
66
|
-
| undefined
|
|
64
|
+
ts.ArrowFunction | ts.FunctionExpression | undefined
|
|
67
65
|
|
|
68
66
|
if (ts.isArrowFunction(firstArg)) {
|
|
69
67
|
functionNode = firstArg
|
|
@@ -179,9 +177,7 @@ export const addFileWithFactory = (
|
|
|
179
177
|
node.initializer
|
|
180
178
|
) {
|
|
181
179
|
let functionNode:
|
|
182
|
-
| ts.
|
|
183
|
-
| ts.FunctionExpression
|
|
184
|
-
| undefined
|
|
180
|
+
ts.ArrowFunction | ts.FunctionExpression | undefined
|
|
185
181
|
if (ts.isArrowFunction(node.initializer)) {
|
|
186
182
|
functionNode = node.initializer
|
|
187
183
|
} else if (ts.isFunctionExpression(node.initializer)) {
|
package/src/add/add-functions.ts
CHANGED
|
@@ -8,14 +8,10 @@ import {
|
|
|
8
8
|
} from '../utils/extract-function-name.js'
|
|
9
9
|
import { extractFunctionNode } from '../utils/extract-function-node.js'
|
|
10
10
|
import { extractUsedWires } from '../utils/extract-services.js'
|
|
11
|
-
import type { AuditDurability } from '@pikku/core/
|
|
12
|
-
import type { FunctionServicesMeta } from '@pikku/core/
|
|
13
|
-
import type { ScenarioSurface } from '@pikku/core/scenario'
|
|
14
|
-
import
|
|
15
|
-
import {
|
|
16
|
-
formatVersionedId,
|
|
17
|
-
parseVersionedId,
|
|
18
|
-
} from '@pikku/core/ecosystem/types'
|
|
11
|
+
import type { AuditDurability } from '@pikku/core/services'
|
|
12
|
+
import type { FunctionServicesMeta } from '@pikku/core/function'
|
|
13
|
+
import type { ScenarioSurface, ScenarioStepKind } from '@pikku/core/scenario'
|
|
14
|
+
import { formatVersionedId, parseVersionedId } from '@pikku/core/utils'
|
|
19
15
|
|
|
20
16
|
/** Binding keys a `pikkuScenarioStep` may declare, in report order. */
|
|
21
17
|
const SCENARIO_SURFACE_KEYS: readonly ScenarioSurface[] = [
|
|
@@ -49,7 +45,7 @@ import { ErrorCode } from '../error-codes.js'
|
|
|
49
45
|
import { findPiiPaths } from '../utils/check-pii-output.js'
|
|
50
46
|
import { findRevealedSecretSinks } from '../utils/check-secret-sinks.js'
|
|
51
47
|
import { isScenarioInstrumentationFunction } from './scenario-instrumentation.js'
|
|
52
|
-
import type { NodeType } from '@pikku/core/
|
|
48
|
+
import type { NodeType } from '@pikku/core/node'
|
|
53
49
|
|
|
54
50
|
const isValidVariableName = (name: string) => {
|
|
55
51
|
const regex = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/
|
|
@@ -597,6 +593,7 @@ export const addFunctions: AddWiring = (
|
|
|
597
593
|
let workflowRetries: number | undefined
|
|
598
594
|
let workflowTimeout: string | undefined
|
|
599
595
|
let scenarioStepSurfaces: ScenarioSurface[] | undefined
|
|
596
|
+
let scenarioStepRequiresActor: boolean | undefined
|
|
600
597
|
let scenarioStepAddon: string | undefined
|
|
601
598
|
let scenarioStepTemplate: string | undefined
|
|
602
599
|
let scopes: string[] | undefined
|
|
@@ -688,8 +685,7 @@ export const addFunctions: AddWiring = (
|
|
|
688
685
|
mcp = getPropertyValue(firstArg, 'mcp') as boolean | undefined
|
|
689
686
|
auth = getPropertyValue(firstArg, 'auth') as boolean | undefined
|
|
690
687
|
permissionsInBody = getPropertyValue(firstArg, 'permissionsInBody') as
|
|
691
|
-
|
|
|
692
|
-
| undefined
|
|
688
|
+
boolean | undefined
|
|
693
689
|
if (permissionsInBody && !options.allow?.permissionsInBody) {
|
|
694
690
|
logger.critical(
|
|
695
691
|
ErrorCode.PERMISSIONS_IN_BODY_NOT_ALLOWED,
|
|
@@ -726,22 +722,15 @@ export const addFunctions: AddWiring = (
|
|
|
726
722
|
}
|
|
727
723
|
readonly_ = getPropertyValue(firstArg, 'readonly') as boolean | undefined
|
|
728
724
|
deploy = getPropertyValue(firstArg, 'deploy') as
|
|
729
|
-
| '
|
|
730
|
-
| 'server'
|
|
731
|
-
| 'auto'
|
|
732
|
-
| undefined
|
|
725
|
+
'serverless' | 'server' | 'auto' | undefined
|
|
733
726
|
approvalRequired = getPropertyValue(firstArg, 'approvalRequired') as
|
|
734
|
-
|
|
|
735
|
-
| undefined
|
|
727
|
+
boolean | undefined
|
|
736
728
|
workflowQueued = getPropertyValue(firstArg, 'workflowQueued') as
|
|
737
|
-
|
|
|
738
|
-
| undefined
|
|
729
|
+
boolean | undefined
|
|
739
730
|
workflowRetries = getPropertyValue(firstArg, 'workflowRetries') as
|
|
740
|
-
|
|
|
741
|
-
| undefined
|
|
731
|
+
number | undefined
|
|
742
732
|
workflowTimeout = getPropertyValue(firstArg, 'workflowTimeout') as
|
|
743
|
-
|
|
|
744
|
-
| undefined
|
|
733
|
+
string | undefined
|
|
745
734
|
// A step declares one implementation per surface it supports; the binding
|
|
746
735
|
// keys that are present ARE the declaration, so there is nothing to read a
|
|
747
736
|
// value from — only names to collect.
|
|
@@ -772,8 +761,7 @@ export const addFunctions: AddWiring = (
|
|
|
772
761
|
scenarioStepSurfaces = ['default']
|
|
773
762
|
if (scenarioStepKind === 'addon') {
|
|
774
763
|
scenarioStepAddon = getPropertyValue(firstArg, 'addon') as
|
|
775
|
-
|
|
|
776
|
-
| undefined
|
|
764
|
+
string | undefined
|
|
777
765
|
if (!scenarioStepAddon) {
|
|
778
766
|
logger.error(
|
|
779
767
|
`• pikkuAddonScenarioStep('${name}') needs a string literal \`addon\` naming the addon whose system acts — the same name its wireAddon declares.`
|
|
@@ -785,10 +773,16 @@ export const addFunctions: AddWiring = (
|
|
|
785
773
|
scenarioStepSurfaces = declaredSurfaces.length
|
|
786
774
|
? declaredSurfaces
|
|
787
775
|
: undefined
|
|
776
|
+
// A browser binding cannot be provisioned without a window to open, so it
|
|
777
|
+
// requires an actor whether or not the step said so.
|
|
778
|
+
scenarioStepRequiresActor =
|
|
779
|
+
getPropertyValue(firstArg, 'actor') === true ||
|
|
780
|
+
declaredSurfaces.includes('browser')
|
|
781
|
+
? true
|
|
782
|
+
: undefined
|
|
788
783
|
}
|
|
789
784
|
scenarioStepTemplate = getPropertyValue(firstArg, 'template') as
|
|
790
|
-
|
|
|
791
|
-
| undefined
|
|
785
|
+
string | undefined
|
|
792
786
|
scopes = getArrayPropertyValue(firstArg, 'scopes') ?? undefined
|
|
793
787
|
|
|
794
788
|
// Extract approvalDescription identifier reference
|
|
@@ -832,13 +826,11 @@ export const addFunctions: AddWiring = (
|
|
|
832
826
|
) {
|
|
833
827
|
const nodeObj = prop.initializer
|
|
834
828
|
nodeDisplayName = getPropertyValue(nodeObj, 'displayName') as
|
|
835
|
-
|
|
|
836
|
-
| null
|
|
829
|
+
string | null
|
|
837
830
|
nodeCategory = getPropertyValue(nodeObj, 'category') as string | null
|
|
838
831
|
nodeType = getPropertyValue(nodeObj, 'type') as NodeType | null
|
|
839
832
|
nodeErrorOutput = getPropertyValue(nodeObj, 'errorOutput') as
|
|
840
|
-
|
|
|
841
|
-
| null
|
|
833
|
+
boolean | null
|
|
842
834
|
|
|
843
835
|
if (!nodeDisplayName) {
|
|
844
836
|
logger.critical(
|
|
@@ -1381,6 +1373,7 @@ export const addFunctions: AddWiring = (
|
|
|
1381
1373
|
workflowRetries: workflowRetries ?? undefined,
|
|
1382
1374
|
workflowTimeout: workflowTimeout ?? undefined,
|
|
1383
1375
|
scenarioStepSurfaces,
|
|
1376
|
+
scenarioStepRequiresActor,
|
|
1384
1377
|
// `persona` is the default reading of an unmarked step, so it is left off
|
|
1385
1378
|
// rather than written into every step meta a project already has.
|
|
1386
1379
|
scenarioStepKind:
|
package/src/add/add-gateway.ts
CHANGED
|
@@ -13,7 +13,7 @@ import { ensureInlineWiringFunction } from '../utils/ensure-function-metadata.js
|
|
|
13
13
|
import { resolveMiddleware } from '../utils/middleware.js'
|
|
14
14
|
import { extractWireNames } from '../utils/post-process.js'
|
|
15
15
|
import { resolveAddonName } from '../utils/resolve-addon-package.js'
|
|
16
|
-
import type { GatewayTransportType } from '@pikku/core/
|
|
16
|
+
import type { GatewayTransportType } from '@pikku/core/gateway'
|
|
17
17
|
|
|
18
18
|
import { ErrorCode } from '../error-codes.js'
|
|
19
19
|
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { strict as assert } from 'assert'
|
|
2
|
+
import { describe, test, before, after } from 'node:test'
|
|
3
|
+
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'fs'
|
|
4
|
+
import { tmpdir } from 'os'
|
|
5
|
+
import { join } from 'path'
|
|
6
|
+
import { inspect } from '../inspector.js'
|
|
7
|
+
import { filterInspectorState } from '../utils/filter-inspector-state.js'
|
|
8
|
+
import type { InspectorLogger, InspectorState } from '../types.js'
|
|
9
|
+
|
|
10
|
+
const ADDON_PACKAGE = '@addon/greeter'
|
|
11
|
+
const NAMESPACE = 'ext'
|
|
12
|
+
const TARGET = `${NAMESPACE}:greet`
|
|
13
|
+
|
|
14
|
+
const logger: InspectorLogger = {
|
|
15
|
+
debug: () => {},
|
|
16
|
+
info: () => {},
|
|
17
|
+
warn: () => {},
|
|
18
|
+
error: () => {},
|
|
19
|
+
diagnostic: () => {},
|
|
20
|
+
critical: () => {},
|
|
21
|
+
hasCriticalErrors: () => false,
|
|
22
|
+
} as unknown as InspectorLogger
|
|
23
|
+
|
|
24
|
+
const writeAddonFixture = (rootDir: string) => {
|
|
25
|
+
writeFileSync(
|
|
26
|
+
join(rootDir, 'package.json'),
|
|
27
|
+
JSON.stringify({ name: 'consumer' })
|
|
28
|
+
)
|
|
29
|
+
const addonDir = join(rootDir, 'node_modules', ADDON_PACKAGE)
|
|
30
|
+
const pikku = join(addonDir, '.pikku')
|
|
31
|
+
mkdirSync(join(pikku, 'function'), { recursive: true })
|
|
32
|
+
mkdirSync(join(pikku, 'schemas', 'schemas'), { recursive: true })
|
|
33
|
+
writeFileSync(
|
|
34
|
+
join(addonDir, 'package.json'),
|
|
35
|
+
JSON.stringify({ name: ADDON_PACKAGE })
|
|
36
|
+
)
|
|
37
|
+
writeFileSync(
|
|
38
|
+
join(pikku, 'function', 'pikku-functions-meta.gen.json'),
|
|
39
|
+
JSON.stringify({
|
|
40
|
+
greet: {
|
|
41
|
+
pikkuFuncId: 'greet',
|
|
42
|
+
name: 'greet',
|
|
43
|
+
sessionless: true,
|
|
44
|
+
inputSchemaName: 'GreetInput',
|
|
45
|
+
outputSchemaName: 'GreetOutput',
|
|
46
|
+
inputs: ['GreetInput'],
|
|
47
|
+
outputs: ['GreetOutput'],
|
|
48
|
+
services: { optimized: true, services: ['logger'] },
|
|
49
|
+
},
|
|
50
|
+
})
|
|
51
|
+
)
|
|
52
|
+
writeFileSync(
|
|
53
|
+
join(pikku, 'schemas', 'schemas', 'GreetInput.schema.json'),
|
|
54
|
+
JSON.stringify({
|
|
55
|
+
type: 'object',
|
|
56
|
+
properties: {
|
|
57
|
+
id: { type: 'string' },
|
|
58
|
+
loud: { type: 'boolean' },
|
|
59
|
+
name: { type: 'string' },
|
|
60
|
+
},
|
|
61
|
+
})
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const writeWiring = (rootDir: string): string => {
|
|
66
|
+
const file = join(rootDir, 'greet.wiring.ts')
|
|
67
|
+
writeFileSync(
|
|
68
|
+
file,
|
|
69
|
+
[
|
|
70
|
+
`import { ref, wireAddon } from '@pikku/core'`,
|
|
71
|
+
`import { wireHTTP } from '@pikku/core/http'`,
|
|
72
|
+
``,
|
|
73
|
+
`wireAddon({ name: '${NAMESPACE}', package: '${ADDON_PACKAGE}' })`,
|
|
74
|
+
``,
|
|
75
|
+
`wireHTTP({`,
|
|
76
|
+
` auth: false,`,
|
|
77
|
+
` method: 'get',`,
|
|
78
|
+
` route: '/greet/:id',`,
|
|
79
|
+
` func: ref('${TARGET}'),`,
|
|
80
|
+
`})`,
|
|
81
|
+
``,
|
|
82
|
+
`wireHTTP({`,
|
|
83
|
+
` auth: false,`,
|
|
84
|
+
` method: 'post',`,
|
|
85
|
+
` route: '/greet',`,
|
|
86
|
+
` query: ['loud'],`,
|
|
87
|
+
` func: ref('${TARGET}'),`,
|
|
88
|
+
`})`,
|
|
89
|
+
].join('\n')
|
|
90
|
+
)
|
|
91
|
+
return file
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
describe('an HTTP route wired with ref() to an addon function', () => {
|
|
95
|
+
let rootDir: string
|
|
96
|
+
let state: InspectorState
|
|
97
|
+
|
|
98
|
+
before(async () => {
|
|
99
|
+
rootDir = mkdtempSync(join(tmpdir(), 'pikku-http-ref-'))
|
|
100
|
+
writeAddonFixture(rootDir)
|
|
101
|
+
const file = writeWiring(rootDir)
|
|
102
|
+
state = await inspect(logger, [file], { rootDir })
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
after(() => {
|
|
106
|
+
rmSync(rootDir, { recursive: true, force: true })
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
test('records the ref target as the route function id', () => {
|
|
110
|
+
assert.equal(state.http.meta.get['/greet/:id']?.pikkuFuncId, TARGET)
|
|
111
|
+
assert.equal(state.http.meta.post['/greet']?.pikkuFuncId, TARGET)
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
// packageName names the package a wire runs in — its middleware, its
|
|
115
|
+
// services, its scoped credentials. The route belongs to the consuming app
|
|
116
|
+
// however its function is reached, so tagging it with the addon's package
|
|
117
|
+
// would silently swap the app's global middleware for the addon's.
|
|
118
|
+
test("leaves the route in the consuming app's package", () => {
|
|
119
|
+
assert.equal(state.http.meta.get['/greet/:id']?.packageName, undefined)
|
|
120
|
+
assert.equal(state.http.meta.post['/greet']?.packageName, undefined)
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
test('mints no wrapper function metadata for the route', () => {
|
|
124
|
+
const wrapperIds = Object.keys(state.functions.meta).filter((id) =>
|
|
125
|
+
id.startsWith('http:')
|
|
126
|
+
)
|
|
127
|
+
assert.deepEqual(wrapperIds, [])
|
|
128
|
+
assert.equal(state.functions.meta[TARGET], undefined)
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
test('keeps each route its own params and query', () => {
|
|
132
|
+
assert.deepEqual(state.http.meta.get['/greet/:id']?.params, ['id'])
|
|
133
|
+
assert.equal(state.http.meta.get['/greet/:id']?.query, undefined)
|
|
134
|
+
assert.equal(state.http.meta.post['/greet']?.params, undefined)
|
|
135
|
+
assert.deepEqual(state.http.meta.post['/greet']?.query, ['loud'])
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
test('marks the addon target as used so tree-shaking keeps it', () => {
|
|
139
|
+
assert.ok(state.serviceAggregation.usedFunctions.has(TARGET))
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
test('survives a filtered bundle together with its addon declaration', () => {
|
|
143
|
+
const filtered = filterInspectorState(state, { names: [TARGET] }, logger)
|
|
144
|
+
assert.equal(filtered.http.meta.get['/greet/:id']?.pikkuFuncId, TARGET)
|
|
145
|
+
assert.equal(filtered.http.meta.post['/greet']?.pikkuFuncId, TARGET)
|
|
146
|
+
assert.ok(filtered.rpc.wireAddonDeclarations.has(NAMESPACE))
|
|
147
|
+
assert.ok(filtered.serviceAggregation.usedFunctions.has(TARGET))
|
|
148
|
+
})
|
|
149
|
+
})
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
getCommonWireMetaData,
|
|
5
5
|
} from '../utils/get-property-value.js'
|
|
6
6
|
import { pathToRegexp } from 'path-to-regexp'
|
|
7
|
-
import type { HTTPMethod } from '@pikku/core/
|
|
7
|
+
import type { HTTPMethod } from '@pikku/core/http'
|
|
8
8
|
import {
|
|
9
9
|
extractFunctionName,
|
|
10
10
|
makeContextBasedId,
|
|
@@ -243,6 +243,17 @@ export function registerHTTPRoute({
|
|
|
243
243
|
)
|
|
244
244
|
: null
|
|
245
245
|
|
|
246
|
+
// A ref()-wired route points straight at the addon function, the same way the
|
|
247
|
+
// CLI and channel adders record one. The addon's own metadata is the contract
|
|
248
|
+
// — and it is authoritative where the checker is not: `ref('ns:fn')` only
|
|
249
|
+
// types precisely once the consumer's own rpc map already lists the addon's
|
|
250
|
+
// functions, so a cold run inferred the whole `FlattenedRPCMap` and a warm one
|
|
251
|
+
// inferred the real input. The addon's metadata says which it is either way.
|
|
252
|
+
//
|
|
253
|
+
// The route stays the consuming app's all the same, so it records no
|
|
254
|
+
// packageName: that names the package a wire *runs in*, and running this one
|
|
255
|
+
// inside the addon would hand it the addon's middleware and services instead
|
|
256
|
+
// of the app's. `ref` reaches the addon over RPC, which enters it properly.
|
|
246
257
|
if (refAddonTarget) {
|
|
247
258
|
const targetMeta = resolveFunctionMeta(state, refAddonTarget)
|
|
248
259
|
if (!targetMeta) {
|
|
@@ -251,25 +262,16 @@ export function registerHTTPRoute({
|
|
|
251
262
|
)
|
|
252
263
|
return
|
|
253
264
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
// Propagate sessionless from the addon target so the runtime can correctly
|
|
266
|
-
// determine whether a session is required for ref()-based routes.
|
|
267
|
-
if (refAddonTarget) {
|
|
268
|
-
const targetMeta = resolveFunctionMeta(state, refAddonTarget)
|
|
269
|
-
const inlineMeta = state.functions.meta[funcName]
|
|
270
|
-
if (targetMeta && inlineMeta && targetMeta.sessionless !== undefined) {
|
|
271
|
-
inlineMeta.sessionless = targetMeta.sessionless
|
|
272
|
-
}
|
|
265
|
+
funcName = refAddonTarget
|
|
266
|
+
} else {
|
|
267
|
+
ensureFunctionMetadata(
|
|
268
|
+
state,
|
|
269
|
+
funcName,
|
|
270
|
+
fullRoute,
|
|
271
|
+
funcInitializer,
|
|
272
|
+
checker,
|
|
273
|
+
extracted.isHelper
|
|
274
|
+
)
|
|
273
275
|
}
|
|
274
276
|
|
|
275
277
|
// The route's own `auth`, falling back to the group's. Recorded on the meta
|
|
@@ -322,13 +324,12 @@ export function registerHTTPRoute({
|
|
|
322
324
|
const input = fnMeta.inputs?.[0] || null
|
|
323
325
|
|
|
324
326
|
const getRouteInputKeys = (): string[] | null => {
|
|
325
|
-
const
|
|
326
|
-
const inputTypes = state.typesLookup.get(targetFuncName)
|
|
327
|
+
const inputTypes = state.typesLookup.get(funcName)
|
|
327
328
|
if (inputTypes && inputTypes.length > 0) {
|
|
328
329
|
return extractTypeKeys(inputTypes[0])
|
|
329
330
|
}
|
|
330
331
|
|
|
331
|
-
const targetMeta = resolveFunctionMeta(state,
|
|
332
|
+
const targetMeta = resolveFunctionMeta(state, funcName)
|
|
332
333
|
if (targetMeta?.inputSchemaName) {
|
|
333
334
|
const schema = state.schemas[targetMeta.inputSchemaName] as any
|
|
334
335
|
const properties = schema?.properties
|
|
@@ -354,7 +355,7 @@ export function registerHTTPRoute({
|
|
|
354
355
|
logger.critical(
|
|
355
356
|
ErrorCode.ROUTE_PARAM_MISMATCH,
|
|
356
357
|
`Route '${fullRoute}' has path parameter(s) [${missingParams.join(', ')}] ` +
|
|
357
|
-
`not found in function '${
|
|
358
|
+
`not found in function '${funcName}' input type. ` +
|
|
358
359
|
`Input type has: [${inputKeys.join(', ')}]`
|
|
359
360
|
)
|
|
360
361
|
return
|
|
@@ -368,7 +369,7 @@ export function registerHTTPRoute({
|
|
|
368
369
|
logger.critical(
|
|
369
370
|
ErrorCode.ROUTE_QUERY_MISMATCH,
|
|
370
371
|
`Route '${fullRoute}' has query parameter(s) [${missingQuery.join(', ')}] ` +
|
|
371
|
-
`not found in function '${
|
|
372
|
+
`not found in function '${funcName}' input type. ` +
|
|
372
373
|
`Input type has: [${inputKeys.join(', ')}]`
|
|
373
374
|
)
|
|
374
375
|
return
|
|
@@ -393,9 +394,6 @@ export function registerHTTPRoute({
|
|
|
393
394
|
const permissions = resolvePermissions(state, obj, tags, checker)
|
|
394
395
|
|
|
395
396
|
state.serviceAggregation.usedFunctions.add(funcName)
|
|
396
|
-
if (refAddonTarget) {
|
|
397
|
-
state.serviceAggregation.usedFunctions.add(refAddonTarget)
|
|
398
|
-
}
|
|
399
397
|
extractWireNames(middleware).forEach((name) =>
|
|
400
398
|
state.serviceAggregation.usedMiddleware.add(name)
|
|
401
399
|
)
|
|
@@ -420,7 +418,6 @@ export function registerHTTPRoute({
|
|
|
420
418
|
state.http.files.add(sourceFile.fileName)
|
|
421
419
|
state.http.meta[method][fullRoute] = {
|
|
422
420
|
pikkuFuncId: funcName,
|
|
423
|
-
...(refAddonTarget && { refTarget: refAddonTarget }),
|
|
424
421
|
...(packageName && { packageName }),
|
|
425
422
|
route: fullRoute,
|
|
426
423
|
sourceFile: sourceFile.fileName,
|