@@ -59,7 +59,11 @@ function sanitizeDesc(desc: string) {
5959async 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 ( / \. d o w n \. s u r q l $ | \. s u r q l $ / 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 ( / \. s u r q l $ / , ".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+
88123function 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
139220async 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