feat: Wildlife Sightings layer from iNaturalist and Acartia

Recent community wildlife observations, as a new Wildlife group in the
layer panel.

- Server: keyless iNaturalist proxy (v2 observations with only the
  projected fields; undocumented v1 UTFGrid density tiles, decoded on the
  server) sharing one paced queue (burst 4, ~1/s, 60/min, 8,000/day budget,
  up to four in flight), in-memory caches and upstream-only cooldowns;
  Acartia Salish Sea marine-mammal feed rounded to 0.01 degrees with
  comments dropped.
- Layer: grid counts drawn as a soft view-ranked density glow; real sampled
  sightings as taxon badges at their true positions with ambient photo
  thumbnails and labels; obscured records stay privacy cells; a clicked
  sighting stays pinned through camera moves and refreshes.
- Anchored photo card per selection with the single iNaturalist link;
  attribution in the layer row and the Data attribution popover.
- Filters (CONFIRMED, taxa presets) round-trip in share links with token 9.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Yehe LiuandClaude Opus 5.5 committed 2026-11-05 16:17:12 -05:00
1 parent edd6212793
commit 14f88b3e08
120 files changed
+21260 -285

No files matched your search

+179
View File
@@ -0,0 +1,179 @@
import { normalizeAcartiaSightings } from '../../src/layers/wildlifeSightings/records.js';
import { coalesceProxyRequest, readResponseJsonCapped } from './common/http.js';
import { makeRateLimiter, clientKey } from './common/rate-limit.js';
import { admitSameSiteKeyless } from './common/same-site.js';
import { providerUserAgent } from './common/userAgent.js';
const API_URL = 'https://acartia.io/api/v1/sightings/current';
// All views share the provider's seven-day snapshot.
const TTL_MS = 600_000;
const STALE_MAX_MS = 24 * 60 * 60_000;
const MAX_RESPONSE_BYTES = 2 * 1024 * 1024;
const TIMEOUT_MS = 15_000;
const COOLDOWN_MS = 60_000;
const COOLDOWN_MIN_MS = 5000;
const COOLDOWN_MAX_MS = 60 * 60_000;
const FAILURE_COOLDOWN_MS = 15_000;
/** Bound upstream Retry-After seconds or dates to five seconds–one hour. */
export function acartiaRetryCooldownMs(raw, nowMs) {
const value = typeof raw === 'string' ? raw.trim() : '';
if (value) {
const seconds = /^\d+(?:\.\d+)?$/.test(value) ? Number(value) : NaN;
const deadline =
!Number.isFinite(seconds) && /[a-z]/i.test(value)
? Date.parse(value)
: NaN;
const delay = Number.isFinite(seconds) ? seconds * 1000 : deadline - nowMs;
if (Number.isFinite(delay) && delay >= 0)
return Math.min(COOLDOWN_MAX_MS, Math.max(COOLDOWN_MIN_MS, delay));
}
return COOLDOWN_MS;
}
/** Salish Sea coverage. Precise coordinates and comments never enter cache. */
export function acartiaProxy({
fetchImpl = (...args) => globalThis.fetch(...args),
now = () => Date.now(),
cache = new Map(),
warn = (...args) => console.warn(...args),
} = {}) {
const inFlight = new Map();
const allow = makeRateLimiter({
windowMs: 60_000,
max: 60,
globalMax: 600,
now,
});
let cooldownUntil = 0;
let cooldownStatus = 429;
let warnedClamp = false;
const retrySeconds = () =>
Math.max(1, Math.ceil((cooldownUntil - now()) / 1000));
const unavailable = (status = 502) =>
Object.assign(new Error('acartia_unavailable'), {
status,
retryAfterS: retrySeconds(),
});
function coolDown(status, delay) {
const until = now() + delay;
if (until > cooldownUntil) {
cooldownUntil = until;
cooldownStatus = status;
} else if (status === 429 && now() < cooldownUntil) cooldownStatus = 429;
}
async function load() {
if (now() < cooldownUntil) throw unavailable(cooldownStatus);
const signal = AbortSignal.timeout(TIMEOUT_MS);
try {
const response = await fetchImpl(API_URL, {
signal,
redirect: 'error',
headers: {
Accept: 'application/json',
'User-Agent': providerUserAgent('wildlife-sightings'),
},
});
if (!response.ok) {
await response.body?.cancel().catch(() => {});
if (response.status === 429) {
const delay = acartiaRetryCooldownMs(
response.headers.get('retry-after'),
now(),
);
if (delay === COOLDOWN_MAX_MS && !warnedClamp) {
warnedClamp = true;
warn('[acartia-proxy] upstream cooldown capped at one hour');
}
coolDown(429, delay);
throw unavailable(429);
}
throw unavailable();
}
// This endpoint documents a JSON array, not a results/data envelope.
const payload = await readResponseJsonCapped(
response,
MAX_RESPONSE_BYTES,
signal,
);
const rows = normalizeAcartiaSightings(payload, now());
if (!rows?.length) throw new Error('invalid_snapshot');
return { fetchedAt: now(), rows };
} catch (error) {
if (error.status !== 429) coolDown(502, FAILURE_COOLDOWN_MS);
warn('[acartia-proxy] upstream refresh failed');
throw unavailable(error.status === 429 ? 429 : 502);
}
}
function age(entry) {
if (!entry) return Infinity;
const elapsed = now() - entry.savedAt;
return elapsed < 0 ? Infinity : elapsed;
}
async function acquire() {
const previous = cache.get('sightings');
if (age(previous) < TTL_MS) return { value: previous.value, stale: false };
if (age(previous) > STALE_MAX_MS) cache.delete('sightings');
try {
const { promise } = coalesceProxyRequest(
inFlight,
'sightings',
async () => {
const value = await load();
cache.set('sightings', { value, savedAt: now() });
return value;
},
);
return { value: await promise, stale: false };
} catch (error) {
if (age(previous) <= STALE_MAX_MS)
return { value: previous.value, stale: true };
throw error;
}
}
async function handler(req, res) {
const json = (status, value, { stale = false, retryAfterS } = {}) => {
if (res.destroyed) return;
res.writeHead(status, {
'Content-Type': 'application/json',
'Cache-Control': 'no-store',
...(status === 405 ? { Allow: 'GET' } : {}),
...(retryAfterS ? { 'Retry-After': String(retryAfterS) } : {}),
...(stale ? { 'X-Data-Stale': 'true' } : {}),
});
res.end(JSON.stringify(value));
};
if (admitSameSiteKeyless(req, res)) return;
if (req.method !== 'GET') return json(405, { error: 'method_not_allowed' });
const pathname = (req.url || '/').split('?')[0];
if (pathname !== '/sightings') return json(404, { error: 'unknown_route' });
if (!allow(clientKey(req)))
return json(429, { error: 'rate_limited' }, { retryAfterS: 60 });
try {
const { value, stale } = await acquire();
return json(200, stale ? { ...value, stale: true } : value, { stale });
} catch (error) {
return json(
error.status === 429 ? 429 : 502,
{ error: 'acartia_unavailable' },
{ retryAfterS: error.retryAfterS },
);
}
}
return {
name: 'acartia',
configureServer({ middlewares }) {
middlewares.use('/api/acartia', handler);
},
configurePreviewServer({ middlewares }) {
middlewares.use('/api/acartia', handler);
},
};
}
+381
View File
@@ -0,0 +1,381 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { acartiaProxy, acartiaRetryCooldownMs } from './acartia.js';
const NOW = Date.parse('2026-11-04T18:05:00Z');
const sighting = (id = 'synthetic-1') => ({
entry_id: id,
latitude: 48.12345,
longitude: -123.12345,
created: '2026-11-04 17:00:00',
type: 'Orca',
no_sighted: '5',
photo_url: 'https://provider.example/photos/synthetic.jpg',
data_source_name: 'Synthetic Whale Network',
data_source_entity: 'Community reports',
trusted: true,
data_source_comments: '[email protected] +1-555-000-0000',
});
function install(options = {}, hook = 'configureServer') {
let handler;
const plugin = acartiaProxy({ now: () => NOW, warn() {}, ...options });
assert.equal(plugin.name, 'acartia');
assert.equal(typeof plugin.configureServer, 'function');
assert.equal(typeof plugin.configurePreviewServer, 'function');
plugin[hook]({
middlewares: {
use(mount, callback) {
assert.equal(mount, '/api/acartia');
handler = callback;
},
},
});
return async (
url = '/sightings',
{ method = 'GET', peer = 'local', headers = {} } = {},
) => {
const res = {
headers: {},
writeHead(status, headers) {
this.status = status;
this.headers = headers;
},
setHeader(key, value) {
this.headers[key] = value;
},
end(value) {
this.status ??= this.statusCode;
this.body = JSON.parse(value);
},
};
await handler(
{ url, method, headers, socket: { remoteAddress: peer } },
res,
);
return res;
};
}
test('cache stores only supported marine mammals inside the coverage box', async () => {
const cache = new Map();
const request = install({
cache,
fetchImpl: async () =>
Response.json([
sighting('orca'),
{ ...sighting('seal'), type: 'Harbor seal' },
{ ...sighting('unknown'), type: 'Unknown' },
{ ...sighting('fish'), type: 'Whale shark' },
{ ...sighting('outside'), latitude: 37.8 },
{ ...sighting('rounded-edge'), latitude: 45.49999 },
]),
});
const result = await request();
assert.equal(result.status, 200);
for (const rows of [result.body.rows, cache.get('sightings').value.rows]) {
assert.deepEqual(
rows.map((row) => [row.stableId, row.cetacean]),
[
['acartia:orca', true],
['acartia:seal', false],
],
);
}
});
for (const hook of ['configureServer', 'configurePreviewServer']) {
test(`${hook}: documented array is normalized and coarsened before caching`, async () => {
const cache = new Map();
let calls = 0;
const request = install(
{
cache,
fetchImpl: async (url, options) => {
calls += 1;
assert.equal(url, 'https://acartia.io/api/v1/sightings/current');
assert.equal(options.redirect, 'error');
assert.ok(options.signal instanceof AbortSignal);
assert.equal(options.headers.Accept, 'application/json');
assert.equal(
options.headers['User-Agent'],
'gods-eye-view-wildlife-sightings-proxy/1.0 (+https://github.com/bilawalsidhu/gods-eye-view)',
);
return Response.json([sighting()]);
},
},
hook,
);
assert.equal(calls, 0);
assert.equal(cache.size, 0);
const result = await request(
'/sightings?url=https://attacker.example&latitude=1',
);
assert.equal(result.status, 200);
assert.equal(result.headers['Cache-Control'], 'no-store');
assert.equal(result.body.fetchedAt, NOW);
assert.equal(result.body.rows[0].stableId, 'acartia:synthetic-1');
assert.equal(result.body.rows[0].lat, 48.12);
assert.equal(result.body.rows[0].lon, -123.12);
assert.equal(result.body.rows[0].provider.name, 'Synthetic Whale Network');
const retained = cache.get('sightings');
assert.equal(retained.value.rows[0].lat, 48.12);
assert.equal(retained.value.rows[0].lon, -123.12);
for (const value of [result.body, [...cache.values()]])
assert.doesNotMatch(
JSON.stringify(value),
/private-observer|555|data_source_comments|48\.12345|123\.12345/,
);
assert.equal(calls, 1);
});
}
test('gate, method and fixed route run before cache or upstream', async () => {
const cache = new Map();
let calls = 0;
const request = install({
cache,
fetchImpl: async () => {
calls += 1;
return Response.json([sighting()]);
},
});
for (let i = 0; i < 65; i += 1) {
assert.equal(
(
await request('/sightings', {
headers: {
host: 'localhost:5173',
origin: 'https://foreign.example',
'sec-fetch-site': 'cross-site',
},
})
).status,
403,
);
}
const method = await request('/sightings', { method: 'POST' });
assert.equal(method.status, 405);
assert.equal(method.headers.Allow, 'GET');
for (const route of ['/', '/unknown', '/sightings/extra']) {
const result = await request(route);
assert.equal(result.status, 404);
assert.equal(result.body.error, 'unknown_route');
}
assert.equal(calls, 0);
assert.equal(cache.size, 0);
const sameOrigin = await request('/sightings', {
headers: {
host: 'gev.example',
origin: 'https://gev.example',
'sec-fetch-site': 'same-origin',
'x-forwarded-proto': 'https',
'x-forwarded-for': '203.0.113.9',
},
});
assert.equal(sameOrigin.status, 200);
});
test('request 61 is locally limited even with a fresh cache', async () => {
let calls = 0;
const request = install({
fetchImpl: async () => {
calls += 1;
return Response.json([sighting()]);
},
});
for (let i = 0; i < 60; i += 1) assert.equal((await request()).status, 200);
const limited = await request();
assert.equal(limited.status, 429);
assert.equal(limited.body.error, 'rate_limited');
assert.equal(limited.headers['Retry-After'], '60');
assert.equal(calls, 1);
});
test('one global snapshot single-flights and stays fresh for ten minutes', async () => {
let at = NOW;
let calls = 0;
let release;
const request = install({
now: () => at,
fetchImpl: async () => {
calls += 1;
if (calls === 1)
await new Promise((resolve) => {
release = resolve;
});
return Response.json([sighting()]);
},
});
const first = request();
const concurrent = request();
await new Promise(setImmediate);
assert.equal(calls, 1);
release();
assert.deepEqual((await first).body, (await concurrent).body);
at += 599_999;
await request();
assert.equal(calls, 1);
at += 1;
await request();
assert.equal(calls, 2);
});
test('empty, unprojectable and invented wrapped snapshots never replace the cache', async () => {
for (const bad of [
[],
[null],
[{ ...sighting(), data_source_name: '' }],
{ results: [sighting()] },
]) {
let at = NOW;
let fail = false;
const cache = new Map();
const request = install({
cache,
now: () => at,
fetchImpl: async () => Response.json(fail ? bad : [sighting()]),
});
const fresh = await request();
const retained = cache.get('sightings');
fail = true;
at += 600_001;
const stale = await request();
assert.equal(stale.status, 200);
assert.equal(stale.headers['X-Data-Stale'], 'true');
assert.equal(stale.body.stale, true);
assert.equal(stale.body.fetchedAt, fresh.body.fetchedAt);
assert.equal(cache.get('sightings'), retained);
const coldCache = new Map();
const cold = install({
cache: coldCache,
fetchImpl: async () => Response.json(bad),
});
const result = await cold();
assert.equal(result.status, 502);
assert.equal(result.body.error, 'acartia_unavailable');
assert.equal(coldCache.size, 0);
}
});
test('failure serves stale for at most 24 hours and never reports a cold success', async () => {
let at = NOW;
let fail = false;
const request = install({
now: () => at,
fetchImpl: async () => {
if (fail) throw new Error('private upstream detail');
return Response.json([sighting()]);
},
});
const first = await request();
fail = true;
at += 23 * 60 * 60_000 + 59 * 60_000;
const stale = await request();
assert.equal(stale.status, 200);
assert.equal(stale.body.stale, true);
assert.equal(stale.body.fetchedAt, first.body.fetchedAt);
at += 2 * 60_000;
const expired = await request();
assert.equal(expired.status, 502);
assert.deepEqual(expired.body, { error: 'acartia_unavailable' });
});
test('upstream 429 honors Retry-After, caps it, and spends no requests while cooling down', async () => {
assert.equal(acartiaRetryCooldownMs('2', NOW), 5000);
assert.equal(acartiaRetryCooldownMs('999999', NOW), 3_600_000);
assert.equal(
acartiaRetryCooldownMs(new Date(NOW + 120_000).toUTCString(), NOW),
120_000,
);
assert.equal(acartiaRetryCooldownMs(null, NOW), 60_000);
let at = NOW;
let calls = 0;
const warnings = [];
const request = install({
now: () => at,
warn: (message) => warnings.push(message),
fetchImpl: async () => {
calls += 1;
return new Response('limit', {
status: 429,
headers: { 'Retry-After': '999999' },
});
},
});
const first = await request();
assert.equal(first.status, 429);
assert.equal(first.body.error, 'acartia_unavailable');
assert.equal(first.headers['Retry-After'], '3600');
at += 5000;
const cooling = await request();
assert.equal(cooling.headers['Retry-After'], '3595');
assert.equal(calls, 1);
assert.equal(
warnings.filter((message) => message.includes('capped')).length,
1,
);
at += 3_600_000;
await request();
assert.equal(calls, 2);
assert.equal(
warnings.filter((message) => message.includes('capped')).length,
1,
);
});
test('upstream 429 also serves a prior snapshot stale', async () => {
let at = NOW;
let fail = false;
let calls = 0;
const request = install({
now: () => at,
fetchImpl: async () => {
calls += 1;
return fail
? new Response('limit', { status: 429 })
: Response.json([sighting()]);
},
});
const first = await request();
fail = true;
at += 600_001;
const stale = await request();
assert.equal(stale.status, 200);
assert.equal(stale.body.stale, true);
assert.equal(stale.body.fetchedAt, first.body.fetchedAt);
at += 1000;
assert.equal((await request()).body.stale, true);
assert.equal(calls, 2);
});
test('bad JSON, 5xx and capped bodies start a 15 second failure cooldown', async () => {
for (const bad of [
() => new Response('{malformed'),
() => new Response('private upstream message', { status: 503 }),
() =>
new Response('{}', {
headers: { 'Content-Length': String(2 * 1024 * 1024 + 1) },
}),
]) {
let at = NOW;
let calls = 0;
const request = install({
now: () => at,
fetchImpl: async () => {
calls += 1;
return calls === 1 ? bad() : Response.json([sighting()]);
},
});
const first = await request();
assert.equal(first.status, 502);
assert.deepEqual(first.body, { error: 'acartia_unavailable' });
assert.equal(first.headers['Retry-After'], '15');
at += 14_999;
assert.equal((await request()).headers['Retry-After'], '1');
assert.equal(calls, 1);
at += 1;
assert.equal((await request()).status, 200);
assert.equal(calls, 2);
}
});
+4
View File
@@ -0,0 +1,4 @@
/** Identify each provider while keeping the project contact URL consistent. */
export function providerUserAgent(name) {
return `gods-eye-view-${name}-proxy/1.0 (+https://github.com/bilawalsidhu/gods-eye-view)`;
}
+370
View File
@@ -0,0 +1,370 @@
import path from 'node:path';
import { promises as fs } from 'node:fs';
import { randomUUID } from 'node:crypto';
import { normalizeInatObservations } from '../../../src/layers/wildlifeSightings/records.js';
import { readResponseJsonCapped } from '../common/http.js';
import { clampInt } from '../common/query.js';
import { providerUserAgent } from '../common/userAgent.js';
import { projectInatGrid, validInatGridTile } from './grid.js';
import { createInatQueue } from './queue.js';
const DAILY_BUDGET = 8000;
const DAY_MS = 24 * 60 * 60_000;
// One page is capped independently of upstream Content-Length.
const MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
const TIMEOUT_MS = 20_000;
const API_URL = 'https://api.inaturalist.org/v1';
// Request only the fields used by the public observation projection.
const OBSERVATIONS_URL = 'https://api.inaturalist.org/v2/observations';
const OBSERVATION_FIELDS =
'(id:!t,uri:!t,location:!t,geojson:all,geoprivacy:!t,taxon_geoprivacy:!t,' +
'obscured:!t,quality_grade:!t,observed_on:!t,' +
'time_observed_at:!t,faves_count:!t,' +
'num_identification_agreements:!t,' +
'photos:(url:!t,attribution:!t,license_code:!t),' +
'taxon:(name:!t,preferred_common_name:!t,iconic_taxon_name:!t,' +
'threatened:!t))';
const COOLDOWN_MS = 60_000;
const FAILURE_COOLDOWN_MS = 15_000;
/** Seconds and HTTP dates are accepted; a missing value earns one minute. */
export function inatRetryCooldownMs(raw, nowMs) {
const value = typeof raw === 'string' ? raw.trim() : '';
if (value) {
const seconds = /^\d+(?:\.\d+)?$/.test(value) ? Number(value) : NaN;
const deadline =
!Number.isFinite(seconds) && /[a-z]/i.test(value)
? Date.parse(value)
: NaN;
const delay = Number.isFinite(seconds) ? seconds * 1000 : deadline - nowMs;
if (Number.isFinite(delay) && delay >= 0) return delay;
}
return COOLDOWN_MS;
}
function upstreamError(code, status, retryAfterS, upstreamStatus) {
return Object.assign(new Error(code), {
code,
status,
retryAfterS,
upstreamStatus,
});
}
function observationFilters(params) {
const query = new URLSearchParams({
verifiable: 'true',
captive: 'false',
// d1 is an observation DATE, unlike creation-time created_d1.
d1: params.observedD1,
});
if (params.taxa !== 'whales') query.set('photos', 'true');
if (params.quality === 'research') query.set('quality_grade', 'research');
if (params.taxa === 'whales') query.set('taxon_id', '152871');
else if (['Aves', 'Mammalia', 'Insecta', 'Plantae'].includes(params.taxa))
query.set('iconic_taxa', params.taxa);
return query;
}
/**
* Shared iNaturalist governor. Only request counters are persisted; observation
* payloads are projected immediately and are never written to disk.
*/
export function createInatClient({
fetchImpl = (...args) => globalThis.fetch(...args),
now = () => Date.now(),
cacheDir = path.join(process.cwd(), '.gev-cache', 'inaturalist'),
warn = (...args) => console.warn(...args),
queueOptions,
} = {}) {
const enqueue = createInatQueue(queueOptions);
let budget = null;
let budgetLoaded = false;
let budgetTail = Promise.resolve();
let cooldownUntil = 0;
// An unavailable UTFGrid tile must never place observation queries into a
// 502 cooldown. Only an upstream 429 is a provider-wide restriction.
const failures = new Map();
function dailyLimit() {
return clampInt(
process.env.INATURALIST_DAILY_BUDGET,
1,
Number.MAX_SAFE_INTEGER,
DAILY_BUDGET,
);
}
function day() {
return new Date(now()).toISOString().slice(0, 10);
}
function currentBudget() {
const date = day();
if (budget?.date !== date) budget = { date, count: 0 };
return budget;
}
async function readBudgetOnce() {
if (budgetLoaded) return;
budgetLoaded = true;
if (cacheDir === null) return;
try {
const value = JSON.parse(
await fs.readFile(path.join(cacheDir, 'budget.json'), 'utf8'),
);
if (
typeof value?.date === 'string' &&
/^\d{4}-\d{2}-\d{2}$/.test(value.date) &&
Number.isSafeInteger(value.count) &&
value.count >= 0
)
budget = { date: value.date, count: value.count };
} catch (error) {
if (error.code !== 'ENOENT')
warn('[inaturalist-proxy] budget read failed');
}
}
async function persistBudget(value) {
if (cacheDir === null) return;
const destination = path.join(cacheDir, 'budget.json');
const temporary = `${destination}.${randomUUID()}.tmp`;
try {
await fs.mkdir(cacheDir, { recursive: true });
await fs.writeFile(temporary, JSON.stringify(value), 'utf8');
await fs.rename(temporary, destination);
} catch {
await fs.rm(temporary, { force: true }).catch(() => {});
warn('[inaturalist-proxy] budget write failed');
}
}
function retrySeconds(until = cooldownUntil) {
return Math.max(0, Math.ceil((until - now()) / 1000));
}
function checkCooldown(resource) {
if (now() < cooldownUntil)
throw upstreamError('inaturalist_rate_limited', 429, retrySeconds(), 429);
const failure = failures.get(resource);
if (failure && now() < failure.until)
throw upstreamError(
'inaturalist_unavailable',
502,
retrySeconds(failure.until),
failure.status,
);
failures.delete(resource);
}
// Reserve and atomically persist one attempt before issuing it. A recovered
// tail prevents one disk/reservation failure from poisoning future calls.
function reserveAttempt(signal, resource) {
const result = budgetTail.then(async () => {
signal?.throwIfAborted();
checkCooldown(resource);
await readBudgetOnce();
signal?.throwIfAborted();
checkCooldown(resource);
const value = currentBudget();
if (value.count >= dailyLimit()) {
const retry = Math.max(
1,
Math.ceil((Date.parse(`${day()}T00:00:00Z`) + DAY_MS - now()) / 1000),
);
throw upstreamError('inaturalist_budget_exhausted', 503, retry);
}
value.count += 1;
await persistBudget({ ...value });
});
budgetTail = result.catch(() => {});
return result;
}
async function requestProjected(url, project, resource, { signal } = {}) {
signal?.throwIfAborted();
checkCooldown(resource);
return enqueue(() => dispatch(url, project, resource, signal), {
signal,
priority: resource === 'observations' ? 0 : 1,
});
}
async function dispatch(url, project, resource, signal) {
signal?.throwIfAborted();
await reserveAttempt(signal, resource);
signal?.throwIfAborted();
checkCooldown(resource);
const timeout = AbortSignal.timeout(TIMEOUT_MS);
const requestSignal = signal ? AbortSignal.any([signal, timeout]) : timeout;
let upstreamStatus;
let retryDelay = FAILURE_COOLDOWN_MS;
try {
const response = await fetchImpl(url, {
signal: requestSignal,
redirect: 'error',
headers: {
Accept: 'application/json',
'User-Agent': providerUserAgent('wildlife-sightings'),
},
});
upstreamStatus = response.status;
if (!response.ok) {
await response.body?.cancel().catch(() => {});
if (response.status === 429) {
const delay = inatRetryCooldownMs(
response.headers.get('retry-after'),
now(),
);
cooldownUntil = Math.max(cooldownUntil, now() + delay);
throw upstreamError(
'inaturalist_rate_limited',
429,
retrySeconds(),
429,
);
}
if (response.headers.has('retry-after'))
retryDelay = inatRetryCooldownMs(
response.headers.get('retry-after'),
now(),
);
throw upstreamError('inaturalist_unavailable', 502);
}
const payload = await readResponseJsonCapped(
response,
MAX_RESPONSE_BYTES,
requestSignal,
);
return project(payload);
} catch (error) {
// Log only safe diagnostic categories, never a URL, body, status text or
// exception message (each can contain request/private information).
const reason = signal?.aborted
? 'abort'
: timeout.aborted || error.name === 'TimeoutError'
? 'timeout'
: upstreamStatus && upstreamStatus >= 400
? 'http'
: error.code === 'RESPONSE_TOO_LARGE'
? 'response-too-large'
: upstreamStatus
? 'invalid-payload'
: 'network';
warn(
`[inaturalist-proxy] upstream refresh failed resource=${resource} status=${upstreamStatus ?? 'none'} reason=${reason}`,
);
if (signal?.aborted) throw error;
// Network, abort, schema and size failures are not upstream cooldown
// signals. A later healthy request may recover immediately.
if (upstreamStatus >= 500)
failures.set(resource, {
until: now() + retryDelay,
status: upstreamStatus,
});
const failure = failures.get(resource);
throw upstreamError(
error.status === 429
? 'inaturalist_rate_limited'
: 'inaturalist_unavailable',
error.status === 429 ? 429 : 502,
error.status === 429
? retrySeconds()
: failure
? retrySeconds(failure.until)
: undefined,
upstreamStatus === 429 || upstreamStatus >= 500
? upstreamStatus
: undefined,
);
}
}
/** Request one bounded observation sample and project its public fields. */
async function getObservations(params, options = {}) {
const query = observationFilters(params);
// View samples stay stable in the route cache until their TTL expires.
query.set('order_by', params.scope === 'view' ? 'random' : 'observed_on');
if (params.scope !== 'view') query.set('order', 'desc');
query.set('per_page', '200');
query.set('fields', OBSERVATION_FIELDS);
if (params.scope === 'view') {
query.set('swlat', String(params.box.south));
query.set('swlng', String(params.box.west));
query.set('nelat', String(params.box.north));
query.set('nelng', String(params.box.east));
}
return requestProjected(
`${OBSERVATIONS_URL}?${query}`,
(payload) => {
if (
!Array.isArray(payload?.results) ||
!Number.isSafeInteger(payload.total_results) ||
payload.total_results < 0
)
throw upstreamError('inaturalist_unavailable', 502);
const rows = normalizeInatObservations(payload.results, {
nowMs: now(),
windowMs: (params.taxa === 'whales' ? 7 : 2) * DAY_MS,
});
// Empty views need total_results: 0. A wholly invalid page must not
// replace good data. Valid rows excluded by the finer rolling-time
// cutoff are a healthy empty result of the upstream DATE over-fetch.
if (
!rows ||
(!rows.length &&
(payload.results.length || payload.total_results) &&
!normalizeInatObservations(payload.results)?.length)
)
throw upstreamError('inaturalist_unavailable', 502);
return {
rows,
totalResults: payload.total_results,
...(payload.total_results > payload.results.length
? { partial: true, rawRowCount: payload.total_results }
: {}),
};
},
'observations',
options,
);
}
/** Decode one validated density tile without observation identifiers. */
async function getGrid(params, options = {}) {
if (!validInatGridTile(params)) throw new TypeError('Invalid grid tile');
const query = observationFilters(params);
return requestProjected(
`${API_URL}/grid/${params.z}/${params.x}/${params.y}.grid.json?${query}`,
(payload) => projectInatGrid(payload, params),
'grid',
options,
);
}
return {
getObservations,
getGrid,
/** Current provider-wide or resource-specific retry information. */
cooldownState(resource) {
if (now() < cooldownUntil)
return {
upstreamStatus: 429,
retryAfterS: retrySeconds(),
};
const failure = failures.get(resource);
if (failure && now() < failure.until)
return {
upstreamStatus: failure.status,
retryAfterS: retrySeconds(failure.until),
};
return undefined;
},
/** The current UTC day's reserved upstream attempts and configured limit. */
budgetState() {
const value = currentBudget();
return { ...value, budget: dailyLimit() };
},
};
}
@@ -0,0 +1,163 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { createInatClient } from './client.js';
import { syntheticGrid } from './grid.fixture.mjs';
const NOW = Date.parse('2026-11-04T18:05:00Z');
const observation = (id, time) => ({
id,
geojson: { coordinates: [-122.3, 47.6] },
quality_grade: 'research',
time_observed_at: time,
observed_on: time.slice(0, 10),
// Upload age does not determine whether an observation is recent.
created_at: '2026-11-04T18:00:00Z',
taxon: { name: 'Synthetic taxon', iconic_taxon_name: 'Aves' },
});
function clientFor(results) {
return createInatClient({
now: () => NOW,
cacheDir: null,
queueOptions: { intervalMs: 0 },
warn() {},
fetchImpl: async () =>
Response.json({ results, total_results: results.length }),
});
}
test('iNaturalist drops ancient and boundary-expired observations uploaded today', async () => {
const client = clientFor([
observation(1, '2010-11-04T18:00:00Z'),
observation(2, '2026-11-02T18:04:59Z'),
observation(3, '2026-11-02T18:05:00Z'),
]);
const result = await client.getObservations({
scope: 'global',
taxa: 'all',
observedD1: '2026-11-02',
});
assert.deepEqual(
result.rows.map((row) => row.stableId),
['inat:3'],
);
});
test('a valid date-filtered page can be empty after the exact recency cutoff', async () => {
const client = clientFor([observation(1, '2026-11-02T06:00:00Z')]);
const result = await client.getObservations({
scope: 'global',
taxa: 'all',
observedD1: '2026-11-02',
});
assert.deepEqual(result.rows, []);
assert.equal(result.totalResults, 1);
});
const params = {
scope: 'view',
box: { south: 47, west: -123, north: 48, east: -122 },
taxa: 'all',
observedD1: '2026-11-02',
};
test('observations and tiles share pacing, and observation priority wins a cold burst', async () => {
let at = 0;
const calls = [];
const client = createInatClient({
cacheDir: null,
now: () => NOW + at,
queueOptions: {
now: () => at,
wait: async (ms) => {
at += ms;
},
},
fetchImpl: async (url) => {
const isGrid = new URL(url).pathname.includes('/grid/');
calls.push([isGrid ? 'grid' : 'observations', at]);
return Response.json(
isGrid ? syntheticGrid() : { results: [], total_results: 0 },
);
},
});
const tiles = Array.from({ length: 3 }, (_, x) =>
client.getGrid({ ...params, z: 2, x, y: 1 }),
);
await Promise.all([...tiles, client.getObservations(params)]);
assert.deepEqual(calls, [
['observations', 0],
['grid', 0],
['grid', 0],
['grid', 0],
]);
assert.equal(client.budgetState().count, 4);
});
test('upstream status and safe failure reasons are logged without upstream messages or URLs', async () => {
for (const [response, expected] of [
[
() =>
new Response('request-secret', {
status: 503,
statusText: 'request-secret',
}),
/status=503 reason=http/,
],
[
() => Promise.reject(new DOMException('request-secret', 'TimeoutError')),
/status=none reason=timeout/,
],
[
() => Response.json({ unexpected: 'request-secret' }),
/status=200 reason=invalid-payload/,
],
]) {
const warnings = [];
const client = createInatClient({
cacheDir: null,
now: () => NOW,
queueOptions: { intervalMs: 0 },
warn: (message) => warnings.push(message),
fetchImpl: response,
});
await assert.rejects(client.getObservations(params), { status: 502 });
assert.equal(warnings.length, 1);
assert.match(warnings[0], expected);
assert.doesNotMatch(warnings[0], /request-secret|https?:|swlat|d1=/);
}
});
test('a dispatched abort is diagnosed, spends only its attempt, and leaves the next request healthy', async () => {
const warnings = [];
const controller = new AbortController();
let started;
const didStart = new Promise((resolve) => {
started = resolve;
});
let calls = 0;
const client = createInatClient({
cacheDir: null,
now: () => NOW,
queueOptions: { intervalMs: 0 },
warn: (message) => warnings.push(message),
fetchImpl: async (url, { signal }) => {
calls += 1;
if (calls > 1) return Response.json({ results: [], total_results: 0 });
started();
return new Promise((resolve, reject) =>
signal.addEventListener('abort', () => reject(signal.reason), {
once: true,
}),
);
},
});
const pending = client.getObservations(params, { signal: controller.signal });
const failed = assert.rejects(pending, { name: 'AbortError' });
await didStart;
controller.abort();
await failed;
assert.match(warnings[0], /resource=observations status=none reason=abort/);
await client.getObservations(params);
assert.equal(calls, 2);
});
@@ -0,0 +1,51 @@
/** Build a synthetic UTFGrid with logical cell counts and repeated hit pixels. */
export function syntheticGrid({
z = 2,
x = 1,
y = 1,
cells = [
{ row: 1, column: 51, count: 6, iconic: 3 },
{ row: 3, column: 49, count: 12, iconic: 47170 },
],
} = {}) {
const pixels = Array.from({ length: 64 }, () => Array(64).fill(' '));
const keys = [''];
const data = {};
cells.forEach((cell, index) => {
const key = String(900_000_000 + index);
keys.push(key);
const value = index + 1;
let code = value + 32;
if (code >= 34) code += 1;
if (code >= 92) code += 1;
const character = String.fromCharCode(code);
for (const [row, column] of [
[cell.row, cell.column],
[cell.row - 1, cell.column],
[cell.row + 1, cell.column],
[cell.row, cell.column - 1],
[cell.row, cell.column + 1],
]) {
if (row >= 0 && row < 64 && column >= 0 && column < 64)
pixels[row][column] = character;
}
data[key] = {
captive: null,
cellCount: cell.count,
geoprivacy: null,
id: Number(key),
latitude:
(Math.atan(
Math.sinh(Math.PI * (1 - 2 * ((y + cell.row / 64) / 2 ** z))),
) *
180) /
Math.PI,
longitude: ((x + cell.column / 64) / 2 ** z) * 360 - 180,
private_location: null,
quality_grade: 'needs_id',
'taxon.iconic_taxon_id': cell.iconic,
uuid: `00000000-0000-4000-8000-${String(index).padStart(12, '0')}`,
};
});
return { grid: pixels.map((row) => row.join('')), keys, data };
}
+127
View File
@@ -0,0 +1,127 @@
// iNaturalist's operational /v1/grid/{z}/{x}/{y}.grid.json endpoint is not
// covered by its v1 Swagger contract. Validate the entire UTFGrid shape before
// accepting any cell; callers preserve the last good sample on schema drift.
import {
wildlifeGridCellId,
WILDLIFE_GRID_MAX_ZOOM,
} from '../../../src/layers/wildlifeSightings/gridViewport.js';
const INAT_GRID_SIZE = 64;
export const INAT_GRID_DECODE_MEASURE = 'wildlife:server:tile-decode';
const grades = new Set(['research', 'needs_id', 'casual']);
const object = (value) =>
!!value && typeof value === 'object' && !Array.isArray(value);
/** Accept only integer XYZ coordinates within the supported zoom range. */
export function validInatGridTile({ z, x, y } = {}) {
return (
Number.isInteger(z) &&
z >= 0 &&
z <= WILDLIFE_GRID_MAX_ZOOM &&
Number.isInteger(x) &&
Number.isInteger(y) &&
x >= 0 &&
y >= 0 &&
x < 2 ** z &&
y < 2 ** z
);
}
/** Pure allow-list projection. A UTFGrid key is an observation identifier and
* its repeated pixels form a dilated hit area, not additional occupied cells.
* Neither those keys nor representative/private fields cross this boundary. */
export function projectInatGrid(payload, tile) {
const start = `${INAT_GRID_DECODE_MEASURE}:start`;
const end = `${INAT_GRID_DECODE_MEASURE}:end`;
performance.mark(start);
try {
return decodeInatGrid(payload, tile);
} finally {
performance.mark(end);
// Keep the latest sample inspectable without retaining every tile forever.
// PerformanceObservers still receive each individual decode measurement.
performance.clearMeasures(INAT_GRID_DECODE_MEASURE);
performance.measure(INAT_GRID_DECODE_MEASURE, start, end);
performance.clearMarks(start);
performance.clearMarks(end);
}
}
function decodeInatGrid(payload, tile) {
const invalid = () => {
throw new TypeError('Invalid iNaturalist UTFGrid');
};
if (
!validInatGridTile(tile) ||
!object(payload) ||
!Array.isArray(payload.grid) ||
payload.grid.length !== INAT_GRID_SIZE ||
!payload.grid.every(
(row) => typeof row === 'string' && row.length === INAT_GRID_SIZE,
) ||
!Array.isArray(payload.keys) ||
!payload.keys.length ||
payload.keys.length > INAT_GRID_SIZE ** 2 + 1 ||
payload.keys[0] !== '' ||
!payload.keys.every(
(key) => typeof key === 'string' && key.length <= 128,
) ||
new Set(payload.keys).size !== payload.keys.length ||
!object(payload.data)
)
invalid();
const dataKeys = Object.keys(payload.data);
if (dataKeys.length !== payload.keys.length - 1) invalid();
for (const key of payload.keys.slice(1)) {
if (!key || !Object.hasOwn(payload.data, key)) invalid();
const value = payload.data[key];
if (
!object(value) ||
!Number.isSafeInteger(value.cellCount) ||
value.cellCount <= 0 ||
!Number.isFinite(value.latitude) ||
Math.abs(value.latitude) > 90 ||
!Number.isFinite(value.longitude) ||
Math.abs(value.longitude) > 180 ||
!grades.has(value.quality_grade) ||
!(
value['taxon.iconic_taxon_id'] === null ||
(Number.isSafeInteger(value['taxon.iconic_taxon_id']) &&
value['taxon.iconic_taxon_id'] > 0)
)
)
invalid();
}
const occupied = new Set();
for (const line of payload.grid) {
for (let column = 0; column < INAT_GRID_SIZE; column += 1) {
let code = line.charCodeAt(column);
if (code < 32 || code === 34 || code === 92) invalid();
if (code >= 93) code -= 1;
if (code >= 35) code -= 1;
const index = code - 32;
if (index < 0 || index >= payload.keys.length) invalid();
if (index) occupied.add(payload.keys[index]);
}
}
if (occupied.size !== dataKeys.length) invalid();
const cells = new Map();
for (const key of occupied) {
const value = payload.data[key];
const id = wildlifeGridCellId(value.latitude, value.longitude, tile.z);
const cell = {
id,
cellCount: value.cellCount,
lat: value.latitude,
lon: value.longitude,
};
// Two different representatives claiming one logical cell are a contract
// change; summing or choosing one silently would fabricate the count.
if (cells.has(id)) invalid();
cells.set(id, cell);
}
const rows = [...cells.values()];
const totalResults = rows.reduce((total, cell) => total + cell.cellCount, 0);
if (!Number.isSafeInteger(totalResults)) invalid();
return { cells: rows, totalResults };
}
+374
View File
@@ -0,0 +1,374 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { projectInatGrid } from './grid.js';
import { syntheticGrid } from './grid.fixture.mjs';
import { installInatProxy, NOW } from './proxy.fixture.mjs';
import { createInatClient } from './client.js';
const tile = { z: 2, x: 1, y: 1 };
const gridUrl = '/grid?z=2&x=1&y=1&taxa=Aves&quality=research';
const install = (options, hook) => installInatProxy(gridUrl, options, hook);
test('UTFGrid projection validates the full shape and counts each logical occupied cell once', () => {
const raw = syntheticGrid();
const projected = projectInatGrid(raw, tile);
assert.equal(raw.grid.length, 64);
assert.ok(raw.grid.every((row) => row.length === 64));
assert.equal(
raw.grid.join('').split('!').length - 1,
5,
'one key is repeated by its hit area',
);
assert.equal(projected.cells.length, 2);
assert.equal(projected.totalResults, 18);
assert.deepEqual(projected.cells[0], {
id: 'grid:2/1/1:1:51',
cellCount: 6,
lat: raw.data[raw.keys[1]].latitude,
lon: raw.data[raw.keys[1]].longitude,
});
for (const cell of projected.cells)
assert.deepEqual(Object.keys(cell).sort(), [
'cellCount',
'id',
'lat',
'lon',
]);
for (const privateField of [
'900000000',
'uuid',
'geoprivacy',
'private_location',
'captive',
'quality_grade',
])
assert.ok(!JSON.stringify(projected).includes(privateField));
});
test('legitimate blank grid and null/unknown iconic ids remain safe', () => {
assert.deepEqual(projectInatGrid(syntheticGrid({ cells: [] }), tile), {
cells: [],
totalResults: 0,
});
const raw = syntheticGrid({
cells: [{ row: 1, column: 1, count: 1, iconic: null }],
});
const projected = projectInatGrid(raw, tile);
assert.equal(projected.cells[0].cellCount, 1);
raw.data[raw.keys[1]]['taxon.iconic_taxon_id'] = 999999;
assert.deepEqual(projectInatGrid(raw, tile), projected);
});
test('schema drift anywhere fails closed, including unused, malformed and orphan cells', () => {
const changes = [
(raw) => {
raw.grid.pop();
},
(raw) => {
raw.grid[0] = '';
},
(raw) => {
raw.grid[0] = '~'.repeat(64);
},
(raw) => {
raw.keys[0] = 'not-empty';
},
(raw) => {
raw.keys.push(raw.keys[1]);
},
(raw) => {
delete raw.data[raw.keys[1]];
},
(raw) => {
raw.data.extra = {};
},
(raw) => {
raw.data[raw.keys[1]].cellCount = -1;
},
(raw) => {
raw.data[raw.keys[1]].latitude = '47';
},
(raw) => {
raw.data[raw.keys[1]].longitude = 181;
},
(raw) => {
raw.data[raw.keys[1]].quality_grade = 'new-grade';
},
(raw) => {
delete raw.data[raw.keys[1]]['taxon.iconic_taxon_id'];
},
(raw) => {
raw.data[raw.keys[1]]['taxon.iconic_taxon_id'] = 'Aves';
},
(raw) => {
raw.grid = Array(64).fill(' '.repeat(64));
},
];
for (const change of changes) {
const raw = syntheticGrid();
change(raw);
assert.throws(
() => projectInatGrid(raw, tile),
/Invalid iNaturalist UTFGrid/,
);
}
});
for (const hook of ['configureServer', 'configurePreviewServer']) {
test(`${hook}: fixed grid URL shares filters, caps and allow-list cache`, async () => {
const calls = [];
const cache = new Map();
const request = install(
{
cache,
fetchImpl: async (url, options) => {
calls.push(new URL(url));
assert.equal(options.redirect, 'error');
assert.ok(options.signal instanceof AbortSignal);
assert.match(
options.headers['User-Agent'],
/^gods-eye-view-wildlife-sightings-proxy/,
);
return Response.json(syntheticGrid());
},
},
hook,
);
assert.equal(calls.length, 0);
const response = await request(
`${gridUrl}&url=https://attacker.test&per_page=999`,
);
assert.equal(response.status, 200);
assert.equal(response.body.cells.length, 2);
assert.equal(response.body.fetchedAt, NOW);
assert.equal(response.headers['Cache-Control'], 'no-store');
assert.equal(
calls[0].origin + calls[0].pathname,
'https://api.inaturalist.org/v1/grid/2/1/1.grid.json',
);
assert.deepEqual(Object.fromEntries(calls[0].searchParams), {
verifiable: 'true',
captive: 'false',
d1: '2026-11-02',
photos: 'true',
quality_grade: 'research',
iconic_taxa: 'Aves',
});
await request(gridUrl);
assert.equal(calls.length, 1, 'fresh stable tile/filter key is cached');
assert.ok(!JSON.stringify([...cache]).includes('private_location'));
assert.ok(!JSON.stringify([...cache]).includes('900000000'));
const whale = await request('/grid?z=2&x=1&y=1&taxa=whales&confirmed=true');
assert.equal(whale.status, 200);
assert.equal(calls[1].searchParams.get('taxon_id'), '152871');
assert.equal(calls[1].searchParams.get('d1'), '2026-10-28');
assert.equal(calls[1].searchParams.has('photos'), false);
});
}
test('grid admission rejects cross-site/method/path/tile before acquisition', async () => {
const request = install({
fetchImpl() {
assert.fail('invalid grid request acquired');
},
});
assert.equal(
(
await request(gridUrl, {
headers: {
host: 'localhost:5173',
origin: 'https://other.test',
'sec-fetch-site': 'cross-site',
},
})
).status,
403,
);
assert.equal((await request('/grid', { method: 'POST' })).status, 405);
assert.equal((await request('/grid/extra')).status, 404);
for (const query of [
'',
'z=2&x=4&y=0',
'z=-1&x=0&y=0',
'z=23&x=0&y=0',
'z=2&x=1.5&y=0',
'z=2&x=&y=0',
'z=2&x=0&y=4',
]) {
const result = await request(`/grid?${query}`);
assert.equal(result.status, 400, query);
assert.equal(result.body.error, 'invalid_tile');
}
});
test('grid refresh single-flight, stale fallback and hour-key expiry keep the original age', async () => {
let time = NOW;
let calls = 0;
let resolve;
let mode = 'pending';
const request = install({
now: () => time,
fetchImpl: async () => {
calls += 1;
if (mode === 'pending')
return new Promise((done) => {
resolve = done;
});
if (mode === 'bad') return Response.json({ grid: [] });
return Response.json(syntheticGrid());
},
});
const first = request();
const second = request();
for (let i = 0; i < 100 && !resolve; i += 1) await new Promise(setImmediate);
assert.equal(calls, 1);
resolve(Response.json(syntheticGrid()));
assert.equal((await first).status, 200);
assert.equal((await second).status, 200);
time += 900_001;
mode = 'bad';
const stale = await request();
assert.equal(stale.status, 200);
assert.equal(stale.body.stale, true);
assert.equal(stale.body.fetchedAt, NOW);
assert.equal(stale.headers['X-Data-Stale'], 'true');
time = NOW + 3_600_001;
const expired = await request();
assert.equal(expired.status, 502);
assert.equal(expired.body.error, 'inaturalist_unavailable');
});
test('grid and observations share the upstream cooldown and daily reservations', async () => {
let calls = 0;
const client = createInatClient({
now: () => NOW,
cacheDir: null,
queueOptions: { intervalMs: 0 },
warn() {},
fetchImpl: async () => {
calls += 1;
return new Response('', {
status: 429,
headers: { 'Retry-After': '120' },
});
},
});
await assert.rejects(
client.getGrid({ ...tile, taxa: 'all', observedD1: '2026-11-02' }),
(error) => error.status === 429 && error.retryAfterS === 120,
);
await assert.rejects(
client.getObservations({
scope: 'global',
taxa: 'all',
observedD1: '2026-11-02',
}),
(error) => error.status === 429,
);
assert.equal(calls, 1);
assert.equal(client.budgetState().count, 1);
});
test('grid body cap and upstream failures return fixed errors; only real 5xx cools down', async () => {
for (const [response, retryAfter] of [
[() => new Response('x'.repeat(8 * 1024 * 1024 + 1)), undefined],
[() => new Response('', { status: 503 }), '15'],
]) {
const request = install({ fetchImpl: async () => response() });
const result = await request();
assert.equal(result.status, 502);
assert.deepEqual(result.body, { error: 'inaturalist_unavailable' });
assert.equal(result.headers['Retry-After'], retryAfter);
}
});
test('a failed tile preserves last good cells and cannot send unrelated observations into 502', async () => {
let at = NOW;
let failGrid = false;
const calls = [];
const warnings = [];
const request = install({
now: () => at,
warn: (message) => warnings.push(message),
fetchImpl: async (url) => {
const isGrid = new URL(url).pathname.includes('/grid/');
calls.push(isGrid ? 'grid' : 'observations');
if (isGrid && failGrid)
return new Response('private upstream text', {
status: 503,
headers: { 'Retry-After': '45' },
});
return Response.json(
isGrid ? syntheticGrid() : { results: [], total_results: 0 },
);
},
});
const first = await request();
at += 900_001;
failGrid = true;
const failed = await request();
assert.equal(failed.status, 200);
assert.equal(failed.headers['X-Data-Stale'], 'true');
assert.equal(failed.body.stale, true);
assert.equal(failed.body.fetchedAt, first.body.fetchedAt);
assert.deepEqual(failed.body.cells, first.body.cells);
assert.equal(
(await request('/observations?scope=global&taxa=whales')).status,
200,
);
assert.deepEqual(calls, ['grid', 'grid', 'observations']);
assert.match(warnings[0], /resource=grid status=503 reason=http/);
assert.doesNotMatch(warnings[0], /private upstream text/);
// The tile cooldown also preserves its data without another upstream try.
const cooling = await request();
assert.equal(cooling.body.stale, true);
assert.equal(cooling.headers['Retry-After'], '45');
assert.equal(cooling.headers['X-Upstream-Status'], '503');
assert.equal(calls.length, 3);
at += 45_000;
failGrid = false;
const refreshing = await request();
assert.equal(refreshing.body.stale, true);
await new Promise(setImmediate);
const recovered = await request();
assert.equal(recovered.status, 200);
assert.equal(recovered.body.stale, undefined);
assert.equal(
calls.length,
4,
'a failed queue task does not poison later tile work',
);
});
test('twelve queued tiles leave observation capacity and give the next dispatch to observations', async () => {
const calls = [];
let release;
const request = install({
fetchImpl: (url) => {
const path = new URL(url).pathname;
calls.push(path);
if (calls.length === 1)
return new Promise((resolve) => {
release = resolve;
});
return Response.json(
path.includes('/grid/')
? syntheticGrid()
: { results: [], total_results: 0 },
);
},
});
const first = request('/grid?z=4&x=0&y=1');
for (let i = 0; i < 100 && !release; i += 1) await new Promise(setImmediate);
assert.ok(release);
const tiles = Array.from({ length: 11 }, (_, i) =>
request(`/grid?z=4&x=${i + 1}&y=1`),
);
const observation = request('/observations?scope=global');
const queuedTile = request('/grid?z=4&x=12&y=1');
release(Response.json(syntheticGrid()));
const results = await Promise.all([first, observation, ...tiles, queuedTile]);
assert.ok(results.every((result) => result.status === 200));
assert.equal(calls.length, 14);
assert.equal(calls[1], '/v2/observations');
});
@@ -0,0 +1,343 @@
import { createInatClient } from './client.js';
import { coalesceProxyRequest } from '../common/http.js';
import { admitSameSiteKeyless } from '../common/same-site.js';
import { requiredFiniteQueryNumber } from '../common/query.js';
import { snapWildlifeBox } from '../../../src/layers/wildlifeSightings/bounds.js';
import { validInatGridTile } from './grid.js';
// The upstream advertises five-minute freshness; global samples cost less often.
const TTL_MS = 300_000;
const GLOBAL_TTL_MS = 600_000;
const INAT_GRID_CACHE_TTL_MS = 15 * 60_000;
const STALE_MAX_MS = 3_600_000;
const INAT_CACHE_MAX = 400;
const HOUR_MS = 3_600_000;
const TAXA = Object.freeze([
'all',
'Aves',
'Mammalia',
'Insecta',
'Plantae',
'whales',
]);
function requestParams(pathname, query, nowMs) {
const grid = pathname === '/grid';
const scope = grid ? 'grid' : (query.get('scope') ?? 'view');
if (!grid && scope !== 'view' && scope !== 'global')
return { error: 'invalid_scope' };
const quality =
query.get('quality') === 'research' ||
(grid && query.get('confirmed') === 'true')
? 'research'
: 'all';
const taxa = TAXA.includes(query.get('taxa')) ? query.get('taxa') : 'all';
let box = null;
let tile;
if (grid) {
tile = Object.fromEntries(
['z', 'x', 'y'].map((name) => [
name,
requiredFiniteQueryNumber(query, name),
]),
);
if (!validInatGridTile(tile)) return { error: 'invalid_tile' };
} else if (scope === 'view') {
const south = requiredFiniteQueryNumber(query, 'south');
const west = requiredFiniteQueryNumber(query, 'west');
const north = requiredFiniteQueryNumber(query, 'north');
const east = requiredFiniteQueryNumber(query, 'east');
box = snapWildlifeBox({ south, west, north, east });
if (!box) return { error: 'invalid_bbox' };
}
const windowMs = (taxa === 'whales' ? 7 * 24 : 48) * HOUR_MS;
const windowKey = new Date(Math.floor((nowMs - windowMs) / HOUR_MS) * HOUR_MS)
.toISOString()
.replace('.000Z', 'Z');
const bounds = box ? `${box.south},${box.west},${box.north},${box.east}` : '';
const baseKey = grid
? `grid:v1:${tile.z}/${tile.x}/${tile.y}:${quality}:${taxa}`
: `v1:${scope}:${bounds}:${quality}:${taxa}`;
return {
scope,
...(grid
? { ...tile, waitForFresh: query.get('refresh') === 'wait' }
: { box }),
quality,
taxa,
observedD1: windowKey.slice(0, 10),
baseKey,
key: `${baseKey}:${windowKey}`,
};
}
/** A shared-provider route for dev and preview; acquisition starts on demand. */
export function inaturalistProxy({
fetchImpl = (...args) => globalThis.fetch(...args),
now = () => Date.now(),
cacheDir,
cache = new Map(),
warn = (...args) => console.warn(...args),
queueOptions,
} = {}) {
const inFlight = new Map();
const consumers = new Map();
let client = null;
function age(entry) {
if (!entry) return Infinity;
const elapsed = now() - entry.savedAt;
return elapsed < 0 ? Infinity : elapsed;
}
function joinFlight(promise, group, signal) {
group.count += 1;
return new Promise((resolve, reject) => {
let released = false;
function release() {
if (released) return;
released = true;
signal?.removeEventListener('abort', abort);
group.count -= 1;
if (!group.count && !group.background && !group.settled) {
group.controller.abort();
// A fresh consumer must not join an abandoned queued generation.
if (inFlight.get(group.key) === promise) inFlight.delete(group.key);
if (consumers.get(group.key) === group) consumers.delete(group.key);
}
}
function abort() {
release();
reject(signal.reason);
}
signal?.addEventListener('abort', abort, { once: true });
if (signal?.aborted) abort();
promise.then(
(value) => {
release();
resolve(value);
},
(error) => {
release();
reject(error);
},
);
});
}
function refresh(params, background = false) {
// Global samples share pending refreshes across hourly window changes.
const flightKey = params.scope === 'global' ? params.baseKey : params.key;
let group = consumers.get(flightKey);
if (!group) {
group = {
key: flightKey,
controller: new AbortController(),
count: 0,
settled: false,
background,
};
consumers.set(flightKey, group);
}
group.background ||= background;
const { promise, shared } = coalesceProxyRequest(
inFlight,
flightKey,
async () => {
client ??= createInatClient({
fetchImpl,
now,
cacheDir,
warn,
queueOptions,
});
const options = { signal: group.controller.signal };
const result = await (params.scope === 'grid'
? client.getGrid(params, options)
: client.getObservations(params, options));
const value = {
fetchedAt: now(),
...(params.scope === 'grid'
? {}
: { scope: params.scope, box: params.box }),
...result,
};
cache.delete(params.key);
cache.set(params.key, {
value,
savedAt: now(),
baseKey: params.baseKey,
});
while (cache.size > INAT_CACHE_MAX)
cache.delete(cache.keys().next().value);
return value;
},
);
if (!shared) {
const settle = () => {
group.settled = true;
if (consumers.get(flightKey) === group) consumers.delete(flightKey);
};
void promise.then(settle, settle);
}
return { promise, group };
}
async function acquire(params, signal) {
signal?.throwIfAborted();
for (const [key, entry] of cache) {
if (age(entry) > STALE_MAX_MS) cache.delete(key);
}
const current = cache.get(params.key);
const ttl =
params.scope === 'grid'
? INAT_GRID_CACHE_TTL_MS
: params.scope === 'global'
? GLOBAL_TTL_MS
: TTL_MS;
if (current && age(current) < ttl) {
cache.delete(params.key);
cache.set(params.key, current);
return { value: current.value, stale: false };
}
// Window-hour rollover can reuse prior cells without claiming a new age.
let previous = current;
let previousKey = params.key;
if (!previous) {
for (const [key, entry] of cache) {
if (
entry.baseKey === params.baseKey &&
(!previous || entry.savedAt > previous.savedAt)
) {
previous = entry;
previousKey = key;
}
}
}
if (params.scope === 'global' && age(previous) < GLOBAL_TTL_MS)
return { value: previous.value, stale: false };
if (
params.scope === 'grid' &&
!params.waitForFresh &&
age(previous) <= STALE_MAX_MS
) {
cache.delete(previousKey);
cache.set(previousKey, previous);
// Retain one background consumer after this HTTP response closes. A stale
// tile is useful immediately; waiting for the token queue defeats SWR.
const cooling = client?.cooldownState('grid');
if (!cooling) {
const { promise } = refresh(params, true);
void promise.catch(() => {});
}
return {
value: previous.value,
stale: true,
...cooling,
};
}
try {
const { promise, group } = refresh(params);
return { value: await joinFlight(promise, group, signal), stale: false };
} catch (error) {
if (signal?.aborted) throw error;
if (age(previous) <= STALE_MAX_MS)
return {
value: previous.value,
stale: true,
retryAfterS: error.retryAfterS,
upstreamStatus: error.upstreamStatus,
};
throw error;
}
}
async function handler(req, res) {
const json = (
status,
value,
{ stale = false, retryAfterS, upstreamStatus } = {},
) => {
if (res.destroyed) return;
res.writeHead(status, {
'Content-Type': 'application/json',
'Cache-Control': 'no-store',
...(status === 405 ? { Allow: 'GET' } : {}),
...(Number.isFinite(retryAfterS)
? { 'Retry-After': String(retryAfterS) }
: {}),
...(upstreamStatus
? { 'X-Upstream-Status': String(upstreamStatus) }
: {}),
...(stale ? { 'X-Data-Stale': 'true' } : {}),
});
res.end(JSON.stringify(value));
};
if (admitSameSiteKeyless(req, res)) return;
if (req.method !== 'GET') return json(405, { error: 'method_not_allowed' });
const url = String(req.url || '/');
const queryAt = url.indexOf('?');
const pathname = queryAt < 0 ? url : url.slice(0, queryAt);
if (pathname !== '/observations' && pathname !== '/grid')
return json(404, { error: 'unknown_route' });
const params = requestParams(
pathname,
new URLSearchParams(queryAt < 0 ? '' : url.slice(queryAt + 1)),
now(),
);
if (params.error) return json(400, { error: params.error });
const controller = new AbortController();
const abort = () => controller.abort();
// IncomingMessage.close also fires after a complete request body. The
// response close identifies the abandoned browser fetch, while aborted
// covers a request cut short before it reached the response stage.
req.once?.('aborted', abort);
res.once?.('close', abort);
try {
const { value, stale, retryAfterS, upstreamStatus } = await acquire(
params,
controller.signal,
);
return json(200, stale ? { ...value, stale: true } : value, {
stale,
retryAfterS,
upstreamStatus,
});
} catch (error) {
if (controller.signal.aborted) return;
const status =
error.code === 'inaturalist_budget_exhausted'
? 503
: error.status === 429
? 429
: 502;
const code =
error.code === 'inaturalist_budget_exhausted'
? error.code
: status === 429
? 'inaturalist_rate_limited'
: 'inaturalist_unavailable';
return json(
status,
{ error: code },
{
retryAfterS: error.retryAfterS,
upstreamStatus: error.upstreamStatus,
},
);
} finally {
req.removeListener?.('aborted', abort);
res.removeListener?.('close', abort);
}
}
return {
name: 'inaturalist',
configureServer({ middlewares }) {
middlewares.use('/api/inaturalist', handler);
},
configurePreviewServer({ middlewares }) {
middlewares.use('/api/inaturalist', handler);
},
};
}
@@ -0,0 +1,893 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { mkdtemp, readFile, readdir, rm } from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import { installInatProxy, NOW } from './proxy.fixture.mjs';
import { createInatClient, inatRetryCooldownMs } from './client.js';
const observation = (id = 1) => ({
id,
uri: `https://www.inaturalist.org/observations/${id}`,
geojson: { coordinates: [-122.351, 47.613] },
time_observed_at: '2026-11-04T17:00:00Z',
observed_on: '2026-11-04',
created_at: '2026-11-04T17:01:00Z',
quality_grade: 'research',
license_code: 'cc-by',
positional_accuracy: 12,
faves_count: 2,
num_identification_agreements: 3,
taxon: {
name: 'Calypte anna',
preferred_common_name: "Anna's hummingbird",
iconic_taxon_name: 'Aves',
},
photos: [
{
url: 'https://inaturalist-open-data.s3.amazonaws.com/photos/123/square.jpeg',
license_code: 'cc-by',
attribution: 'Synthetic photographer, CC BY',
},
],
user: { login: 'must-never-leave-server' },
});
const envelope = (rows = [observation()], total = rows.length) => ({
total_results: total,
page: 1,
per_page: 200,
results: rows,
});
const boxUrl = (index = 0, extra = '') =>
`/observations?south=47.51&west=${-122.49 + index * 0.2}&north=47.69&east=${-122.31 + index * 0.2}${extra}`;
function install(options = {}, hook = 'configureServer') {
return installInatProxy(
boxUrl(),
{
// Serial dispatch makes queued cancellation and coalescing observable.
queueOptions: { intervalMs: 0, windowMs: 0, maxInFlight: 1 },
...options,
},
hook,
);
}
async function until(check) {
for (let i = 0; i < 100; i += 1) {
if (check()) return;
await new Promise(setImmediate);
}
assert.fail('expected fake upstream calls were not dispatched');
}
for (const hook of ['configureServer', 'configurePreviewServer']) {
test(`${hook}: actual v2 envelope, fixed query, projection and privacy`, async () => {
const calls = [];
const cache = new Map();
const request = install(
{
cache,
fetchImpl: async (url, options) => {
calls.push(new URL(url));
assert.equal(options.redirect, 'error');
assert.ok(options.signal instanceof AbortSignal);
assert.equal(options.headers.Accept, 'application/json');
assert.equal(
options.headers['User-Agent'],
'gods-eye-view-wildlife-sightings-proxy/1.0 (+https://github.com/bilawalsidhu/gods-eye-view)',
);
return Response.json(envelope());
},
},
hook,
);
assert.equal(calls.length, 0, 'construction and mounting do not acquire');
const result = await request(
boxUrl(0, '&url=https://attacker.example&per_page=999'),
);
assert.equal(result.status, 200);
assert.equal(result.headers['Cache-Control'], 'no-store');
assert.equal(result.body.rows[0].stableId, 'inat:1');
assert.equal(result.body.fetchedAt, NOW);
assert.equal(result.body.scope, 'view');
assert.deepEqual(result.body.box, {
south: 47.5,
west: -122.5,
north: 47.7,
east: -122.3,
});
const query = calls[0].searchParams;
assert.equal(
calls[0].origin + calls[0].pathname,
'https://api.inaturalist.org/v2/observations',
);
// Only the projected fields are requested, never user or identification data.
assert.match(query.get('fields'), /^\(id:!t,uri:!t,.*photos:\(url:!t/);
assert.doesNotMatch(query.get('fields'), /user|identifications/);
assert.equal(query.get('swlat'), '47.5');
assert.equal(query.get('swlng'), '-122.5');
assert.equal(query.get('nelat'), '47.7');
assert.equal(query.get('nelng'), '-122.3');
assert.equal(query.get('d1'), '2026-11-02');
assert.equal(query.has('created_d1'), false);
assert.equal(query.get('per_page'), '200');
assert.equal(query.get('verifiable'), 'true');
assert.equal(query.get('photos'), 'true');
assert.equal(query.get('captive'), 'false');
assert.equal(query.get('order_by'), 'random');
assert.equal(query.has('order'), false);
assert.doesNotMatch(
JSON.stringify(result.body),
/must-never-leave-server|"user"/,
);
assert.doesNotMatch(
JSON.stringify([...cache.values()]),
/must-never-leave-server|"user"/,
);
});
}
test('gate, method, route and query validation run before quota, cache or upstream', async () => {
const cache = new Map();
let calls = 0;
const request = install({
cache,
fetchImpl: async () => {
calls += 1;
return Response.json(envelope());
},
});
for (let i = 0; i < 32; i += 1) {
const response = await request('/observations?scope=global', {
headers: {
host: 'localhost:5173',
origin: 'https://foreign.example',
'sec-fetch-site': 'cross-site',
},
});
assert.equal(response.status, 403);
}
const method = await request('/observations', { method: 'POST' });
assert.equal(method.status, 405);
assert.equal(method.headers.Allow, 'GET');
for (const route of ['/', '/unknown', '/observations/extra', '//[invalid'])
assert.equal((await request(route)).body.error, 'unknown_route');
assert.equal(
(await request('/observations?scope=planet')).body.error,
'invalid_scope',
);
for (const query of [
'',
'south=NaN&west=0&north=1&east=1',
'south=&west=0&north=1&east=1',
'south=0&west=0&north=0&east=1',
'south=0&west=1&north=1&east=0',
'south=-91&west=0&north=1&east=1',
'south=0&west=-181&north=1&east=1',
'south=0&west=0&north=41&east=1',
'south=0&west=0&north=1&east=41',
]) {
const result = await request(`/observations?${query}`);
assert.equal(result.status, 400, query);
assert.equal(result.body.error, 'invalid_bbox', query);
}
assert.equal(cache.size, 0);
assert.equal(calls, 0);
const allowed = await request('/observations?scope=global', {
headers: {
host: 'gev.example',
origin: 'https://gev.example',
'sec-fetch-site': 'same-origin',
'x-forwarded-proto': 'https',
'x-forwarded-for': '203.0.113.9',
},
});
assert.equal(allowed.status, 200);
});
test('cached requests never exhaust a local request admission limiter', async () => {
let calls = 0;
const request = install({
fetchImpl: async () => {
calls += 1;
return Response.json(envelope());
},
});
for (let i = 0; i < 30; i += 1) assert.equal((await request()).status, 200);
const cached = await request();
assert.equal(cached.status, 200);
assert.equal(cached.headers['Retry-After'], undefined);
assert.equal(calls, 1);
});
test('proxy rejects oversized snapped spans before acquisition and preserves canonical client boxes', async () => {
const urls = [];
const request = install({
fetchImpl: async (url) => {
urls.push(new URL(url));
return Response.json(envelope());
},
});
for (const query of [
'south=10.2&west=0&north=50.1&east=1',
'south=0&west=10.2&north=1&east=50.1',
]) {
const result = await request(`/observations?${query}`);
assert.equal(result.status, 400);
assert.equal(result.body.error, 'invalid_bbox');
}
assert.equal(urls.length, 0);
const canonical =
'/observations?south=47.4&west=-122.3&north=47.6&east=-122.1';
const first = await request(canonical);
assert.equal(first.status, 200);
assert.deepEqual(first.body.box, {
south: 47.4,
west: -122.3,
north: 47.6,
east: -122.1,
});
assert.equal(urls[0].searchParams.get('swlat'), '47.4');
assert.equal(urls[0].searchParams.get('swlng'), '-122.3');
const second = await request(canonical);
assert.deepEqual(second.body, first.body);
assert.equal(urls.length, 1, 'canonical boxes share their cache key');
});
test('view snapping, whales, research and global queries obey the requested policy', async () => {
const urls = [];
const request = install({
fetchImpl: async (url) => {
urls.push(new URL(url));
return Response.json(envelope());
},
});
await request(
'/observations?south=40.2&west=-123.7&north=46.1&east=-121.4&quality=research&taxa=Aves',
);
assert.equal(urls[0].searchParams.get('swlat'), '40');
assert.equal(urls[0].searchParams.get('nelat'), '47');
assert.equal(urls[0].searchParams.get('per_page'), '200');
assert.equal(urls[0].searchParams.get('quality_grade'), 'research');
assert.equal(urls[0].searchParams.get('iconic_taxa'), 'Aves');
await request(boxUrl(0, '&taxa=whales'));
const whales = urls[1].searchParams;
assert.equal(whales.get('taxon_id'), '152871');
assert.equal(whales.get('d1'), '2026-10-28');
assert.equal(whales.has('created_d1'), false);
assert.equal(whales.has('photos'), false);
assert.equal(whales.has('iconic_taxa'), false);
await request('/observations?scope=global&quality=typo&taxa=typo&south=bad');
const global = urls[2].searchParams;
for (const field of [
'swlat',
'swlng',
'nelat',
'nelng',
'quality_grade',
'iconic_taxa',
])
assert.equal(global.has(field), false, field);
assert.equal(global.get('per_page'), '200');
assert.equal(global.get('d1'), '2026-11-02');
assert.equal(global.has('created_d1'), false);
assert.equal(global.get('order_by'), 'observed_on');
assert.equal(global.get('order'), 'desc');
await request('/observations?scope=global&taxa=whales');
const globalWhales = urls[3].searchParams;
assert.equal(globalWhales.get('d1'), '2026-10-28');
assert.equal(globalWhales.has('created_d1'), false);
assert.equal(globalWhales.get('order_by'), 'observed_on');
assert.equal(globalWhales.get('order'), 'desc');
});
test('fresh cache hits and global ten-minute TTL spend no upstream quota', async () => {
let at = NOW;
let calls = 0;
const request = install({
now: () => at,
fetchImpl: async () => {
calls += 1;
return Response.json(envelope());
},
});
const first = await request();
at += 299_999;
assert.deepEqual((await request()).body, first.body);
at += 1;
await request();
assert.equal(calls, 2);
await request('/observations?scope=global');
at += 599_999;
await request('/observations?scope=global');
assert.equal(calls, 3);
at += 1;
await request('/observations?scope=global');
assert.equal(calls, 4);
});
test('the same snapped view and window hour returns one cached random sample until expiry', async () => {
let at = NOW;
let draws = 0;
const cache = new Map();
const request = install({
cache,
now: () => at,
fetchImpl: async (url) => {
const query = new URL(url).searchParams;
assert.equal(query.get('order_by'), 'random');
assert.equal(query.get('per_page'), '200');
return Response.json(envelope([observation(++draws)]));
},
});
const first = await request();
for (const elapsed of [1, 1000, 60_000, 299_999]) {
at = NOW + elapsed;
const next = await request();
assert.deepEqual(next.body, first.body);
}
assert.equal(draws, 1);
assert.match(cache.keys().next().value, /2026-11-02T18:00:00Z$/);
at = NOW + 300_000;
const refresh = await request();
assert.equal(refresh.body.rows[0].stableId, 'inat:2');
assert.equal(draws, 2);
});
test('disconnected queued views are dropped while a remaining coalesced consumer keeps its request', async () => {
const calls = [];
const releases = [];
const cache = new Map();
const request = install({
cache,
fetchImpl: (url, { signal }) => {
calls.push({ url: new URL(url), signal });
return new Promise((resolve) => releases.push(resolve));
},
});
const first = request(boxUrl(0));
await until(() => releases.length === 1);
const superseded = new AbortController();
const oldView = request(boxUrl(1), { signal: superseded.signal });
const oneConsumer = new AbortController();
const one = request(boxUrl(2), { signal: oneConsumer.signal });
const other = request(boxUrl(2));
superseded.abort();
oneConsumer.abort();
await Promise.all([oldView, one]);
// Rejoining the abandoned key creates a healthy generation; the old
// coalescing promise must not later delete or cancel this replacement.
const replacement = request(boxUrl(1));
releases[0](Response.json(envelope()));
await until(() => releases.length === 2);
assert.equal(calls[1].url.searchParams.get('swlng'), '-122.1');
assert.equal(calls[1].signal.aborted, false);
releases[1](Response.json(envelope([observation(2)])));
await until(() => releases.length === 3);
assert.equal(calls[2].url.searchParams.get('swlng'), '-122.3');
releases[2](Response.json(envelope([observation(3)])));
const results = await Promise.all([first, other, replacement]);
assert.ok(results.every((result) => result.status === 200));
assert.equal(
calls.length,
3,
'the abandoned queued generation never dispatched',
);
assert.equal(cache.size, 3);
assert.equal(results[1].body.rows[0].stableId, 'inat:2');
assert.equal(results[2].body.rows[0].stableId, 'inat:3');
});
test('observation windows reject old sightings uploaded today and keep recent observations', async () => {
for (const [taxa, recentTime, expectedD1] of [
['all', '2026-11-04T17:00:00Z', '2026-11-02'],
['whales', '2026-10-30T17:00:00Z', '2026-10-28'],
]) {
let upstreamQuery;
const request = install({
fetchImpl: async (url) => {
upstreamQuery = new URL(url).searchParams;
return Response.json(
envelope([
{
...observation(80),
time_observed_at: '2010-06-01T12:00:00Z',
observed_on: '2010-06-01',
},
{
...observation(81),
time_observed_at: recentTime,
observed_on: recentTime.slice(0, 10),
},
]),
);
},
});
const result = await request(boxUrl(0, `&taxa=${taxa}`));
assert.equal(result.status, 200, taxa);
assert.equal(upstreamQuery.get('d1'), expectedD1);
assert.equal(upstreamQuery.has('created_d1'), false);
assert.equal(upstreamQuery.get('order_by'), 'random');
assert.deepEqual(
result.body.rows.map((row) => row.stableId),
['inat:81'],
);
}
});
test('same-key requests single-flight and a seventeenth distinct in-flight key queues', async () => {
const releases = [];
const request = install({
fetchImpl: () => new Promise((resolve) => releases.push(resolve)),
});
const a = request();
const b = request();
await until(() => releases.length === 1);
releases.shift()(Response.json(envelope()));
assert.deepEqual((await a).body, (await b).body);
const pending = Array.from({ length: 16 }, (_, i) => request(boxUrl(i + 1)));
await until(() => releases.length === 1);
pending.push(request(boxUrl(18)));
for (let i = 0; i < 17; i += 1) {
await until(() => releases.length > i);
releases[i](Response.json(envelope()));
}
assert.ok((await Promise.all(pending)).every(({ status }) => status === 200));
});
test('a fresh global sample survives the hourly observation-window rollover without another fetch', async () => {
let at = Date.parse('2026-11-04T18:59:00Z');
let calls = 0;
const request = install({
now: () => at,
fetchImpl: async () => {
calls += 1;
return Response.json(envelope());
},
});
const first = await request('/observations?scope=global');
at += 60_000;
const nextHour = await request('/observations?scope=global');
assert.deepEqual(nextHour.body, first.body);
assert.equal(nextHour.body.stale, undefined);
assert.equal(calls, 1);
at += 9 * 60_000;
await request('/observations?scope=global');
assert.equal(calls, 2);
});
test('pending global refresh coalesces across the hourly observation-window rollover with a busy queue', async () => {
let at = Date.parse('2026-11-04T18:59:59Z');
const releases = [];
const cache = new Map();
const request = install({
cache,
now: () => at,
fetchImpl: (url) =>
new Promise((resolve) =>
releases.push({
resolve,
url: new URL(url),
}),
),
});
const first = request(
'/observations?scope=global&quality=research&taxa=Aves',
);
const views = Array.from({ length: 15 }, (_, i) => request(boxUrl(i)));
await until(() => releases.length === 1);
assert.equal(releases[0].url.searchParams.get('d1'), '2026-11-02');
at = Date.parse('2026-11-04T19:00:01Z');
const nextHour = request(
'/observations?scope=global&quality=research&taxa=Aves',
);
await new Promise(setImmediate);
assert.equal(
releases.length,
1,
'the same global query joins the pending refresh',
);
const distinct = request(
'/observations?scope=global&quality=research&taxa=Mammalia',
);
for (let i = 0; i < 17; i += 1) {
await until(() => releases.length > i);
releases[i].resolve(Response.json(envelope()));
}
const [original, joined] = await Promise.all([first, nextHour]);
assert.equal(original.status, 200);
assert.equal((await distinct).status, 200);
assert.deepEqual(joined.body, original.body);
assert.ok((await Promise.all(views)).every(({ status }) => status === 200));
const globalEntries = [...cache.entries()].filter(
([, entry]) => entry.value.scope === 'global',
);
assert.equal(globalEntries.length, 2);
assert.match(globalEntries[0][0], /2026-11-02T18:00:00Z$/);
assert.equal(
(await request('/observations?scope=global&quality=research&taxa=Aves'))
.status,
200,
);
assert.equal(
releases.length,
17,
'the accepted global answer keeps its ten-minute freshness',
);
});
test('view refreshes retain distinct hourly keys while a previous-hour request is pending', async () => {
let at = Date.parse('2026-11-04T18:59:59Z');
const releases = [];
const request = install({
now: () => at,
fetchImpl: () => new Promise((resolve) => releases.push(resolve)),
});
const first = request();
await until(() => releases.length === 1);
at = Date.parse('2026-11-04T19:00:01Z');
const nextHour = request();
await new Promise(setImmediate);
assert.equal(
releases.length,
1,
'the next view queues behind the current call',
);
releases[0](Response.json(envelope()));
await until(() => releases.length === 2);
releases[1](Response.json(envelope()));
assert.ok(
(await Promise.all([first, nextHour])).every(
({ status }) => status === 200,
),
);
});
test('upstream minute ceiling waits for request 61 across client addresses', async () => {
let at = NOW;
let calls = 0;
const request = install({
now: () => at,
queueOptions: {
now: () => at,
intervalMs: 0,
wait: async (ms) => {
at += ms;
},
},
fetchImpl: async () => {
calls += 1;
return Response.json(envelope());
},
});
for (let i = 0; i < 60; i += 1)
assert.equal(
(await request(boxUrl(i), { peer: `10.0.0.${i}` })).status,
200,
);
const queued = await request(boxUrl(60), { peer: '10.0.1.1' });
assert.equal(queued.status, 200);
assert.equal(queued.headers['Retry-After'], undefined);
assert.equal(at, NOW + 60_000);
assert.equal(calls, 61);
});
test('views refresh after many prior requests without fabricating an upstream limit', async () => {
let at = NOW;
let calls = 0;
const request = install({
now: () => at,
fetchImpl: async () => {
calls += 1;
return Response.json(envelope());
},
});
const first = await request();
at += 300_001;
for (let i = 1; i <= 60; i += 1)
await request(boxUrl(i), { peer: `10.0.0.${i}` });
const stale = await request();
assert.equal(stale.status, 200);
assert.equal(stale.body.stale, undefined);
assert.notEqual(stale.body.fetchedAt, first.body.fetchedAt);
assert.equal(calls, 62);
});
test('bounded samples preserve partial metadata, and well-formed empty views are valid', async () => {
let at = NOW;
let fail = false;
const request = install({
now: () => at,
fetchImpl: async () =>
fail
? new Response('unavailable', { status: 503 })
: Response.json(envelope([observation()], 500)),
});
const first = await request();
assert.equal(first.body.partial, true);
assert.equal(first.body.rawRowCount, 500);
fail = true;
at += 59 * 60_000;
const stale = await request();
assert.equal(
stale.status,
200,
'hourly window changes still permit stale fallback',
);
assert.equal(stale.headers['X-Data-Stale'], 'true');
assert.equal(stale.body.stale, true);
assert.equal(stale.body.fetchedAt, first.body.fetchedAt);
assert.equal(stale.body.partial, true);
at += 2 * 60_000;
const expired = await request();
assert.equal(expired.status, 502);
assert.equal(expired.body.error, 'inaturalist_unavailable');
const empty = install({
fetchImpl: async () => Response.json(envelope([], 0)),
});
const result = await empty();
assert.equal(result.status, 200);
assert.deepEqual(result.body.rows, []);
});
test('bad envelopes, JSON and capped bodies fail closed without inventing a cooldown', async () => {
for (const bad of [
() => Response.json({ total_results: 1, results: {} }),
() => Response.json({ total_results: 1, observations: [observation()] }),
() => Response.json({ total_results: '1', results: [observation()] }),
() => Response.json(envelope([], 1)),
() => Response.json(envelope([{ ...observation(), id: null }], 1)),
() => new Response('{malformed'),
() =>
new Response('{}', {
headers: { 'Content-Length': String(8 * 1024 * 1024 + 1) },
}),
]) {
let at = NOW;
let calls = 0;
const request = install({
now: () => at,
fetchImpl: async () => {
calls += 1;
return calls === 1 ? bad() : Response.json(envelope());
},
});
const first = await request();
assert.equal(first.status, 502);
assert.equal(first.body.error, 'inaturalist_unavailable');
assert.equal(first.headers['Retry-After'], undefined);
assert.equal((await request(boxUrl(1))).status, 200);
assert.equal(calls, 2);
}
});
test('a wholly invalid iNat page cannot replace a previously valid snapshot', async () => {
let at = NOW;
let fail = false;
const cache = new Map();
const request = install({
cache,
now: () => at,
fetchImpl: async () =>
Response.json(
fail
? envelope([{ ...observation(), quality_grade: 'casual' }])
: envelope(),
),
});
const first = await request();
const original = cache.values().next().value;
fail = true;
at += 300_001;
const stale = await request();
assert.equal(stale.status, 200);
assert.equal(stale.body.stale, true);
assert.equal(stale.body.fetchedAt, first.body.fetchedAt);
assert.equal(cache.values().next().value, original);
});
test('429 cooldown honors seconds, HTTP dates, and long upstream deadlines', async () => {
assert.equal(inatRetryCooldownMs(null, NOW), 60_000);
assert.equal(inatRetryCooldownMs('0', NOW), 0);
assert.equal(inatRetryCooldownMs('2', NOW), 2000);
assert.equal(inatRetryCooldownMs('999999', NOW), 999_999_000);
assert.equal(inatRetryCooldownMs('7200', NOW), 7_200_000);
assert.equal(
inatRetryCooldownMs(new Date(NOW + 7_200_000).toUTCString(), NOW),
7_200_000,
);
let at = NOW;
const releases = [];
const warnings = [];
const request = install({
now: () => at,
warn: (message) => warnings.push(message),
fetchImpl: () => new Promise((resolve) => releases.push(resolve)),
});
const first = request();
const later = request(boxUrl(1));
await until(() => releases.length === 1);
releases[0](
new Response('limit', {
status: 429,
headers: { 'Retry-After': '999999' },
}),
);
assert.equal((await first).headers['Retry-After'], '999999');
assert.equal((await later).headers['Retry-After'], '999999');
at += 1000;
assert.equal((await request(boxUrl(1))).headers['Retry-After'], '999998');
at += 5000;
const cooling = await request(boxUrl(2));
assert.equal(cooling.status, 429);
assert.equal(cooling.headers['Retry-After'], '999993');
assert.equal(
releases.length,
1,
'queued calls do not dispatch during cooldown',
);
assert.equal(
warnings.filter((message) => message.includes('capped')).length,
0,
);
});
for (const [upstreamStatus, expectedStatus, code, retryAfterS] of [
[429, 429, 'inaturalist_rate_limited', 120],
[503, 502, 'inaturalist_unavailable', 120],
]) {
test(`concurrent reservations honor an immediate upstream ${upstreamStatus} before dispatch`, async () => {
let calls = 0;
const warnings = [];
const client = createInatClient({
now: () => 0,
cacheDir: null,
queueOptions: { intervalMs: 0 },
warn: (message) => warnings.push(message),
fetchImpl: async () => {
calls += 1;
return new Response('synthetic failure', {
status: upstreamStatus,
headers: { 'Retry-After': '120' },
});
},
});
const params = {
scope: 'global',
quality: 'all',
taxa: 'all',
observedD1: '1969-12-30',
};
const results = await Promise.allSettled(
Array.from({ length: 3 }, () => client.getObservations(params)),
);
assert.equal(
calls,
1,
'pending reservations must not dispatch during cooldown',
);
for (const result of results) {
assert.equal(result.status, 'rejected');
assert.equal(result.reason.status, expectedStatus);
assert.equal(result.reason.code, code);
assert.equal(result.reason.retryAfterS, retryAfterS);
}
assert.equal(
warnings.length,
1,
'only the dispatched refresh logs a failure',
);
await assert.rejects(client.getObservations(params), {
status: expectedStatus,
code,
retryAfterS,
});
assert.equal(calls, 1);
});
}
test('budget is read lazily, persisted as counters only, enforced across restart and UTC rollover', async (t) => {
const parent = await mkdtemp(path.join(os.tmpdir(), 'gev-inat-budget-'));
t.after(() => rm(parent, { recursive: true, force: true }));
const cacheDir = path.join(parent, 'not-created');
const before = process.env.INATURALIST_DAILY_BUDGET;
process.env.INATURALIST_DAILY_BUDGET = '2';
t.after(() =>
before === undefined
? delete process.env.INATURALIST_DAILY_BUDGET
: (process.env.INATURALIST_DAILY_BUDGET = before),
);
let at = NOW;
let calls = 0;
const options = {
now: () => at,
cacheDir,
warn() {},
fetchImpl: async () => {
calls += 1;
return Response.json(envelope());
},
};
const client = createInatClient(options);
assert.deepEqual(
await readdir(parent),
[],
'construction performs no directory write',
);
const params = {
scope: 'global',
quality: 'all',
taxa: 'all',
observedD1: '2026-11-02',
};
await Promise.all([
client.getObservations(params),
client.getObservations(params),
]);
assert.deepEqual(
JSON.parse(await readFile(path.join(cacheDir, 'budget.json'), 'utf8')),
{ date: '2026-11-04', count: 2 },
);
assert.deepEqual(
await readdir(cacheDir),
['budget.json'],
'atomic temporary files are retired',
);
const restarted = createInatClient(options);
await assert.rejects(restarted.getObservations(params), {
status: 503,
code: 'inaturalist_budget_exhausted',
});
assert.equal(calls, 2);
at = Date.parse('2026-11-05T00:00:00Z');
await restarted.getObservations(params);
assert.deepEqual(restarted.budgetState(), {
date: '2026-11-05',
count: 1,
budget: 2,
});
assert.deepEqual(
JSON.parse(await readFile(path.join(cacheDir, 'budget.json'), 'utf8')),
{ date: '2026-11-05', count: 1 },
);
assert.equal(calls, 3);
});
test('memory cache is an LRU bounded to 400 projected entries', async () => {
let at = NOW;
const cache = new Map();
const request = install({
cache,
now: () => at,
fetchImpl: async () => Response.json(envelope()),
});
for (let i = 0; i < 400; i += 1) {
if (i && i % 20 === 0) at += 61_000;
await request(boxUrl(i), { peer: String(i) });
}
assert.equal(cache.size, 400);
const firstKey = cache.keys().next().value;
const secondKey = [...cache.keys()][1];
await request(boxUrl(0));
await request(boxUrl(400));
assert.equal(cache.size, 400);
assert.equal(cache.has(firstKey), true);
assert.equal(cache.has(secondKey), false);
});
test('aborted client calls spend no budget and never fetch', async () => {
const controller = new AbortController();
controller.abort();
const client = createInatClient({
now: () => NOW,
cacheDir: null,
queueOptions: { intervalMs: 0 },
fetchImpl: () => assert.fail('aborted call fetched'),
});
await assert.rejects(
client.getObservations({}, { signal: controller.signal }),
{ name: 'AbortError' },
);
assert.equal(client.budgetState().count, 0);
});
@@ -0,0 +1,70 @@
import assert from 'node:assert/strict';
import { EventEmitter } from 'node:events';
import { inaturalistProxy } from './observations.js';
export const NOW = Date.parse('2026-11-04T18:05:00Z');
/** Mount the proxy over synthetic HTTP objects, without opening a socket. */
export function installInatProxy(
defaultUrl,
options = {},
hook = 'configureServer',
) {
let handler;
const plugin = inaturalistProxy({
now: () => NOW,
cacheDir: null,
queueOptions: { intervalMs: 0, windowMs: 0 },
warn() {},
...options,
});
assert.equal(plugin.name, 'inaturalist');
assert.equal(typeof plugin.configureServer, 'function');
assert.equal(typeof plugin.configurePreviewServer, 'function');
plugin[hook]({
middlewares: {
use(mount, callback) {
assert.equal(mount, '/api/inaturalist');
handler = callback;
},
},
});
return async (
url = defaultUrl,
{ method = 'GET', peer = 'local', headers = {}, signal } = {},
) => {
const res = Object.assign(new EventEmitter(), {
headers: {},
writeHead(status, headers) {
this.status = status;
Object.assign(this.headers, headers);
},
setHeader(key, value) {
this.headers[key] = value;
},
end(value) {
this.status ??= this.statusCode;
this.body = JSON.parse(value);
},
});
const abort = () => {
res.destroyed = true;
res.emit('close');
};
signal?.addEventListener('abort', abort, { once: true });
try {
await handler(
Object.assign(new EventEmitter(), {
url,
method,
headers,
socket: { remoteAddress: peer },
}),
res,
);
} finally {
signal?.removeEventListener('abort', abort);
}
return res;
};
}
+109
View File
@@ -0,0 +1,109 @@
import { setTimeout as delay } from 'node:timers/promises';
// A four-request burst keeps a cold view responsive; thereafter every provider
// call shares the same one-per-second token supply. The rolling minute ceiling
// queues the few requests a token bucket alone could allow above 60/min.
const INAT_INTERVAL_MS = 1000;
const INAT_BURST = 4;
const INAT_WINDOW_MS = 60_000;
const INAT_MAX_PER_WINDOW = 60;
const INAT_MAX_IN_FLIGHT = 4;
/** Observation queries outrank tiles waiting for a token. Aborted consumers
* leave the queue before an attempt or daily-budget reservation is spent. */
export function createInatQueue({
now = () => performance.now(),
wait = (ms) => delay(ms),
intervalMs = INAT_INTERVAL_MS,
burst = INAT_BURST,
windowMs = INAT_WINDOW_MS,
maxInFlight = INAT_MAX_IN_FLIGHT,
} = {}) {
const pending = [];
const dispatchedAt = [];
let tokens = burst;
let refilledAt = null;
let running = false;
let inFlight = 0;
let slotFreed = null;
function refill() {
const at = now();
if (refilledAt !== null && intervalMs > 0)
tokens = Math.min(
burst,
tokens + Math.max(0, at - refilledAt) / intervalMs,
);
if (intervalMs <= 0) tokens = burst;
refilledAt = at;
}
function schedule() {
if (running) return;
running = true;
// A microtask also lets same-turn observation queries take priority.
void Promise.resolve().then(drain);
}
async function drain() {
try {
while (pending.length) {
if (inFlight >= maxInFlight) {
await new Promise((resolve) => (slotFreed = resolve));
continue;
}
refill();
const at = now();
while (dispatchedAt.length && dispatchedAt[0] <= at - windowMs)
dispatchedAt.shift();
const tokenWait = tokens < 1 ? (1 - tokens) * intervalMs : 0;
const windowWait =
windowMs > 0 && dispatchedAt.length >= INAT_MAX_PER_WINDOW
? dispatchedAt[0] + windowMs - at
: 0;
if (tokenWait > 0 || windowWait > 0) {
await wait(Math.ceil(Math.max(tokenWait, windowWait)));
continue;
}
// Stable sorting preserves FIFO order within each priority.
pending.sort((a, b) => a.priority - b.priority);
const job = pending.shift();
job.signal?.removeEventListener('abort', job.abort);
if (job.signal?.aborted) {
job.reject(job.signal.reason);
continue;
}
tokens -= 1;
if (windowMs > 0) dispatchedAt.push(at);
inFlight += 1;
// A rejected task releases its slot without stopping the queue.
void new Promise((resolve) => resolve(job.task()))
.then(job.resolve, job.reject)
.finally(() => {
inFlight -= 1;
const wake = slotFreed;
slotFreed = null;
wake?.();
});
}
} finally {
running = false;
}
}
return function enqueue(task, { signal, priority = 1 } = {}) {
if (signal?.aborted) return Promise.reject(signal.reason);
return new Promise((resolve, reject) => {
const job = { task, signal, priority, resolve, reject };
job.abort = () => {
const index = pending.indexOf(job);
if (index >= 0) pending.splice(index, 1);
signal.removeEventListener('abort', job.abort);
reject(signal.reason);
};
signal?.addEventListener('abort', job.abort, { once: true });
pending.push(job);
schedule();
});
};
}
+111
View File
@@ -0,0 +1,111 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { createInatQueue } from './queue.js';
async function until(check) {
for (let i = 0; i < 100; i += 1) {
if (check()) return;
await new Promise(setImmediate);
}
assert.fail('queue did not reach its expected state');
}
test('shared governor paces a four-call burst within the rolling minute ceiling', async () => {
let at = 0;
const waits = [];
const queue = createInatQueue({
now: () => at,
wait: async (ms) => {
waits.push(ms);
at += ms;
},
});
const dispatched = [];
await Promise.all(
Array.from({ length: 65 }, () => queue(() => dispatched.push(at))),
);
assert.equal(dispatched.length, 65);
assert.deepEqual(dispatched.slice(0, 7), [0, 0, 0, 0, 1000, 2000, 3000]);
assert.deepEqual(waits.slice(0, 3), [1000, 1000, 1000]);
for (const time of dispatched)
assert.ok(
dispatched.filter((other) => other > time - 60_000 && other <= time)
.length <= 60,
);
assert.equal(dispatched[60], 60_000);
});
test('an observation arriving during the pacing wait overtakes queued tiles', async () => {
let at = 0;
const waits = [];
const queue = createInatQueue({
burst: 1,
now: () => at,
wait: (ms) => new Promise((resolve) => waits.push({ ms, resolve })),
});
const dispatched = [];
await queue(() => dispatched.push('first tile'));
const tile = queue(() => dispatched.push('waiting tile'));
await until(() => waits.length === 1);
const observation = queue(() => dispatched.push('observation'), {
priority: 0,
});
at += waits[0].ms;
waits[0].resolve();
await observation;
assert.deepEqual(dispatched, ['first tile', 'observation']);
await until(() => waits.length === 2);
at += waits[1].ms;
waits[1].resolve();
await tile;
assert.deepEqual(dispatched, ['first tile', 'observation', 'waiting tile']);
});
test('superseded tasks leave before dispatch, and a rejection cannot poison the tail', async () => {
const queue = createInatQueue({ intervalMs: 0 });
const dispatched = [];
let release;
const first = queue(
() =>
new Promise((resolve) => {
release = resolve;
}),
);
await until(() => release);
const controller = new AbortController();
const superseded = queue(() => assert.fail('superseded tile dispatched'), {
signal: controller.signal,
});
const dropped = assert.rejects(superseded, { name: 'AbortError' });
controller.abort();
await dropped;
const failed = queue(() => {
dispatched.push('failed');
throw new Error('synthetic');
});
const failure = assert.rejects(failed, /synthetic/);
const succeeding = queue(() => dispatched.push('recovered'));
release();
await Promise.all([first, failure, succeeding]);
assert.deepEqual(dispatched, ['failed', 'recovered']);
});
test('a slow call does not hold later calls; at most four run at once', async () => {
const queue = createInatQueue({ intervalMs: 0, windowMs: 0 });
const releases = [];
const started = [];
const slow = (id) => () =>
new Promise((resolve) => {
started.push(id);
releases.push(() => resolve(id));
});
const jobs = [1, 2, 3, 4, 5].map((id) => queue(slow(id)));
await until(() => started.length === 4);
assert.deepEqual(started, [1, 2, 3, 4], 'four start without waiting');
releases[1]();
assert.equal(await jobs[1], 2, 'a later call finishes before the first');
await until(() => started.length === 5);
assert.deepEqual(started, [1, 2, 3, 4, 5], 'a freed slot starts the next');
for (const release of releases) release();
assert.deepEqual(await Promise.all(jobs), [1, 2, 3, 4, 5]);
});
+218
View File
@@ -0,0 +1,218 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { createInatClient } from './client.js';
import { installInatProxy, NOW } from './proxy.fixture.mjs';
import { syntheticGrid } from './grid.fixture.mjs';
import { INAT_GRID_DECODE_MEASURE } from './grid.js';
const gridUrl = '/grid?z=2&x=1&y=1&taxa=Aves';
const params = {
scope: 'global',
taxa: 'all',
observedD1: '2026-11-02',
};
const tick = () => new Promise(setImmediate);
const install = (options, hook) => installInatProxy(gridUrl, options, hook);
test('tile cache lasts fifteen minutes, then returns stale before revalidation finishes', async () => {
let at = NOW;
let calls = 0;
let release;
const request = install({
now: () => at,
fetchImpl: () => {
calls += 1;
return calls === 1
? Response.json(syntheticGrid())
: new Promise((resolve) => {
release = resolve;
});
},
});
const first = await request();
at += 899_999;
const freshPromise = request();
await tick();
assert.equal(calls, 1, 'a fresh tile never spends another upstream attempt');
assert.deepEqual((await freshPromise).body, first.body);
at += 1;
const stalePromise = request();
let resolved = false;
void stalePromise.then(() => {
resolved = true;
});
await tick();
try {
assert.equal(
resolved,
true,
'stale cells publish while refresh is pending',
);
const stale = await stalePromise;
assert.equal(stale.body.stale, true);
assert.equal(stale.body.fetchedAt, NOW);
assert.deepEqual(stale.body.cells, first.body.cells);
await request();
assert.equal(calls, 2, 'all stale consumers share one background refresh');
const refreshPromise = request(`${gridUrl}&refresh=wait`);
let refreshed = false;
void refreshPromise.then(() => {
refreshed = true;
});
await tick();
assert.equal(
refreshed,
false,
'a background consumer can await the same refresh',
);
release(Response.json(syntheticGrid()));
const fresh = await refreshPromise;
assert.equal(fresh.body.stale, undefined);
assert.equal(fresh.body.fetchedAt, at);
assert.equal(
calls,
2,
'waiting for fresh cells never duplicates upstream work',
);
release = null;
} finally {
release?.(Response.json(syntheticGrid()));
}
});
test('more than sixteen queued views and thirty local requests never report local rate limiting', async () => {
let release;
let calls = 0;
const request = install({
fetchImpl: async () => {
calls += 1;
if (calls === 1)
await new Promise((resolve) => {
release = resolve;
});
return Response.json(syntheticGrid());
},
});
const pending = Array.from({ length: 40 }, (_, x) =>
request(`/grid?z=6&x=${x}&y=1`),
);
await tick();
release();
const results = await Promise.all(pending);
assert.ok(results.every((result) => result.status === 200));
assert.equal(calls, 40);
});
test('only real upstream 429 or 5xx failures start cooldown', async () => {
for (const bad of [
() => Promise.reject(new Error('network')),
() => Response.json({ malformed: true }),
() => new Response('', { status: 404 }),
]) {
let calls = 0;
const client = createInatClient({
now: () => NOW,
cacheDir: null,
queueOptions: { intervalMs: 0, windowMs: 0 },
warn() {},
fetchImpl: () => {
calls += 1;
return calls === 1
? bad()
: Response.json({ results: [], total_results: 0 });
},
});
await assert.rejects(client.getObservations(params), { status: 502 });
await client.getObservations(params);
assert.equal(
calls,
2,
'a non-cooldown failure must not hold the next attempt',
);
}
});
test('actual upstream cooldown is marked separately from a local budget stop', async () => {
for (const upstream of [429, 503]) {
let calls = 0;
const request = install({
fetchImpl: () => {
calls += 1;
return new Response('', {
status: upstream,
headers: { 'Retry-After': '120' },
});
},
});
const first = await request();
assert.equal(first.headers['X-Upstream-Status'], String(upstream));
assert.equal(first.headers['Retry-After'], '120');
await request();
assert.equal(calls, 1);
}
});
test('upstream Retry-After zero survives the HTTP response and permits an immediate next request', async () => {
for (const upstream of [429, 503]) {
let calls = 0;
const request = install({
fetchImpl: () => {
calls += 1;
return calls === 1
? new Response('', {
status: upstream,
headers: { 'Retry-After': '0' },
})
: Response.json(syntheticGrid());
},
});
const first = await request();
assert.equal(first.headers['X-Upstream-Status'], String(upstream));
assert.equal(first.headers['Retry-After'], '0');
assert.equal((await request()).status, 200);
assert.equal(calls, 2);
}
});
test('a local daily budget stop has a distinct code and no upstream cooldown marker', async (t) => {
const before = process.env.INATURALIST_DAILY_BUDGET;
process.env.INATURALIST_DAILY_BUDGET = '1';
t.after(() => {
if (before === undefined) delete process.env.INATURALIST_DAILY_BUDGET;
else process.env.INATURALIST_DAILY_BUDGET = before;
});
let calls = 0;
const request = install({
fetchImpl: () => {
calls += 1;
return Response.json(syntheticGrid());
},
});
assert.equal((await request()).status, 200);
const stopped = await request('/grid?z=2&x=0&y=1');
assert.equal(stopped.status, 503);
assert.equal(stopped.body.error, 'inaturalist_budget_exhausted');
assert.equal(stopped.headers['X-Upstream-Status'], undefined);
assert.equal((await request()).status, 200, 'cached data remains available');
assert.equal(calls, 1);
});
test('cached tiles avoid UTFGrid decode and decode timing entries stay bounded', async () => {
const request = install({
fetchImpl: () => Response.json(syntheticGrid()),
});
await request();
const sample = performance.getEntriesByName(INAT_GRID_DECODE_MEASURE)[0];
assert.ok(sample.duration >= 0);
for (let i = 0; i < 10; i += 1) await request();
assert.equal(
performance.getEntriesByName(INAT_GRID_DECODE_MEASURE)[0],
sample,
);
for (let x = 0; x < 4; x += 1) await request(`/grid?z=3&x=${x}&y=1`);
assert.equal(
performance.getEntriesByName(INAT_GRID_DECODE_MEASURE).length,
1,
);
});
+4
View File
@@ -29,6 +29,8 @@ import { firePerimetersProxy } from './firePerimeters.js';
import { gnssIntegrityProxy } from './gnssIntegrity.js';
import { ghostMapsProxy } from './ghostMaps.js';
import { droneAirspaceProxy } from './droneAirspace.js';
import { inaturalistProxy } from './inaturalist/observations.js';
import { acartiaProxy } from './acartia.js';
import { cycloneProxy } from './cyclones.js';
import { windProxy } from './wind.js';
@@ -81,6 +83,8 @@ function localProviderPlugins({
gnssIntegrityProxy(),
ghostMapsProxy(),
droneAirspaceProxy(),
inaturalistProxy(),
acartiaProxy(),
keySetupEndpoint(),
];
}