diff --git a/apps/ensapi/src/cache/indexing-status.cache.ts b/apps/ensapi/src/cache/indexing-status.cache.ts index 4777fdaf8..23aad9684 100644 --- a/apps/ensapi/src/cache/indexing-status.cache.ts +++ b/apps/ensapi/src/cache/indexing-status.cache.ts @@ -1,23 +1,18 @@ -import { EnsNodeMetadataKeys } from "@ensnode/ensdb-sdk"; +import { type EnsDbReader, EnsNodeMetadataKeys } from "@ensnode/ensdb-sdk"; import { type CrossChainIndexingStatusSnapshot, IndexingMetadataContextStatusCodes, SWRCache, } from "@ensnode/ensnode-sdk"; -import { lazyProxy } from "@/lib/lazy"; import { makeLogger } from "@/lib/logger"; const logger = makeLogger("indexing-status.cache"); export type IndexingStatusCache = SWRCache; -// lazyProxy defers construction until first use so that this module can be -// imported without env vars being present (e.g. during OpenAPI generation). -// SWRCache with proactivelyInitialize:true starts background polling immediately -// on construction, which would trigger ensDbClient before env vars are available. /** - * Cache for {@link CrossChainIndexingStatusSnapshot}, which is loaded + * Build SWR Cache for {@link CrossChainIndexingStatusSnapshot}, which is loaded * from ENSDb on demand. The cached value is expected to be updated * very frequently, following the update frequency of * {@link IndexingMetadataContextInitialized.indexingStatus} in ENSDb. @@ -25,52 +20,43 @@ export type IndexingStatusCache = SWRCache; * proactive revalidation interval to ensure that the cached value is * as fresh as possible. */ -export const indexingStatusCache = lazyProxy( - () => - new SWRCache({ - fn: async function loadIndexingStatusSnapshot() { - // Async import `di` here to avoid circular dependency between this cache module and the DI container module. - // NOTE: It will not be required soon, as we plan to create a factory function for this cache - // that accepts the necessary dependencies as parameters, instead of importing from the DI container. - const di = await import("@/di").then((mod) => mod.default); - const { ensDbClient } = di.context; +export function buildIndexingStatusCache(ensDbClient: EnsDbReader): IndexingStatusCache { + return new SWRCache({ + fn: async function loadIndexingStatusSnapshot() { + try { + const indexingMetadataContext = await ensDbClient.getIndexingMetadataContext(); - try { - const indexingMetadataContext = await ensDbClient.getIndexingMetadataContext(); - - if ( - indexingMetadataContext.statusCode !== IndexingMetadataContextStatusCodes.Initialized - ) { - // The IndexingMetadataContext has not been initialized in ENSDb yet. - // This might happen during application startup, i.e. when ENSDb - // has not yet been populated with the IndexingMetadataContext record. - // Therefore, throw an error to trigger the subsequent catch handler. - throw new Error("Indexing Metadata Context was uninitialized in ENSDb."); - } - - // The CrossChainIndexingStatusSnapshot has been successfully loaded for caching. - // Therefore, return it so that this current invocation of `readCache` will: - // - Replace the currently cached value (if any) with this new value. - // - Return this non-null value. - return indexingMetadataContext.indexingStatus; - } catch (error) { - // IndexingMetadataContext was uninitialized in ENSDb. - // Therefore, throw an error so that this current invocation of `readCache` will: - // - Reject the newly fetched response (if any) such that it won't be cached. - // - Return the most recently cached value from prior invocations, or `null` if no prior invocation successfully cached a value. - logger.error( - error, - `Error occurred while loading Indexing Metadata Context record from ENSNode Metadata table in ENSDb. ` + - `Where clause applied: ("ensIndexerSchemaName" = "${ensDbClient.ensIndexerSchemaName}", "key" = "${EnsNodeMetadataKeys.IndexingMetadataContext}"). ` + - `The cached indexing status snapshot (if any) will not be updated.`, - ); - throw error; + if (indexingMetadataContext.statusCode !== IndexingMetadataContextStatusCodes.Initialized) { + // The IndexingMetadataContext has not been initialized in ENSDb yet. + // This might happen during application startup, i.e. when ENSDb + // has not yet been populated with the IndexingMetadataContext record. + // Therefore, throw an error to trigger the subsequent catch handler. + throw new Error("Indexing Metadata Context was uninitialized in ENSDb."); } - }, - // We need to refresh the indexing status cache very frequently. - // ENSDb won't have issues handling this frequency of queries. - ttl: 1, // 1 second - proactiveRevalidationInterval: 1, // 1 second - proactivelyInitialize: true, - }), -); + + // The CrossChainIndexingStatusSnapshot has been successfully loaded for caching. + // Therefore, return it so that this current invocation of `readCache` will: + // - Replace the currently cached value (if any) with this new value. + // - Return this non-null value. + return indexingMetadataContext.indexingStatus; + } catch (error) { + // IndexingMetadataContext was uninitialized in ENSDb. + // Therefore, throw an error so that this current invocation of `readCache` will: + // - Reject the newly fetched response (if any) such that it won't be cached. + // - Return the most recently cached value from prior invocations, or `null` if no prior invocation successfully cached a value. + logger.error( + error, + `Error occurred while loading Indexing Metadata Context record from ENSNode Metadata table in ENSDb. ` + + `Where clause applied: ("ensIndexerSchemaName" = "${ensDbClient.ensIndexerSchemaName}", "key" = "${EnsNodeMetadataKeys.IndexingMetadataContext}"). ` + + `The cached indexing status snapshot (if any) will not be updated.`, + ); + throw error; + } + }, + // We need to refresh the indexing status cache very frequently. + // ENSDb won't have issues handling this frequency of queries. + ttl: 1, // 1 second + proactiveRevalidationInterval: 1, // 1 second + proactivelyInitialize: true, + }); +} diff --git a/apps/ensapi/src/cache/referral-edition-snapshots.cache.ts b/apps/ensapi/src/cache/referral-edition-snapshots.cache.ts index f06277473..55e44806b 100644 --- a/apps/ensapi/src/cache/referral-edition-snapshots.cache.ts +++ b/apps/ensapi/src/cache/referral-edition-snapshots.cache.ts @@ -16,8 +16,6 @@ import { assumeReferralProgramEditionImmutablyClosed } from "@/lib/ensanalytics/ import { getReferralEditionSnapshot } from "@/lib/ensanalytics/referrer-leaderboard/get-referral-edition-snapshot"; import { makeLogger } from "@/lib/logger"; -import { indexingStatusCache } from "./indexing-status.cache"; - const logger = makeLogger("referral-edition-snapshots-cache"); /** @@ -79,6 +77,7 @@ function createEditionSnapshotBuilder( ); } + const { indexingStatusCache } = di.context; const indexingStatus = await indexingStatusCache.read(); if (indexingStatus instanceof Error) { logger.error( diff --git a/apps/ensapi/src/cache/referral-program-edition-set.cache.ts b/apps/ensapi/src/cache/referral-program-edition-set.cache.ts index b4d7eec1d..819f6f34b 100644 --- a/apps/ensapi/src/cache/referral-program-edition-set.cache.ts +++ b/apps/ensapi/src/cache/referral-program-edition-set.cache.ts @@ -8,7 +8,6 @@ import { minutesToSeconds } from "date-fns"; import { type CachedResult, SWRCache } from "@ensnode/ensnode-sdk"; -import { lazyProxy } from "@/lib/lazy"; import { makeLogger } from "@/lib/logger"; const logger = makeLogger("referral-program-edition-set-cache"); @@ -21,65 +20,10 @@ function partiallyRedactUrl(url: URL): string { return `${url.origin}${url.pathname}`; } -/** - * Loads the referral program edition config set from the configured URL. - * - * If no URL is configured, the referral program is treated as having zero configured editions - * and no network or ENSDb work is performed. - */ -async function loadReferralProgramEditionConfigSet( - _cachedResult?: CachedResult, -): Promise { - // Async import `di` here to avoid circular dependency between this cache module and the DI container module. - // NOTE: It will not be required soon, as we plan to create a factory function for this cache - // that accepts the necessary dependencies as parameters, instead of importing from the DI container. - const di = await import("@/di").then((mod) => mod.default); - const { referralProgramEditionConfigSetUrl } = di.context.ensApiConfig; - - // If no URL is configured, treat the referral program as having zero editions. - if (!referralProgramEditionConfigSetUrl) { - logger.info( - "REFERRAL_PROGRAM_EDITIONS is not set; referral program edition config set is empty", - ); - return buildReferralProgramEditionConfigSet([]); - } - - const logSafeUrl = partiallyRedactUrl(referralProgramEditionConfigSetUrl); - - logger.info(`Loading referral program edition config set from: ${logSafeUrl}`); - try { - const editionConfigSet = await ENSReferralsClient.getReferralProgramEditionConfigSet( - referralProgramEditionConfigSetUrl, - ); - - // Strip any unrecognized editions immediately — they are client-side forward-compatibility - // placeholders that must never enter the server's operational config set (they can't be - // serialized and would cause API handlers to crash). - for (const [slug, editionConfig] of editionConfigSet) { - if (editionConfig.rules.awardModel === ReferralProgramAwardModels.Unrecognized) { - logger.warn( - { editionSlug: slug, originalAwardModel: editionConfig.rules.originalAwardModel }, - `Skipping edition with unrecognized award model`, - ); - editionConfigSet.delete(slug); - } - } - - logger.info(`Successfully loaded ${editionConfigSet.size} referral program editions`); - return editionConfigSet; - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - logger.error(error, "Error occurred while loading referral program edition config set"); - throw new Error( - `Failed to load referral program edition config set from ${logSafeUrl}: ${errorMessage}`, - ); - } -} - export type ReferralProgramEditionConfigSetCache = SWRCache; /** - * SWR Cache for the referral program edition config set. + * Build SWR Cache for the referral program edition config set. * * Once successfully loaded, the edition config set is cached indefinitely and never revalidated. * This ensures the JSON is only fetched once during the application lifecycle. @@ -87,17 +31,57 @@ export type ReferralProgramEditionConfigSetCache = SWRCache( - () => - new SWRCache({ - fn: loadReferralProgramEditionConfigSet, - ttl: Number.POSITIVE_INFINITY, - errorTtl: minutesToSeconds(1), - proactiveRevalidationInterval: undefined, - proactivelyInitialize: true, - }), -); +export function buildReferralProgramEditionConfigSetCache( + referralProgramEditionConfigSetUrl: URL | undefined, +): ReferralProgramEditionConfigSetCache { + return new SWRCache({ + fn: async function loadReferralProgramEditionConfigSet( + _cachedResult?: CachedResult, + ): Promise { + // If no URL is configured, treat the referral program as having zero editions. + if (!referralProgramEditionConfigSetUrl) { + logger.info( + "REFERRAL_PROGRAM_EDITIONS is not set; referral program edition config set is empty", + ); + return buildReferralProgramEditionConfigSet([]); + } + + const logSafeUrl = partiallyRedactUrl(referralProgramEditionConfigSetUrl); + + logger.info(`Loading referral program edition config set from: ${logSafeUrl}`); + try { + const editionConfigSet = await ENSReferralsClient.getReferralProgramEditionConfigSet( + referralProgramEditionConfigSetUrl, + ); + + // Strip any unrecognized editions immediately — they are client-side forward-compatibility + // placeholders that must never enter the server's operational config set (they can't be + // serialized and would cause API handlers to crash). + for (const [slug, editionConfig] of editionConfigSet) { + if (editionConfig.rules.awardModel === ReferralProgramAwardModels.Unrecognized) { + logger.warn( + { editionSlug: slug, originalAwardModel: editionConfig.rules.originalAwardModel }, + `Skipping edition with unrecognized award model`, + ); + editionConfigSet.delete(slug); + } + } + + logger.info(`Successfully loaded ${editionConfigSet.size} referral program editions`); + return editionConfigSet; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + logger.error(error, "Error occurred while loading referral program edition config set"); + throw new Error( + `Failed to load referral program edition config set from ${logSafeUrl}: ${errorMessage}`, + ); + } + }, + ttl: Number.POSITIVE_INFINITY, + errorTtl: minutesToSeconds(1), + proactiveRevalidationInterval: undefined, + proactivelyInitialize: true, + }); +} diff --git a/apps/ensapi/src/cache/stack-info.cache.ts b/apps/ensapi/src/cache/stack-info.cache.ts index cc1940c27..fa43d47b7 100644 --- a/apps/ensapi/src/cache/stack-info.cache.ts +++ b/apps/ensapi/src/cache/stack-info.cache.ts @@ -1,6 +1,6 @@ import { minutesToSeconds } from "date-fns"; -import { EnsNodeMetadataKeys } from "@ensnode/ensdb-sdk"; +import { type EnsDbReader, EnsNodeMetadataKeys } from "@ensnode/ensdb-sdk"; import { buildEnsNodeStackInfo, type EnsNodeStackInfo, @@ -9,18 +9,13 @@ import { SWRCache, } from "@ensnode/ensnode-sdk"; -import { buildEnsApiPublicConfig } from "@/config/config.schema"; -import { lazyProxy } from "@/lib/lazy"; +import { buildEnsApiPublicConfig, type EnsApiConfig } from "@/config/config.schema"; import logger from "@/lib/logger"; export type EnsNodeStackInfoCache = SWRCache; -// lazyProxy defers construction until first use so that this module can be -// imported without env vars being present (e.g. during OpenAPI generation). -// SWRCache with proactivelyInitialize:true starts background polling immediately -// on construction, which would trigger ensDbClient before env vars are available. /** - * Cache for {@link EnsNodeStackInfo}, which is loaded from ENSDb on demand. + * Build SWR Cache for {@link EnsNodeStackInfo}, which is loaded from ENSDb on demand. * Once successfully loaded, the {@link EnsNodeStackInfo} is cached and kept up-to-date * by proactive revalidation, since the {@link EnsNodeStackInfo} might change during * the lifecycle of the ENSApi instance, for example, when @@ -34,62 +29,56 @@ export type EnsNodeStackInfoCache = SWRCache; * - ttl: 1 minute - Allow cached value to be fresh for up to 1 minute. * - errorTtl: 1 minute - If loading fails, retry on next access after 1 minute. * - proactiveRevalidationInterval: 5 minutes - Refresh the cached value every 5 minutes. - * - proactivelyInitialize: true - Load immediately on startup + * - proactivelyInitialize: true - Load immediately when the cache is constructed */ -export const stackInfoCache = lazyProxy( - () => - new SWRCache({ - fn: async function loadEnsNodeStackInfo() { - // Async import `di` here to avoid circular dependency between this cache module and the DI container module. - // NOTE: It will not be required soon, as we plan to create a factory function for this cache - // that accepts the necessary dependencies as parameters, instead of importing from the DI container. - const di = await import("@/di").then((mod) => mod.default); - const { ensDbClient } = di.context; +export function buildStackInfoCache( + ensDbClient: EnsDbReader, + ensApiConfig: EnsApiConfig, +): EnsNodeStackInfoCache { + return new SWRCache({ + fn: async function loadEnsNodeStackInfo() { + try { + const indexingMetadataContext = await ensDbClient.getIndexingMetadataContext(); - try { - const indexingMetadataContext = await ensDbClient.getIndexingMetadataContext(); - - if ( - indexingMetadataContext.statusCode !== IndexingMetadataContextStatusCodes.Initialized - ) { - // The IndexingMetadataContext has not been initialized in ENSDb yet. - // This might happen during application startup, i.e. when ENSDb - // has not yet been populated with the IndexingMetadataContext record. - // Therefore, throw an error to trigger the subsequent catch handler. - throw new Error("Indexing Metadata Context was uninitialized in ENSDb."); - } + if (indexingMetadataContext.statusCode !== IndexingMetadataContextStatusCodes.Initialized) { + // The IndexingMetadataContext has not been initialized in ENSDb yet. + // This might happen during application startup, i.e. when ENSDb + // has not yet been populated with the IndexingMetadataContext record. + // Therefore, throw an error to trigger the subsequent catch handler. + throw new Error("Indexing Metadata Context was uninitialized in ENSDb."); + } - const ensIndexerStackInfo = indexingMetadataContext.stackInfo; - const ensNodeStackInfo = buildEnsNodeStackInfo( - buildEnsApiPublicConfig(di.context.ensApiConfig, ensIndexerStackInfo.ensIndexer), - ensIndexerStackInfo.ensDb, - ensIndexerStackInfo.ensIndexer, - ensIndexerStackInfo.ensRainbow, - ); + const ensIndexerStackInfo = indexingMetadataContext.stackInfo; + const ensNodeStackInfo = buildEnsNodeStackInfo( + buildEnsApiPublicConfig(ensApiConfig, ensIndexerStackInfo.ensIndexer), + ensIndexerStackInfo.ensDb, + ensIndexerStackInfo.ensIndexer, + ensIndexerStackInfo.ensRainbow, + ); - // The EnsNodeStackInfo has been successfully built for caching. - // Therefore, return it so that this current invocation of `readCache` will: - // - Replace the currently cached value (if any) with this new value. - // - Return this non-null value. - return ensNodeStackInfo; - } catch (error) { - // IndexingMetadataContext was uninitialized in ENSDb. - // Therefore, throw an error so that this current invocation of `readCache` will: - // - Reject the newly fetched response (if any) such that it won't be cached. - // - Return the most recently cached value from prior invocations, or `null` if no prior invocation successfully cached a value. - logger.error( - error, - `Error occurred while loading Indexing Metadata Context record from ENSNode Metadata table in ENSDb. ` + - `Where clause applied: ("ensIndexerSchemaName" = "${ensDbClient.ensIndexerSchemaName}", "key" = "${EnsNodeMetadataKeys.IndexingMetadataContext}"). ` + - `The cached EnsNodeStackInfo object (if any) will not be updated.`, - ); + // The EnsNodeStackInfo has been successfully built for caching. + // Therefore, return it so that this current invocation of `readCache` will: + // - Replace the currently cached value (if any) with this new value. + // - Return this non-null value. + return ensNodeStackInfo; + } catch (error) { + // IndexingMetadataContext was uninitialized in ENSDb. + // Therefore, throw an error so that this current invocation of `readCache` will: + // - Reject the newly fetched response (if any) such that it won't be cached. + // - Return the most recently cached value from prior invocations, or `null` if no prior invocation successfully cached a value. + logger.error( + error, + `Error occurred while loading Indexing Metadata Context record from ENSNode Metadata table in ENSDb. ` + + `Where clause applied: ("ensIndexerSchemaName" = "${ensDbClient.ensIndexerSchemaName}", "key" = "${EnsNodeMetadataKeys.IndexingMetadataContext}"). ` + + `The cached EnsNodeStackInfo object (if any) will not be updated.`, + ); - throw error; - } - }, - ttl: minutesToSeconds(1), - errorTtl: minutesToSeconds(1), - proactiveRevalidationInterval: minutesToSeconds(5), - proactivelyInitialize: true, - }), -); + throw error; + } + }, + ttl: minutesToSeconds(1), + errorTtl: minutesToSeconds(1), + proactiveRevalidationInterval: minutesToSeconds(5), + proactivelyInitialize: true, + }); +} diff --git a/apps/ensapi/src/config/config.singleton.test.ts b/apps/ensapi/src/config/config.singleton.test.ts index a513ef0b8..fa4e773af 100644 --- a/apps/ensapi/src/config/config.singleton.test.ts +++ b/apps/ensapi/src/config/config.singleton.test.ts @@ -40,29 +40,38 @@ vi.mock("@ensnode/ensdb-sdk", async (importOriginal) => { }; }); -vi.mock("@/cache/indexing-status.cache", () => ({ - indexingStatusCache: { +vi.mock("@/cache/indexing-status.cache", () => { + const indexingStatusCache = { read: vi.fn().mockResolvedValue({}), destroy: vi.fn(), - }, -})); + }; + return { + buildIndexingStatusCache: vi.fn(() => indexingStatusCache), + }; +}); -vi.mock("@/cache/stack-info.cache", () => ({ - stackInfoCache: { +vi.mock("@/cache/stack-info.cache", () => { + const stackInfoCache = { read: vi.fn().mockResolvedValue({}), destroy: vi.fn(), peek: vi.fn().mockReturnValue({ ensIndexer: { namespace: "mainnet" }, }), - }, -})); + }; + return { + buildStackInfoCache: vi.fn(() => stackInfoCache), + }; +}); -vi.mock("@/cache/referral-program-edition-set.cache", () => ({ - referralProgramEditionConfigSetCache: { +vi.mock("@/cache/referral-program-edition-set.cache", () => { + const referralProgramEditionConfigSetCache = { read: vi.fn().mockResolvedValue({}), destroy: vi.fn(), - }, -})); + }; + return { + buildReferralProgramEditionConfigSetCache: vi.fn(() => referralProgramEditionConfigSetCache), + }; +}); vi.mock("viem", async (importOriginal) => { const mod = await importOriginal(); diff --git a/apps/ensapi/src/di.ts b/apps/ensapi/src/di.ts index 596ddc0a1..bfa7f39b8 100644 --- a/apps/ensapi/src/di.ts +++ b/apps/ensapi/src/di.ts @@ -6,13 +6,12 @@ import { type EnsDbConfig, EnsDbReader } from "@ensnode/ensdb-sdk"; import type { EnsNodeStackInfo } from "@ensnode/ensnode-sdk"; import type { RpcConfig } from "@ensnode/ensnode-sdk/internal"; -import { type IndexingStatusCache, indexingStatusCache } from "@/cache/indexing-status.cache"; +import { buildIndexingStatusCache, type IndexingStatusCache } from "@/cache/indexing-status.cache"; import { + buildReferralProgramEditionConfigSetCache, type ReferralProgramEditionConfigSetCache, - referralProgramEditionConfigSetCache, } from "@/cache/referral-program-edition-set.cache"; -import type { EnsNodeStackInfoCache } from "@/cache/stack-info.cache"; -import { stackInfoCache } from "@/cache/stack-info.cache"; +import { buildStackInfoCache, type EnsNodeStackInfoCache } from "@/cache/stack-info.cache"; import type { EnsApiConfig } from "@/config/config.schema"; import { buildConfigFromEnvironment, buildRootChainRpcConfig } from "@/config/config.schema"; import { buildEnsDbConfigFromEnvironment } from "@/config/ensdb-config"; @@ -165,7 +164,7 @@ export function buildEnsApiDiContext(ensApiEnvironment: EnsApiEnvironment): EnsA get indexingStatusCache(): IndexingStatusCache { if (instances.indexingStatusCache === undefined) { - instances.indexingStatusCache = indexingStatusCache; + instances.indexingStatusCache = buildIndexingStatusCache(context.ensDbClient); } return instances.indexingStatusCache; @@ -173,7 +172,9 @@ export function buildEnsApiDiContext(ensApiEnvironment: EnsApiEnvironment): EnsA get referralProgramEditionConfigSetCache(): ReferralProgramEditionConfigSetCache { if (instances.referralProgramEditionConfigSetCache === undefined) { - instances.referralProgramEditionConfigSetCache = referralProgramEditionConfigSetCache; + instances.referralProgramEditionConfigSetCache = buildReferralProgramEditionConfigSetCache( + context.ensApiConfig.referralProgramEditionConfigSetUrl, + ); } return instances.referralProgramEditionConfigSetCache; @@ -181,7 +182,7 @@ export function buildEnsApiDiContext(ensApiEnvironment: EnsApiEnvironment): EnsA get stackInfoCache(): EnsNodeStackInfoCache { if (instances.stackInfoCache === undefined) { - instances.stackInfoCache = stackInfoCache; + instances.stackInfoCache = buildStackInfoCache(context.ensDbClient, context.ensApiConfig); } return instances.stackInfoCache; diff --git a/apps/ensapi/src/lib/lazy.ts b/apps/ensapi/src/lib/lazy.ts deleted file mode 100644 index 615d1ab1e..000000000 --- a/apps/ensapi/src/lib/lazy.ts +++ /dev/null @@ -1,47 +0,0 @@ -const UNSET = Symbol("UNSET"); - -/** - * Creates a lazy singleton — the factory is called at most once, on first invocation. - * Correctly handles factories that return `null` or `undefined`. - * - * Returns a getter function. Use this for primitive or nullable values where a Proxy - * is not suitable (e.g. `bigint | null`). For objects, prefer `lazyProxy`. - */ -export function lazy(factory: () => T): () => T { - let cached: T | typeof UNSET = UNSET; - return () => { - if (cached === UNSET) cached = factory(); - return cached as T; - }; -} - -/** - * Creates a lazy singleton object exposed as a stable Proxy reference. - * The factory is called at most once, on first property access. - * - * Unlike `lazy()`, this returns the object itself (not a getter function), so - * consumers can import and use it directly without calling a getter: - * - * ```ts - * export const myCache = lazyProxy(() => new SWRCache(...)); - * // usage: myCache.read() ← no getter call needed - * ``` - * - * Not suitable for primitives or nullable values — use `lazy()` for those. - */ -export function lazyProxy(factory: () => T): T { - const getInstance = lazy(factory); - return new Proxy({} as T, { - get(_, prop) { - const instance = getInstance(); - const value = Reflect.get(instance, prop as string, instance); - if (typeof value === "function") { - return (value as (...args: unknown[]) => unknown).bind(instance); - } - return value; - }, - has(_, prop) { - return Reflect.has(getInstance(), prop); - }, - }); -} diff --git a/apps/ensapi/src/middleware/indexing-status.middleware.ts b/apps/ensapi/src/middleware/indexing-status.middleware.ts index 90d986f1d..570bc8253 100644 --- a/apps/ensapi/src/middleware/indexing-status.middleware.ts +++ b/apps/ensapi/src/middleware/indexing-status.middleware.ts @@ -6,7 +6,7 @@ import { SWRCache, } from "@ensnode/ensnode-sdk"; -import { indexingStatusCache } from "@/cache/indexing-status.cache"; +import di from "@/di"; import { factory, producing } from "@/lib/hono-factory"; /** @@ -42,6 +42,7 @@ export type IndexingStatusMiddlewareVariables = { export const indexingStatusMiddleware = producing( ["indexingStatus"], factory.createMiddleware(async (c, next) => { + const { indexingStatusCache } = di.context; const indexingStatus = await indexingStatusCache.read(); if (indexingStatus instanceof Error) { diff --git a/apps/ensapi/src/middleware/referral-program-edition-set.middleware.ts b/apps/ensapi/src/middleware/referral-program-edition-set.middleware.ts index c3528c373..89ee20051 100644 --- a/apps/ensapi/src/middleware/referral-program-edition-set.middleware.ts +++ b/apps/ensapi/src/middleware/referral-program-edition-set.middleware.ts @@ -1,6 +1,6 @@ import type { ReferralProgramEditionConfigSet } from "@namehash/ens-referrals"; -import { referralProgramEditionConfigSetCache } from "@/cache/referral-program-edition-set.cache"; +import di from "@/di"; import { factory, producing } from "@/lib/hono-factory"; /** @@ -29,6 +29,7 @@ export type ReferralProgramEditionConfigSetMiddlewareVariables = { export const referralProgramEditionConfigSetMiddleware = producing( ["referralProgramEditionConfigSet"], factory.createMiddleware(async (c, next) => { + const { referralProgramEditionConfigSetCache } = di.context; const editionConfigSet = await referralProgramEditionConfigSetCache.read(); c.set("referralProgramEditionConfigSet", editionConfigSet); await next(); diff --git a/apps/ensapi/src/middleware/stack-info.middleware.ts b/apps/ensapi/src/middleware/stack-info.middleware.ts index 7011ff638..5f45788c3 100644 --- a/apps/ensapi/src/middleware/stack-info.middleware.ts +++ b/apps/ensapi/src/middleware/stack-info.middleware.ts @@ -1,6 +1,6 @@ import type { EnsNodeStackInfo } from "@ensnode/ensnode-sdk"; -import { stackInfoCache } from "@/cache/stack-info.cache"; +import di from "@/di"; import { factory, producing } from "@/lib/hono-factory"; import { makeLogger } from "@/lib/logger"; @@ -38,6 +38,7 @@ export interface StackInfoMiddlewareVariables { export const stackInfoMiddleware = producing( ["stackInfo"], factory.createMiddleware(async (c, next) => { + const { stackInfoCache } = di.context; const stackInfo = await stackInfoCache.read(); if (stackInfo instanceof Error) { diff --git a/apps/ensapi/src/omnigraph-api/lib/find-domains/find-domains-resolver-helpers.ts b/apps/ensapi/src/omnigraph-api/lib/find-domains/find-domains-resolver-helpers.ts index 758bdfff8..abcdb1881 100644 --- a/apps/ensapi/src/omnigraph-api/lib/find-domains/find-domains-resolver-helpers.ts +++ b/apps/ensapi/src/omnigraph-api/lib/find-domains/find-domains-resolver-helpers.ts @@ -7,10 +7,6 @@ import type { OrderDirection } from "@/omnigraph-api/schema/order-direction"; /** * The order column / expression for each `DomainsOrderBy` value. - * - * Computed lazily using sql template so importing this module doesn't access the lazyProxy-backed - * `ensIndexerSchema` at module load time (test harnesses import it without env-driven DB - * config wired up). */ function getOrderColumn(orderBy: typeof DomainsOrderBy.$inferType): SQL { const { ensIndexerSchema } = di.context;