refactor: add configurable Nominatim adapter and portable source helpers

This commit is contained in:
Sameh Khamis committed 2026-09-14 22:08:16 -07:00
1 parent 2b1092bf89
commit 66eb6d6a7c
14 files changed
+646 -152

No files matched your search

+54
View File
@@ -0,0 +1,54 @@
/**
* Read a fetch() Response body as text with a hard byte cap. Rejects early on an
* oversized Content-Length, then streams with a running cap so a chunked or
* length-omitted response cannot blow past the limit. Throws { code:'RESPONSE_TOO_LARGE' }.
*/
export async function readResponseTextCapped(response, maxBytes, signal) {
const tooLarge = () =>
Object.assign(new Error('Upstream response too large'), {
code: 'RESPONSE_TOO_LARGE',
});
const declared = Number(response.headers.get('content-length'));
if (Number.isFinite(declared) && declared > maxBytes) {
void response.body?.cancel().catch(() => {});
throw tooLarge();
}
signal?.throwIfAborted();
const reader = response.body?.getReader?.();
if (!reader) {
const text = await response.text();
signal?.throwIfAborted();
if (new TextEncoder().encode(text).byteLength > maxBytes) throw tooLarge();
return text;
}
const cancel = () => {
void reader.cancel().catch(() => {});
};
signal?.addEventListener('abort', cancel, { once: true });
const decoder = new TextDecoder();
let out = '';
let total = 0;
try {
for (;;) {
signal?.throwIfAborted();
const { done, value } = await reader.read();
signal?.throwIfAborted();
if (done) break;
total += value.byteLength;
if (total > maxBytes) throw tooLarge();
out += decoder.decode(value, { stream: true });
}
return out + decoder.decode();
} catch (error) {
cancel();
throw error;
} finally {
signal?.removeEventListener('abort', cancel);
reader.releaseLock();
}
}
/** Parse a fetch() JSON response only after enforcing a hard byte cap. */
export async function readResponseJsonCapped(response, maxBytes, signal) {
return JSON.parse(await readResponseTextCapped(response, maxBytes, signal));
}
+48
View File
@@ -0,0 +1,48 @@
/**
* Single-pass lexer: blank out quoted literals (→ empty quotes) and strip line
* and block comments — recognizing each in one walk so a comment marker INSIDE a
* quoted string is treated as string content, not a comment (and vice versa).
* Chained regex replaces get the ordering wrong (a quoted slash-slash would hide
* the rest of the line), which is exactly the bypass this avoids.
*/
export function stripOverpassNoise(src) {
let out = '';
let i = 0;
const n = src.length;
while (i < n) {
const c = src[i];
if (c === '"' || c === "'") {
const quote = c;
i += 1;
while (i < n) {
if (src[i] === '\\') {
i += 2;
continue;
} // escaped char
if (src[i] === quote) {
i += 1;
break;
} // closing quote
i += 1;
}
out += quote + quote; // collapse the literal to empty quotes
continue;
}
if (c === '/' && src[i + 1] === '*') {
i += 2;
while (i < n && !(src[i] === '*' && src[i + 1] === '/')) i += 1;
i += 2;
out += ' ';
continue;
}
if (c === '/' && src[i + 1] === '/') {
i += 2;
while (i < n && src[i] !== '\n') i += 1;
out += ' ';
continue;
}
out += c;
i += 1;
}
return out;
}