Next.js website for Rocky Mountain Vending company featuring: - Product catalog with Stripe integration - Service areas and parts pages - Admin dashboard with Clerk authentication - SEO optimized pages with JSON-LD structured data Co-authored-by: Cursor <cursoragent@cursor.com>
83 lines
3.4 KiB
Text
83 lines
3.4 KiB
Text
/**
|
|
* @license
|
|
* Copyright 2018 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import log from 'lighthouse-logger';
|
|
|
|
import {ArbitraryEqualityMap} from '../lib/arbitrary-equality-map.js';
|
|
import {isUnderTest} from '../lib/lh-env.js';
|
|
|
|
/**
|
|
* Decorate computableArtifact with a caching `request()` method which will
|
|
* automatically call `computableArtifact.compute_()` under the hood.
|
|
* @template {{name: string, compute_(dependencies: unknown, context: LH.Artifacts.ComputedContext): Promise<unknown>}} C
|
|
* @template {Array<keyof LH.Util.FirstParamType<C['compute_']>>} K
|
|
* @param {C} computableArtifact
|
|
* @param {(K & ([keyof LH.Util.FirstParamType<C['compute_']>] extends [K[number]] ? unknown : never)) | null} keys
|
|
* List of properties of `dependencies` used by `compute_`; other properties are filtered out.
|
|
* Use `null` to allow all properties. Ensures that only required properties are used for caching result.
|
|
* For optional properties of `dependencies`, undefined cannot be used and if found is treated as an error.
|
|
* This is to guard against developer mistakes. For optional properties, make it nullable instead.
|
|
*/
|
|
function makeComputedArtifact(computableArtifact, keys) {
|
|
// tsc (3.1) has more difficulty with template inter-references in jsdoc, so
|
|
// give types to params and return value the long way, essentially recreating
|
|
// polymorphic-this behavior for C.
|
|
/**
|
|
* Return an automatically cached result from the computed artifact.
|
|
* @param {LH.Util.FirstParamType<C['compute_']>} dependencies
|
|
* @param {LH.Artifacts.ComputedContext} context
|
|
* @return {ReturnType<C['compute_']>}
|
|
*/
|
|
const request = (dependencies, context) => {
|
|
const computedName = computableArtifact.name;
|
|
|
|
// Guard against missing properties. Optional properties must be passed as null - if missing or
|
|
// undefined, throw an error.
|
|
for (const key of keys || []) {
|
|
if (dependencies && typeof dependencies === 'object' && dependencies[key] === undefined) {
|
|
// eslint-disable-next-line max-len
|
|
const err = new Error(`missing required key "${String(key)}" for computed artifact ${computableArtifact.name}`);
|
|
if (isUnderTest) {
|
|
throw err;
|
|
} else {
|
|
// For now, simply log in production.
|
|
log.error(`lh:computed:${computedName}`, err);
|
|
}
|
|
}
|
|
}
|
|
|
|
const pickedDependencies = keys ?
|
|
Object.fromEntries(keys.map(key => [key, dependencies[key]])) :
|
|
dependencies;
|
|
|
|
// NOTE: break immutability solely for this caching-controller function.
|
|
const computedCache = /** @type {Map<string, ArbitraryEqualityMap>} */ (context.computedCache);
|
|
|
|
const cache = computedCache.get(computedName) || new ArbitraryEqualityMap();
|
|
computedCache.set(computedName, cache);
|
|
|
|
/** @type {ReturnType<C['compute_']>|undefined} */
|
|
const computed = cache.get(pickedDependencies);
|
|
if (computed) {
|
|
return computed;
|
|
}
|
|
|
|
const status = {msg: `Computing artifact: ${computedName}`, id: `lh:computed:${computedName}`};
|
|
log.time(status, 'verbose');
|
|
|
|
const artifactPromise = /** @type {ReturnType<C['compute_']>} */
|
|
(computableArtifact.compute_(pickedDependencies, context));
|
|
cache.set(pickedDependencies, artifactPromise);
|
|
|
|
artifactPromise.then(() => log.timeEnd(status)).catch(() => log.timeEnd(status));
|
|
|
|
return artifactPromise;
|
|
};
|
|
|
|
return Object.assign(computableArtifact, {request});
|
|
}
|
|
|
|
export {makeComputedArtifact};
|