mirror of
https://github.com/djdevin/recflare.git
synced 2026-10-25 23:18:51 -07:00
[econ] catalog fix
This commit is contained in:
@@ -72,9 +72,9 @@ export {
|
|||||||
*
|
*
|
||||||
* `catalog_id` is the small NUMERIC handle the site uses where a key would be unwieldy. For a
|
* `catalog_id` is the small NUMERIC handle the site uses where a key would be unwieldy. For a
|
||||||
* row the store lists it is the listing's own `PurchasableItemId` — the number `sf3-2025.json`
|
* row the store lists it is the listing's own `PurchasableItemId` — the number `sf3-2025.json`
|
||||||
* sells it under — and for a skin the store never listed it is handed out by the loader from
|
* sells it under, including the skins we added to the store (numbered there from
|
||||||
* `CATALOG_ID_BASE`, well above any real one. It is unique where set, and the migration that
|
* `UNLISTED_SKIN_ID_BASE`, 1 000 000). A row the file does not list has none and is NULL. It is
|
||||||
* adds it is 0016, since 0015 was already applied.
|
* unique where set, and the migration that adds it is 0016, since 0015 was already applied.
|
||||||
*
|
*
|
||||||
* Nullability is load-bearing rather than incidental: `tooltip` is genuinely NULL on some rows
|
* Nullability is load-bearing rather than incidental: `tooltip` is genuinely NULL on some rows
|
||||||
* of BOTH kinds and `""` on others, and the client's DTOs serve the distinction through, so the
|
* of BOTH kinds and `""` on others, and the client's DTOs serve the distinction through, so the
|
||||||
@@ -120,10 +120,9 @@ export interface CatalogRow {
|
|||||||
* the store's `PurchasableItemId`, so the number resolves a row here and a listing in
|
* the store's `PurchasableItemId`, so the number resolves a row here and a listing in
|
||||||
* `sf3-2025.json` alike.
|
* `sf3-2025.json` alike.
|
||||||
*
|
*
|
||||||
* Still not an identity: an unlisted skin's number is a load-order surrogate, and a store
|
* Still not an identity: a store could renumber. Never store it, never treat it as what an
|
||||||
* could renumber. Never store it, never treat it as what an item IS — `item_key` is that,
|
* item IS — `item_key` is that. NULL for a row `sf3-2025.json` does not list (a merge keeps
|
||||||
* and it is what the inventory holds. NULL only between a row existing and a load numbering
|
* such rows); every reader filters on `catalog_id IS NOT NULL`.
|
||||||
* it.
|
|
||||||
*/
|
*/
|
||||||
catalog_id: number | null
|
catalog_id: number | null
|
||||||
kind: string
|
kind: string
|
||||||
@@ -259,8 +258,8 @@ export async function getCatalogItem(db: D1Database, itemKey: string): Promise<C
|
|||||||
/**
|
/**
|
||||||
* One catalog row by its numeric handle — a store `PurchasableItemId` for a listed item.
|
* One catalog row by its numeric handle — a store `PurchasableItemId` for a listed item.
|
||||||
*
|
*
|
||||||
* Fine for a request that looked the number up moments ago; never for anything stored, since
|
* The number the client looks items up by, since it is the one sf3-2025.json serves. Not for
|
||||||
* an unlisted skin's number is reassigned by every load.
|
* anything stored: `item_key` is the identity, and a fresher store dump could renumber.
|
||||||
*/
|
*/
|
||||||
export async function getCatalogItemById(
|
export async function getCatalogItemById(
|
||||||
db: D1Database,
|
db: D1Database,
|
||||||
|
|||||||
@@ -204,17 +204,20 @@ async function verifyLoad(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Every row must have come out with a numeric handle. A NULL here is a load that stopped
|
// Every row this load wrote must carry its `PurchasableItemId`. Ids are cleared before the
|
||||||
// between writing rows and numbering them, which nothing else would notice until a lookup by
|
// inserts and only the inserts set them, so exactly the loaded rows are numbered — fewer is a
|
||||||
// number quietly returned nothing.
|
// load cut short, which nothing else would notice until a lookup by id quietly found nothing.
|
||||||
const unnumbered = await execSql(
|
// Rows the file does not list stay NULL by design and are not counted.
|
||||||
'SELECT COUNT(*) AS n FROM catalog WHERE catalog_id IS NULL',
|
const numbered = await execSql(
|
||||||
|
'SELECT COUNT(*) AS n FROM catalog WHERE catalog_id IS NOT NULL',
|
||||||
remote,
|
remote,
|
||||||
OWNER
|
OWNER
|
||||||
)
|
)
|
||||||
const missing = Number(unnumbered.results[0]?.n ?? 0)
|
const withId = Number(numbered.results[0]?.n ?? 0)
|
||||||
if (missing > 0) {
|
if (withId !== rows.length) {
|
||||||
throw new Error(`load did not finish: ${missing} row(s) have no catalog_id. Re-run it.`)
|
throw new Error(
|
||||||
|
`load did not finish: ${withId} row(s) carry a PurchasableItemId, expected ${rows.length}. Re-run it.`
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,8 +268,7 @@ const load = new Command('load')
|
|||||||
// load carries the store's numbers: without this, a row already holding one of them
|
// load carries the store's numbers: without this, a row already holding one of them
|
||||||
// (because it was in an earlier load under a different key, or the store renumbered)
|
// (because it was in an earlier load under a different key, or the store renumbered)
|
||||||
// collides and the whole merge fails. Nulling first also means a row left un-numbered
|
// collides and the whole merge fails. Nulling first also means a row left un-numbered
|
||||||
// afterwards is visibly a row the store no longer lists, which the tail statement
|
// afterwards is visibly a row the store no longer lists.
|
||||||
// below then numbers above everything this load assigned.
|
|
||||||
statements.push('UPDATE catalog SET catalog_id = NULL;')
|
statements.push('UPDATE catalog SET catalog_id = NULL;')
|
||||||
}
|
}
|
||||||
for (let start = 0; start < rows.length; start += CHUNK) {
|
for (let start = 0; start < rows.length; start += CHUNK) {
|
||||||
@@ -279,18 +281,11 @@ const load = new Command('load')
|
|||||||
`\nON CONFLICT(item_key) DO UPDATE SET ${CONFLICT_UPDATE};`
|
`\nON CONFLICT(item_key) DO UPDATE SET ${CONFLICT_UPDATE};`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (!opts.replace) {
|
// No numbering step after the inserts. A row's `catalog_id` is its listing's
|
||||||
// Anything still un-numbered is a row the store no longer lists — a merge keeps those,
|
// `PurchasableItemId` in sf3-2025.json and nothing else; a row a merge keeps that the file
|
||||||
// so they need handles too. They go ABOVE the highest id this load assigned, so they can
|
// no longer lists has no such number, so it stays NULL (every reader filters on
|
||||||
// never collide with it, ordered by rowid for determinism. Usually this matches nothing
|
// `catalog_id IS NOT NULL`). Inventing one would hand the client an id the store it is
|
||||||
// at all, and it is a single statement either way.
|
// served does not contain.
|
||||||
const highest = Math.max(0, ...rows.map((r) => r.id))
|
|
||||||
statements.push(
|
|
||||||
`UPDATE catalog SET catalog_id = ${highest} +\n` +
|
|
||||||
`\t(SELECT COUNT(*) FROM catalog c WHERE c.catalog_id IS NULL AND c.rowid <= catalog.rowid)\n` +
|
|
||||||
`WHERE catalog_id IS NULL;`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
const sql = statements.join('\n')
|
const sql = statements.join('\n')
|
||||||
|
|
||||||
if (opts.dryRun) {
|
if (opts.dryRun) {
|
||||||
|
|||||||
Reference in New Issue
Block a user