cache 1password reference probes so the stack editor stops re-billing the api limit
This commit is contained in:
3 files changed
+175
-4
No files matched your search
@@ -29,6 +29,21 @@ const cache = new Map<string, Entry>();
|
||||
// provider call. Cleared when the call settles (success or failure).
|
||||
const inflight = new Map<string, Promise<string[]>>();
|
||||
|
||||
// Same two guards for the INLINE op:// reference probe (resolveSecretReferences),
|
||||
// keyed by (providerId + the sorted ref set). Separate maps from the bulk ones so a
|
||||
// bulk selector and a set of refs never collide. This is the path that drained the
|
||||
// 1Password limit "just by opening the editor" (#1436): runProbe fires on open and
|
||||
// on every debounced edit, and each fire resolved every op:// ref again with no cache.
|
||||
const refsCache = new Map<string, Entry>();
|
||||
const refsInflight = new Map<string, Promise<string[]>>();
|
||||
|
||||
/** Stable cache key for a ref set: order-independent (sorted) so the same refs typed
|
||||
* in a different order hit one entry, and JSON-encoded so a ref value that itself
|
||||
* contains the separator can't collide two different sets onto one key. */
|
||||
function refsKey(providerId: number, refs: string[]): string {
|
||||
return `${providerId}:${JSON.stringify([...refs].sort())}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the KEY NAMES available under a provider's bulk selector, cached for
|
||||
* ~30s per (providerId, selector). Values are discarded immediately - only the
|
||||
@@ -65,3 +80,43 @@ export async function probeBulkKeysCached(
|
||||
inflight.set(cacheKey, call);
|
||||
return call;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns which of the given inline op:// (etc.) references currently RESOLVE in the
|
||||
* provider, cached ~30s per (providerId, ref set) with the same single-flight guard as
|
||||
* the bulk path. Only the resolved reference STRINGS are returned - secret values are
|
||||
* discarded immediately, never stored. This is what keeps the editor's live probe from
|
||||
* re-billing the provider's rate limit on every open and every debounced keystroke.
|
||||
* Propagates the provider's errors to the caller.
|
||||
*/
|
||||
export async function probeRefsCached(
|
||||
providerId: number,
|
||||
provider: SecretProvider,
|
||||
config: SecretProviderConfig,
|
||||
refs: string[]
|
||||
): Promise<string[]> {
|
||||
if (refs.length === 0) return [];
|
||||
|
||||
const cacheKey = refsKey(providerId, refs);
|
||||
const now = Date.now();
|
||||
const hit = refsCache.get(cacheKey);
|
||||
if (hit) {
|
||||
if (now - hit.at < TTL_MS) return hit.keys;
|
||||
refsCache.delete(cacheKey);
|
||||
}
|
||||
|
||||
const pending = refsInflight.get(cacheKey);
|
||||
if (pending) return pending;
|
||||
|
||||
const call = (async () => {
|
||||
const resolved = await provider.resolveSecretReferences(config, refs);
|
||||
const names = [...resolved.keys()]; // ref strings only; values dropped
|
||||
refsCache.set(cacheKey, { keys: names, at: Date.now() });
|
||||
return names;
|
||||
})().finally(() => {
|
||||
refsInflight.delete(cacheKey);
|
||||
});
|
||||
|
||||
refsInflight.set(cacheKey, call);
|
||||
return call;
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { getSecretProviderById } from '$lib/server/db';
|
||||
import { authorize } from '$lib/server/authorize';
|
||||
import { getProvider } from '$lib/server/secretproviders';
|
||||
import { UnsupportedOperationError, parseProviderError } from '$lib/server/secretproviders/shared';
|
||||
import { probeBulkKeysCached } from '$lib/server/secretproviders/probe-cache';
|
||||
import { probeBulkKeysCached, probeRefsCached } from '$lib/server/secretproviders/probe-cache';
|
||||
|
||||
/**
|
||||
* Live probe of a stored provider: given a bulk selector and/or inline op://
|
||||
@@ -75,8 +75,7 @@ export const POST: RequestHandler = async ({ params, cookies, request }) => {
|
||||
|
||||
if (refs.length && provider.supportsReferences) {
|
||||
try {
|
||||
const resolved = await provider.resolveSecretReferences(row.config, refs);
|
||||
resolvedRefs = [...resolved.keys()];
|
||||
resolvedRefs = await probeRefsCached(id, provider, row.config, refs);
|
||||
} catch (e) {
|
||||
if (!(e instanceof UnsupportedOperationError)) {
|
||||
return json({ ok: false, error: shortError(e) }, { status: 200 });
|
||||
|
||||
Reference in new issue
Block a user