feat: complete dashcam catalog ingestion demo
Master Production Deploy / production (push) Failing after 49s

This commit is contained in:
2026-07-31 02:01:58 +03:00
parent ae6c35145f
commit 2ce58fcbc0
62 changed files with 6341 additions and 178 deletions
+87
View File
@@ -0,0 +1,87 @@
const namedEntities: Record<string, string> = {
amp: '&',
gt: '>',
lt: '<',
quot: '"',
apos: "'",
nbsp: ' ',
};
export function decodeHtml(value: string) {
return value.replace(/&(#x[0-9a-f]+|#\d+|[a-z]+);/gi, (entity, code: string) => {
const normalized = code.toLowerCase();
if (normalized.startsWith('#x')) {
return String.fromCodePoint(Number.parseInt(normalized.slice(2), 16));
}
if (normalized.startsWith('#')) {
return String.fromCodePoint(Number.parseInt(normalized.slice(1), 10));
}
return namedEntities[normalized] ?? entity;
});
}
export function stripTags(value: string) {
return decodeHtml(value.replace(/<[^>]*>/g, ' ')).replace(/\s+/g, ' ').trim();
}
export function normalizeWhitespace(value: string) {
return value.trim().replace(/\s+/g, ' ');
}
export function uniqueStable<T>(values: T[]) {
return [...new Set(values)];
}
function parseJsonPayload(raw: string) {
return JSON.parse(decodeHtml(raw).trim()) as unknown;
}
function flattenJsonLd(input: unknown): unknown[] {
if (Array.isArray(input)) {
return input.flatMap(flattenJsonLd);
}
if (input && typeof input === 'object') {
const object = input as Record<string, unknown>;
const graph = object['@graph'];
return graph ? [object, ...flattenJsonLd(graph)] : [object];
}
return [];
}
export function extractJsonLdObjects(html: string) {
const scripts = [...html.matchAll(/<script[^>]+type=["']application\/ld\+json["'][^>]*>([\s\S]*?)<\/script>/gi)];
const objects: unknown[] = [];
for (const [, raw] of scripts) {
try {
objects.push(...flattenJsonLd(parseJsonPayload(raw)));
} catch {
// Ignore malformed analytics or partial JSON-LD blocks.
}
}
return objects;
}
export function getObjectString(value: unknown) {
if (typeof value === 'string') return normalizeWhitespace(value);
if (value && typeof value === 'object' && typeof (value as { name?: unknown }).name === 'string') {
return normalizeWhitespace((value as { name: string }).name);
}
return undefined;
}
export function readMetaContent(html: string, property: string) {
const escaped = property.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const pattern = new RegExp(`<meta[^>]+(?:property|name)=["']${escaped}["'][^>]+content=["']([^"']+)["'][^>]*>`, 'i');
const match = html.match(pattern);
return match ? decodeHtml(match[1]).trim() : undefined;
}