Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/thirty-bees-divide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ensnode/ensnode-sdk": minor
---

Added `peek` method to `SWRCache` class.
5 changes: 2 additions & 3 deletions apps/ensapi/src/cache/referral-edition-snapshots.cache.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import config from "@/config";

import {
hasEnsAnalyticsConfigSupport,
hasEnsAnalyticsIndexingStatusSupport,
Expand All @@ -13,6 +11,7 @@ import { minutesToSeconds } from "date-fns";

import { type CachedResult, getLatestIndexedBlockRef, SWRCache } from "@ensnode/ensnode-sdk";

import di from "@/di";
import { assumeReferralProgramEditionImmutablyClosed } from "@/lib/ensanalytics/referrer-leaderboard/closeout";
import { getReferralEditionSnapshot } from "@/lib/ensanalytics/referrer-leaderboard/get-referral-edition-snapshot";
import { makeLogger } from "@/lib/logger";
Expand Down Expand Up @@ -73,7 +72,7 @@ function createEditionSnapshotBuilder(
// the cache could capture a snapshot derived from a not-yet-final indexer state, or one with
// silently dropped rows because a required namespace plugin is inactive, and serve it for the
// rest of its (effectively infinite, for closed editions) TTL.
const configSupport = hasEnsAnalyticsConfigSupport(config.ensIndexerPublicConfig);
const configSupport = hasEnsAnalyticsConfigSupport(di.context.stackInfo.ensIndexer);
if (!configSupport.supported) {
throw new Error(
`Unable to generate edition snapshot for ${editionSlug}. ${configSupport.reason}`,
Expand Down
16 changes: 10 additions & 6 deletions apps/ensapi/src/cache/referral-program-edition-set.cache.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import config from "@/config";

import {
buildReferralProgramEditionConfigSet,
ENSReferralsClient,
Expand Down Expand Up @@ -32,20 +30,26 @@ function partiallyRedactUrl(url: URL): string {
async function loadReferralProgramEditionConfigSet(
_cachedResult?: CachedResult<ReferralProgramEditionConfigSet>,
): Promise<ReferralProgramEditionConfigSet> {
// 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 (!config.referralProgramEditionConfigSetUrl) {
if (!referralProgramEditionConfigSetUrl) {
logger.info(
"REFERRAL_PROGRAM_EDITIONS is not set; referral program edition config set is empty",
);
return buildReferralProgramEditionConfigSet([]);
}

const logSafeUrl = partiallyRedactUrl(config.referralProgramEditionConfigSetUrl);
const logSafeUrl = partiallyRedactUrl(referralProgramEditionConfigSetUrl);

logger.info(`Loading referral program edition config set from: ${logSafeUrl}`);
try {
const editionConfigSet = await ENSReferralsClient.getReferralProgramEditionConfigSet(
config.referralProgramEditionConfigSetUrl,
referralProgramEditionConfigSetUrl,
);

// Strip any unrecognized editions immediately — they are client-side forward-compatibility
Expand All @@ -72,7 +76,7 @@ async function loadReferralProgramEditionConfigSet(
}
}

type ReferralProgramEditionConfigSetCache = SWRCache<ReferralProgramEditionConfigSet>;
export type ReferralProgramEditionConfigSetCache = SWRCache<ReferralProgramEditionConfigSet>;

/**
* SWR Cache for the referral program edition config set.
Expand Down
9 changes: 6 additions & 3 deletions apps/ensapi/src/cache/stack-info.cache.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import config from "@/config";

import { minutesToSeconds } from "date-fns";

import { EnsNodeMetadataKeys } from "@ensnode/ensdb-sdk";
Expand Down Expand Up @@ -56,9 +54,14 @@ export const stackInfoCache = lazyProxy<EnsNodeStackInfoCache>(
throw new Error("Indexing Metadata Context was uninitialized in ENSDb.");
}

// 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 ensIndexerStackInfo = indexingMetadataContext.stackInfo;
const ensNodeStackInfo = buildEnsNodeStackInfo(
buildEnsApiPublicConfig(config, ensIndexerStackInfo.ensIndexer),
buildEnsApiPublicConfig(di.context.ensApiConfig, ensIndexerStackInfo.ensIndexer),
ensIndexerStackInfo.ensDb,
ensIndexerStackInfo.ensIndexer,
ensIndexerStackInfo.ensRainbow,
Expand Down
102 changes: 33 additions & 69 deletions apps/ensapi/src/config/config.schema.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

import type { RpcConfig } from "@ensnode/ensnode-sdk/internal";

import { ensApiVersionInfo } from "@/lib/version-info";

vi.mock("@/lib/ensdb/singleton", () => ({
Expand All @@ -17,12 +15,14 @@ vi.mock("@/config/ensdb-config", () => ({
},
}));

import { buildConfigFromEnvironment, buildEnsApiPublicConfig } from "@/config/config.schema";
import { ENSNamespaceIds } from "@ensnode/ensnode-sdk";

import {
BASE_ENV,
indexingMetadataContextInitialized,
VALID_RPC_URL,
} from "@/config/config.schema.mock";
buildConfigFromEnvironment,
buildEnsApiPublicConfig,
buildRootChainRpcConfig,
} from "@/config/config.schema";
import { BASE_ENV, indexingMetadataContextInitialized } from "@/config/config.schema.mock";
import { ENSApi_DEFAULT_PORT } from "@/config/defaults";
import logger from "@/lib/logger";

Expand All @@ -42,29 +42,14 @@ describe("buildConfigFromEnvironment", () => {
it("returns a valid config object using environment variables", async () => {
const exitSpy = mockProcessExit();

const { ensIndexer: ensIndexerPublicConfig } = indexingMetadataContextInitialized.stackInfo;
const config = await buildConfigFromEnvironment(BASE_ENV);
const config = buildConfigFromEnvironment(BASE_ENV);

expect(exitSpy).not.toHaveBeenCalled();
exitSpy.mockRestore();

expect(config).toStrictEqual({
port: ENSApi_DEFAULT_PORT,
ensDbUrl: BASE_ENV.ENSDB_URL,
ensIndexerSchemaName: BASE_ENV.ENSINDEXER_SCHEMA_NAME,
theGraphApiKey: undefined,

ensIndexerPublicConfig,
namespace: ensIndexerPublicConfig.namespace,
rpcConfigs: new Map([
[
1,
{
httpRPCs: [new URL(BASE_ENV.RPC_URL_1)],
websocketRPC: undefined,
} satisfies RpcConfig,
],
]),
referralProgramEditionConfigSetUrl: undefined,
});
});
Expand All @@ -73,7 +58,7 @@ describe("buildConfigFromEnvironment", () => {
const exitSpy = mockProcessExit();
const editionsUrl = "https://example.com/editions.json";

const config = await buildConfigFromEnvironment({
const config = buildConfigFromEnvironment({
...BASE_ENV,
REFERRAL_PROGRAM_EDITIONS: editionsUrl,
});
Expand All @@ -87,7 +72,7 @@ describe("buildConfigFromEnvironment", () => {
it("includes theGraphApiKey when provided", async () => {
const exitSpy = mockProcessExit();

const config = await buildConfigFromEnvironment({
const config = buildConfigFromEnvironment({
...BASE_ENV,
THEGRAPH_API_KEY: "my-api-key",
});
Expand All @@ -113,12 +98,12 @@ describe("buildConfigFromEnvironment", () => {
it("logs error and exits when REFERRAL_PROGRAM_EDITIONS is not a valid URL", async () => {
const testEnv = structuredClone(BASE_ENV);

await expect(
expect(() =>
buildConfigFromEnvironment({
...testEnv,
REFERRAL_PROGRAM_EDITIONS: "not-a-url",
}),
).rejects.toThrow("process.exit");
).toThrow("process.exit");

expect(logger.error).toHaveBeenCalledExactlyOnceWith(
expect.stringContaining("REFERRAL_PROGRAM_EDITIONS is not a valid URL: not-a-url"),
Expand All @@ -129,37 +114,43 @@ describe("buildConfigFromEnvironment", () => {
it("logs error message when QuickNode RPC config was partially configured (missing endpoint name)", async () => {
const testEnv = structuredClone(BASE_ENV);

await expect(
buildConfigFromEnvironment({
...testEnv,
QUICKNODE_API_KEY: "my-api-key",
}),
).rejects.toThrow("process.exit");
expect(() =>
buildRootChainRpcConfig(
{
...testEnv,
QUICKNODE_API_KEY: "my-api-key",
},
ENSNamespaceIds.Mainnet,
),
).toThrow("process.exit");

expect(logger.error).toHaveBeenCalledExactlyOnceWith(
new Error(
"Use of the QUICKNODE_API_KEY environment variable requires use of the QUICKNODE_ENDPOINT_NAME environment variable as well.",
),
"Failed to build EnsApiConfig",
"Failed to build the root chain RPC config",
);
expect(process.exit).toHaveBeenCalledExactlyOnceWith(1);
});

it("logs error message when QuickNode RPC config was partially configured (missing API key)", async () => {
const testEnv = structuredClone(BASE_ENV);

await expect(
buildConfigFromEnvironment({
...testEnv,
QUICKNODE_ENDPOINT_NAME: "my-endpoint-name",
}),
).rejects.toThrow("process.exit");
expect(() =>
buildRootChainRpcConfig(
{
...testEnv,
QUICKNODE_ENDPOINT_NAME: "my-endpoint-name",
},
ENSNamespaceIds.Mainnet,
),
).toThrow("process.exit");

expect(logger.error).toHaveBeenCalledExactlyOnceWith(
new Error(
"Use of the QUICKNODE_ENDPOINT_NAME environment variable requires use of the QUICKNODE_API_KEY environment variable as well.",
),
"Failed to build EnsApiConfig",
"Failed to build the root chain RPC config",
);
expect(process.exit).toHaveBeenCalledExactlyOnceWith(1);
});
Expand All @@ -171,19 +162,6 @@ describe("buildEnsApiPublicConfig", () => {
const { ensIndexer: ensIndexerPublicConfig } = indexingMetadataContextInitialized.stackInfo;
const ensApiConfig = {
port: ENSApi_DEFAULT_PORT,
ensDbUrl: BASE_ENV.ENSDB_URL,
ensIndexerSchemaName: BASE_ENV.ENSINDEXER_SCHEMA_NAME,
ensIndexerPublicConfig,
namespace: ensIndexerPublicConfig.namespace,
rpcConfigs: new Map([
[
1,
{
httpRPCs: [new URL(VALID_RPC_URL)],
websocketRPC: undefined,
} satisfies RpcConfig,
],
]),
referralProgramEditionConfigSetUrl: undefined,
};

Expand All @@ -203,11 +181,7 @@ describe("buildEnsApiPublicConfig", () => {
const { ensIndexer: ensIndexerPublicConfig } = indexingMetadataContextInitialized.stackInfo;
const ensApiConfig = {
port: ENSApi_DEFAULT_PORT,
ensDbUrl: BASE_ENV.ENSDB_URL,
ensIndexerSchemaName: BASE_ENV.ENSINDEXER_SCHEMA_NAME,
ensIndexerPublicConfig,
namespace: ensIndexerPublicConfig.namespace,
rpcConfigs: new Map(),
theGraphApiKey: "my-api-key",
referralProgramEditionConfigSetUrl: undefined,
};

Comment thread
tk-o marked this conversation as resolved.
Expand All @@ -225,11 +199,6 @@ describe("buildEnsApiPublicConfig", () => {

const ensApiConfig = {
port: ENSApi_DEFAULT_PORT,
ensDbUrl: BASE_ENV.ENSDB_URL,
ensIndexerSchemaName: BASE_ENV.ENSINDEXER_SCHEMA_NAME,
ensIndexerPublicConfig,
namespace: ensIndexerPublicConfig.namespace,
rpcConfigs: new Map(),
referralProgramEditionConfigSetUrl: undefined,
theGraphApiKey: "secret-api-key",
};
Expand All @@ -254,11 +223,6 @@ describe("buildEnsApiPublicConfig", () => {

const ensApiConfig = {
port: ENSApi_DEFAULT_PORT,
ensDbUrl: BASE_ENV.ENSDB_URL,
ensIndexerSchemaName: BASE_ENV.ENSINDEXER_SCHEMA_NAME,
ensIndexerPublicConfig,
namespace: ensIndexerPublicConfig.namespace,
rpcConfigs: new Map(),
referralProgramEditionConfigSetUrl: undefined,
theGraphApiKey: undefined,
};
Expand Down
Loading
Loading