diff --git a/actions-languageserver/src/index.ts b/actions-languageserver/src/index.ts index bea1564..8523f5e 100644 --- a/actions-languageserver/src/index.ts +++ b/actions-languageserver/src/index.ts @@ -18,6 +18,7 @@ import { RepositoryContext, } from "./initializationOptions"; import { onCompletion } from "./on-completion"; +import { TTLCache } from "./utils/cache"; // Create a connection for the server, using Node's IPC as a transport. // Also include all preview / proposed LSP features. @@ -28,6 +29,7 @@ const documents: TextDocuments = new TextDocuments(TextDocument); let sessionToken: string | undefined; let repos: RepositoryContext[] = []; +const cache = new TTLCache(); let hasConfigurationCapability = false; let hasWorkspaceFolderCapability = false; @@ -108,7 +110,8 @@ connection.onCompletion( position, documents.get(textDocument.uri)!, sessionToken, - repos.find((repo) => textDocument.uri.startsWith(repo.workspaceUri)) + repos.find((repo) => textDocument.uri.startsWith(repo.workspaceUri)), + cache, ); } ); diff --git a/actions-languageserver/src/on-completion.ts b/actions-languageserver/src/on-completion.ts index 9d314cc..89e45c3 100644 --- a/actions-languageserver/src/on-completion.ts +++ b/actions-languageserver/src/on-completion.ts @@ -1,10 +1,11 @@ import { complete } from "@github/actions-languageservice/complete"; import { WorkflowContext } from "@github/actions-languageservice/context/workflow-context"; -import { ValueProviderConfig } from "@github/actions-languageservice/value-providers/config"; +import { Value, ValueProviderConfig } from "@github/actions-languageservice/value-providers/config"; import { Octokit } from "@octokit/rest"; import { CompletionItem, Position } from "vscode-languageserver"; import { TextDocument } from "vscode-languageserver-textdocument"; import { RepositoryContext } from "./initializationOptions"; +import { TTLCache } from "./utils/cache"; import { getEnvironments } from "./value-providers/job-environment"; import { getRunnerLabels } from "./value-providers/runs-on"; @@ -12,25 +13,38 @@ export async function onCompletion( position: Position, document: TextDocument, sessionToken: string | undefined, - repoContext: RepositoryContext | undefined + repoContext: RepositoryContext | undefined, + cache: TTLCache, ): Promise { const config: ValueProviderConfig = { getCustomValues: async (key: string, context: WorkflowContext) => - getCustomValues(key, context, sessionToken, repoContext), + getCustomValuesWithCache(key, context, sessionToken, repoContext, cache), }; return await complete(document, position, config); } -async function getCustomValues( +async function getCustomValuesWithCache( key: string, context: WorkflowContext, sessionToken: string | undefined, repo: RepositoryContext | undefined, -) { + cache: TTLCache, + ): Promise { if (!sessionToken || !repo) { return; } + const cacheKey = `${repo.owner}/${repo.name}/${key}`; + return cache.get(cacheKey, undefined, async () => await getCustomValues(key, context, sessionToken, repo)); +} + + +async function getCustomValues( + key: string, + _: WorkflowContext, + sessionToken: string, + repo: RepositoryContext, +): Promise { const octokit = new Octokit({ auth: sessionToken, }); diff --git a/actions-languageserver/src/utils/cache.ts b/actions-languageserver/src/utils/cache.ts new file mode 100644 index 0000000..7bd791a --- /dev/null +++ b/actions-languageserver/src/utils/cache.ts @@ -0,0 +1,47 @@ +// From https://github.com/cschleiden/github-actions-parser/blob/a81dec9b7462dbcff08fbad0792f5ad549d9de7d/src/lib/workflowschema/workflowSchema.ts +interface CacheEntry { + cachedAt: number; + content: T; +} + +export class TTLCache { + private cache = new Map>(); + + constructor(private defaultTTLinMS: number = 10 * 60 * 1000) {} + + /** + * + * @param key Key to cache value under + * @param ttlInMS How long is the content valid. If optional, default value will be used + * @param getter Function to retrieve content if not in cache + */ + async get( + key: string, + ttlInMS: number | undefined, + getter: () => Promise + ): Promise { + const hasEntry = this.cache.has(key); + const e = hasEntry && this.cache.get(key); + if ( + hasEntry && + e && + e.cachedAt > Date.now() - (ttlInMS || this.defaultTTLinMS) + ) { + return e.content as T; + } + + try { + const content = await getter(); + + this.cache.set(key, { + cachedAt: Date.now(), + content, + }); + + return content; + } catch (e) { + this.cache.delete(key); + throw e; + } + } +} \ No newline at end of file