Skip to content

Commit a55cbc6

Browse files
author
Nap Joseph Calub
committed
feat: update migration scripts
1 parent 7a280a2 commit a55cbc6

3 files changed

Lines changed: 122 additions & 21 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
-- Down migration: setup_user
2+
3+
BEGIN TRANSACTION;
4+
5+
REMOVE INDEX user_email_index ON user;
6+
REMOVE ACCESS user ON DATABASE;
7+
REMOVE TABLE user;
8+
9+
COMMIT TRANSACTION;

migrations/20250710_093749_setup_user.surql

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
-- Migration: setup_user
22

3+
BEGIN TRANSACTION;
4+
35
DEFINE TABLE user SCHEMAFULL
4-
PERMISSIONS
5-
FOR select, update WHERE id = $auth.id,
6-
FOR create, delete NONE;
6+
PERMISSIONS
7+
FOR select, update WHERE id = $auth.id,
8+
FOR create, delete NONE;
79
DEFINE FIELD email ON user TYPE string
8-
ASSERT
9-
$value != NONE
10-
AND string::is::email($value);
10+
ASSERT
11+
$value != NONE
12+
AND string::is::email($value);
1113
DEFINE FIELD password ON user TYPE string;
1214
DEFINE FIELD enabled ON user TYPE bool;
1315

@@ -33,3 +35,5 @@ DEFINE ACCESS user ON DATABASE TYPE RECORD
3335
};
3436

3537
DEFINE INDEX user_email_index ON user COLUMNS email UNIQUE;
38+
39+
COMMIT TRANSACTION;

scripts/migrations.ts

Lines changed: 103 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,11 @@ function sanitizeDesc(desc: string) {
5959
async function listMigrationFiles() {
6060
const files: string[] = [];
6161
for await (const entry of Deno.readDir(MIGRATIONS_DIR)) {
62-
if (entry.isFile && entry.name.endsWith(".surql")) {
62+
if (
63+
entry.isFile &&
64+
entry.name.endsWith(".surql") &&
65+
!entry.name.endsWith(".down.surql")
66+
) {
6367
files.push(entry.name);
6468
}
6569
}
@@ -85,6 +89,37 @@ async function markMigrationApplied(filename: string) {
8589
});
8690
}
8791

92+
async function unmarkMigrationApplied(filename: string) {
93+
await surrealConnect();
94+
await db.delete(new RecordId(MIGRATION_TABLE, filename));
95+
}
96+
97+
async function applyDown(filename: string) {
98+
if (!filename) {
99+
console.error("Filename is required.");
100+
return;
101+
}
102+
await ensureMigrationsDir();
103+
const base = filename.replace(/\.down\.surql$|\.surql$/i, "");
104+
const files = await listMigrationFiles();
105+
const match = files.find((f) => f.startsWith(base));
106+
if (!match) {
107+
console.error(`Migration file not found for base: ${base}`);
108+
return;
109+
}
110+
const downFile = match.replace(/\.surql$/, ".down.surql");
111+
try {
112+
const sql = await readMigrationFile(downFile);
113+
console.log(`Applying down migration: ${downFile}`);
114+
await db.query(sql);
115+
await unmarkMigrationApplied(match);
116+
console.log(`Rolled back migration: ${match}`);
117+
} catch (e) {
118+
const msg = e instanceof Error ? e.message : String(e);
119+
console.error(`Failed to apply down migration: ${downFile}: ${msg}`);
120+
}
121+
}
122+
88123
function printTable(rows: { [key: string]: string }[], columns: string[]) {
89124
if (!rows.length) return;
90125
const colWidths = columns.map((col) =>
@@ -107,33 +142,79 @@ async function create(desc: string) {
107142
const ts = nowTimestamp();
108143
const safeDesc = sanitizeDesc(desc);
109144
const fname = `${ts}_${safeDesc}.surql`;
145+
const downFname = `${ts}_${safeDesc}.down.surql`;
110146
const fpath = join(MIGRATIONS_DIR, fname);
111-
await Deno.writeTextFile(fpath, `-- Migration: ${desc}\n`);
147+
const downFpath = join(MIGRATIONS_DIR, downFname);
148+
await Deno.writeTextFile(
149+
fpath,
150+
`
151+
-- Migration: ${desc}
152+
153+
BEGIN TRANSACTION;
154+
155+
-- Your migration code here
156+
157+
COMMIT TRANSACTION;
158+
`,
159+
);
160+
await Deno.writeTextFile(downFpath, `-- Down migration: ${desc}\n`);
112161
console.log(`Created migration: ${fpath}`);
162+
console.log(`Created down migration: ${downFpath}`);
113163
}
114164

115-
async function apply() {
165+
async function apply(target?: string) {
116166
await ensureMigrationsDir();
117167
const files = await listMigrationFiles();
118168
const appliedRows = await getAppliedMigrations();
119169
const applied = new Set(appliedRows.map((row) => row.filename));
120170
let appliedCount = 0;
121-
for (const file of files) {
122-
if (applied.has(file)) {
123-
console.log(`Already applied: ${file}`);
124-
continue;
171+
const failed: string[] = [];
172+
if (target && target !== "--all") {
173+
if (!files.includes(target)) {
174+
console.error(`Migration file not found: ${target}`);
175+
return;
176+
}
177+
if (applied.has(target)) {
178+
console.log(`Already applied: ${target}`);
179+
return;
180+
}
181+
try {
182+
const sql = await readMigrationFile(target);
183+
console.log(`Applying: ${target}`);
184+
await db.query(sql);
185+
await markMigrationApplied(target);
186+
appliedCount++;
187+
} catch (e) {
188+
console.error(`Failed to apply ${target}: ${e}`);
189+
failed.push(target);
190+
}
191+
} else {
192+
for (const file of files) {
193+
if (applied.has(file)) {
194+
console.log(`Already applied: ${file}`);
195+
continue;
196+
}
197+
try {
198+
const sql = await readMigrationFile(file);
199+
console.log(`Applying: ${file}`);
200+
await db.query(sql);
201+
await markMigrationApplied(file);
202+
appliedCount++;
203+
} catch (e) {
204+
console.error(`Failed to apply ${file}: ${e}`);
205+
failed.push(file);
206+
}
125207
}
126-
const sql = await readMigrationFile(file);
127-
console.log(`Applying: ${file}`);
128-
await db.query(sql);
129-
await markMigrationApplied(file);
130-
appliedCount++;
131208
}
132209
if (appliedCount === 0) {
133-
console.log("No new migrations to apply.");
134-
} else {
210+
console.log("No new migrations applied.");
211+
} else if (!failed.length) {
135212
console.log(`Applied ${appliedCount} migration(s).`);
136213
}
214+
if (failed.length) {
215+
console.log("Failed migrations:");
216+
for (const f of failed) console.log(` - ${f}`);
217+
}
137218
}
138219

139220
async function list() {
@@ -179,7 +260,14 @@ if (import.meta.main) {
179260
await create(args.join("_"));
180261
break;
181262
case "apply":
182-
await apply();
263+
if (args[0] && args[0] !== "--all") {
264+
await apply(args[0]);
265+
} else {
266+
await apply();
267+
}
268+
break;
269+
case "down":
270+
await applyDown(args[0]);
183271
break;
184272
case "list":
185273
await list();

0 commit comments

Comments
 (0)