Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,10 @@
import { describe, expect, it } from "@suaveplan/testing/runner";
describe("@suaveplan/migration dep-graph", () => {
it("can import the main entry point", { timeout: 30000 }, async () => {
const mod = await import("../src/index.js");
expect(mod.MigrationRunner).toBeDefined();
expect(mod.MemoryMigrationStore).toBeDefined();
expect(mod.MIGRATION_CODES).toBeDefined();
});
});

View File

@@ -0,0 +1,112 @@
import { beforeEach, describe, expect, it } from "@suaveplan/testing/runner";
import { BatchMigrator } from "../src/batch/batch-migrator/batch-migrator.js";
import { MemoryMigrationLock } from "../src/lock/memory-lock/memory-lock.js";
import { MigrationRunner } from "../src/runner/migration-runner/migration-runner.js";
import { MigrationStatusDashboard } from "../src/status/migration-status/migration-status.js";
import { MemoryMigrationStore } from "../src/store/memory-store/memory-store.js";
import type { Migration } from "../src/types/migration.types.js";
const migrations: Migration[] = [
{
id: "001_create_users",
name: "Create users table",
async up(_ctx) {},
async down(_ctx) {},
},
{
id: "002_add_email",
name: "Add email column",
async up(_ctx) {},
async down(_ctx) {},
},
{
id: "003_create_posts",
name: "Create posts table",
async up(_ctx) {},
async down(_ctx) {},
},
];
describe("MigrationRunner integration", () => {
let store: MemoryMigrationStore;
beforeEach(() => {
store = new MemoryMigrationStore();
});
it("full up/down cycle", { timeout: 30000 }, async () => {
const runner = new MigrationRunner({ migrations, store });
const upResult = await runner.up();
expect(upResult.applied).toHaveLength(3);
expect(upResult.direction).toBe("up");
const statusAfterUp = await runner.status();
expect(statusAfterUp.every((s) => s.status === "applied")).toBe(true);
const downResult = await runner.down({ steps: 1 });
expect(downResult.applied).toHaveLength(1);
expect(downResult.applied[0]).toBe("003_create_posts");
// Up again applies only the rolled-back one
const upResult2 = await runner.up();
expect(upResult2.applied).toHaveLength(1);
});
it("idempotent up", { timeout: 30000 }, async () => {
const runner = new MigrationRunner({ migrations, store });
await runner.up();
const second = await runner.up();
expect(second.applied).toHaveLength(0);
});
it("with lock — runner acquires and releases", {
timeout: 30000,
}, async () => {
const lock = new MemoryMigrationLock();
const runner = new MigrationRunner({ migrations, store, lock });
const result = await runner.up();
expect(result.applied).toHaveLength(3);
expect(await lock.isLocked()).toBe(false);
});
it("dry-run does not write records", { timeout: 30000 }, async () => {
const runner = new MigrationRunner({ migrations, store, dryRun: true });
const result = await runner.up();
expect(result.dryRun).toBe(true);
expect(await store.getApplied()).toHaveLength(0);
});
it("status dashboard reflects runner results", {
timeout: 30000,
}, async () => {
const runner = new MigrationRunner({ migrations, store });
await runner.up();
const dashboard = new MigrationStatusDashboard({ store, migrations });
const health = await dashboard.healthCheck();
expect(health.healthy).toBe(true);
});
it("batch migrator runs multiple targets", { timeout: 30000 }, async () => {
const store2 = new MemoryMigrationStore();
const runner1 = new MigrationRunner({
migrations: migrations.slice(0, 2),
store,
});
const runner2 = new MigrationRunner({
migrations: migrations.slice(0, 2),
store: store2,
});
const batch = new BatchMigrator({
targets: [
{ name: "db1", runner: runner1 },
{ name: "db2", runner: runner2 },
],
});
const result = await batch.run();
expect(result.succeeded).toEqual(["db1", "db2"]);
expect(result.failed).toHaveLength(0);
});
});

View File

@@ -0,0 +1,7 @@
import { webcrypto } from "node:crypto";
type WritableGlobal = { crypto?: Crypto };
const g = globalThis as WritableGlobal;
if (g.crypto === undefined) {
g.crypto = webcrypto as unknown as Crypto;
}