Build inspector workflows with the new pkg/inspectorapi facade and cleanly separate UI adapters from analysis/runtime logic.
pkg/inspectorapi is the user-facing facade for inspector workflows. It wraps reusable analysis, runtime, navigation, and tree helpers into task-oriented methods so adapters can stay thin and deterministic.
The hybrid layer exists to stop UI models from owning business orchestration. Before pkg/inspectorapi, command models were directly composing pkg/jsparse, pkg/inspector/analysis, and pkg/inspector/runtime calls. That worked, but the same workflow logic was duplicated in adapter code and was difficult to reuse for REST or LSP endpoints.
The facade solves this by centralizing use cases:
This keeps adapter responsibilities focused on interaction and rendering.
The package is intentionally small and explicit. contracts.go defines user-facing request/response DTOs and service errors. service.go implements use cases over the existing extracted packages.
Import path:
import "github.com/go-go-golems/go-go-goja/pkg/inspectorapi"
Core constructor:
svc := inspectorapi.NewService()
The service stores document sessions in-memory and exposes methods keyed by DocumentID.
Most integrations follow a simple lifecycle: open a document, run static/rich queries, optionally merge runtime data, then close the document session. This keeps stateful context explicit and makes adapter behavior testable.
resp, err := svc.OpenDocument(inspectorapi.OpenDocumentRequest{
Filename: "sample.js",
Source: source,
})
if err != nil {
return err
}
docID := resp.DocumentID
if len(resp.Diagnostics) > 0 {
// parse diagnostics are still available even if analysis exists
}
globalsResp, err := svc.ListGlobals(inspectorapi.ListGlobalsRequest{DocumentID: docID})
if err != nil {
return err
}
for _, g := range globalsResp.Globals {
fmt.Printf("%s (%s)\n", g.Name, g.Kind.String())
}
membersResp, err := svc.ListMembers(inspectorapi.ListMembersRequest{
DocumentID: docID,
GlobalName: "Foo",
}, nil)
if err != nil {
return err
}
bindingDecl, err := svc.BindingDeclarationLine(inspectorapi.BindingDeclarationRequest{
DocumentID: docID,
Name: "Foo",
})
if err != nil {
return err
}
if bindingDecl.Found {
fmt.Printf("Foo declared at line %d\n", bindingDecl.Line)
}
rt := inspectorruntime.NewSession()
_ = rt.Load(source)
declared := inspectorapi.DeclaredBindingsFromSource(`const fromRepl = 1`)
merged, err := svc.MergeRuntimeGlobals(inspectorapi.MergeRuntimeGlobalsRequest{
DocumentID: docID,
Declared: declared,
}, rt)
if err != nil {
return err
}
_ = merged
_ = svc.CloseDocument(inspectorapi.CloseDocumentRequest{DocumentID: docID})
The service API is designed around use cases rather than parser internals. Each method takes a typed request struct and returns a typed response struct.
Document lifecycle:
OpenDocumentOpenDocumentFromAnalysisUpdateDocumentCloseDocumentAnalysisStatic analysis use cases:
ListGlobalsListMembersBindingDeclarationLineMemberDeclarationLineRuntime-aware helpers:
DeclaredBindingsFromSource (package-level helper)MergeRuntimeGlobalsNavigation/tree wrappers:
BuildTreeRowsSyncSourceToTreeSyncTreeToSourceA reliable integration depends on understanding coordinate and state expectations. The facade follows existing inspector conventions and keeps them explicit in request/response contracts.
SyncSourceToTreeRequest.CursorLine and CursorCol are 0-based.BindingDeclarationLine and MemberDeclarationLine return 1-based source lines.Index.VisibleNodes() ordering.SyncSourceToTree may expand index ancestors (ExpandTo) to maintain visibility semantics.When building adapters, avoid ad hoc conversion logic in key handlers. Keep conversions in one boundary function.
The preferred adapter design is: state model calls service methods, then maps DTOs to view state. The model should not re-implement analysis orchestration.
Recommended split:
This is the same pattern used in the current cmd/smalltalk-inspector cutover.
pkg/inspectorapi has focused service tests in pkg/inspectorapi/service_test.go. These validate end-to-end behavior for the new facade, including runtime merge and sync wrappers.
For changes to this package, run:
go test ./pkg/inspectorapi/... -count=1
go test ./cmd/smalltalk-inspector/... -count=1
go test ./... -count=1
If you change contracts, update both service tests and adapter tests in the same commit.
| Problem | Cause | Solution |
|---|---|---|
ErrDocumentNotFound from service calls | Document was never opened or already closed | Store returned DocumentID from OpenDocument and close only when done |
ListMembers returns empty on value globals | Runtime session not provided for runtime-derived members | Pass *runtime.Session to ListMembers |
Jump methods return Found=false | Name is missing, unresolved, or runtime-only | Verify global/member name comes from ListGlobals/ListMembers output |
| Source/tree sync not found | Cursor line/column out of range or no node at cursor | Clamp cursor in adapter and retry with current source state |
| Runtime merge misses REPL symbols | Declarations not extracted from snippet or runtime value undefined | Use inspectorapi.DeclaredBindingsFromSource and ensure symbol exists in runtime |
If you are migrating an older adapter, remove direct usage of inspector/analysis.Session orchestration from the UI model and replace it with service calls. Do not add temporary compatibility wrappers unless explicitly required by a ticket.
Clean-cutover checklist:
inspectorapi.Service to model state.inspectorapi.DeclaredBindingsFromSource.MergeRuntimeGlobals.glaze help jsparse-framework-referenceglaze help inspector-example-user-guidegoja-repl help repl-usageglaze help creating-modules