import { Knex } from "knex"; /** * Rebuilds `sdk_cache` for per-entity last-content dedupe. * * The original table keyed rows by (type, content-hash) as insert-only set * membership: "have we ever sent this exact content within the TTL window?". * That cannot express a value returning to a previously-seen state (A -> B -> A), * so a corrective write whose content matches an earlier one is silently deduped. * * The new shape stores one row per (type, entity_key) holding that entity's last * content hash, so dedupe becomes "does this differ from what we last sent for * this entity?". The SDK cache is ephemeral (rebuilt from migrations on each * container start), so there is no data to preserve — we drop and recreate. */ export async function up(knex: Knex): Promise { await knex.schema.dropTableIfExists("sdk_cache"); await knex.schema.createTable("sdk_cache", (table) => { table.increments("id"); table.string("type").notNullable().index(); table.string("entity_key").notNullable(); table.string("content_hash").notNullable(); // Epoch milliseconds; compared directly against Date.now() in removeExpiredObjects. // Indexed so TTL cleanup is a range scan rather than a full table scan. table.bigInteger("updated_at").notNullable().index(); table.unique(["type", "entity_key"]); // one row per entity }); } export async function down(knex: Knex): Promise { await knex.schema.dropTableIfExists("sdk_cache"); await knex.schema.createTable("sdk_cache", (table) => { table.increments("id"); table.string("type").notNullable().index(); table.string("key").notNullable(); table.timestamp("created_at").defaultTo(knex.fn.now()); table.unique(["type", "key"]); }); }