[econ] fix some storefronts

This commit is contained in:
Devin Zuczek
2026-10-19 12:27:05 -04:00
parent be1b275a0a
commit d918936795
11 changed files with 305 additions and 25 deletions
+19 -6
View File
@@ -53,10 +53,12 @@ export const STORE_DUMP = 'apps/econ/static/db/Watch_EnumValue_3.json'
export const SKINS = 'apps/econ/static/db/skins.json'
/**
* Rows per INSERT. Batched because thousands of single-row statements parse far more slowly
* than a few dozen multi-row ones, and D1 charges per statement on the remote path.
* The largest INSERT a load builds, in bytes of SQL. Batched because thousands of single-row
* statements parse far more slowly than a few dozen multi-row ones, and D1 charges per statement
* on the remote path; capped by SIZE rather than row count because D1 refuses a statement over
* 100 KB, and each row carries its whole store listing (~1.2 KB, up to ~1.4 KB).
*/
const CHUNK = 100
const STATEMENT_BYTES = 64 * 1024
/**
* One SQL literal. Booleans become 1/0 (SQLite has no boolean), `undefined` is treated as the
@@ -271,16 +273,27 @@ const load = new Command('load')
// afterwards is visibly a row the store no longer lists.
statements.push('UPDATE catalog SET catalog_id = NULL;')
}
for (let start = 0; start < rows.length; start += CHUNK) {
const batch = rows.slice(start, start + CHUNK)
let batch: string[] = []
let batchBytes = 0
const flush = (): void => {
if (batch.length === 0) return
statements.push(
`INSERT INTO catalog (${CATALOG_INSERT_COLUMNS.join(', ')}) VALUES\n` +
batch.map((r) => `\t(${r.values.map(lit).join(', ')})`).join(',\n') +
batch.join(',\n') +
// Harmless after a DELETE (nothing can conflict), and kept there anyway so both
// paths run the identical statement rather than two shapes that could diverge.
`\nON CONFLICT(item_key) DO UPDATE SET ${CONFLICT_UPDATE};`
)
batch = []
batchBytes = 0
}
for (const row of rows) {
const tuple = `\t(${row.values.map(lit).join(', ')})`
if (batch.length > 0 && batchBytes + tuple.length > STATEMENT_BYTES) flush()
batch.push(tuple)
batchBytes += tuple.length + 2
}
flush()
// No numbering step after the inserts. A row's `catalog_id` is its listing's
// `PurchasableItemId` in sf3-2025.json and nothing else; a row a merge keeps that the file
// no longer lists has no such number, so it stays NULL (every reader filters on
+16
View File
@@ -95,6 +95,22 @@ async function runD1(worker: string, extraArgs: string[], remote: boolean): Prom
// dependency of @repo/tools, so it's not on this process's PATH).
const out = await $`pnpm exec wrangler ${args}`.quiet()
return out.stdout
} catch (e) {
// `.quiet()` hides wrangler's output, and runx exits on a failed process WITHOUT
// printing it — so a D1 error (a constraint, a missing column, an unapplied migration)
// surfaced as a bare "exited with code 1". Rethrow with what wrangler actually said.
if (e instanceof ProcessOutput) {
const said = [e.stderr, e.stdout]
.join('\n')
.split('\n')
.filter((line) => /ERROR|✘|error/i.test(line))
.join('\n')
.trim()
throw new Error(
`wrangler d1 execute failed (exit ${e.exitCode}):\n${said || `${e.stderr}\n${e.stdout}`.trim()}`
)
}
throw e
} finally {
if (cleanup) await cleanup()
}