-
Notifications
You must be signed in to change notification settings - Fork 2
/
migrations.ts
100 lines (93 loc) · 3.44 KB
/
migrations.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import {createCli, type TrpcCliMeta, trpcServer, z} from '../../src'
import * as trpcCompat from '../../src/trpc-compat'
const trpc = trpcServer.initTRPC.meta<TrpcCliMeta>().create()
const migrations = getMigrations()
const searchProcedure = trpc.procedure
.input(
z.object({
status: z.enum(['executed', 'pending']).optional().describe('Filter to only show migrations with this status'),
}),
)
.use(async ({next, input}) => {
return next({
ctx: {
filter: (list: typeof migrations) => list.filter(m => !input.status || m.status === input.status),
},
})
})
const router = trpc.router({
up: trpc.procedure
.meta({description: 'Apply migrations. By default all pending migrations will be applied.'})
.input(
z.union([
z.object({}).strict(), // use strict here to make sure `{step: 1}` doesn't "match" this first, just by having an ignore `step` property
z.object({
to: z.string().describe('Mark migrations up to this one as exectued'),
}),
z.object({
step: z.number().int().positive().describe('Mark this many migrations as executed'),
}),
]),
)
.query(async ({input}) => {
let toBeApplied = migrations
if ('to' in input) {
const index = migrations.findIndex(m => m.name === input.to)
toBeApplied = migrations.slice(0, index + 1)
}
if ('step' in input) {
const start = migrations.findIndex(m => m.status === 'pending')
toBeApplied = migrations.slice(0, start + input.step)
}
toBeApplied.forEach(m => (m.status = 'executed'))
return migrations.map(m => `${m.name}: ${m.status}`)
}),
create: trpc.procedure
.meta({description: 'Create a new migration'})
.input(
z.object({name: z.string(), content: z.string()}), //
)
.mutation(async ({input}) => {
migrations.push({...input, status: 'pending'})
return migrations
}),
list: searchProcedure.meta({description: 'List all migrations'}).query(({ctx}) => ctx.filter(migrations)),
search: trpc.router({
byName: searchProcedure
.meta({description: 'Look for migrations by name'})
.input(z.object({name: z.string()}))
.query(({ctx, input}) => {
return ctx.filter(migrations.filter(m => m.name === input.name))
}),
byContent: searchProcedure
.meta({description: 'Look for migrations by their script content'})
.input(
z.object({searchTerm: z.string().describe('Only show migrations whose `content` value contains this string')}),
)
.query(({ctx, input}) => {
return ctx.filter(migrations.filter(m => m.content.includes(input.searchTerm)))
}),
}),
}) satisfies trpcCompat.Trpc10RouterLike
const cli = createCli({
router,
alias: (fullName, {command}) => {
if (fullName === 'status') {
return 's'
}
if (fullName === 'searchTerm' && command.startsWith('search.')) {
return 'q'
}
return undefined
},
})
void cli.run()
function getMigrations() {
return [
{name: 'one', content: 'create table one(id int, name text)', status: 'executed'},
{name: 'two', content: 'create view two as select name from one', status: 'executed'},
{name: 'three', content: 'create table three(id int, foo int)', status: 'pending'},
{name: 'four', content: 'create view four as select foo from three', status: 'pending'},
{name: 'five', content: 'create table five(id int)', status: 'pending'},
]
}