• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

supabase / storage / 14351891695

09 Apr 2025 07:51AM UTC coverage: 77.767% (-0.2%) from 77.92%
14351891695

push

github

web-flow
feat: freeze migration name (#656)

1424 of 1986 branches covered (71.7%)

Branch coverage included in aggregate %.

147 of 223 new or added lines in 8 files covered. (65.92%)

1 existing line in 1 file now uncovered.

16397 of 20930 relevant lines covered (78.34%)

156.18 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

66.12
/src/http/plugins/db.ts
1
import fastifyPlugin from 'fastify-plugin'
1✔
2
import { getConfig, MultitenantMigrationStrategy } from '../../config'
1✔
3
import {
1✔
4
  getServiceKeyUser,
1✔
5
  getTenantConfig,
1✔
6
  TenantConnection,
1✔
7
  getPostgresConnection,
1✔
8
} from '@internal/database'
1✔
9
import { verifyJWT } from '@internal/auth'
1✔
10
import { logSchema } from '@internal/monitoring'
1✔
11
import { createMutexByKey } from '@internal/concurrency'
1✔
12
import {
1✔
13
  areMigrationsUpToDate,
1✔
14
  DBMigration,
1✔
15
  lastLocalMigrationName,
1✔
16
  progressiveMigrations,
1✔
17
  runMigrationsOnTenant,
1✔
18
  updateTenantMigrationsState,
1✔
19
} from '@internal/database/migrations'
1✔
20

1✔
21
declare module 'fastify' {
1✔
22
  interface FastifyRequest {
1✔
23
    db: TenantConnection
1✔
24
    latestMigration?: keyof typeof DBMigration
1✔
25
  }
1✔
26
}
1✔
27

1✔
28
const { dbMigrationStrategy, isMultitenant, dbMigrationFreezeAt } = getConfig()
1✔
29

1✔
30
export const db = fastifyPlugin(
1✔
31
  async function db(fastify) {
1✔
32
    fastify.register(migrations)
2,016✔
33

2,016✔
34
    fastify.decorateRequest('db', null)
2,016✔
35

2,016✔
36
    fastify.addHook('preHandler', async (request) => {
2,016✔
37
      const adminUser = await getServiceKeyUser(request.tenantId)
100✔
38
      const userPayload =
100✔
39
        request.jwtPayload ?? (await verifyJWT<{ role?: string }>(request.jwt, adminUser.jwtSecret))
100!
40

100✔
41
      request.db = await getPostgresConnection({
100✔
42
        user: {
100✔
43
          payload: userPayload,
100✔
44
          jwt: request.jwt,
100✔
45
        },
100✔
46
        superUser: adminUser,
100✔
47
        tenantId: request.tenantId,
100✔
48
        host: request.headers['x-forwarded-host'] as string,
100✔
49
        headers: request.headers,
100✔
50
        path: request.url,
100✔
51
        method: request.method,
100✔
52
        operation: () => request.operation?.type,
100✔
53
      })
100✔
54
    })
2,016✔
55

2,016✔
56
    fastify.addHook('onSend', async (request, reply, payload) => {
2,016✔
57
      if (request.db) {
113✔
58
        request.db.dispose().catch((e) => {
100✔
59
          logSchema.error(request.log, 'Error disposing db connection', {
×
60
            type: 'db-connection',
×
61
            error: e,
×
62
          })
×
63
        })
100✔
64
      }
100✔
65
      return payload
113✔
66
    })
2,016✔
67

2,016✔
68
    fastify.addHook('onTimeout', async (request) => {
2,016✔
69
      if (request.db) {
×
70
        request.db.dispose().catch((e) => {
×
71
          logSchema.error(request.log, 'Error disposing db connection', {
×
72
            type: 'db-connection',
×
73
            error: e,
×
74
          })
×
75
        })
×
76
      }
×
77
    })
2,016✔
78

2,016✔
79
    fastify.addHook('onRequestAbort', async (request) => {
2,016✔
80
      if (request.db) {
2✔
81
        request.db.dispose().catch((e) => {
2✔
82
          logSchema.error(request.log, 'Error disposing db connection', {
×
83
            type: 'db-connection',
×
84
            error: e,
×
85
          })
×
86
        })
2✔
87
      }
2✔
88
    })
2,016✔
89
  },
2,016✔
90
  { name: 'db-init' }
1✔
91
)
1✔
92

1✔
93
interface DbSuperUserPluginOptions {
1✔
94
  disableHostCheck?: boolean
1✔
95
  maxConnections?: number
1✔
96
}
1✔
97

1✔
98
export const dbSuperUser = fastifyPlugin<DbSuperUserPluginOptions>(
1✔
99
  async function dbSuperUser(fastify, opts) {
1✔
100
    fastify.register(migrations)
630✔
101
    fastify.decorateRequest('db', null)
630✔
102

630✔
103
    fastify.addHook('preHandler', async (request) => {
630✔
104
      const adminUser = await getServiceKeyUser(request.tenantId)
11✔
105

11✔
106
      request.db = await getPostgresConnection({
11✔
107
        user: adminUser,
11✔
108
        superUser: adminUser,
11✔
109
        tenantId: request.tenantId,
11✔
110
        host: request.headers['x-forwarded-host'] as string,
11✔
111
        path: request.url,
11✔
112
        method: request.method,
11✔
113
        headers: request.headers,
11✔
114
        disableHostCheck: opts.disableHostCheck,
11✔
115
        maxConnections: opts.maxConnections,
11✔
116
        operation: () => request.operation?.type,
11✔
117
      })
11✔
118
    })
630✔
119

630✔
120
    fastify.addHook('onSend', async (request, reply, payload) => {
630✔
121
      if (request.db) {
13✔
122
        request.db.dispose().catch((e) => {
11✔
123
          logSchema.error(request.log, 'Error disposing db connection', {
×
124
            type: 'db-connection',
×
125
            error: e,
×
126
          })
×
127
        })
11✔
128
      }
11✔
129

13✔
130
      return payload
13✔
131
    })
630✔
132

630✔
133
    fastify.addHook('onTimeout', async (request) => {
630✔
134
      if (request.db) {
×
135
        request.db.dispose().catch((e) => {
×
136
          logSchema.error(request.log, 'Error disposing db connection', {
×
137
            type: 'db-connection',
×
138
            error: e,
×
139
          })
×
140
        })
×
141
      }
×
142
    })
630✔
143

630✔
144
    fastify.addHook('onRequestAbort', async (request) => {
630✔
145
      if (request.db) {
×
146
        request.db.dispose().catch((e) => {
×
147
          logSchema.error(request.log, 'Error disposing db connection', {
×
148
            type: 'db-connection',
×
149
            error: e,
×
150
          })
×
151
        })
×
152
      }
×
153
    })
630✔
154
  },
630✔
155
  { name: 'db-superuser-init' }
1✔
156
)
1✔
157

1✔
158
/**
1✔
159
 * Handle database migration for multitenant applications when a request is made
1✔
160
 */
1✔
161
export const migrations = fastifyPlugin(
1✔
162
  async function migrations(fastify) {
1✔
163
    fastify.addHook('preHandler', async (req) => {
2,646✔
164
      if (isMultitenant) {
111!
165
        const { migrationVersion } = await getTenantConfig(req.tenantId)
×
166
        req.latestMigration = migrationVersion
×
167
        return
×
168
      }
×
169

111✔
170
      req.latestMigration = await lastLocalMigrationName()
111✔
171
    })
2,646✔
172

2,646✔
173
    if (dbMigrationStrategy === MultitenantMigrationStrategy.ON_REQUEST) {
2,646✔
174
      const migrationsMutex = createMutexByKey()
2,646✔
175

2,646✔
176
      fastify.addHook('preHandler', async (request) => {
2,646✔
177
        // migrations are handled via async migrations
111✔
178
        if (!isMultitenant) {
111✔
179
          return
111✔
180
        }
111✔
181

×
182
        const tenant = await getTenantConfig(request.tenantId)
×
183
        const migrationsUpToDate = await areMigrationsUpToDate(request.tenantId)
×
184

×
185
        if (tenant.syncMigrationsDone || migrationsUpToDate) {
111!
186
          return
×
187
        }
×
188

×
189
        await migrationsMutex(request.tenantId, async () => {
×
190
          const tenant = await getTenantConfig(request.tenantId)
×
191

×
192
          if (tenant.syncMigrationsDone) {
×
193
            return
×
194
          }
×
195

×
NEW
196
          await runMigrationsOnTenant({
×
NEW
197
            databaseUrl: tenant.databaseUrl,
×
NEW
198
            tenantId: request.tenantId,
×
NEW
199
            upToMigration: dbMigrationFreezeAt,
×
NEW
200
          })
×
201
          await updateTenantMigrationsState(request.tenantId)
×
202
          tenant.syncMigrationsDone = true
×
203
        })
×
204
      })
2,646✔
205
    }
2,646✔
206

2,646✔
207
    if (dbMigrationStrategy === MultitenantMigrationStrategy.PROGRESSIVE) {
2,646!
208
      fastify.addHook('preHandler', async (request) => {
×
209
        if (!isMultitenant) {
×
210
          return
×
211
        }
×
212

×
213
        const tenant = await getTenantConfig(request.tenantId)
×
214
        const migrationsUpToDate = await areMigrationsUpToDate(request.tenantId)
×
215

×
216
        // migrations are up to date
×
217
        if (tenant.syncMigrationsDone || migrationsUpToDate) {
×
218
          return
×
219
        }
×
220

×
221
        progressiveMigrations.addTenant(request.tenantId)
×
222
      })
×
223
    }
×
224
  },
2,646✔
225
  { name: 'db-migrations' }
1✔
226
)
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc