chore: add no floating promises lint rule (#4249)

* chore: add no floating promises lint rule

* minor cleanup

* fix test by only running if node 22 or greater

---------

Co-authored-by: Iain Sproat <68657+iainsproat@users.noreply.github.com>
This commit is contained in:
Kristaps Fabians Geikins
2025-03-25 13:36:49 +02:00
committed by GitHub
parent 4371945b71
commit f76a2c34d3
16 changed files with 154 additions and 148 deletions
+1 -1
View File
@@ -46,9 +46,9 @@ const configs = [
],
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/no-unsafe-return': 'error',
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/no-base-to-string': 'off',
'@typescript-eslint/no-misused-promises': 'off', // breaks async middlewares (could be fixed tho)
'@typescript-eslint/no-floating-promises': 'off', // too many false positives in knex query builders
'@typescript-eslint/restrict-template-expressions': 'off', // too restrictive
'@typescript-eslint/no-unsafe-enum-comparison': 'off', // too restrictive
'@typescript-eslint/unbound-method': 'off', // too many false positives
@@ -86,7 +86,7 @@ export const validateStoredAuthCodeFactory =
// Token is valid, confirm user is authorized to access specified resources.
if (resources?.workspaceId) {
emit({
await emit({
eventName: 'workspace.authorized',
payload: { userId: payload.userId, workspaceId: resources?.workspaceId }
})
+3 -3
View File
@@ -304,7 +304,7 @@ export const init: SpeckleModule['init'] = async ({ app }) => {
])(req, res, next)
},
async (req, res) => {
errorHandler(req, res, async (req, res) => {
await errorHandler(req, res, async (req, res) => {
const streamId = req.params.streamId
const [projectDb, projectStorage] = await Promise.all([
getProjectDbClient({ projectId: streamId }),
@@ -339,7 +339,7 @@ export const init: SpeckleModule['init'] = async ({ app }) => {
await authMiddlewareCreator(createStreamReadPermissions())(req, res, next)
},
async (req, res) => {
errorHandler(req, res, async (req, res) => {
await errorHandler(req, res, async (req, res) => {
const streamId = req.params.streamId
const [projectDb, projectStorage] = await Promise.all([
getProjectDbClient({ projectId: streamId }),
@@ -378,7 +378,7 @@ export const init: SpeckleModule['init'] = async ({ app }) => {
const getBlobMetadataCollection = getBlobMetadataCollectionFactory({
db: projectDb
})
errorHandler(req, res, async (req, res) => {
await errorHandler(req, res, async (req, res) => {
const blobMetadataCollection = await getBlobMetadataCollection({
streamId: req.params.streamId,
query: fileName as string
+1 -1
View File
@@ -45,7 +45,7 @@ const main = async () => {
return execution
}
main().then(() => {
void main().then(() => {
// weird TS typing issue
yargs.exit(0, undefined as unknown as Error)
})
@@ -66,9 +66,9 @@ export const hasScopes: GraphqlDirectiveBuilder = () => {
const currentScopes = context.scopes
await Promise.all(
requiredScopes.map(async (requiredScope: string) => {
requiredScopes.map((requiredScope: string) =>
validateScopes(currentScopes, requiredScope)
})
)
)
const data = await resolve.apply(this, args)
@@ -46,7 +46,7 @@ export const scheduledCallbackWrapper = async (
'The triggered task execution {taskName} failed at {scheduledTime}'
)
} finally {
releaseTaskLock(lock)
await releaseTaskLock(lock)
}
}
@@ -48,6 +48,8 @@ import { parse, Parser } from 'csv-parse'
import { createReadStream } from 'fs'
import { createObjectsBatchedAndNoClosuresFactory } from '@/modules/core/services/objects/management'
const IS_NODE_22_OR_ABOVE = process.versions.node.split('.').map(Number)[0] >= 22
const getServerInfo = getServerInfoFactory({ db })
const getUser = legacyGetUserFactory({ db })
const requestNewEmailVerification = requestNewEmailVerificationFactory({
@@ -99,144 +101,148 @@ describe('Objects streaming REST @core', () => {
const ctx = await beforeEachContext()
;({ serverAddress } = await initializeTestServer(ctx))
})
it('should close database connections if client connection is prematurely closed', async () => {
const userId = await createUser({
name: 'emails user',
email: createRandomEmail(),
password: createRandomPassword()
})
const user = await getUser(userId)
const project = {
id: '',
name: 'test project',
ownerId: userId
}
await createTestStream(project as unknown as BasicTestStream, user)
const token = `Bearer ${await createPersonalAccessToken(
user.id,
'test token user A',
[
Scopes.Streams.Read,
Scopes.Streams.Write,
Scopes.Users.Read,
Scopes.Users.Email,
Scopes.Tokens.Write,
Scopes.Tokens.Read,
Scopes.Profile.Read,
Scopes.Profile.Email
]
)}`
const manyObjs: { commit: RawSpeckleObject; objs: RawSpeckleObject[] } =
generateManyObjects(3333, 'perlin merlin magic')
const objsIds = manyObjs.objs.map((o) => o.id)
await createObjectsBatched({ streamId: project.id, objects: manyObjs.objs })
for (let i = 0; i < 4; i++) {
forceCloseStreamingConnection({
serverAddress,
projectId: project.id,
token,
objsIds
;(IS_NODE_22_OR_ABOVE ? it : it.skip)(
'should close database connections if client connection is prematurely closed',
async () => {
const userId = await createUser({
name: 'emails user',
email: createRandomEmail(),
password: createRandomPassword()
})
}
const user = await getUser(userId)
//sleep for a bit to allow the server to close the connections
await new Promise((r) => setTimeout(r, 3000))
const gaugeContents = await determineRemainingDatabaseConnectionCapacity({
serverAddress
})
expect(parseInt(gaugeContents), gaugeContents).to.gte(4) //expect all connections to become available again after the client closes them
})
const project = {
id: '',
name: 'test project',
ownerId: userId
}
await createTestStream(project as unknown as BasicTestStream, user)
it('should stream model with some failing feature', async () => {
const userId = await createUser({
name: 'emails user',
email: createRandomEmail(),
password: createRandomPassword()
})
const user = await getUser(userId)
const token = `Bearer ${await createPersonalAccessToken(
user.id,
'test token user A',
[
Scopes.Streams.Read,
Scopes.Streams.Write,
Scopes.Users.Read,
Scopes.Users.Email,
Scopes.Tokens.Write,
Scopes.Tokens.Read,
Scopes.Profile.Read,
Scopes.Profile.Email
]
)}`
const project = {
id: '',
name: 'test project',
ownerId: userId
}
await createTestStream(project as unknown as BasicTestStream, user)
const manyObjs: { commit: RawSpeckleObject; objs: RawSpeckleObject[] } =
generateManyObjects(3333, 'perlin merlin magic')
const objsIds = manyObjs.objs.map((o) => o.id)
const token = `Bearer ${await createPersonalAccessToken(
user.id,
'test token user A',
[
Scopes.Streams.Read,
Scopes.Streams.Write,
Scopes.Users.Read,
Scopes.Users.Email,
Scopes.Tokens.Write,
Scopes.Tokens.Read,
Scopes.Profile.Read,
Scopes.Profile.Email
]
)}`
// import CSV file
const csvStream = createReadStream(
//FIXME this relies on running this test from `packages/server` directory
`${process.cwd()}/test/assets/failing-streaming-model-f547dc4e88.csv`
)
// eslint-disable-next-line camelcase
.pipe(parse({ delimiter: ',', from_line: 2 }))
function csvParserAsPromise(
stream: Parser
): Promise<{ manyObjs: RawSpeckleObject[]; objsIds: string[] }> {
const manyObjs: RawSpeckleObject[] = []
const objsIds: string[] = []
return new Promise((resolve, reject) => {
stream.on('data', (row: string[]) => {
const obj = JSON.parse(row[1])
manyObjs.push(obj)
objsIds.push(row[0])
await createObjectsBatched({ streamId: project.id, objects: manyObjs.objs })
for (let i = 0; i < 4; i++) {
await forceCloseStreamingConnection({
serverAddress,
projectId: project.id,
token,
objsIds
})
stream.on('end', () => resolve({ manyObjs, objsIds }))
// eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
stream.on('error', (error: unknown) => reject(error))
}
//sleep for a bit to allow the server to close the connections
await new Promise((r) => setTimeout(r, 3000))
const gaugeContents = await determineRemainingDatabaseConnectionCapacity({
serverAddress
})
expect(parseInt(gaugeContents), gaugeContents).to.gte(4) //expect all connections to become available again after the client closes them
}
const { manyObjs, objsIds } = await csvParserAsPromise(csvStream)
const preGaugeContents = await determineRemainingDatabaseConnectionCapacity({
serverAddress
})
expect(
parseInt(preGaugeContents),
`Prior to test, we did not have sufficient DB connections free: ${preGaugeContents}`
).to.gte(4) // all connections are available before the test
await createObjectsBatched({ streamId: project.id, objects: manyObjs })
for (let i = 0; i < 1; i++) {
forceCloseStreamingConnection({
serverAddress,
projectId: project.id,
token,
objsIds
)
;(IS_NODE_22_OR_ABOVE ? it : it.skip)(
'should stream model with some failing feature',
async () => {
const userId = await createUser({
name: 'emails user',
email: createRandomEmail(),
password: createRandomPassword()
})
}
const user = await getUser(userId)
//sleep for a bit to allow the server to close the connections
await new Promise((r) => setTimeout(r, 3000))
const postGaugeContents = await determineRemainingDatabaseConnectionCapacity({
serverAddress
})
expect(
parseInt(postGaugeContents),
`After the test, we did not have sufficient DB connections free: ${postGaugeContents}`
).to.gte(4) //expect all connections to become available again after the client closes them
}).timeout(50000)
const project = {
id: '',
name: 'test project',
ownerId: userId
}
await createTestStream(project as unknown as BasicTestStream, user)
const token = `Bearer ${await createPersonalAccessToken(
user.id,
'test token user A',
[
Scopes.Streams.Read,
Scopes.Streams.Write,
Scopes.Users.Read,
Scopes.Users.Email,
Scopes.Tokens.Write,
Scopes.Tokens.Read,
Scopes.Profile.Read,
Scopes.Profile.Email
]
)}`
// import CSV file
const csvStream = createReadStream(
//FIXME this relies on running this test from `packages/server` directory
`${process.cwd()}/test/assets/failing-streaming-model-f547dc4e88.csv`
)
// eslint-disable-next-line camelcase
.pipe(parse({ delimiter: ',', from_line: 2 }))
function csvParserAsPromise(
stream: Parser
): Promise<{ manyObjs: RawSpeckleObject[]; objsIds: string[] }> {
const manyObjs: RawSpeckleObject[] = []
const objsIds: string[] = []
return new Promise((resolve, reject) => {
stream.on('data', (row: string[]) => {
const obj = JSON.parse(row[1])
manyObjs.push(obj)
objsIds.push(row[0])
})
stream.on('end', () => resolve({ manyObjs, objsIds }))
// eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
stream.on('error', (error: unknown) => reject(error))
})
}
const { manyObjs, objsIds } = await csvParserAsPromise(csvStream)
const preGaugeContents = await determineRemainingDatabaseConnectionCapacity({
serverAddress
})
expect(
parseInt(preGaugeContents),
`Prior to test, we did not have sufficient DB connections free: ${preGaugeContents}`
).to.gte(4) // all connections are available before the test
await createObjectsBatched({ streamId: project.id, objects: manyObjs })
for (let i = 0; i < 1; i++) {
await forceCloseStreamingConnection({
serverAddress,
projectId: project.id,
token,
objsIds
})
}
//sleep for a bit to allow the server to close the connections
await new Promise((r) => setTimeout(r, 3000))
const postGaugeContents = await determineRemainingDatabaseConnectionCapacity({
serverAddress
})
expect(
parseInt(postGaugeContents),
`After the test, we did not have sufficient DB connections free: ${postGaugeContents}`
).to.gte(4) //expect all connections to become available again after the client closes them
}
).timeout(50000)
})
const forceCloseStreamingConnection = async (params: {
@@ -437,7 +437,7 @@ describe('Core @user-emails', () => {
assertLowercase(updatedEmail)
randomCaseGuy.email = newEmail
updateEmailDirectly(newEmail)
await updateEmailDirectly(newEmail)
})
it('with validateAndCreateUserEmailFactory()', async () => {
+2 -2
View File
@@ -155,12 +155,12 @@ const scheduleWorkspaceTrialExpiry = ({
'Workspace trial expired for {workspaceIds}.'
)
await Promise.all(
expiredWorkspacePlans.map(async (plan) => {
expiredWorkspacePlans.map(async (plan) =>
emit({
eventName: 'gatekeeper.workspace-trial-expired',
payload: { workspaceId: plan.workspaceId }
})
})
)
)
}
}
@@ -55,7 +55,7 @@ export const createRenderRequestFactory =
id: crs({ length: 10 })
})
deps.publish(ProjectSubscriptions.ProjectVersionGendoAIRenderCreated, {
await deps.publish(ProjectSubscriptions.ProjectVersionGendoAIRenderCreated, {
projectVersionGendoAIRenderCreated: newRecord
})
@@ -109,7 +109,7 @@ export const updateRenderRequestFactory =
input: { ...input, updatedAt: new Date() },
id: baseRequest.id
})
deps.publish(ProjectSubscriptions.ProjectVersionGendoAIRenderUpdated, {
await deps.publish(ProjectSubscriptions.ProjectVersionGendoAIRenderUpdated, {
projectVersionGendoAIRenderUpdated: record
})
@@ -136,7 +136,7 @@ const isMultiregionJob = (job: Bull.Job): job is Bull.Job<MultiregionJob> => {
*/
export const startQueue = async () => {
const queue = getQueue()
queue.process(async (job) => {
void queue.process(async (job) => {
if (!isMultiregionJob(job)) {
throw new MultiRegionInvalidJobError()
}
@@ -115,7 +115,7 @@ export function registerNotificationHandlers(
*/
export async function consumeIncomingNotifications() {
const queue = getQueue()
queue.process(async (job): Promise<NotificationJobResult> => {
void queue.process(async (job): Promise<NotificationJobResult> => {
let notificationType: Optional<NotificationType>
try {
notificationsLogger.info('New notification received...')
+1 -1
View File
@@ -127,7 +127,7 @@ export const init: SpeckleModule['init'] = ({ app, isInitial, metricsRegister })
})
app.use(previewRouter)
previewResponseQueue.process(async (payload, done) => {
void previewResponseQueue.process(async (payload, done) => {
const parsedMessage = previewResultPayload.safeParse(payload.data)
if (!parsedMessage.success) {
logger.error(
@@ -409,7 +409,7 @@ describe('Workspace SSO repositories', () => {
})
afterEach(async () => {
truncateTables(['user_sso_sessions'])
await truncateTables(['user_sso_sessions'])
})
describe('when deleting an sso provider that exists', async () => {
@@ -834,7 +834,7 @@ describe('Workspaces GQL CRUD', () => {
authorId: ''
}
createTestCommit(testVersion, {
await createTestCommit(testVersion, {
owner: testAdminUser,
stream: workspaceProject
})
@@ -191,7 +191,7 @@ export const initKnexPrometheusMetrics = async (params: {
// configure hooks on knex
for (const dbClient of await params.getAllDbClients()) {
if (initializedRegions.includes(dbClient.regionKey)) continue
initKnexPrometheusMetricsForRegionEvents({
await initKnexPrometheusMetricsForRegionEvents({
logger: params.logger,
region: dbClient.regionKey,
db: dbClient.client