← Files DataARCHIVED FILE

assets/data-app-runtime/worker.mjs

65 KB · Sep 30, 2026 · 23:19 UTC

↓ Download file

const maxBlockLayoutRegions=50;const maxBlockLayoutItems=500;const maxBlockLayoutColumns=12;const maxBlockLayoutRevision=1e6;const defaultBlockMinimumSpans=Object.freeze({metric:2,chart:3,custom:3,table:3,block:3});const unsafeObjectKeys=new Set([`__proto__`,`prototype`,`constructor`]);function plainObject$3(value){return value!==null&&typeof value===`object`&&!Array.isArray(value)&&[Object.prototype,null].includes(Object.getPrototypeOf(value))}function validBlockLayoutId(value){return typeof value===`string`&&value.trim()===value&&value.length>0&&value.length<=200&&!unsafeObjectKeys.has(value)}function normalizedLayoutEntry(value){if(!plainObject$3(value)||!Array.isArray(value.order)||value.order.length>500||Object.keys(value).some(key=>![`order`,`spans`,`preferredSpans`,`soloSpans`,`rows`,`authoredRevision`].includes(key))||value.authoredRevision!==void 0&&(!Number.isSafeInteger(value.authoredRevision)||value.authoredRevision<1||value.authoredRevision>1e6))return null;const order=[];const seen=new Set;for(const id of value.order){if(!validBlockLayoutId(id)||seen.has(id))return null;seen.add(id);order.push(id)}const entry={order};if(value.authoredRevision!==void 0)entry.authoredRevision=value.authoredRevision;for(const field of[`spans`,`preferredSpans`,`soloSpans`]){if(value[field]===void 0)continue;if(!plainObject$3(value[field])||Object.keys(value[field]).length>500)return null;const spans={};for(const[id,span]of Object.entries(value[field])){if(!validBlockLayoutId(id)||!seen.has(id)||!Number.isInteger(span)||span<1||span>12)return null;spans[id]=span}if(Object.keys(spans).length)entry[field]=spans}if(value.rows!==void 0){if(!Array.isArray(value.rows)||value.rows.length>500)return null;const rowIds=new Set;const assigned=new Set;const rows=[];for(const row of value.rows){if(!plainObject$3(row)||Object.keys(row).some(key=>![`id`,`items`,`sectionId`].includes(key))||!validBlockLayoutId(row.id)||rowIds.has(row.id)||!Array.isArray(row.items)||row.sectionId!==void 0&&!validBlockLayoutId(row.sectionId)||row.items.length>500)return null;rowIds.add(row.id);for(const id of row.items){if(!seen.has(id)||assigned.has(id))return null;assigned.add(id)}rows.push({id:row.id,items:[...row.items]})}if(assigned.size!==seen.size)return null;entry.rows=rows}return entry}function normalizeBlockLayouts(value){if(!plainObject$3(value))return{};const normalized={};for(const[regionId,layout]of Object.entries(value).slice(0,50)){if(!validBlockLayoutId(regionId))continue;const entry=normalizedLayoutEntry(layout);if(entry)normalized[regionId]=entry}return normalized}function validateBlockLayouts(value){if(!plainObject$3(value)||Object.keys(value).length>50)throw new Error(`Block layouts must be a bounded object of sortable regions.`);for(const[regionId,layout]of Object.entries(value)){if(!validBlockLayoutId(regionId))throw new Error(`Block layout region IDs must be bounded, nonempty, and safe.`);if(!normalizedLayoutEntry(layout))throw new Error(`Block layout "${regionId}" must contain unique safe IDs and bounded column spans.`)}return normalizeBlockLayouts(value)}const dataAppScheduleDays=[{value:`MO`,label:`Monday`,short:`M`},{value:`TU`,label:`Tuesday`,short:`T`},{value:`WE`,label:`Wednesday`,short:`W`},{value:`TH`,label:`Thursday`,short:`T`},{value:`FR`,label:`Friday`,short:`F`},{value:`SA`,label:`Saturday`,short:`S`},{value:`SU`,label:`Sunday`,short:`S`}];const frequencies=new Set([`hourly`,`weekdays`,`daily`,`weekly`,`custom`]);const dayValues=new Set(dataAppScheduleDays.map(({value})=>value));function normalizeDataAppRefreshSchedule(value){if(!value||typeof value!==`object`||Array.isArray(value)||!frequencies.has(value.frequency))return null;if(value.frequency===`hourly`)return{frequency:`hourly`};if(typeof value.time!==`string`||!/^(?:[01]\d|2[0-3]):[0-5]\d$/u.test(value.time))return null;const requestedDays=new Set(Array.isArray(value.days)?value.days.filter(day=>typeof day===`string`&&dayValues.has(day)):[]);const days=dataAppScheduleDays.filter(({value:day})=>requestedDays.has(day)).map(({value:day})=>day);if(value.frequency===`custom`&&days.length===0)return null;if(value.frequency===`weekly`&&days.length!==1)return null;return{frequency:value.frequency,time:value.time,...[`weekly`,`custom`].includes(value.frequency)?{days}:{}}}const chartTypes=[`line`,`area`,`stackedArea`,`bar`,`horizontalBar`,`stackedBar`,`stackedBar100`,`horizontalStackedBar`,`horizontalStackedBar100`,`histogram`,`scatter`,`heatmap`,`pie`,`leaderboard`,`rankedList`,`sparkline`,`funnel`,`waterfall`,`boxPlot`,`sankey`];const colors=Array.from({length:8},(_,index)=>`var(--chart-${index+1})`);const categoricalPaletteOrder=[0,12,6,18,3,15,9,21,1,13,7,19,4,16,10,22,2,14,8,20,5,17,11,23];const categoryColors=Array.from({length:categoricalPaletteOrder.length},(_,index)=>`oklch(from var(--chart-1) l max(c, 0.16) calc(h + ${categoricalPaletteOrder[index]*15}))`);const maxChartAnnotations=8;const annotationChartTypes=Object.freeze([`line`,`area`,`bar`,`horizontalBar`]);const keysByKind={benchmark:[`id`,`kind`,`label`,`field`,`measure`,`at`],event:[`id`,`kind`,`label`,`field`,`at`],range:[`id`,`kind`,`label`,`at`,`end`],point:[`id`,`kind`,`label`,`field`,`at`]};const plainObject$2=value=>value!==null&&typeof value===`object`&&!Array.isArray(value)&&[Object.prototype,null].includes(Object.getPrototypeOf(value));const textValue=(value,limit)=>typeof value===`string`&&value.trim().length>0&&value.length<=limit&&!/[\u0000-\u001f\u007f]/u.test(value);const anchorValue=value=>textValue(value,300)||typeof value===`number`&&Number.isFinite(value);const owns=(value,key)=>value!=null&&Object.hasOwn(value,key);function temporalValue(value){if(typeof value!==`string`||!/^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:\d{2})?)?$/u.test(value))return null;const day=value.slice(0,10);const dayTime=Date.parse(`${day}T00:00:00Z`);const time=Date.parse(value);return Number.isFinite(dayTime)&&new Date(dayTime).toISOString().slice(0,10)===day&&Number.isFinite(time)?time:null}function normalizeChartAnnotations(value){if(value===void 0)return[];if(!Array.isArray(value)||value.length>8)throw new Error(`Chart annotations must be a list of at most ${8} entries.`);const ids=new Set;return value.map(entry=>{if(!plainObject$2(entry)||typeof entry.kind!==`string`||!owns(keysByKind,entry.kind))throw new Error(`Invalid chart annotation kind.`);const allowed=keysByKind[entry.kind];if(Object.keys(entry).some(key=>!allowed.includes(key)))throw new Error(`Chart annotations contain unsupported fields.`);if(!textValue(entry.id,100)||!textValue(entry.label,160))throw new Error(`Chart annotations require bounded, nonempty IDs and labels.`);const id=entry.id.trim();if(ids.has(id))throw new Error(`Chart annotation IDs must be unique.`);ids.add(id);const result={id,kind:entry.kind,label:entry.label.trim()};if(entry.kind!==`range`){if(!textValue(entry.field,200))throw new Error(`Chart annotations require a reviewed field.`);result.field=entry.field}if(entry.kind===`benchmark`){if(!textValue(entry.measure,200))throw new Error(`Chart benchmarks require their plotted measure.`);result.measure=entry.measure}if(entry.kind!==`benchmark`||owns(entry,`at`)){if(!anchorValue(entry.at))throw new Error(`Chart annotations require an exact x-domain anchor.`);result.at=entry.at}if(entry.kind===`event`&&temporalValue(entry.at)===null)throw new Error(`Chart event annotations require an exact temporal anchor.`);if(entry.kind===`range`){const start=temporalValue(entry.at);const end=temporalValue(entry.end);if(start===null||end===null||end<=start)throw new Error(`Chart annotation ranges require ascending, exact temporal anchors.`);result.end=entry.end}return result})}const fieldName=value=>typeof value===`string`&&value.trim().length>0;const wideMeasureTypes=[`line`,`sparkline`,`area`,`stackedArea`,`bar`,`horizontalBar`,`horizontal-bar`,`stackedBar`,`stackedBar100`,`horizontalStackedBar`,`horizontalStackedBar100`];function getChartSpecError(spec,{id,partial=false}={}){const invalid=message=>`Invalid chart${id?` ${JSON.stringify(id)}`:``}: ${message}`;if(spec===null||typeof spec!==`object`||Array.isArray(spec))return invalid(`chart must be a configuration object.`);if((!partial||Object.hasOwn(spec,`type`))&&!chartTypes.includes(spec.type)&&spec.type!==`horizontal-bar`)return invalid(`chart.type must be one of: ${chartTypes.join(`, `)}.`);if(Array.isArray(spec.y)){const fields=spec.y.length&&spec.y.length<=40&&spec.y.every(fieldName)?spec.y:[`measureA`,`measureB`];return invalid(`chart.y must be a single field name, not an array. For multiple measures, use y: ${JSON.stringify(fields[0])}, fields: ${JSON.stringify(fields)}.`)}const hasMeasureList=wideMeasureTypes.includes(spec.type)&&(spec.fields?.length||spec.presentation&&spec.barOptions?.series?.length);if((!partial&&!hasMeasureList||Object.hasOwn(spec,`y`)&&spec.y!==void 0)&&!fieldName(spec.y))return invalid(`chart.y must be a nonempty string field name.`);const requiresX=![`histogram`,`sankey`].includes(spec.type);if((!partial&&requiresX||spec.x!=null&&spec.x!==``)&&!fieldName(spec.x))return invalid(`chart.x must be a nonempty string field name.`);for(const key of[`series`,`source`,`target`])if(spec[key]!=null&&spec[key]!==``&&!fieldName(spec[key]))return invalid(`chart.${key} must be a single string field name.`);for(const key of[`fields`,`barFields`,`stages`]){if(spec[key]==null)continue;if(!Array.isArray(spec[key])||key!==`barFields`&&!spec[key].length)return invalid(`chart.${key} must be a field list${key===`barFields`?``:` with at least one field`}.`);if(!spec[key].every(fieldName))return invalid(`chart.${key} must contain only nonempty string field names.`)}if(!partial&&spec.type===`sankey`&&(spec.stages??[spec.source??spec.x,spec.target??spec.series]).filter(Boolean).length<2)return invalid(`a Sankey chart needs at least two stage fields, using chart.stages or chart.source and chart.target.`);return null}function assertChartSpec(spec,options){const error=getChartSpecError(spec,options);if(error)throw new Error(error)}const maxPresentationBytes=128e3;const maxEntries=500;const maxTextEntryBytes=2e4;const allowedKeys=new Set([`theme`,`appearance`,`title`,`description`,`hiddenBlocks`,`componentTitles`,`textEdits`,`chartOverrides`,`filters`,`assumptions`,`notes`,`refreshSchedule`,`tabs`,`blockLayouts`,`verification`,`tabViews`]);const objectKeys=[`componentTitles`,`textEdits`,`chartOverrides`,`filters`,`assumptions`,`blockLayouts`,`tabViews`];const reviewedDataKeys=new Set([`rows`,`queries`,`sourceRows`,`displayRows`,`sql`,`provenance`]);const appearanceOptions=new Set([`system`,`light`,`dark`]);function plainObject$1(value){return value!==null&&typeof value===`object`&&!Array.isArray(value)&&[Object.prototype,null].includes(Object.getPrototypeOf(value))}function normalizeVerification(value){if(!plainObject$1(value)||Object.keys(value).length!==2||Object.keys(value).some(key=>![`verifiedBy`,`verifiedAt`].includes(key)))return void 0;if(typeof value.verifiedBy!==`string`||typeof value.verifiedAt!==`string`)return void 0;const verifiedBy=value.verifiedBy.trim().toLowerCase();if(verifiedBy.length>254||!/^[^\s@]+@[^\s@]+\.[^\s@]+$/u.test(verifiedBy))return void 0;if(!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/u.test(value.verifiedAt))return void 0;const date=new Date(value.verifiedAt);if(Number.isNaN(date.valueOf())||date.toISOString()!==value.verifiedAt)return void 0;return{verifiedBy,verifiedAt:value.verifiedAt}}function normalizeTabs(value){if(!Array.isArray(value))return void 0;const ids=new Set;const tabs=[];for(const entry of value.slice(0,50)){if(!plainObject$1(entry)||typeof entry.id!==`string`||typeof entry.label!==`string`)continue;const id=entry.id.trim();const label=entry.label.trim();if(!id||id.length>100||!label||label.length>100||ids.has(id))continue;ids.add(id);tabs.push({id,label})}return tabs.length?tabs:void 0}function normalizePresentation(value={}){if(!plainObject$1(value))return{};const result={};for(const key of allowedKeys){const current=value[key];if(current===void 0||current===null)continue;if(key===`appearance`){if(appearanceOptions.has(current))result[key]=current}else if([`theme`,`title`,`description`,`notes`].includes(key)){if(typeof current===`string`&&current.trim())result[key]=current}else if(key===`refreshSchedule`){const schedule=normalizeDataAppRefreshSchedule(current);if(schedule)result[key]=schedule}else if(key===`verification`){const verification=normalizeVerification(current);if(verification)result.verification=verification}else if(key===`hiddenBlocks`){if(Array.isArray(current))result[key]=[...new Set(current.filter(entry=>typeof entry===`string`&&entry.length<=200))].slice(0,maxEntries)}else if(key===`tabs`){const tabs=normalizeTabs(current);if(tabs)result.tabs=tabs}else if(key===`blockLayouts`){const layouts=normalizeBlockLayouts(current);if(Object.keys(layouts).length)result.blockLayouts=layouts}else if([`componentTitles`,`textEdits`].includes(key)&&plainObject$1(current))result[key]=Object.fromEntries(Object.entries(current).filter(([entry,text])=>entry.trim()&&entry.length<=200&&typeof text===`string`&&(text.trim()||key===`textEdits`&&text===``)&&text.length<=maxTextEntryBytes).slice(0,maxEntries));else if(plainObject$1(current))result[key]=Object.fromEntries(Object.entries(current).filter(([entry])=>entry.length<=200).slice(0,maxEntries))}return result}function validatePresentation(value){if(!plainObject$1(value))throw new Error(`Presentation must be a JSON object.`);const unexpected=Object.keys(value).filter(key=>!allowedKeys.has(key));if(unexpected.length)throw new Error(`Unsupported presentation fields: ${unexpected.join(`, `)}.`);if(JSON.stringify(value).length>maxPresentationBytes)throw new Error(`Presentation exceeds the size limit.`);if(value.hiddenBlocks!==void 0&&(!Array.isArray(value.hiddenBlocks)||value.hiddenBlocks.length>maxEntries||value.hiddenBlocks.some(entry=>typeof entry!==`string`)))throw new Error(`Hidden blocks must be a bounded list of component IDs.`);if(value.refreshSchedule!==void 0&&!normalizeDataAppRefreshSchedule(value.refreshSchedule))throw new Error(`Refresh schedule must include a valid repeat and any required time and selected days.`);if(value.verification!==void 0&&!normalizeVerification(value.verification))throw new Error(`Verification must contain only a valid creator email and canonical UTC timestamp.`);if(value.tabs!==void 0&&(!Array.isArray(value.tabs)||value.tabs.length===0||value.tabs.length>50||value.tabs.some(entry=>!plainObject$1(entry)||Object.keys(entry).some(key=>![`id`,`label`].includes(key))||typeof entry.id!==`string`||!entry.id.trim()||entry.id.length>100||typeof entry.label!==`string`||!entry.label.trim()||entry.label.length>100)||new Set(value.tabs.map(({id})=>id.trim())).size!==value.tabs.length))throw new Error(`Tabs must be a bounded list of unique page IDs and labels.`);if(value.appearance!==void 0&&!appearanceOptions.has(value.appearance))throw new Error(`Appearance must be system, light, or dark.`);if(value.blockLayouts!==void 0)validateBlockLayouts(value.blockLayouts);for(const key of objectKeys)if(value[key]!==void 0&&(!plainObject$1(value[key])||Object.keys(value[key]).length>maxEntries))throw new Error(`${key} must be a bounded JSON object.`);for(const key of[`componentTitles`,`textEdits`])if(Object.entries(value[key]??{}).some(([entry,text])=>!entry.trim()||entry.length>200||typeof text!==`string`||!text.trim()&&!(key===`textEdits`&&text===``)||text.length>maxTextEntryBytes))throw new Error(`${key} must contain bounded text values${key===`componentTitles`?` with nonempty titles`:` (empty strings may clear text)`}.`);for(const[id,spec]of Object.entries(value.chartOverrides??{})){if(!plainObject$1(spec))throw new Error(`Chart overrides must contain chart configuration objects.`);if(Object.keys(spec).some(key=>reviewedDataKeys.has(key)))throw new Error(`Chart overrides cannot contain reviewed data or provenance.`);assertChartSpec(spec,{id,partial:true});if(`referenceLines`in spec)throw new Error(`Chart annotations are not supported.`);if(`annotations`in spec)normalizeChartAnnotations(spec.annotations);if(`showAnnotations`in spec&&typeof spec.showAnnotations!==`boolean`)throw new Error(`Chart annotation visibility must be a boolean.`)}for(const key of[`theme`,`title`,`description`,`notes`])if(value[key]!==void 0&&typeof value[key]!==`string`)throw new Error(`${key} must be a string.`);return normalizePresentation(value)}const currentId=`current`;const encoder$1=new TextEncoder;const maxBatchBytes=256*1024;const batchEnvelopeReserve=1024;const maxBindings=100;const rowsPerStatement=Math.floor(maxBindings/5);const pageCandidates=1024;const pageBytes=256*1024;const schemas$1=[`CREATE TABLE IF NOT EXISTS data_app_execution_times_v2 (
    revision TEXT PRIMARY KEY, executed_at TEXT
  )`,`CREATE TABLE IF NOT EXISTS data_app_snapshot_head_v2 (
    id TEXT PRIMARY KEY, current_generation TEXT NOT NULL, seed_sha256 TEXT NOT NULL
  )`,`CREATE TABLE IF NOT EXISTS data_app_generations_v2 (
    generation TEXT PRIMARY KEY, metadata_json TEXT NOT NULL, seed_sha256 TEXT NOT NULL
  )`,`CREATE TABLE IF NOT EXISTS data_app_query_revisions_v2 (
    generation TEXT NOT NULL, query_id TEXT NOT NULL, position INTEGER NOT NULL,
    query_json TEXT NOT NULL, revision TEXT NOT NULL, row_count INTEGER NOT NULL,
    PRIMARY KEY (generation, query_id)
  )`,`CREATE TABLE IF NOT EXISTS data_app_rows_v2 (
    generation TEXT NOT NULL, query_id TEXT NOT NULL, revision TEXT NOT NULL,
    position INTEGER NOT NULL, row_json TEXT NOT NULL,
    PRIMARY KEY (generation, query_id, revision, position)
  )`];function failure$1(code,message){const error=new Error(message);error.code=code;return error}function checkDatabase(database){if(typeof database?.prepare!==`function`||typeof database.batch!==`function`)throw failure$1(`DATABASE_UNAVAILABLE`,`The Sites D1 database is unavailable.`)}function descriptor(sql,params=[]){if(params.length>maxBindings)throw failure$1(`BINDING_LIMIT`,`A statement exceeds 100 bindings.`);const bytes=encoder$1.encode(JSON.stringify({sql,params})).byteLength;if(bytes>maxBatchBytes-batchEnvelopeReserve-2)throw failure$1(`SNAPSHOT_VALUE_TOO_LARGE`,`One snapshot value exceeds the bounded database-write budget.`);return{sql,params,bytes}}async function execute(database,descriptors){if(!descriptors.length)return[];if(2+descriptors.reduce((sum,item)=>sum+item.bytes+1,0)>maxBatchBytes-batchEnvelopeReserve)throw failure$1(`BATCH_LIMIT`,`The database batch exceeds its serialized byte budget.`);const result=await database.batch(descriptors.map(({sql,params})=>database.prepare(sql).bind(...params)));if(!Array.isArray(result)||result.length!==descriptors.length||result.some(item=>item?.success===false))throw failure$1(`DATABASE_BATCH_FAILED`,`The database did not complete the snapshot batch.`);return result}function writer(database){let pending=[],size=2;return{async push(item){if(size+item.bytes+1>maxBatchBytes-batchEnvelopeReserve)await this.flush();pending.push(item);size+=item.bytes+1},async flush(){if(!pending.length)return;const batch=pending;pending=[];size=2;await execute(database,batch)}}}async function ensureSchema(database){checkDatabase(database);await execute(database,schemas$1.map(sql=>descriptor(sql)))}function head(database){return database.prepare(`SELECT current_generation, seed_sha256 FROM data_app_snapshot_head_v2 WHERE id = ?`).bind(currentId).first()}async function rejectPopulatedLegacy(database){const existing=await database.prepare(`SELECT name FROM sqlite_master WHERE type = 'table' AND name IN ('data_app_snapshots', 'data_app_queries', 'data_app_query_rows')`).all();for(const{name}of existing.results??[])if(await database.prepare(`SELECT 1 AS populated FROM ${name} LIMIT 1`).first())throw failure$1(`LEGACY_SNAPSHOT_REQUIRES_MIGRATION`,`Existing legacy snapshot data requires an explicit reviewed migration; no data was reset.`)}function plainObject(value){return value!==null&&typeof value===`object`&&!Array.isArray(value)}async function writeRows(batch,generation,queryId,revision,rows){if(!Array.isArray(rows)||rows.some(row=>!plainObject(row)))throw failure$1(`INVALID_QUERY_ROWS`,`Data app query rows must be an array of objects.`);let values=[];const sql=count=>`INSERT INTO data_app_rows_v2 (generation, query_id, revision, position, row_json) VALUES `+Array.from({length:count},()=>`(?, ?, ?, ?, ?)`).join(`, `);for(let position=0;position<rows.length;position+=1){const row=[generation,queryId,revision,position,JSON.stringify(rows[position])];descriptor(sql(1),row);const combined=values.concat(row);const combinedBytes=encoder$1.encode(JSON.stringify({sql:sql(combined.length/5),params:combined})).byteLength;if(values.length&&(combined.length>maxBindings||combinedBytes>maxBatchBytes-batchEnvelopeReserve-3)){await batch.push(descriptor(sql(values.length/5),values));values=row}else values=combined;if(values.length===rowsPerStatement*5){await batch.push(descriptor(sql(rowsPerStatement),values));values=[]}}if(values.length)await batch.push(descriptor(sql(values.length/5),values))}async function initializeSnapshot(database,loadSeed,seedSha256){if(typeof loadSeed!==`function`||typeof seedSha256!==`string`||!/^[a-f\d]{64}$/u.test(seedSha256))throw failure$1(`INVALID_SEED_DESCRIPTOR`,`A lazy reviewed seed loader and its precomputed SHA-256 are required.`);await ensureSchema(database);const previous=await head(database);if(previous?.seed_sha256===seedSha256)return previous;if(!previous)await rejectPopulatedLegacy(database);const seed=await loadSeed();if(!plainObject(seed)||!plainObject(seed.queries))throw failure$1(`INVALID_SEED`,`The reviewed snapshot must contain a queries object.`);const generation=crypto.randomUUID();const{queries,...metadata}=seed;const batch=writer(database);await batch.push(descriptor(`INSERT INTO data_app_generations_v2 (generation, metadata_json, seed_sha256) VALUES (?, ?, ?)`,[generation,JSON.stringify(metadata),seedSha256]));let position=0;for(const[queryId,query]of Object.entries(queries)){if(!plainObject(query))throw failure$1(`INVALID_SEED`,`Each reviewed query must be an object.`);const{rows=[],...definition}=query;if(!Array.isArray(rows))throw failure$1(`INVALID_QUERY_ROWS`,`Reviewed query rows must be an array.`);const revision=crypto.randomUUID();await batch.push(descriptor(`INSERT INTO data_app_query_revisions_v2 (generation, query_id, position, query_json, revision, row_count) VALUES (?, ?, ?, ?, ?, ?)`,[generation,queryId,position,JSON.stringify(definition),revision,rows.length]));await writeRows(batch,generation,queryId,revision,rows);position+=1}await batch.flush();if(await database.prepare(`SELECT q.query_id FROM data_app_query_revisions_v2 q LEFT JOIN data_app_rows_v2 r ON r.generation = q.generation AND r.query_id = q.query_id AND r.revision = q.revision WHERE q.generation = ? GROUP BY q.query_id, q.row_count HAVING COUNT(r.position) <> q.row_count LIMIT 1`).bind(generation).first())throw failure$1(`INCOMPLETE_SNAPSHOT`,`Staged snapshot row counts are incomplete.`);const[activated]=await execute(database,[previous?descriptor(`UPDATE data_app_snapshot_head_v2 SET current_generation = ?, seed_sha256 = ? WHERE id = ? AND current_generation = ? AND seed_sha256 = ?`,[generation,seedSha256,currentId,previous.current_generation,previous.seed_sha256]):descriptor(`INSERT INTO data_app_snapshot_head_v2 (id, current_generation, seed_sha256) VALUES (?, ?, ?) ON CONFLICT(id) DO NOTHING`,[currentId,generation,seedSha256])]);if(activated.meta?.changes===1)return{current_generation:generation,seed_sha256:seedSha256};const winner=await head(database);if(winner?.seed_sha256===seedSha256)return winner;throw failure$1(`SNAPSHOT_HEAD_CONFLICT`,`The snapshot head changed during initialization; retry the current deployment.`)}async function capturedSnapshot(database,seedSha256){const[metadata,definitions]=await execute(database,[descriptor(`SELECT h.current_generation, h.seed_sha256, g.metadata_json FROM data_app_snapshot_head_v2 h JOIN data_app_generations_v2 g ON g.generation = h.current_generation WHERE h.id = ?`,[currentId]),descriptor(`SELECT q.generation, q.query_id, q.revision, q.row_count, e.executed_at FROM data_app_query_revisions_v2 q LEFT JOIN data_app_execution_times_v2 e ON e.revision = q.revision JOIN data_app_snapshot_head_v2 h ON h.current_generation = q.generation WHERE h.id = ? ORDER BY q.position`,[currentId])]);const captured=metadata.results?.[0];if(!captured||captured.seed_sha256!==seedSha256)throw failure$1(`SNAPSHOT_HEAD_CONFLICT`,`The reviewed snapshot changed before its response was captured.`);return{metadata:captured.metadata_json,queries:definitions.results??[]}}function objectPrefix(json){if(typeof json!==`string`||!json.startsWith(`{`)||!json.endsWith(`}`))throw failure$1(`INVALID_STORED_JSON`,`Stored snapshot metadata is invalid.`);return json.slice(0,-1)+(json.length>2?`,`:``)}async function*snapshotChunks(database,captured){yield objectPrefix(captured.metadata)+`"queries":{`;let firstQuery=true;for(const query of captured.queries){const definition=await database.prepare(`SELECT query_json FROM data_app_query_revisions_v2 WHERE generation = ? AND query_id = ?`).bind(query.generation,query.query_id).first();if(!definition)throw failure$1(`INCOMPLETE_SNAPSHOT`,`An immutable snapshot query definition is missing.`);if(query.executed_at!=null){const value=JSON.parse(definition.query_json);value.source={...value.source,executedAt:query.executed_at};definition.query_json=JSON.stringify(value)}yield(firstQuery?``:`,`)+JSON.stringify(query.query_id)+`:`+objectPrefix(definition.query_json)+`"rows":[`;firstQuery=false;let position=0,firstRow=true;while(position<query.row_count){const rows=(await database.prepare(`SELECT position, row_json FROM (SELECT position, row_json, SUM(length(CAST(row_json AS BLOB))) OVER (ORDER BY position) AS page_bytes FROM (SELECT position, row_json FROM data_app_rows_v2 WHERE generation = ? AND query_id = ? AND revision = ? AND position >= ? ORDER BY position LIMIT ?)) WHERE page_bytes <= ? ORDER BY position`).bind(query.generation,query.query_id,query.revision,position,pageCandidates,pageBytes).all()).results??[];if(!rows.length)throw failure$1(`INCOMPLETE_SNAPSHOT`,`An immutable snapshot revision is missing rows.`);for(const row of rows){if(row.position!==position)throw failure$1(`INCOMPLETE_SNAPSHOT`,`Snapshot row positions are not contiguous.`);position+=1}yield(firstRow?``:`,`)+rows.map(row=>row.row_json).join(`,`);firstRow=false}yield`]}`}yield`}}`}async function snapshotResponse(database,loadSeed,seedSha256){await initializeSnapshot(database,loadSeed,seedSha256);const iterator=snapshotChunks(database,await capturedSnapshot(database,seedSha256));const body=new ReadableStream({async pull(controller){try{const{value,done}=await iterator.next();if(done)controller.close();else controller.enqueue(encoder$1.encode(value))}catch(error){controller.error(error);await iterator.return?.()}},async cancel(){await iterator.return?.()}});return new Response(body,{headers:{"content-type":`application/json; charset=utf-8`,"cache-control":`private, no-store`}})}async function storedQueryExists(database,loadSeed,seedSha256,queryId){await initializeSnapshot(database,loadSeed,seedSha256);const query=await database.prepare(`SELECT 1 AS found FROM data_app_query_revisions_v2 q JOIN data_app_snapshot_head_v2 h ON h.current_generation = q.generation WHERE h.id = ? AND h.seed_sha256 = ? AND q.query_id = ?`).bind(currentId,seedSha256,queryId).first();return Boolean(query)}async function updateBoundedQueries(database,updates,generatedAt,seedSha256){await ensureSchema(database);if(typeof generatedAt!==`string`||!Number.isFinite(Date.parse(generatedAt)))throw failure$1(`INVALID_GENERATED_AT`,`A valid generatedAt timestamp is required.`);const current=await head(database);if(!current)throw failure$1(`SNAPSHOT_NOT_INITIALIZED`,`Initialize the reviewed snapshot before updating a query.`);if(seedSha256&&current.seed_sha256!==seedSha256)throw failure$1(`SNAPSHOT_HEAD_CONFLICT`,`The reviewed snapshot changed before this query update.`);const generation=current.current_generation;const batch=writer(database),replacements=[];for(const{queryId,rows,executedAt}of updates){const previous=await database.prepare(`SELECT revision FROM data_app_query_revisions_v2 WHERE generation = ? AND query_id = ?`).bind(generation,queryId).first();if(!previous)throw failure$1(`QUERY_NOT_FOUND`,`Data app query was not found.`);const revision=crypto.randomUUID();await writeRows(batch,generation,queryId,revision,rows);await batch.push(descriptor(`INSERT INTO data_app_execution_times_v2 (revision, executed_at) VALUES (?, COALESCE(?, (SELECT executed_at FROM data_app_execution_times_v2 WHERE revision = ?)))`,[revision,executedAt??null,previous.revision]));await batch.flush();if((await database.prepare(`SELECT COUNT(*) AS count FROM data_app_rows_v2 WHERE generation = ? AND query_id = ? AND revision = ?`).bind(generation,queryId,revision).first())?.count!==rows.length)throw failure$1(`INCOMPLETE_QUERY`,`The staged query revision is incomplete.`);replacements.push({queryId,revision,previous:previous.revision,count:rows.length})}const payload=JSON.stringify(replacements);const[updated]=await execute(database,[descriptor(`WITH expected AS MATERIALIZED (SELECT json_extract(value, '$.queryId') AS id, json_extract(value, '$.revision') AS revision, json_extract(value, '$.previous') AS previous, json_extract(value, '$.count') AS count FROM json_each(?)) `.slice(0,-1)+`, allowed AS MATERIALIZED (SELECT 1 WHERE EXISTS (SELECT 1 FROM data_app_snapshot_head_v2 WHERE id = ? AND current_generation = ?) AND NOT EXISTS (SELECT 1 FROM expected e LEFT JOIN data_app_query_revisions_v2 q ON q.generation = ? AND q.query_id = e.id WHERE q.revision IS NOT e.previous)) UPDATE data_app_query_revisions_v2 SET revision = (SELECT revision FROM expected WHERE id = query_id), row_count = (SELECT count FROM expected WHERE id = query_id) WHERE generation = ? AND query_id IN (SELECT id FROM expected) AND EXISTS (SELECT 1 FROM allowed)`,[payload,currentId,generation,generation,generation]),descriptor(`WITH expected AS MATERIALIZED (SELECT json_extract(value, '$.queryId') AS id, json_extract(value, '$.revision') AS revision, json_extract(value, '$.previous') AS previous, json_extract(value, '$.count') AS count FROM json_each(?)) UPDATE data_app_generations_v2 SET metadata_json = json_set(metadata_json, '$.generatedAt', ?) WHERE generation = ? AND NOT EXISTS (SELECT 1 FROM expected e LEFT JOIN data_app_query_revisions_v2 q ON q.generation = ? AND q.query_id = e.id WHERE q.revision IS NOT e.revision)`,[payload,generatedAt,generation,generation])]);if(updated.meta?.changes!==updates.length)throw failure$1(`QUERY_REVISION_CONFLICT`,`The query or snapshot changed while new rows were staged; retry against the current revision.`)}const legacySnapshotId=`current`;const legacyRowsPerStatement=30;const legacySchemas=[`CREATE TABLE IF NOT EXISTS data_app_snapshots (
    id TEXT PRIMARY KEY,
    metadata_json TEXT NOT NULL,
    seed_sha256 TEXT NOT NULL
  )`,`CREATE TABLE IF NOT EXISTS data_app_queries (
    id TEXT PRIMARY KEY,
    position INTEGER NOT NULL,
    query_json TEXT NOT NULL
  )`,`CREATE TABLE IF NOT EXISTS data_app_query_rows (
    query_id TEXT NOT NULL,
    position INTEGER NOT NULL,
    row_json TEXT NOT NULL,
    PRIMARY KEY (query_id, position)
  )`];async function legacyFingerprint(snapshot){const digest=await crypto.subtle.digest(`SHA-256`,new TextEncoder().encode(JSON.stringify(snapshot)));return[...new Uint8Array(digest)].map(byte=>byte.toString(16).padStart(2,`0`)).join(``)}function legacyInsertRows(database,queryId,rows){const statements=[];for(let offset=0;offset<rows.length;offset+=legacyRowsPerStatement){const chunk=rows.slice(offset,offset+legacyRowsPerStatement);const placeholders=chunk.map(()=>`(?, ?, ?)`).join(`, `);const values=chunk.flatMap((row,index)=>[queryId,offset+index,JSON.stringify(row)]);statements.push(database.prepare(`INSERT INTO data_app_query_rows (query_id, position, row_json) VALUES ${placeholders}`).bind(...values))}return statements}async function legacyInitializeSnapshot(database,snapshot,seedFingerprint){if(typeof database?.prepare!==`function`||typeof database.batch!==`function`)throw new Error(`The Sites D1 database is unavailable.`);await database.batch(legacySchemas.map(schema=>database.prepare(schema)));const seedHash=seedFingerprint??await legacyFingerprint(snapshot);if((await database.prepare(`SELECT seed_sha256 FROM data_app_snapshots WHERE id = ?`).bind(legacySnapshotId).first())?.seed_sha256===seedHash)return;const{queries={},...metadata}=snapshot;const statements=[database.prepare(`DELETE FROM data_app_query_rows`),database.prepare(`DELETE FROM data_app_queries`),database.prepare(`INSERT INTO data_app_snapshots (id, metadata_json, seed_sha256) VALUES (?, ?, ?) ON CONFLICT(id) DO UPDATE SET metadata_json = excluded.metadata_json, seed_sha256 = excluded.seed_sha256`).bind(legacySnapshotId,JSON.stringify(metadata),seedHash)];for(const[position,[queryId,query]]of Object.entries(queries).entries()){const{rows=[],...definition}=query;statements.push(database.prepare(`INSERT INTO data_app_queries (id, position, query_json) VALUES (?, ?, ?)`).bind(queryId,position,JSON.stringify(definition)));statements.push(...legacyInsertRows(database,queryId,rows))}await database.batch(statements)}async function storedSnapshot(database,seedSnapshot,seedFingerprint){await legacyInitializeSnapshot(database,seedSnapshot,seedFingerprint);const[snapshot,queryResult,rowResult]=await database.batch([database.prepare(`SELECT metadata_json FROM data_app_snapshots WHERE id = ?`).bind(legacySnapshotId),database.prepare(`SELECT id, query_json FROM data_app_queries ORDER BY position`),database.prepare(`SELECT query_id, row_json FROM data_app_query_rows ORDER BY query_id, position`)]);const queries=Object.fromEntries(queryResult.results.map(({id,query_json})=>[id,{...JSON.parse(query_json),rows:[]}]));for(const{query_id,row_json}of rowResult.results)queries[query_id].rows.push(JSON.parse(row_json));return{...JSON.parse(snapshot.results[0].metadata_json),queries}}async function updateStoredQueries(database,updates,generatedAt){await database.batch([...updates.flatMap(({queryId,rows,executedAt})=>[database.prepare(`DELETE FROM data_app_query_rows WHERE query_id = ?`).bind(queryId),...legacyInsertRows(database,queryId,rows),...executedAt===void 0?[]:[database.prepare(`UPDATE data_app_queries SET query_json = json_set(query_json, '$.source.executedAt', ?) WHERE id = ?`).bind(executedAt,queryId)]]),database.prepare(`UPDATE data_app_snapshots SET metadata_json = json_set(metadata_json, '$.generatedAt', ?) WHERE id = ?`).bind(generatedAt,legacySnapshotId)])}const encoder=new TextEncoder;const schemas=[`CREATE TABLE IF NOT EXISTS data_app_object_execution_times_v1 (
    revision TEXT PRIMARY KEY, executed_at TEXT
  )`,`CREATE TABLE IF NOT EXISTS data_app_object_head_v1 (
    id TEXT PRIMARY KEY, seed_sha256 TEXT NOT NULL, generation TEXT NOT NULL
  )`,`CREATE TABLE IF NOT EXISTS data_app_object_snapshots_v1 (
    seed_sha256 TEXT PRIMARY KEY, generated_at TEXT
  )`,`CREATE TABLE IF NOT EXISTS data_app_object_queries_v1 (
    seed_sha256 TEXT NOT NULL, query_id TEXT NOT NULL, revision TEXT NOT NULL,
    bytes INTEGER NOT NULL, PRIMARY KEY (seed_sha256, query_id)
  )`];function failure(code,message){return Object.assign(new Error(message),{code})}function validateSnapshotIndex(index,asset){const offset=value=>Number.isSafeInteger(value)&&value>=0&&value<asset.bytes;const range=value=>Array.isArray(value)&&value.length===2&&offset(value[0])&&Number.isSafeInteger(value[1])&&value[1]>value[0]&&value[1]<=asset.bytes;if(!index||index.version!==1||index.sha256!==asset.sha256||index.bytes!==asset.bytes||!offset(index.end)||index.generatedAt!==null&&!range(index.generatedAt)||!index.queries||typeof index.queries!==`object`||Array.isArray(index.queries)||Object.values(index.queries).some(query=>!query||!offset(query.end)||typeof query.empty!==`boolean`||query.rows!==null&&!range(query.rows)||query.source!=null&&(!range(query.source.range)||typeof query.source.object!==`boolean`||typeof query.source.empty!==`boolean`||query.source.executedAt!==null&&!range(query.source.executedAt)))||index.replacements!==void 0&&(!Array.isArray(index.replacements)||index.replacements.some(item=>!range([item.start,item.end])||![`0`,`null`].includes(item.text))))throw failure(`INVALID_SNAPSHOT_INDEX`,`The hosted snapshot index does not match its immutable asset.`)}async function usesObjectSnapshot(database){if(typeof database?.prepare!==`function`||typeof database.batch!==`function`)throw failure(`DATABASE_UNAVAILABLE`,`The Sites D1 database is unavailable.`);const tables=await database.prepare(`SELECT name FROM sqlite_master WHERE type = 'table' AND name IN ('data_app_snapshot_head_v2', 'data_app_snapshots', 'data_app_queries', 'data_app_query_rows', 'data_app_object_queries_v1')`).all();let legacy=false,objectEdits=false;for(const{name}of tables.results??[])if(await database.prepare(`SELECT 1 AS populated FROM ${name} LIMIT 1`).first())if(name===`data_app_object_queries_v1`)objectEdits=true;else legacy=true;if(legacy&&objectEdits)throw failure(`LEGACY_SNAPSHOT_REQUIRES_MIGRATION`,`Both snapshot storage versions contain data; an explicit reviewed migration is required. No owner edits were reset.`);return!legacy}function activeHead(database){return database.prepare(`SELECT seed_sha256, generation FROM data_app_object_head_v1 WHERE id = 'current'`).first()}async function initialize(database,seedSha256){await database.batch(schemas.map(sql=>database.prepare(sql)));const previous=await activeHead(database);if(previous?.seed_sha256===seedSha256)return previous;const generation=crypto.randomUUID();const statements=[previous?database.prepare(`UPDATE data_app_object_head_v1 SET seed_sha256 = ?, generation = ? WHERE id = 'current' AND generation = ?`).bind(seedSha256,generation,previous.generation):database.prepare(`INSERT INTO data_app_object_head_v1 (id, seed_sha256, generation) VALUES ('current', ?, ?) ON CONFLICT(id) DO NOTHING`).bind(seedSha256,generation),database.prepare(`INSERT INTO data_app_object_snapshots_v1 (seed_sha256, generated_at) VALUES (?, NULL) ON CONFLICT(seed_sha256) DO NOTHING`).bind(seedSha256)];if(previous)statements.push(database.prepare(`DELETE FROM data_app_object_queries_v1 WHERE seed_sha256 = ? AND EXISTS (SELECT 1 FROM data_app_object_head_v1 WHERE id = 'current' AND generation = ?)`).bind(seedSha256,generation),database.prepare(`UPDATE data_app_object_snapshots_v1 SET generated_at = NULL WHERE seed_sha256 = ? AND EXISTS (SELECT 1 FROM data_app_object_head_v1 WHERE id = 'current' AND generation = ?)`).bind(seedSha256,generation));const[activated]=await database.batch(statements);if(activated?.meta?.changes===1)return{seed_sha256:seedSha256,generation};const winner=await activeHead(database);if(winner?.seed_sha256===seedSha256)return winner;throw failure(`SNAPSHOT_HEAD_CONFLICT`,`The snapshot head changed during initialization; retry the current deployment.`)}function rowsKey(seed,revision){return`data-app/query-rows/${seed}/${revision}`}async function objectSnapshotIsUnedited(database,seedSha256){const active=await initialize(database,seedSha256);const[head,edits]=await database.batch([database.prepare(`SELECT s.generated_at FROM data_app_object_snapshots_v1 s JOIN data_app_object_head_v1 h ON h.seed_sha256 = s.seed_sha256 WHERE h.id = 'current' AND h.seed_sha256 = ? AND h.generation = ?`).bind(seedSha256,active.generation),database.prepare(`SELECT 1 AS edited FROM data_app_object_queries_v1 WHERE seed_sha256 = ? LIMIT 1`).bind(seedSha256)]);if(head.success===false||edits.success===false)throw failure(`SNAPSHOT_UNAVAILABLE`,`The stored snapshot revision could not be read.`);if(!head.results?.length)throw failure(`SNAPSHOT_HEAD_CONFLICT`,`The reviewed snapshot changed before its response was captured.`);return head.results[0].generated_at===null&&edits.results?.length===0}async function*objectBytes(object,expectedBytes){const reader=object.body.getReader();let count=0;try{while(true){const{value,done}=await reader.read();if(done)break;count+=value.byteLength;if(count>expectedBytes)throw failure(`INCOMPLETE_SNAPSHOT`,`A stored snapshot object has an unexpected length.`);yield value}if(count!==expectedBytes)throw failure(`INCOMPLETE_SNAPSHOT`,`A stored snapshot object is incomplete.`)}finally{await reader.cancel()}}async function*replacementBytes(bucket,seed,patch){if(patch.text!==void 0){yield encoder.encode(patch.text);return}if(patch.prefix)yield encoder.encode(patch.prefix);const object=await bucket.get(rowsKey(seed,patch.revision));if(!object?.body||object.size!==patch.bytes||object.customMetadata?.revision!==patch.revision){await object?.body?.cancel();throw failure(`INCOMPLETE_QUERY`,`An immutable query revision is unavailable.`)}yield*objectBytes(object,patch.bytes)}function responsePatches(index,captured){const changed=new Map(captured.queries.map(query=>[query.query_id,query]));const patches=[];for(const[id,query]of Object.entries(index.queries)){const update=changed.get(id);if(update){patches.push({start:query.rows?.[0]??query.end,end:query.rows?.[1]??query.end,...update,prefix:query.rows?``:`${query.empty?``:`,`}"rows":`});if(update.executed_at!=null){if(query.source===void 0)throw failure(`INVALID_SNAPSHOT_INDEX`,`Republish this dashboard with source timestamp offsets before refreshing source times.`);const source=query.source,timestamp=JSON.stringify(update.executed_at);if(source?.object)patches.push({start:source.executedAt?.[0]??source.range[1]-1,end:source.executedAt?.[1]??source.range[1]-1,text:(source.executedAt?``:`${source.empty?``:`,`}"executedAt":`)+timestamp});else patches.push({start:source?.range[0]??query.end,end:source?.range[1]??query.end,text:(source?``:`,"source":`)+`{"executedAt":${timestamp}}`})}}else if(!query.rows)patches.push({start:query.end,end:query.end,text:`${query.empty?``:`,`}"rows":[]`})}if(captured.generatedAt!==null)patches.push({start:index.generatedAt?.[0]??index.end,end:index.generatedAt?.[1]??index.end,text:(index.generatedAt?``:`,"generatedAt":`)+JSON.stringify(captured.generatedAt)});const overlays=patches.slice().sort((a,b)=>a.start-b.start);let position=0;for(const item of[...index.replacements??[]].sort((a,b)=>a.start-b.start)){while(position<overlays.length&&overlays[position].end<=item.start)position++;const overlay=overlays[position];if(!overlay||item.start<overlay.start||item.end>overlay.end)patches.push(item)}patches.sort((a,b)=>a.start-b.start);for(let i=1;i<patches.length;i++)if(patches[i].start<patches[i-1].end)throw failure(`INVALID_SNAPSHOT_INDEX`,`Hosted snapshot replacements overlap.`);return patches}async function*spliceSnapshot(base,index,patches,bucket,seed){const reader=base.body.getReader();let pending,offset=0,position=0;async function*advance(end,emit){while(position<end){if(!pending||offset===pending.byteLength){const next=await reader.read();if(next.done)throw failure(`INCOMPLETE_SNAPSHOT`,`The immutable snapshot is incomplete.`);pending=next.value;offset=0}const count=Math.min(end-position,pending.byteLength-offset);if(emit&&count)yield pending.subarray(offset,offset+count);position+=count;offset+=count}}try{for(const patch of patches){yield*advance(patch.start,true);yield*replacementBytes(bucket,seed,patch);yield*advance(patch.end,false)}yield*advance(index.bytes,true);if(pending&&offset!==pending.byteLength||!(await reader.read()).done)throw failure(`INCOMPLETE_SNAPSHOT`,`The immutable snapshot has an unexpected length.`)}finally{await reader.cancel()}}async function objectQueryRowsResponse(bucket,asset,index,queryId){const rows=index.queries[queryId].rows;const offset=rows?.[0]??0,length=rows?rows[1]-rows[0]:1;const patches=rows?(index.replacements??[]).filter(item=>item.start>=rows[0]&&item.end<=rows[1]).map(item=>({...item,start:item.start-offset,end:item.end-offset})).sort((a,b)=>a.start-b.start):[];for(let i=1;i<patches.length;i+=1)if(patches[i].start<patches[i-1].end)throw failure(`INVALID_SNAPSHOT_INDEX`,`Hosted snapshot replacements overlap.`);const object=await bucket?.get(asset.key,{range:{offset,length}});if(!object?.body||object.size!==asset.bytes||object.customMetadata?.sha256!==asset.sha256||object.range?.offset!==offset||object.range?.length!==length){await object?.body?.cancel();throw failure(`INCOMPLETE_QUERY`,`The immutable query rows are unavailable or incomplete.`)}const headers={"content-type":`application/json; charset=utf-8`,"cache-control":`private, no-store`};if(!rows){await object.body.cancel();return new Response(`[]`,{headers})}if(!patches.length)return new Response(object.body,{headers});const iterator=spliceSnapshot(object,{bytes:length},patches,bucket);return new Response(new ReadableStream({async pull(controller){try{const{done,value}=await iterator.next();if(done)controller.close();else controller.enqueue(value)}catch(error){controller.error(error);await iterator.return()}},async cancel(){await iterator.return()}}),{headers})}async function objectSnapshotResponse(database,bucket,base,index,seedSha256){let iterator;try{const active=await initialize(database,seedSha256);const[head,queries]=await database.batch([database.prepare(`SELECT s.generated_at FROM data_app_object_snapshots_v1 s JOIN data_app_object_head_v1 h ON h.seed_sha256 = s.seed_sha256 WHERE h.id = 'current' AND h.seed_sha256 = ? AND h.generation = ?`).bind(seedSha256,active.generation),database.prepare(`SELECT q.query_id, q.revision, q.bytes, e.executed_at FROM data_app_object_queries_v1 q LEFT JOIN data_app_object_execution_times_v1 e ON e.revision = q.revision WHERE q.seed_sha256 = ?`).bind(seedSha256)]);if(head.success===false||queries.success===false)throw failure(`SNAPSHOT_UNAVAILABLE`,`The stored snapshot revision could not be read.`);if(!head.results?.length)throw failure(`SNAPSHOT_HEAD_CONFLICT`,`The reviewed snapshot changed before its response was captured.`);iterator=spliceSnapshot(base,index,responsePatches(index,{generatedAt:head.results[0].generated_at,queries:queries.results??[]}),bucket,seedSha256)}catch(error){await base.body?.cancel();throw error}return new Response(new ReadableStream({async pull(controller){try{const{done,value}=await iterator.next();if(done)controller.close();else controller.enqueue(value)}catch(error){controller.error(error);await iterator.return()}},async cancel(){await iterator.return()}}),{headers:{"content-type":`application/json; charset=utf-8`,"cache-control":`private, no-store`}})}async function updateObjectQueries(database,bucket,seedSha256,updates,generatedAt,index){const active=await initialize(database,seedSha256);const replacements=[];for(const{queryId,rows,executedAt}of updates){if(executedAt!==void 0&&index?.queries[queryId]?.source===void 0)throw failure(`INVALID_SNAPSHOT_INDEX`,`Republish this dashboard with source timestamp offsets before refreshing source times.`);const previous=await database.prepare(`SELECT revision FROM data_app_object_queries_v1 WHERE seed_sha256 = ? AND query_id = ?`).bind(seedSha256,queryId).first();const revision=crypto.randomUUID(),serialized=JSON.stringify(rows);const bytes=encoder.encode(serialized).byteLength;const stored=await bucket.put(rowsKey(seedSha256,revision),serialized,{customMetadata:{revision},httpMetadata:{contentType:`application/json; charset=utf-8`}});if(!stored||stored.size!==bytes)throw failure(`INCOMPLETE_QUERY`,`The new query revision was not completely stored.`);await database.prepare(`INSERT INTO data_app_object_execution_times_v1 (revision, executed_at) VALUES (?, COALESCE(?, (SELECT executed_at FROM data_app_object_execution_times_v1 WHERE revision = ?)))`).bind(revision,executedAt??null,previous?.revision??null).run();replacements.push({queryId,revision,bytes,previous:previous?.revision??null})}if(!await usesObjectSnapshot(database))throw failure(`SNAPSHOT_HEAD_CONFLICT`,`The snapshot storage changed while new rows were staged; retry the current deployment.`);const payload=JSON.stringify(replacements);const[activated]=await database.batch([database.prepare(`WITH expected AS MATERIALIZED (SELECT json_extract(value, '$.queryId') AS id, json_extract(value, '$.revision') AS revision, json_extract(value, '$.previous') AS previous, json_extract(value, '$.bytes') AS bytes FROM json_each(?)) `.slice(0,-1)+`, allowed AS MATERIALIZED (SELECT 1 WHERE EXISTS (SELECT 1 FROM data_app_object_head_v1 WHERE id = 'current' AND generation = ?) AND NOT EXISTS (SELECT 1 FROM expected e LEFT JOIN data_app_object_queries_v1 q ON q.seed_sha256 = ? AND q.query_id = e.id WHERE q.revision IS NOT e.previous)) INSERT INTO data_app_object_queries_v1 (seed_sha256, query_id, revision, bytes) SELECT ?, id, revision, bytes FROM expected WHERE EXISTS (SELECT 1 FROM allowed) ON CONFLICT(seed_sha256, query_id) DO UPDATE SET revision = excluded.revision, bytes = excluded.bytes`).bind(payload,active.generation,seedSha256,seedSha256),database.prepare(`WITH expected AS MATERIALIZED (SELECT json_extract(value, '$.queryId') AS id, json_extract(value, '$.revision') AS revision, json_extract(value, '$.previous') AS previous, json_extract(value, '$.bytes') AS bytes FROM json_each(?)) UPDATE data_app_object_snapshots_v1 SET generated_at = ? WHERE seed_sha256 = ? AND NOT EXISTS (SELECT 1 FROM expected e LEFT JOIN data_app_object_queries_v1 q ON q.seed_sha256 = ? AND q.query_id = e.id WHERE q.revision IS NOT e.revision)`).bind(payload,generatedAt,seedSha256,seedSha256)]);if(activated?.meta?.changes!==updates.length)throw failure(`QUERY_REVISION_CONFLICT`,`The query changed while new rows were staged; retry against the current revision.`);await usesObjectSnapshot(database)}function normalizeOwnerEmail(value){if(typeof value!==`string`||/\p{Cc}/u.test(value))return null;const email=value.trim().toLowerCase();return email.length<=254&&/^[^\s@]+@[^\s@]+\.[^\s@]+$/u.test(email)?email:null}const id=`current`;const presentationSchemaSql=`
  CREATE TABLE IF NOT EXISTS data_app_presentation_v1 (
    id TEXT PRIMARY KEY,
    presentation_json TEXT NOT NULL,
    revision INTEGER NOT NULL,
    updated_at TEXT NOT NULL
  )
`;function json(value,status=200){return new Response(JSON.stringify(value),{status,headers:{"content-type":`application/json; charset=utf-8`,"cache-control":`private, no-store`}})}function componentPermalinkPath(pathname){const match=/^\/_data\/(charts|components)\/([^/]+)(\/detail)?$/u.exec(pathname);if(!match||match[1]===`components`&&match[3])return false;try{const componentId=decodeURIComponent(match[2]);return componentId.length<=200&&Boolean(componentId.trim())&&componentId!==`.`&&componentId!==`..`&&!/[\\/\0]/u.test(componentId)}catch{return false}}function authenticatedViewerEmail(request){return normalizeOwnerEmail(request.headers.get(`oai-authenticated-user-email`))}async function sha256(value){const digest=await crypto.subtle.digest(`SHA-256`,new TextEncoder().encode(value));return[...new Uint8Array(digest)].map(byte=>byte.toString(16).padStart(2,`0`)).join(``)}function isOwnerHash(value){return typeof value===`string`&&/^[a-f\d]{64}$/u.test(value)}function htmlWithSitesProject(dataAppHtml,projectId){if(!projectId)return dataAppHtml;if(!/^[A-Za-z0-9][A-Za-z0-9_-]*$/u.test(projectId))throw new Error(`The Sites project ID is invalid.`);const tags=/<!--[\s\S]*?-->|<(script|style|title|textarea)\b(?:"[^"]*"|'[^']*'|[^'">])*>[\s\S]*?<\/\1\s*>|<(\/?)([a-z][a-z\d:-]*)\b((?:"[^"]*"|'[^']*'|[^'">])*)>/giu;let head;for(const tag of dataAppHtml.matchAll(tags)){const closing=tag[2];const name=tag[3]?.toLowerCase();if(!head){if(name===`head`&&!closing)head=tag;continue}if(name===`head`&&closing)break;if(name!==`meta`||closing)continue;for(const attribute of tag[4].matchAll(/([^\s=/>]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+)))?/gu))if(attribute[1].toLowerCase()===`name`&&(attribute[2]??attribute[3]??attribute[4]??``).toLowerCase()===`data-app-sites-project`)throw new Error(`The reviewed Data app HTML must not define its Sites project identity.`)}if(!head)throw new Error(`The reviewed Data app HTML must contain a head element.`);const marker=`<meta name="data-app-sites-project" content="${projectId}">`;return`${dataAppHtml.slice(0,head.index+head[0].length)}${marker}${dataAppHtml.slice(head.index+head[0].length)}`}function createDataAppWorker({html:dataAppHtml,projectId=``,seedSnapshot,initialPresentation={},deploymentAssets,deploymentUploadAuthorization,seedSnapshotSha256,snapshotIndex}){if(seedSnapshotSha256!==void 0&&!isOwnerHash(seedSnapshotSha256))throw new Error(`The canonical Data app seed fingerprint is invalid.`);const servedHtml=deploymentAssets?null:htmlWithSitesProject(dataAppHtml,projectId);if(deploymentAssets){for(const kind of[`html`,`snapshot`]){const asset=deploymentAssets[kind];if(!asset||!/^[a-f\d]{64}$/u.test(asset.sha256)||asset.key!==`data-app/${kind}/${asset.sha256}`||!Number.isSafeInteger(asset.bytes)||asset.bytes<=0)throw new Error(`The Data app deployment asset descriptor is invalid.`)}if(snapshotIndex)validateSnapshotIndex(snapshotIndex,deploymentAssets.snapshot)}else if(snapshotIndex)throw new Error(`An indexed snapshot requires immutable deployment assets.`);async function deploymentAsset(environment,kind){const asset=await environment.BUCKET?.get(deploymentAssets[kind].key);if(!asset||asset.size!==deploymentAssets[kind].bytes||asset.customMetadata?.sha256!==deploymentAssets[kind].sha256)throw new Error(`The Data app ${kind} asset is unavailable.`);return asset}async function reviewedSeed(environment){if(!deploymentAssets)return seedSnapshot;return(await deploymentAsset(environment,`snapshot`)).json()}const seedFingerprint=seedSnapshotSha256??deploymentAssets?.snapshot.sha256;let legacySeedFingerprint;const inlineSnapshot=async database=>{legacySeedFingerprint??=sha256(JSON.stringify(seedSnapshot));return storedSnapshot(database,seedSnapshot,await legacySeedFingerprint)};async function uploadCanProceed(request,environment){if(await viewerCanEdit(request,environment))return true;const token=request.headers.get(`x-data-app-deployment-token`);return Boolean(token&&token.length<=256&&deploymentUploadAuthorization&&Date.now()<Date.parse(deploymentUploadAuthorization.expiresAt)&&isOwnerHash(deploymentUploadAuthorization.sha256)&&await sha256(token)===deploymentUploadAuthorization.sha256)}function storageFailure(error){const conflict=[`SNAPSHOT_HEAD_CONFLICT`,`QUERY_REVISION_CONFLICT`].includes(error?.code);return json({error:error instanceof Error?error.message:`Snapshot storage is unavailable.`,code:error?.code??`SNAPSHOT_UNAVAILABLE`},conflict?409:503)}const presentationSeed={...validatePresentation(initialPresentation)};delete presentationSeed.verification;async function viewerCanEdit(request,environment){const ownerEmailSha256=environment?.DATA_APP_OWNER_EMAIL_SHA256;if(!isOwnerHash(ownerEmailSha256))return false;const email=authenticatedViewerEmail(request);return email!==null&&await sha256(email)===ownerEmailSha256}async function storedPresentation(database){if(!database?.prepare)throw new Error(`The Sites D1 database is unavailable.`);await database.prepare(presentationSchemaSql).run();await database.prepare(`INSERT INTO data_app_presentation_v1 (id, presentation_json, revision, updated_at) VALUES (?, ?, ?, ?) ON CONFLICT(id) DO NOTHING`).bind(id,JSON.stringify(presentationSeed),0,new Date().toISOString()).run();const row=await database.prepare(`SELECT presentation_json, revision, updated_at FROM data_app_presentation_v1 WHERE id = ?`).bind(id).first();return{presentation:JSON.parse(row.presentation_json),revision:row.revision,updatedAt:row.updated_at}}return{async fetch(request,environment){const{pathname,searchParams}=new URL(request.url);if(deploymentAssets&&pathname===`/api/deployment-assets/html`&&request.method===`GET`){if(!await uploadCanProceed(request,environment))return json({error:`Deployment upload authorization is required.`},403);try{const asset=await deploymentAsset(environment,`html`);return new Response(asset.body,{headers:{"content-type":`application/octet-stream`,"cache-control":`private, no-store`}})}catch{return json({error:`Deployment HTML is unavailable.`},503)}}if(deploymentAssets&&pathname===`/api/deployment-assets/snapshot`&&request.method===`GET`){const parameters=[...searchParams];const historicalHash=parameters.length===1&&parameters[0][0]===`sha256`?parameters[0][1]:null;if(parameters.length&&!isOwnerHash(historicalHash))return json({error:`An immutable snapshot read requires one valid sha256 parameter.`},400);if(!(historicalHash?await viewerCanEdit(request,environment):await uploadCanProceed(request,environment)))return json({error:`Immutable snapshot read authorization is required.`},403);try{const asset=historicalHash?await environment.BUCKET?.get(`data-app/snapshot/${historicalHash}`):await deploymentAsset(environment,`snapshot`);if(!asset||!Number.isSafeInteger(asset.size)||asset.size<=0||asset.customMetadata?.sha256!==(historicalHash??deploymentAssets.snapshot.sha256))throw new Error(`The immutable snapshot asset is unavailable.`);return new Response(asset.body,{headers:{"content-type":`application/octet-stream`,"cache-control":`private, no-store`}})}catch{return json({error:`Immutable snapshot is unavailable.`},503)}}if(deploymentAssets&&pathname.startsWith(`/api/deployment-assets/`)&&request.method===`PUT`){if(!await uploadCanProceed(request,environment))return json({error:`Deployment upload authorization is required.`},403);const kind=pathname.slice(23);if(kind!==`html`&&kind!==`snapshot`)return json({error:`Unknown deployment asset.`},404);if(kind===`snapshot`&&searchParams.size)return json({error:`Only the current configured snapshot can be uploaded.`},400);const descriptor=deploymentAssets[kind];const encoding=(request.headers.get(`content-encoding`)??`identity`).trim().toLowerCase();if(encoding!==`identity`&&encoding!==`gzip`)return json({error:`Deployment asset encoding is not supported.`},400);const contentLength=request.headers.get(`content-length`);if(encoding===`gzip`?!/^[1-9]\d*$/u.test(contentLength??``)||!Number.isSafeInteger(Number(contentLength)):contentLength!==String(descriptor.bytes))return json({error:`Deployment asset size does not match.`},400);if(!request.body)return json({error:`Deployment asset body is required.`},400);if(!environment.BUCKET?.put)return json({error:`Deployment asset storage is unavailable.`},503);try{const body=encoding===`gzip`?request.body.pipeThrough(new DecompressionStream(`gzip`)).pipeThrough(new FixedLengthStream(descriptor.bytes)):request.body;const stored=await environment.BUCKET.put(descriptor.key,body,{sha256:descriptor.sha256,httpMetadata:{contentType:kind===`html`?`text/html; charset=utf-8`:`application/json; charset=utf-8`},customMetadata:{sha256:descriptor.sha256}});if(!stored||stored.size!==descriptor.bytes)return json({error:`Deployment asset size does not match.`},400);return json({kind,sha256:descriptor.sha256,bytes:stored.size})}catch{return json({error:`Deployment asset upload failed integrity or storage validation.`},400)}}const isComponentPermalink=(request.method===`GET`||request.method===`HEAD`)&&componentPermalinkPath(pathname);if(pathname===`/`||pathname===`/index.html`||isComponentPermalink){if(deploymentAssets)try{const asset=await deploymentAsset(environment,`html`);return new Response(request.method===`HEAD`?null:asset.body,{headers:{"content-type":`text/html; charset=utf-8`,"cache-control":`private, no-store`}})}catch{return new Response(`Data app deployment assets are unavailable.`,{status:503})}return new Response(isComponentPermalink&&request.method===`HEAD`?null:servedHtml,{headers:{"content-type":`text/html; charset=utf-8`}})}if(pathname===`/api/snapshot/head`&&request.method===`GET`){if(searchParams.size)return json({error:`Snapshot head parameters are not supported.`},400);try{if(!snapshotIndex||!await usesObjectSnapshot(environment.DB)||!await objectSnapshotIsUnedited(environment.DB,seedFingerprint))return json({supported:false});await(await deploymentAsset(environment,`snapshot`)).body?.cancel();return json({supported:true,snapshotSha256:deploymentAssets.snapshot.sha256,generatedAt:null,queries:Object.fromEntries(Object.keys(snapshotIndex.queries).map(queryId=>[queryId,{revision:`seed`}]))})}catch(error){return storageFailure(error)}}if(pathname===`/api/query-rows`&&request.method===`GET`){if(!snapshotIndex)return json({error:`Lazy query loading is unavailable for this snapshot.`},409);if(searchParams.size!==3||[`queryId`,`snapshot`,`revision`].some(name=>searchParams.getAll(name).length!==1))return json({error:`A query ID, current snapshot and query revision are required.`},400);const queryId=searchParams.get(`queryId`);if(!Object.hasOwn(snapshotIndex.queries,queryId))return json({error:`Data app query was not found.`},404);if(searchParams.get(`snapshot`)!==deploymentAssets.snapshot.sha256||searchParams.get(`revision`)!==`seed`)return json({error:`The snapshot changed; reload the complete current snapshot.`},409);try{if(!await usesObjectSnapshot(environment.DB)||!await objectSnapshotIsUnedited(environment.DB,seedFingerprint))return json({error:`The snapshot changed; reload the complete current snapshot.`},409);return await objectQueryRowsResponse(environment.BUCKET,deploymentAssets.snapshot,snapshotIndex,queryId)}catch(error){return storageFailure(error)}}if(pathname===`/api/snapshot`&&request.method===`GET`)try{if(snapshotIndex&&await usesObjectSnapshot(environment.DB)){const base=await deploymentAsset(environment,`snapshot`);return await objectSnapshotResponse(environment.DB,environment.BUCKET,base,snapshotIndex,seedFingerprint)}return deploymentAssets?await snapshotResponse(environment.DB,()=>reviewedSeed(environment),seedFingerprint):json(await inlineSnapshot(environment.DB))}catch(error){return storageFailure(error)}if(pathname===`/api/presentation`&&request.method===`GET`)return json({...await storedPresentation(environment.DB),canEdit:await viewerCanEdit(request,environment),ownerEnvironmentConfigured:isOwnerHash(environment?.DATA_APP_OWNER_EMAIL_SHA256)});if(pathname===`/api/presentation`&&request.method===`PUT`){if(!await viewerCanEdit(request,environment))return json({error:`Only the current Site owner can edit the Data app presentation.`},403);let body;let presentation;try{body=await request.json();presentation=validatePresentation(body.presentation)}catch(error){return json({error:error instanceof Error?error.message:`Presentation is invalid.`},400)}if(!Number.isSafeInteger(body.revision)||body.revision<0)return json({error:`A valid presentation revision is required.`},400);if(body.verificationAction!==void 0&&body.verificationAction!==`verify`&&body.verificationAction!==`remove`)return json({error:`Dashboard verification action must be verify or remove.`},400);const current=await storedPresentation(environment.DB);if(current.revision!==body.revision)return json(current,409);const updatedAt=new Date().toISOString();if(body.verificationAction===`remove`)delete presentation.verification;else if(body.verificationAction===`verify`)presentation.verification=current.presentation.verification??{verifiedBy:authenticatedViewerEmail(request),verifiedAt:updatedAt};else if(presentation.verification){if(!current.presentation.verification)return json({error:`Dashboard verification requires an explicit server-side verification action.`},400);presentation.verification=current.presentation.verification}if(!(await environment.DB.prepare(`UPDATE data_app_presentation_v1 SET presentation_json = ?, revision = revision + 1, updated_at = ? WHERE id = ? AND revision = ?`).bind(JSON.stringify(presentation),updatedAt,id,body.revision).run()).meta?.changes)return json(await storedPresentation(environment.DB),409);return json({presentation,revision:body.revision+1,updatedAt})}if((pathname===`/api/queries`||pathname.startsWith(`/api/queries/`))&&request.method===`PUT`){if(!await viewerCanEdit(request,environment))return json({error:`Only the current Site owner can update reviewed Data app data.`},403);const batch=pathname===`/api/queries`;const body=await request.json();if(!body||typeof body!==`object`||Array.isArray(body)||Object.keys(body).some(key=>!(batch?[`updates`]:[`rows`,`executedAt`]).includes(key)))return json({error:`Data app query updates are invalid.`},400);const updates=batch?body.updates:[{queryId:decodeURIComponent(pathname.slice(13)),...body}];if(!Array.isArray(updates)||!updates.length||updates.length>100||updates.some(update=>!update||typeof update!==`object`||Array.isArray(update)||Object.keys(update).some(key=>![`queryId`,`rows`,`executedAt`].includes(key))||typeof update.queryId!==`string`||!Array.isArray(update.rows)||update.rows.length>1e4||update.rows.some(row=>row===null||typeof row!==`object`||Array.isArray(row))||(batch||update.executedAt!==void 0)&&(typeof update.executedAt!==`string`||!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.test(update.executedAt)||!Number.isFinite(Date.parse(update.executedAt))))||new Set(updates.map(({queryId})=>queryId)).size!==updates.length)return json({error:`Data app query updates require unique query IDs, valid rows, and source execution times.`},400);let objectStorage=false;try{objectStorage=Boolean(snapshotIndex&&await usesObjectSnapshot(environment.DB));if(objectStorage)await(await deploymentAsset(environment,`snapshot`)).body?.cancel();for(const{queryId}of updates)if(!(objectStorage?Object.hasOwn(snapshotIndex.queries,queryId):deploymentAssets?await storedQueryExists(environment.DB,()=>reviewedSeed(environment),seedFingerprint,queryId):Object.hasOwn((await inlineSnapshot(environment.DB)).queries,queryId)))return json({error:`Data app query was not found.`},404)}catch(error){return storageFailure(error)}const generatedAt=new Date().toISOString();try{if(objectStorage)await updateObjectQueries(environment.DB,environment.BUCKET,seedFingerprint,updates,generatedAt,snapshotIndex);else if(deploymentAssets)await updateBoundedQueries(environment.DB,updates,generatedAt,seedFingerprint);else await updateStoredQueries(environment.DB,updates,generatedAt)}catch(error){return storageFailure(error)}return json({...batch?{updates}:updates[0],generatedAt})}return new Response(`Not found`,{status:404})}}}export{createDataAppWorker,validatePresentation};

SHA-256: 286d289ec9f8fa16b49ab0837fe28d9468d2fd5085ef993cf0a23422cff57410