1/** @App.Context.Aggregate — the resources every request handler shares */2modulePuduLangMcp.App.Context34importStd.ConcurrentasConcurrent5importStd.EnvasEnv6importStd.ResultasResult7importStd.SyncasSync8importPuduLangMcp.Constants.ServerasServer9importPuduLangMcp.Domain.Docs.ChapterasChapter10importPuduLangMcp.Domain.Reference.EntryasEntry11importPuduLangMcp.Errors.ToolErrorasToolError12importPuduLangMcp.Services.ReferenceasReference13importPuduLangMcp.Services.ToolchainasToolchain14importPuduLangMcp.Services.WorkspaceasWorkspace1516/** @App.Context.IndexState — progress of the background reference index */17exporttypeIndexState = Building | Ready(Array[Entry.Entry]) | Broken(Str)1819/** @App.Context.Context — toolchain, workspace, corpus, and index for handlers */20exporttypeContext = {21 toolchain: Toolchain.Toolchain,22 workspace: Workspace.Workspace,23 chapters: Array[Chapter.Chapter],24 sections: Array[Chapter.Section],25 moduleFiles: Array[Reference.ModuleFile],26 docsRevision: Str,27 index: Sync.Cell[IndexState]28}2930/// How often a request waiting for the index looks at it again.31constINDEX_POLL_MS: Int = 503233/// The context for a toolchain, workspace, and corpus, with the index building in the background.34exportfncreate(toolchain: Toolchain.Toolchain, workspace: Workspace.Workspace, chapters: Array[Chapter.Chapter], docsRevision: Str) -> Context {35let files = Reference.moduleFiles(toolchain.library, workspace.root)36let index = Sync.cell(Building)37let context = Context {38 toolchain: toolchain,39 workspace: workspace,40 chapters: chapters,41 sections: Chapter.allSections(&chapters),42 moduleFiles: files,43 docsRevision: docsRevision,44 index: index45 }46let builder = context.toolchain47let target = context.index48let started = Concurrent.start(fn() -> () {49let state = matchReference.build(&builder, &files) {50caseOk(found) => Ready(found)51caseErr(problem) => Broken(ToolError.explain(&problem))52 }53let _stored = Sync.set(&target, state)54 })55ifResult.isErr(&started) {56let _stored = Sync.set(&context.index, Broken("The reference index could not be started."))57 }58 context59}6061/// The reference index, waiting a bounded time while it is still being built.62exportfnentries(context: &Context) -> Result[Array[Entry.Entry], ToolError.ToolFailure] {63let deadline = Env.elapsedMilliseconds() + Server.INDEX_WAIT_MS64loop {65matchResult.unwrapOr(Sync.get(&context.index), Broken("The reference index could not be read.")) {66caseReady(found) => { returnOk(found) }67caseBroken(reason) => { returnErr(ToolError.Unavailable(reason)) }68caseBuilding => {69if indexDeadlineReached(Env.elapsedMilliseconds(), deadline) {70returnErr(ToolError.Unavailable("The reference index is still being built from the installed toolchain; try again shortly."))71 }72let _slept = Concurrent.sleep(INDEX_POLL_MS)73 }74 }75 }76}7778/// Whether the reference-index wait has reached its deadline.79exportfnindexDeadlineReached(now: Int, deadline: Int) -> Bool {80 now >= deadline81}82