Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 | 8x 43x 43x 125x 43x 2x 1x 1x 2x 2x 31x 31x 31x 31x 31x 31x 31x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 7x 7x 7x 43x | // Centralized cache-invalidation contract for marketplace entities.
//
// One `invalidateEntity()` call per write replaces the scattered ad-hoc
// `cache.delete()` calls. This is the single source of truth for "what goes
// stale when X changes" — required to make 24h L2 TTLs safe (a missed
// invalidation at 24h = stale-for-a-day, vs 5 min before).
//
// Scope: KV-layer caches only — point deletes for detail keys + generation
// bumps for lists. The per-isolate enriched-project recompute stays in
// project-mutations.ts (it recomputes, it doesn't merely invalidate).
import {
bumpGeneration,
CACHE_KEYS,
type DualCache,
type GenerationEntity,
MARKETPLACE,
} from "./cache";
export type EntityType =
| "pro"
| "project"
| "room"
| "blog"
| "blogTaxonomy"
| "locality";
export interface InvalidateEntityOpts {
/** Entity id (pro id / room id). */
id?: string;
/** Current URL slug. */
slug?: string;
/** Previous slug after a rename. Its detail cache must also clear, else the
* old URL serves a stale 200 instead of the 301 the SSR page now issues. */
oldSlug?: string;
/** Owning pro id (project / room writes). */
proId?: string;
}
// pros/projects/rooms cards cross-reference each other (a project's pro name,
// a pro card's projectCount, a project's room covers), so a content write to
// any of them must bust all three listing namespaces — matching the prior
// behaviour where a project write did deletePattern() over the pros list.
const CONTENT_GENS: GenerationEntity[] = ["pros", "projects", "rooms"];
/**
* Invalidate every marketplace cache affected by a write to `type`.
*
* Designed to be called via `c.executionCtx.waitUntil()` — non-blocking, and
* every underlying cache op is individually non-fatal (see DualCache).
*/
export async function invalidateEntity(
cache: DualCache,
type: EntityType,
opts: InvalidateEntityOpts = {},
): Promise<void> {
const tasks: Promise<void>[] = [];
const bump = (entities: GenerationEntity[]) => {
for (const e of entities) tasks.push(bumpGeneration(cache, e));
};
switch (type) {
case "pro": {
if (opts.id) {
tasks.push(cache.delete(MARKETPLACE.proFull(opts.id)));
tasks.push(cache.delete(MARKETPLACE.proCard(opts.id)));
}
bump(CONTENT_GENS);
break;
}
case "project": {
Eif (opts.proId) {
tasks.push(cache.delete(MARKETPLACE.proProjects(opts.proId)));
tasks.push(cache.delete(MARKETPLACE.proFull(opts.proId)));
// proCard embeds portfolioCovers (derived from the pro's
// projects/rooms/media), so a project write must clear it too.
tasks.push(cache.delete(MARKETPLACE.proCard(opts.proId)));
}
tasks.push(cache.delete(CACHE_KEYS.MARKETPLACE_ROOM_CATEGORIES));
bump(CONTENT_GENS);
break;
}
case "room": {
// Room detail + card are both keyed by room id, so the id alone
// clears them — writes that only know roomId (media uploads) stay
// correct. (slug/oldSlug are ignored for rooms.)
Eif (opts.id) {
tasks.push(cache.delete(MARKETPLACE.roomDetail(opts.id)));
tasks.push(cache.delete(MARKETPLACE.roomCard(opts.id)));
}
Eif (opts.proId) {
tasks.push(cache.delete(MARKETPLACE.proProjects(opts.proId)));
tasks.push(cache.delete(MARKETPLACE.proFull(opts.proId)));
// proCard embeds portfolioCovers (derived from the pro's
// projects/rooms/media), so a room write must clear it too.
tasks.push(cache.delete(MARKETPLACE.proCard(opts.proId)));
}
tasks.push(cache.delete(CACHE_KEYS.MARKETPLACE_ROOM_CATEGORIES));
bump(CONTENT_GENS);
break;
}
case "blog": {
Eif (opts.slug)
tasks.push(cache.delete(MARKETPLACE.blogBySlug(opts.slug)));
Eif (opts.oldSlug)
tasks.push(cache.delete(MARKETPLACE.blogBySlug(opts.oldSlug)));
bump(["blogs"]);
break;
}
case "blogTaxonomy": {
tasks.push(cache.delete(MARKETPLACE.blogCategories));
tasks.push(cache.delete(MARKETPLACE.blogTags));
bump(["blogs"]);
break;
}
case "locality": {
tasks.push(cache.delete(MARKETPLACE.localitiesAll));
// City/locality names are embedded in pro/project/room cards.
bump(CONTENT_GENS);
break;
}
}
await Promise.all(tasks);
}
|