MIRROR: javascript for ๐Ÿœ's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

split/clean up code in js.reports

+256 -249
+4
docs/reports/src/env.ts
··· 1 + export type Bindings = { 2 + ASSETS: Fetcher; 3 + DB: D1Database; 4 + };
+63
docs/reports/src/helpers.ts
··· 1 + import type { Bindings } from './env'; 2 + 3 + let antLogoDataUrl: Promise<string> | null = null; 4 + let ogFontBytes: Promise<Uint8Array[]> | null = null; 5 + 6 + export async function sha256(input: string): Promise<string> { 7 + const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(input)); 8 + return [...new Uint8Array(digest)].map(byte => byte.toString(16).padStart(2, '0')).join(''); 9 + } 10 + 11 + export function publicUrl(requestUrl: string, id: string): string { 12 + const url = new URL(requestUrl); 13 + return `${url.origin}/${id}`; 14 + } 15 + 16 + export function publicOgImageUrl(requestUrl: string, id: string): string { 17 + const url = new URL(requestUrl); 18 + return `${url.origin}/og/${id}.png`; 19 + } 20 + 21 + export function ogImageId(value: string): string { 22 + return value.endsWith('.png') ? value.slice(0, -4) : value; 23 + } 24 + 25 + function bytesToBase64(bytes: Uint8Array): string { 26 + let binary = ''; 27 + const chunkSize = 0x8000; 28 + for (let i = 0; i < bytes.length; i += chunkSize) { 29 + binary += String.fromCharCode(...bytes.subarray(i, i + chunkSize)); 30 + } 31 + return btoa(binary); 32 + } 33 + 34 + async function loadAntLogoDataUrl(env: Bindings, requestUrl: string): Promise<string> { 35 + const response = await env.ASSETS.fetch(new Request(new URL('/assets/ant.png', requestUrl))); 36 + if (!response.ok) return ''; 37 + const bytes = new Uint8Array(await response.arrayBuffer()); 38 + return `data:image/png;base64,${bytesToBase64(bytes)}`; 39 + } 40 + 41 + async function loadAssetBytes( 42 + env: Bindings, 43 + requestUrl: string, 44 + path: string, 45 + ): Promise<Uint8Array> { 46 + const response = await env.ASSETS.fetch(new Request(new URL(path, requestUrl))); 47 + if (!response.ok) return new Uint8Array(); 48 + return new Uint8Array(await response.arrayBuffer()); 49 + } 50 + 51 + export function getAntLogoDataUrl(env: Bindings, requestUrl: string): Promise<string> { 52 + antLogoDataUrl ??= loadAntLogoDataUrl(env, requestUrl); 53 + return antLogoDataUrl; 54 + } 55 + 56 + export function getOgFontBytes(env: Bindings, requestUrl: string): Promise<Uint8Array[]> { 57 + ogFontBytes ??= Promise.all([ 58 + loadAssetBytes(env, requestUrl, '/assets/Arial.ttf'), 59 + loadAssetBytes(env, requestUrl, '/assets/Arial-Bold.ttf'), 60 + loadAssetBytes(env, requestUrl, '/assets/BerkeleyMono-Regular.woff2'), 61 + ]); 62 + return ogFontBytes; 63 + }
-248
docs/reports/src/index.ts
··· 1 - import { Hono } from 'hono'; 2 - import { renderOgPng } from './og'; 3 - import { asc, eq } from 'drizzle-orm'; 4 - import { drizzle } from 'drizzle-orm/d1'; 5 - import { renderBlank, renderReport } from './view'; 6 - import { generate as generateShortUuid } from 'short-uuid'; 7 - 8 - import { 9 - CrashReportSchema, 10 - frames as frameTable, 11 - reportFrames, 12 - reports, 13 - type CrashReport, 14 - } from './schema'; 15 - 16 - type Bindings = { ASSETS: Fetcher; DB: D1Database }; 17 - const app = new Hono<{ Bindings: Bindings }>(); 18 - 19 - let antLogoDataUrl: Promise<string> | null = null; 20 - let ogFontBytes: Promise<Uint8Array[]> | null = null; 21 - 22 - app.get('/', c => c.html(renderBlank(), 404)); 23 - 24 - async function reportTrace(report: CrashReport): Promise<string> { 25 - const traceInput = JSON.stringify({ 26 - runtime: report.runtime, 27 - version: report.version, 28 - target: report.target, 29 - os: report.os, 30 - arch: report.arch, 31 - code: report.code, 32 - reason: report.reason, 33 - addr: report.addr, 34 - frames: report.frames, 35 - }); 36 - 37 - return sha256(traceInput); 38 - } 39 - 40 - async function sha256(input: string): Promise<string> { 41 - const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(input)); 42 - return [...new Uint8Array(digest)].map(byte => byte.toString(16).padStart(2, '0')).join(''); 43 - } 44 - 45 - function publicUrl(requestUrl: string, id: string): string { 46 - const url = new URL(requestUrl); 47 - return `${url.origin}/${id}`; 48 - } 49 - 50 - function publicOgImageUrl(requestUrl: string, id: string): string { 51 - const url = new URL(requestUrl); 52 - return `${url.origin}/og/${id}.png`; 53 - } 54 - 55 - function bytesToBase64(bytes: Uint8Array): string { 56 - let binary = ''; 57 - const chunkSize = 0x8000; 58 - for (let i = 0; i < bytes.length; i += chunkSize) { 59 - binary += String.fromCharCode(...bytes.subarray(i, i + chunkSize)); 60 - } 61 - return btoa(binary); 62 - } 63 - 64 - async function loadAntLogoDataUrl(env: Bindings, requestUrl: string): Promise<string> { 65 - const response = await env.ASSETS.fetch(new Request(new URL('/assets/ant.png', requestUrl))); 66 - if (!response.ok) return ''; 67 - const bytes = new Uint8Array(await response.arrayBuffer()); 68 - return `data:image/png;base64,${bytesToBase64(bytes)}`; 69 - } 70 - 71 - async function loadAssetBytes( 72 - env: Bindings, 73 - requestUrl: string, 74 - path: string, 75 - ): Promise<Uint8Array> { 76 - const response = await env.ASSETS.fetch(new Request(new URL(path, requestUrl))); 77 - if (!response.ok) return new Uint8Array(); 78 - return new Uint8Array(await response.arrayBuffer()); 79 - } 80 - 81 - function getAntLogoDataUrl(env: Bindings, requestUrl: string): Promise<string> { 82 - antLogoDataUrl ??= loadAntLogoDataUrl(env, requestUrl); 83 - return antLogoDataUrl; 84 - } 85 - 86 - function ogImageId(value: string): string { 87 - return value.endsWith('.png') ? value.slice(0, -4) : value; 88 - } 89 - 90 - function getOgFontBytes(env: Bindings, requestUrl: string): Promise<Uint8Array[]> { 91 - ogFontBytes ??= Promise.all([ 92 - loadAssetBytes(env, requestUrl, '/assets/Arial.ttf'), 93 - loadAssetBytes(env, requestUrl, '/assets/Arial-Bold.ttf'), 94 - loadAssetBytes(env, requestUrl, '/assets/BerkeleyMono-Regular.woff2'), 95 - ]); 96 - return ogFontBytes; 97 - } 98 - 99 - const reportFromRows = ( 100 - row: typeof reports.$inferSelect, 101 - frameRows: { frame: string }[], 102 - ): CrashReport => ({ 103 - schema: 1, 104 - runtime: 'ant', 105 - version: row.version, 106 - target: row.target, 107 - os: row.os, 108 - arch: row.arch, 109 - kind: row.kind, 110 - code: row.code, 111 - reason: row.reason, 112 - addr: row.faultAddress, 113 - elapsedMs: row.elapsedMs, 114 - peakRss: row.peakRss, 115 - frames: frameRows.map(row => row.frame), 116 - }); 117 - 118 - async function insertReportFrames( 119 - db: ReturnType<typeof drizzle>, 120 - reportId: string, 121 - frames: string[], 122 - ): Promise<void> { 123 - if (!frames.length) return; 124 - const frameHashes = await Promise.all(frames.map(frame => sha256(frame))); 125 - const uniqueFrames = new Map<string, string>(); 126 - 127 - frames.forEach((frame, index) => { 128 - uniqueFrames.set(frameHashes[index], frame); 129 - }); 130 - 131 - await db 132 - .insert(frameTable) 133 - .values([...uniqueFrames].map(([hash, frame]) => ({ hash, frame }))) 134 - .onConflictDoNothing(); 135 - 136 - await db.insert(reportFrames).values( 137 - frameHashes.map((frameHash, index) => ({ 138 - frameHash, 139 - reportId, 140 - frameIndex: index, 141 - })), 142 - ); 143 - } 144 - 145 - async function getReportFrames( 146 - db: ReturnType<typeof drizzle>, 147 - reportId: string, 148 - ): Promise<{ frame: string }[]> { 149 - return db 150 - .select({ 151 - frame: frameTable.frame, 152 - }) 153 - .from(reportFrames) 154 - .innerJoin(frameTable, eq(reportFrames.frameHash, frameTable.hash)) 155 - .where(eq(reportFrames.reportId, reportId)) 156 - .orderBy(asc(reportFrames.frameIndex)); 157 - } 158 - 159 - async function getReport(db: ReturnType<typeof drizzle>, id: string): Promise<CrashReport | null> { 160 - const [row] = await db.select().from(reports).where(eq(reports.id, id)).limit(1); 161 - 162 - if (!row) return null; 163 - const frames = await getReportFrames(db, row.id); 164 - return reportFromRows(row, frames); 165 - } 166 - 167 - app.get('/og/:image', async c => { 168 - const db = drizzle(c.env.DB); 169 - const id = ogImageId(c.req.param('image')); 170 - 171 - const cache = caches.default; 172 - const cacheKey = new Request(publicOgImageUrl(c.req.url, id), { method: 'GET' }); 173 - 174 - const cached = await cache.match(cacheKey); 175 - if (cached) return cached; 176 - 177 - const report = await getReport(db, id); 178 - if (!report) return c.notFound(); 179 - 180 - const png = await renderOgPng( 181 - report, 182 - await getAntLogoDataUrl(c.env, c.req.url), 183 - await getOgFontBytes(c.env, c.req.url), 184 - ); 185 - 186 - const response = c.body(png, 200, { 187 - 'Content-Type': 'image/png', 188 - 'Cache-Control': 'public, max-age=2592000, immutable', 189 - }); 190 - 191 - c.executionCtx.waitUntil(cache.put(cacheKey, response.clone())); 192 - return response; 193 - }); 194 - 195 - app.get('/:id', async c => { 196 - const db = drizzle(c.env.DB); 197 - const id = c.req.param('id'); 198 - const report = await getReport(db, id); 199 - 200 - if (!report) return c.html(renderBlank(), 404); 201 - return c.html(renderReport(report, publicUrl(c.req.url, id), publicOgImageUrl(c.req.url, id))); 202 - }); 203 - 204 - app.post('/report', async c => { 205 - let rawReport: unknown; 206 - try { 207 - rawReport = await c.req.json(); 208 - } catch { 209 - return c.json({ error: 'invalid payload' }, 400); 210 - } 211 - 212 - const parsedReport = CrashReportSchema.safeParse(rawReport); 213 - if (!parsedReport.success) return c.json({ error: 'invalid report' }, 400); 214 - 215 - const report = parsedReport.data; 216 - const trace = await reportTrace(report); 217 - 218 - const db = drizzle(c.env.DB); 219 - const id = generateShortUuid(); 220 - 221 - const now = new Date(); 222 - const expires = new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000); 223 - 224 - await db.insert(reports).values({ 225 - id, 226 - trace, 227 - hitCount: 1, 228 - runtime: report.runtime, 229 - version: report.version, 230 - kind: report.kind, 231 - reason: report.reason, 232 - code: report.code, 233 - target: report.target, 234 - os: report.os, 235 - arch: report.arch, 236 - faultAddress: report.addr, 237 - elapsedMs: report.elapsedMs, 238 - peakRss: report.peakRss, 239 - firstSeenAt: now.toISOString(), 240 - lastSeenAt: now.toISOString(), 241 - expiresAt: expires.toISOString(), 242 - }); 243 - 244 - await insertReportFrames(db, id, report.frames); 245 - return c.text(publicUrl(c.req.url, id), 200); 246 - }); 247 - 248 - export default app;
+114
docs/reports/src/reports.ts
··· 1 + import { sha256 } from './helpers'; 2 + import { asc, eq } from 'drizzle-orm'; 3 + import { drizzle } from 'drizzle-orm/d1'; 4 + import { generate as generateShortUuid } from 'short-uuid'; 5 + import { frames as frameTable, reportFrames, reports, type CrashReport } from './schema'; 6 + 7 + type ReportDb = ReturnType<typeof drizzle>; 8 + 9 + async function reportTrace(report: CrashReport): Promise<string> { 10 + const traceInput = JSON.stringify({ 11 + runtime: report.runtime, 12 + version: report.version, 13 + target: report.target, 14 + os: report.os, 15 + arch: report.arch, 16 + code: report.code, 17 + reason: report.reason, 18 + addr: report.addr, 19 + frames: report.frames, 20 + }); 21 + 22 + return sha256(traceInput); 23 + } 24 + 25 + const reportFromRows = ( 26 + row: typeof reports.$inferSelect, 27 + frameRows: { frame: string }[], 28 + ): CrashReport => ({ 29 + schema: 1, 30 + runtime: 'ant', 31 + version: row.version, 32 + target: row.target, 33 + os: row.os, 34 + arch: row.arch, 35 + kind: row.kind, 36 + code: row.code, 37 + reason: row.reason, 38 + addr: row.faultAddress, 39 + elapsedMs: row.elapsedMs, 40 + peakRss: row.peakRss, 41 + frames: frameRows.map(row => row.frame), 42 + }); 43 + 44 + async function insertReportFrames(db: ReportDb, reportId: string, frames: string[]): Promise<void> { 45 + if (!frames.length) return; 46 + const frameHashes = await Promise.all(frames.map(frame => sha256(frame))); 47 + const uniqueFrames = new Map<string, string>(); 48 + 49 + frames.forEach((frame, index) => { 50 + uniqueFrames.set(frameHashes[index], frame); 51 + }); 52 + 53 + await db 54 + .insert(frameTable) 55 + .values([...uniqueFrames].map(([hash, frame]) => ({ hash, frame }))) 56 + .onConflictDoNothing(); 57 + 58 + await db.insert(reportFrames).values( 59 + frameHashes.map((frameHash, index) => ({ 60 + frameHash, 61 + reportId, 62 + frameIndex: index, 63 + })), 64 + ); 65 + } 66 + 67 + async function getReportFrames(db: ReportDb, reportId: string): Promise<{ frame: string }[]> { 68 + return db 69 + .select({ 70 + frame: frameTable.frame, 71 + }) 72 + .from(reportFrames) 73 + .innerJoin(frameTable, eq(reportFrames.frameHash, frameTable.hash)) 74 + .where(eq(reportFrames.reportId, reportId)) 75 + .orderBy(asc(reportFrames.frameIndex)); 76 + } 77 + 78 + export async function getReport(db: ReportDb, id: string): Promise<CrashReport | null> { 79 + const [row] = await db.select().from(reports).where(eq(reports.id, id)).limit(1); 80 + 81 + if (!row) return null; 82 + const frames = await getReportFrames(db, row.id); 83 + return reportFromRows(row, frames); 84 + } 85 + 86 + export async function createReport(db: ReportDb, report: CrashReport): Promise<string> { 87 + const id = generateShortUuid(); 88 + const trace = await reportTrace(report); 89 + const now = new Date(); 90 + const expires = new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000); 91 + 92 + await db.insert(reports).values({ 93 + id, 94 + trace, 95 + hitCount: 1, 96 + runtime: report.runtime, 97 + version: report.version, 98 + kind: report.kind, 99 + reason: report.reason, 100 + code: report.code, 101 + target: report.target, 102 + os: report.os, 103 + arch: report.arch, 104 + faultAddress: report.addr, 105 + elapsedMs: report.elapsedMs, 106 + peakRss: report.peakRss, 107 + firstSeenAt: now.toISOString(), 108 + lastSeenAt: now.toISOString(), 109 + expiresAt: expires.toISOString(), 110 + }); 111 + 112 + await insertReportFrames(db, id, report.frames); 113 + return id; 114 + }
+74
docs/reports/src/routes.ts
··· 1 + import { Hono } from 'hono'; 2 + import { renderOgPng } from './og'; 3 + import type { Bindings } from './env'; 4 + import { drizzle } from 'drizzle-orm/d1'; 5 + import { CrashReportSchema } from './schema'; 6 + import { renderBlank, renderReport } from './view'; 7 + import { createReport, getReport } from './reports'; 8 + 9 + import { 10 + getAntLogoDataUrl, 11 + getOgFontBytes, 12 + ogImageId, 13 + publicOgImageUrl, 14 + publicUrl, 15 + } from './helpers'; 16 + 17 + const app = new Hono<{ Bindings: Bindings }>(); 18 + 19 + app.get('/', c => c.html(renderBlank(), 404)); 20 + 21 + app.get('/og/:image', async c => { 22 + const db = drizzle(c.env.DB); 23 + const id = ogImageId(c.req.param('image')); 24 + 25 + const cache = caches.default; 26 + const cacheKey = new Request(publicOgImageUrl(c.req.url, id), { method: 'GET' }); 27 + 28 + const cached = await cache.match(cacheKey); 29 + if (cached) return cached; 30 + 31 + const report = await getReport(db, id); 32 + if (!report) return c.notFound(); 33 + 34 + const png = await renderOgPng( 35 + report, 36 + await getAntLogoDataUrl(c.env, c.req.url), 37 + await getOgFontBytes(c.env, c.req.url), 38 + ); 39 + 40 + const response = c.body(png, 200, { 41 + 'Content-Type': 'image/png', 42 + 'Cache-Control': 'public, max-age=2592000, immutable', 43 + }); 44 + 45 + c.executionCtx.waitUntil(cache.put(cacheKey, response.clone())); 46 + return response; 47 + }); 48 + 49 + app.get('/:id', async c => { 50 + const db = drizzle(c.env.DB); 51 + const id = c.req.param('id'); 52 + const report = await getReport(db, id); 53 + 54 + if (!report) return c.html(renderBlank(), 404); 55 + return c.html(renderReport(report, publicUrl(c.req.url, id), publicOgImageUrl(c.req.url, id))); 56 + }); 57 + 58 + app.post('/report', async c => { 59 + let rawReport: unknown; 60 + try { 61 + rawReport = await c.req.json(); 62 + } catch { 63 + return c.json({ error: 'invalid payload' }, 400); 64 + } 65 + 66 + const parsedReport = CrashReportSchema.safeParse(rawReport); 67 + if (!parsedReport.success) return c.json({ error: 'invalid report' }, 400); 68 + 69 + const db = drizzle(c.env.DB); 70 + const id = await createReport(db, parsedReport.data); 71 + return c.text(publicUrl(c.req.url, id), 200); 72 + }); 73 + 74 + export default app;
+1 -1
docs/reports/wrangler.toml
··· 1 1 name = "js-report" 2 - main = "src/index.ts" 2 + main = "src/routes.ts" 3 3 4 4 workers_dev = false 5 5 compatibility_date = "2026-04-25"