90 lines
2.9 KiB
TypeScript
90 lines
2.9 KiB
TypeScript
|
|
/* eslint-disable */
|
||
|
|
/**
|
||
|
|
* Migration: add an index on `order_items.item_status`.
|
||
|
|
*
|
||
|
|
* Why this exists
|
||
|
|
* ───────────────
|
||
|
|
* The /orders page's tab badges ("New (12)", "Processing (4)") and
|
||
|
|
* its server-side tab filter both run queries of the form
|
||
|
|
* `item_status = "pending" || item_status = "approval_needed" || ...`
|
||
|
|
* on the `order_items` collection. With ~18k items and no index on
|
||
|
|
* `item_status`, every load is a full table scan — the single biggest
|
||
|
|
* cost on the orders page after the deep expand on the orders
|
||
|
|
* collection itself.
|
||
|
|
*
|
||
|
|
* Adding the index makes these queries O(log n) B-tree lookups. The
|
||
|
|
* page-load budget drops from seconds to sub-second on the cold path
|
||
|
|
* (realtime refetches benefit even more because the tab-counts cache
|
||
|
|
* is bypassed for them).
|
||
|
|
*
|
||
|
|
* Schema
|
||
|
|
* ──────
|
||
|
|
* order_items.item_status select (existing field, no schema change)
|
||
|
|
*
|
||
|
|
* The index is non-unique (the same status appears on many rows).
|
||
|
|
*
|
||
|
|
* Idempotent — safe to re-run. The migration checks for the index
|
||
|
|
* before adding it; subsequent runs print "already present, skipping."
|
||
|
|
*
|
||
|
|
* Run with:
|
||
|
|
* npx tsx scripts/migrations/2026-08-04-add-order-items-item-status-index.ts
|
||
|
|
*/
|
||
|
|
import PocketBase from "pocketbase";
|
||
|
|
import { readFileSync } from "node:fs";
|
||
|
|
import { resolve } from "node:path";
|
||
|
|
|
||
|
|
function loadEnvFile(name: string) {
|
||
|
|
try {
|
||
|
|
const raw = readFileSync(resolve(process.cwd(), name), "utf8");
|
||
|
|
for (const line of raw.split("\n")) {
|
||
|
|
const trimmed = line.trim();
|
||
|
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
||
|
|
const m = trimmed.match(/^([A-Z0-9_]+)\s*=\s*(.+)$/i);
|
||
|
|
if (m && !process.env[m[1]]) {
|
||
|
|
process.env[m[1]] = m[2].replace(/^["']|["']$/g, "").trim();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} catch {
|
||
|
|
// missing file — fine
|
||
|
|
}
|
||
|
|
}
|
||
|
|
loadEnvFile(".env");
|
||
|
|
loadEnvFile(".env.local");
|
||
|
|
|
||
|
|
async function main() {
|
||
|
|
const url = process.env.NEXT_PUBLIC_POCKETBASE_URL;
|
||
|
|
const email = process.env.PB_ADMIN_EMAIL;
|
||
|
|
const password = process.env.PB_ADMIN_PASSWORD;
|
||
|
|
|
||
|
|
if (!url || !email || !password) {
|
||
|
|
throw new Error(
|
||
|
|
"Missing NEXT_PUBLIC_POCKETBASE_URL / PB_ADMIN_EMAIL / PB_ADMIN_PASSWORD in env",
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
const pb = new PocketBase(url);
|
||
|
|
await pb.admins.authWithPassword(email, password);
|
||
|
|
|
||
|
|
const collection = await pb.collections.getOne("order_items");
|
||
|
|
const INDEX_NAME = "idx_oi_item_status";
|
||
|
|
const INDEX_SQL = `CREATE INDEX \`${INDEX_NAME}\` ON \`order_items\` (\`item_status\`)`;
|
||
|
|
|
||
|
|
const existing = (collection.indexes || []) as string[];
|
||
|
|
if (existing.includes(INDEX_SQL)) {
|
||
|
|
console.log("order_items.item_status index already present, skipping.");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
await pb.collections.update(collection.id, {
|
||
|
|
indexes: [...existing, INDEX_SQL],
|
||
|
|
});
|
||
|
|
|
||
|
|
console.log("Added order_items.item_status index (idx_oi_item_status).");
|
||
|
|
console.log("Run /orders in the browser — the initial load should be visibly faster.");
|
||
|
|
}
|
||
|
|
|
||
|
|
main().catch((err) => {
|
||
|
|
console.error(err);
|
||
|
|
process.exit(1);
|
||
|
|
});
|