Skip to content
Merged
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
29 changes: 21 additions & 8 deletions server.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -847,15 +847,24 @@ function normalizeSchemaFetchUrl(url) {
return u;
}

async function fetchJsonWithTimeout(url, timeoutMs) {
async function fetchJsonWithTimeout(url, timeoutMs, options = {}) {
if (typeof fetch !== "function") throw new Error("global fetch is not available (requires Node 18+)");
const u = normalizeSchemaFetchUrl(url);
const preferBuiltin = options?.preferBuiltin === true;

cachePrune(schemaJsonCache, { ttlMs: JSON_CACHE_TTL_MS, maxEntries: MAX_JSON_CACHE_ENTRIES, tsField: "fetchedAt" });

const cached = schemaJsonCache.get(u);
if (cached) return cached.schema;

if (preferBuiltin) {
const builtinSchema = getBuiltinSchema(u);
if (builtinSchema) {
schemaJsonCache.set(u, { fetchedAt: Date.now(), schema: builtinSchema });
return builtinSchema;
}
}

const ac = new AbortController();
const t = setTimeout(() => ac.abort(), timeoutMs);

Expand All @@ -882,12 +891,12 @@ async function fetchJsonWithTimeout(url, timeoutMs) {
}
}

function makeAjv() {
function makeAjv(options = {}) {
const ajv = new Ajv({
allErrors: true,
strict: false,
validateSchema: false,
loadSchema: async (uri) => await fetchJsonWithTimeout(uri, SCHEMA_FETCH_TIMEOUT_MS),
loadSchema: async (uri) => await fetchJsonWithTimeout(uri, SCHEMA_FETCH_TIMEOUT_MS, options),
});
addFormats(ajv);
return ajv;
Expand All @@ -897,7 +906,7 @@ function receiptSchemaUrlForVerb(verb) {
return `${SCHEMA_HOST}/schemas/v1.1.0/commons/${verb}/receipts/${verb}.receipt.schema.json`;
}

async function getValidatorForVerb(verb) {
async function getValidatorForVerb(verb, options = {}) {
cachePrune(validatorCache, {
ttlMs: VALIDATOR_CACHE_TTL_MS,
maxEntries: MAX_VALIDATOR_CACHE_ENTRIES,
Expand All @@ -909,7 +918,7 @@ async function getValidatorForVerb(verb) {
if (inflightValidator.has(verb)) return await inflightValidator.get(verb);

const build = (async () => {
const ajv = makeAjv();
const ajv = makeAjv(options);
const url = receiptSchemaUrlForVerb(verb);

// Preload shared refs (best effort)
Expand All @@ -919,12 +928,12 @@ async function getValidatorForVerb(verb) {
`${SCHEMA_HOST}/schemas/v1.1.0/_shared/x402.schema.json`,
`${SCHEMA_HOST}/schemas/v1.1.0/_shared/identity.schema.json`,
];
await Promise.all(shared.map((u) => fetchJsonWithTimeout(u, SCHEMA_FETCH_TIMEOUT_MS).catch(() => null)));
await Promise.all(shared.map((u) => fetchJsonWithTimeout(u, SCHEMA_FETCH_TIMEOUT_MS, options).catch(() => null)));
} catch {
// ignore
}

const schema = await fetchJsonWithTimeout(url, SCHEMA_FETCH_TIMEOUT_MS);
const schema = await fetchJsonWithTimeout(url, SCHEMA_FETCH_TIMEOUT_MS, options);
const validate = await withTimeout(ajv.compileAsync(schema), SCHEMA_VALIDATE_BUDGET_MS, "ajv_compile_budget_exceeded");

validatorCache.set(verb, { compiledAt: Date.now(), validate });
Expand Down Expand Up @@ -973,7 +982,11 @@ function startWarmWorker() {
if (hasValidatorCached(verb)) continue;

try {
await withTimeout(getValidatorForVerb(verb), PREWARM_PER_VERB_BUDGET_MS, "prewarm_per_verb_timeout");
await withTimeout(
getValidatorForVerb(verb, { preferBuiltin: true }),
PREWARM_PER_VERB_BUDGET_MS,
"prewarm_per_verb_timeout"
);
} catch (e) {
console.warn("[prewarm] verb failed", verb, e?.message || e);
}
Expand Down
Loading