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

typeorm / typeorm / 19549987525

20 Nov 2025 08:11PM UTC coverage: 80.769% (+4.3%) from 76.433%
19549987525

push

github

web-flow
ci: run tests on commits to master and next (#11783)

Co-authored-by: Oleg "OSA413" Sokolov <OSA413@users.noreply.github.com>

26500 of 32174 branches covered (82.36%)

Branch coverage included in aggregate %.

91252 of 113615 relevant lines covered (80.32%)

88980.79 hits per line

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

79.72
/src/query-builder/SoftDeleteQueryBuilder.ts
1
import { QueryBuilder } from "./QueryBuilder"
26✔
2
import { ObjectLiteral } from "../common/ObjectLiteral"
26✔
3
import { EntityTarget } from "../common/EntityTarget"
26✔
4
import { DataSource } from "../data-source/DataSource"
26✔
5
import { QueryRunner } from "../query-runner/QueryRunner"
26✔
6
import { WhereExpressionBuilder } from "./WhereExpressionBuilder"
26✔
7
import { Brackets } from "./Brackets"
26✔
8
import { UpdateResult } from "./result/UpdateResult"
26✔
9
import { ReturningStatementNotSupportedError } from "../error/ReturningStatementNotSupportedError"
26✔
10
import { ReturningResultsEntityUpdator } from "./ReturningResultsEntityUpdator"
26✔
11
import { OrderByCondition } from "../find-options/OrderByCondition"
26✔
12
import { LimitOnUpdateNotSupportedError } from "../error/LimitOnUpdateNotSupportedError"
26✔
13
import { MissingDeleteDateColumnError } from "../error/MissingDeleteDateColumnError"
26✔
14
import { UpdateValuesMissingError } from "../error/UpdateValuesMissingError"
26✔
15
import { TypeORMError } from "../error"
26✔
16
import { DriverUtils } from "../driver/DriverUtils"
26✔
17
import { InstanceChecker } from "../util/InstanceChecker"
26✔
18

26✔
19
/**
26✔
20
 * Allows to build complex sql queries in a fashion way and execute those queries.
26✔
21
 */
26✔
22
export class SoftDeleteQueryBuilder<Entity extends ObjectLiteral>
26✔
23
    extends QueryBuilder<Entity>
26✔
24
    implements WhereExpressionBuilder
26✔
25
{
26✔
26
    readonly "@instanceof" = Symbol.for("SoftDeleteQueryBuilder")
26✔
27

26✔
28
    // -------------------------------------------------------------------------
26✔
29
    // Constructor
26✔
30
    // -------------------------------------------------------------------------
26✔
31

26✔
32
    constructor(
26✔
33
        connectionOrQueryBuilder: DataSource | QueryBuilder<any>,
1,156✔
34
        queryRunner?: QueryRunner,
1,156✔
35
    ) {
1,156✔
36
        super(connectionOrQueryBuilder as any, queryRunner)
1,156✔
37
        this.expressionMap.aliasNamePrefixingEnabled = false
1,156✔
38
    }
1,156✔
39

26✔
40
    // -------------------------------------------------------------------------
26✔
41
    // Public Implemented Methods
26✔
42
    // -------------------------------------------------------------------------
26✔
43

26✔
44
    /**
26✔
45
     * Gets generated SQL query without parameters being replaced.
26✔
46
     */
26✔
47
    getQuery(): string {
26✔
48
        let sql = this.createUpdateExpression()
1,072✔
49
        sql += this.createCteExpression()
1,072✔
50
        sql += this.createOrderByExpression()
1,072✔
51
        sql += this.createLimitExpression()
1,072✔
52
        return this.replacePropertyNamesForTheWholeQuery(sql.trim())
1,072✔
53
    }
1,072✔
54

26✔
55
    /**
26✔
56
     * Executes sql generated by query builder and returns raw database results.
26✔
57
     */
26✔
58
    async execute(): Promise<UpdateResult> {
26✔
59
        const queryRunner = this.obtainQueryRunner()
1,072✔
60
        let transactionStartedByUs: boolean = false
1,072✔
61

1,072✔
62
        try {
1,072✔
63
            // start transaction if it was enabled
1,072✔
64
            if (
1,072✔
65
                this.expressionMap.useTransaction === true &&
1,072!
66
                queryRunner.isTransactionActive === false
×
67
            ) {
1,072!
68
                await queryRunner.startTransaction()
×
69
                transactionStartedByUs = true
×
70
            }
×
71

1,072✔
72
            // call before soft remove and recover methods in listeners and subscribers
1,072✔
73
            if (
1,072✔
74
                this.expressionMap.callListeners === true &&
1,072✔
75
                this.expressionMap.mainAlias!.hasMetadata
428✔
76
            ) {
1,072✔
77
                if (this.expressionMap.queryType === "soft-delete")
428✔
78
                    await queryRunner.broadcaster.broadcast(
428✔
79
                        "BeforeSoftRemove",
260✔
80
                        this.expressionMap.mainAlias!.metadata,
260✔
81
                    )
260✔
82
                else if (this.expressionMap.queryType === "restore")
168✔
83
                    await queryRunner.broadcaster.broadcast(
168✔
84
                        "BeforeRecover",
168✔
85
                        this.expressionMap.mainAlias!.metadata,
168✔
86
                    )
168✔
87
            }
428✔
88

1,072✔
89
            // if update entity mode is enabled we may need extra columns for the returning statement
1,072✔
90
            const returningResultsEntityUpdator =
1,072✔
91
                new ReturningResultsEntityUpdator(
1,072✔
92
                    queryRunner,
1,072✔
93
                    this.expressionMap,
1,072✔
94
                )
1,072✔
95
            if (
1,072✔
96
                this.expressionMap.updateEntity === true &&
1,072✔
97
                this.expressionMap.mainAlias!.hasMetadata &&
1,072✔
98
                this.expressionMap.whereEntities.length > 0
1,072✔
99
            ) {
1,072✔
100
                this.expressionMap.extraReturningColumns =
616✔
101
                    returningResultsEntityUpdator.getSoftDeletionReturningColumns()
616✔
102
            }
616✔
103

1,072✔
104
            // execute update query
1,072✔
105
            const [sql, parameters] = this.getQueryAndParameters()
1,072✔
106

1,072✔
107
            const queryResult = await queryRunner.query(sql, parameters, true)
1,072✔
108
            const updateResult = UpdateResult.from(queryResult)
864✔
109

864✔
110
            // if we are updating entities and entity updation is enabled we must update some of entity columns (like version, update date, etc.)
864✔
111
            if (
864✔
112
                this.expressionMap.updateEntity === true &&
864✔
113
                this.expressionMap.mainAlias!.hasMetadata &&
1,072✔
114
                this.expressionMap.whereEntities.length > 0
864✔
115
            ) {
1,072✔
116
                await returningResultsEntityUpdator.update(
504✔
117
                    updateResult,
504✔
118
                    this.expressionMap.whereEntities,
504✔
119
                )
504✔
120
            }
504✔
121

864✔
122
            // call after soft remove and recover methods in listeners and subscribers
864✔
123
            if (
864✔
124
                this.expressionMap.callListeners === true &&
864✔
125
                this.expressionMap.mainAlias!.hasMetadata
332✔
126
            ) {
1,072✔
127
                if (this.expressionMap.queryType === "soft-delete")
332✔
128
                    await queryRunner.broadcaster.broadcast(
332✔
129
                        "AfterSoftRemove",
212✔
130
                        this.expressionMap.mainAlias!.metadata,
212✔
131
                    )
212✔
132
                else if (this.expressionMap.queryType === "restore")
120✔
133
                    await queryRunner.broadcaster.broadcast(
120✔
134
                        "AfterRecover",
120✔
135
                        this.expressionMap.mainAlias!.metadata,
120✔
136
                    )
120✔
137
            }
332✔
138

864✔
139
            // close transaction if we started it
864✔
140
            if (transactionStartedByUs) await queryRunner.commitTransaction()
1,072!
141

864✔
142
            return updateResult
864✔
143
        } catch (error) {
1,072✔
144
            // rollback transaction if we started it
208✔
145
            if (transactionStartedByUs) {
208!
146
                try {
×
147
                    await queryRunner.rollbackTransaction()
×
148
                } catch (rollbackError) {}
×
149
            }
×
150
            throw error
208✔
151
        } finally {
1,072!
152
            if (queryRunner !== this.queryRunner) {
1,072✔
153
                // means we created our own query runner
428✔
154
                await queryRunner.release()
428✔
155
            }
428✔
156
        }
1,072✔
157
    }
1,072✔
158

26✔
159
    // -------------------------------------------------------------------------
26✔
160
    // Public Methods
26✔
161
    // -------------------------------------------------------------------------
26✔
162

26✔
163
    /**
26✔
164
     * Specifies FROM which entity's table select/update/delete/soft-delete will be executed.
26✔
165
     * Also sets a main string alias of the selection data.
26✔
166
     */
26✔
167
    from<T extends ObjectLiteral>(
26✔
168
        entityTarget: EntityTarget<T>,
1,072✔
169
        aliasName?: string,
1,072✔
170
    ): SoftDeleteQueryBuilder<T> {
1,072✔
171
        entityTarget = InstanceChecker.isEntitySchema(entityTarget)
1,072✔
172
            ? entityTarget.options.name
1,072!
173
            : entityTarget
1,072✔
174
        const mainAlias = this.createFromAlias(entityTarget, aliasName)
1,072✔
175
        this.expressionMap.setMainAlias(mainAlias)
1,072✔
176
        return this as any as SoftDeleteQueryBuilder<T>
1,072✔
177
    }
1,072✔
178

26✔
179
    /**
26✔
180
     * Sets WHERE condition in the query builder.
26✔
181
     * If you had previously WHERE expression defined,
26✔
182
     * calling this function will override previously set WHERE conditions.
26✔
183
     * Additionally you can add parameters used in where expression.
26✔
184
     */
26✔
185
    where(
26✔
186
        where:
476✔
187
            | string
476✔
188
            | ((qb: this) => string)
476✔
189
            | Brackets
476✔
190
            | ObjectLiteral
476✔
191
            | ObjectLiteral[],
476✔
192
        parameters?: ObjectLiteral,
476✔
193
    ): this {
476✔
194
        this.expressionMap.wheres = [] // don't move this block below since computeWhereParameter can add where expressions
476✔
195
        const condition = this.getWhereCondition(where)
476✔
196
        if (condition)
476✔
197
            this.expressionMap.wheres = [
476✔
198
                { type: "simple", condition: condition },
392✔
199
            ]
392✔
200
        if (parameters) this.setParameters(parameters)
476✔
201
        return this
392✔
202
    }
392✔
203

26✔
204
    /**
26✔
205
     * Adds new AND WHERE condition in the query builder.
26✔
206
     * Additionally you can add parameters used in where expression.
26✔
207
     */
26✔
208
    andWhere(
26✔
209
        where:
×
210
            | string
×
211
            | ((qb: this) => string)
×
212
            | Brackets
×
213
            | ObjectLiteral
×
214
            | ObjectLiteral[],
×
215
        parameters?: ObjectLiteral,
×
216
    ): this {
×
217
        this.expressionMap.wheres.push({
×
218
            type: "and",
×
219
            condition: this.getWhereCondition(where),
×
220
        })
×
221
        if (parameters) this.setParameters(parameters)
×
222
        return this
×
223
    }
×
224

26✔
225
    /**
26✔
226
     * Adds new OR WHERE condition in the query builder.
26✔
227
     * Additionally you can add parameters used in where expression.
26✔
228
     */
26✔
229
    orWhere(
26✔
230
        where:
616✔
231
            | string
616✔
232
            | ((qb: this) => string)
616✔
233
            | Brackets
616✔
234
            | ObjectLiteral
616✔
235
            | ObjectLiteral[],
616✔
236
        parameters?: ObjectLiteral,
616✔
237
    ): this {
616✔
238
        this.expressionMap.wheres.push({
616✔
239
            type: "or",
616✔
240
            condition: this.getWhereCondition(where),
616✔
241
        })
616✔
242
        if (parameters) this.setParameters(parameters)
616!
243
        return this
616✔
244
    }
616✔
245

26✔
246
    /**
26✔
247
     * Adds new AND WHERE with conditions for the given ids.
26✔
248
     */
26✔
249
    whereInIds(ids: any | any[]): this {
26✔
250
        return this.where(this.getWhereInIdsCondition(ids))
×
251
    }
×
252

26✔
253
    /**
26✔
254
     * Adds new AND WHERE with conditions for the given ids.
26✔
255
     */
26✔
256
    andWhereInIds(ids: any | any[]): this {
26✔
257
        return this.andWhere(this.getWhereInIdsCondition(ids))
×
258
    }
×
259

26✔
260
    /**
26✔
261
     * Adds new OR WHERE with conditions for the given ids.
26✔
262
     */
26✔
263
    orWhereInIds(ids: any | any[]): this {
26✔
264
        return this.orWhere(this.getWhereInIdsCondition(ids))
616✔
265
    }
616✔
266
    /**
26✔
267
     * Optional returning/output clause.
26✔
268
     * This will return given column values.
26✔
269
     */
26✔
270
    output(columns: string[]): this
26✔
271

26✔
272
    /**
26✔
273
     * Optional returning/output clause.
26✔
274
     * Returning is a SQL string containing returning statement.
26✔
275
     */
26✔
276
    output(output: string): this
26✔
277

26✔
278
    /**
26✔
279
     * Optional returning/output clause.
26✔
280
     */
26✔
281
    output(output: string | string[]): this
26✔
282

26✔
283
    /**
26✔
284
     * Optional returning/output clause.
26✔
285
     */
26✔
286
    output(output: string | string[]): this {
26✔
287
        return this.returning(output)
×
288
    }
×
289

26✔
290
    /**
26✔
291
     * Optional returning/output clause.
26✔
292
     * This will return given column values.
26✔
293
     */
26✔
294
    returning(columns: string[]): this
26✔
295

26✔
296
    /**
26✔
297
     * Optional returning/output clause.
26✔
298
     * Returning is a SQL string containing returning statement.
26✔
299
     */
26✔
300
    returning(returning: string): this
26✔
301

26✔
302
    /**
26✔
303
     * Optional returning/output clause.
26✔
304
     */
26✔
305
    returning(returning: string | string[]): this
26✔
306

26✔
307
    /**
26✔
308
     * Optional returning/output clause.
26✔
309
     */
26✔
310
    returning(returning: string | string[]): this {
26✔
311
        // not all databases support returning/output cause
×
312
        if (!this.connection.driver.isReturningSqlSupported("update")) {
×
313
            throw new ReturningStatementNotSupportedError()
×
314
        }
×
315

×
316
        this.expressionMap.returning = returning
×
317
        return this
×
318
    }
×
319

26✔
320
    /**
26✔
321
     * Sets ORDER BY condition in the query builder.
26✔
322
     * If you had previously ORDER BY expression defined,
26✔
323
     * calling this function will override previously set ORDER BY conditions.
26✔
324
     *
26✔
325
     * Calling order by without order set will remove all previously set order bys.
26✔
326
     */
26✔
327
    orderBy(): this
26✔
328

26✔
329
    /**
26✔
330
     * Sets ORDER BY condition in the query builder.
26✔
331
     * If you had previously ORDER BY expression defined,
26✔
332
     * calling this function will override previously set ORDER BY conditions.
26✔
333
     */
26✔
334
    orderBy(
26✔
335
        sort: string,
26✔
336
        order?: "ASC" | "DESC",
26✔
337
        nulls?: "NULLS FIRST" | "NULLS LAST",
26✔
338
    ): this
26✔
339

26✔
340
    /**
26✔
341
     * Sets ORDER BY condition in the query builder.
26✔
342
     * If you had previously ORDER BY expression defined,
26✔
343
     * calling this function will override previously set ORDER BY conditions.
26✔
344
     */
26✔
345
    orderBy(order: OrderByCondition): this
26✔
346

26✔
347
    /**
26✔
348
     * Sets ORDER BY condition in the query builder.
26✔
349
     * If you had previously ORDER BY expression defined,
26✔
350
     * calling this function will override previously set ORDER BY conditions.
26✔
351
     */
26✔
352
    orderBy(
26✔
353
        sort?: string | OrderByCondition,
×
354
        order: "ASC" | "DESC" = "ASC",
×
355
        nulls?: "NULLS FIRST" | "NULLS LAST",
×
356
    ): this {
×
357
        if (sort) {
×
358
            if (typeof sort === "object") {
×
359
                this.expressionMap.orderBys = sort as OrderByCondition
×
360
            } else {
×
361
                if (nulls) {
×
362
                    this.expressionMap.orderBys = {
×
363
                        [sort as string]: { order, nulls },
×
364
                    }
×
365
                } else {
×
366
                    this.expressionMap.orderBys = { [sort as string]: order }
×
367
                }
×
368
            }
×
369
        } else {
×
370
            this.expressionMap.orderBys = {}
×
371
        }
×
372
        return this
×
373
    }
×
374

26✔
375
    /**
26✔
376
     * Adds ORDER BY condition in the query builder.
26✔
377
     */
26✔
378
    addOrderBy(
26✔
379
        sort: string,
×
380
        order: "ASC" | "DESC" = "ASC",
×
381
        nulls?: "NULLS FIRST" | "NULLS LAST",
×
382
    ): this {
×
383
        if (nulls) {
×
384
            this.expressionMap.orderBys[sort] = { order, nulls }
×
385
        } else {
×
386
            this.expressionMap.orderBys[sort] = order
×
387
        }
×
388
        return this
×
389
    }
×
390

26✔
391
    /**
26✔
392
     * Sets LIMIT - maximum number of rows to be selected.
26✔
393
     */
26✔
394
    limit(limit?: number): this {
26✔
395
        this.expressionMap.limit = limit
56✔
396
        return this
56✔
397
    }
56✔
398

26✔
399
    /**
26✔
400
     * Indicates if entity must be updated after update operation.
26✔
401
     * This may produce extra query or use RETURNING / OUTPUT statement (depend on database).
26✔
402
     * Enabled by default.
26✔
403
     */
26✔
404
    whereEntity(entity: Entity | Entity[]): this {
26✔
405
        if (!this.expressionMap.mainAlias!.hasMetadata)
616✔
406
            throw new TypeORMError(
616!
407
                `.whereEntity method can only be used on queries which update real entity table.`,
×
408
            )
×
409

616✔
410
        this.expressionMap.wheres = []
616✔
411
        const entities: Entity[] = Array.isArray(entity) ? entity : [entity]
616!
412
        entities.forEach((entity) => {
616✔
413
            const entityIdMap =
616✔
414
                this.expressionMap.mainAlias!.metadata.getEntityIdMap(entity)
616✔
415
            if (!entityIdMap)
616✔
416
                throw new TypeORMError(
616!
417
                    `Provided entity does not have ids set, cannot perform operation.`,
×
418
                )
×
419

616✔
420
            this.orWhereInIds(entityIdMap)
616✔
421
        })
616✔
422

616✔
423
        this.expressionMap.whereEntities = entities
616✔
424
        return this
616✔
425
    }
616✔
426

26✔
427
    /**
26✔
428
     * Indicates if entity must be updated after update operation.
26✔
429
     * This may produce extra query or use RETURNING / OUTPUT statement (depend on database).
26✔
430
     * Enabled by default.
26✔
431
     */
26✔
432
    updateEntity(enabled: boolean): this {
26✔
433
        this.expressionMap.updateEntity = enabled
644✔
434
        return this
644✔
435
    }
644✔
436

26✔
437
    // -------------------------------------------------------------------------
26✔
438
    // Protected Methods
26✔
439
    // -------------------------------------------------------------------------
26✔
440

26✔
441
    /**
26✔
442
     * Creates UPDATE express used to perform insert query.
26✔
443
     */
26✔
444
    protected createUpdateExpression() {
26✔
445
        const metadata = this.expressionMap.mainAlias!.hasMetadata
1,072✔
446
            ? this.expressionMap.mainAlias!.metadata
1,072✔
447
            : undefined
1,072!
448
        if (!metadata)
1,072✔
449
            throw new TypeORMError(
1,072!
450
                `Cannot get entity metadata for the given alias "${this.expressionMap.mainAlias}"`,
×
451
            )
×
452
        if (!metadata.deleteDateColumn) {
1,072✔
453
            throw new MissingDeleteDateColumnError(metadata)
168✔
454
        }
168✔
455

904✔
456
        // prepare columns and values to be updated
904✔
457
        const updateColumnAndValues: string[] = []
904✔
458

904✔
459
        switch (this.expressionMap.queryType) {
904✔
460
            case "soft-delete":
1,072✔
461
                updateColumnAndValues.push(
652✔
462
                    this.escape(metadata.deleteDateColumn.databaseName) +
652✔
463
                        " = CURRENT_TIMESTAMP",
652✔
464
                )
652✔
465
                break
652✔
466
            case "restore":
1,072✔
467
                updateColumnAndValues.push(
252✔
468
                    this.escape(metadata.deleteDateColumn.databaseName) +
252✔
469
                        " = NULL",
252✔
470
                )
252✔
471
                break
252✔
472
            default:
1,072!
473
                throw new TypeORMError(
×
474
                    `The queryType must be "soft-delete" or "restore"`,
×
475
                )
×
476
        }
1,072✔
477
        if (metadata.versionColumn)
904✔
478
            updateColumnAndValues.push(
1,072!
479
                this.escape(metadata.versionColumn.databaseName) +
×
480
                    " = " +
×
481
                    this.escape(metadata.versionColumn.databaseName) +
×
482
                    " + 1",
×
483
            )
×
484
        if (metadata.updateDateColumn)
904✔
485
            updateColumnAndValues.push(
1,072✔
486
                this.escape(metadata.updateDateColumn.databaseName) +
56✔
487
                    " = CURRENT_TIMESTAMP",
56✔
488
            ) // todo: fix issue with CURRENT_TIMESTAMP(6) being used, can "DEFAULT" be used?!
1,072✔
489

904✔
490
        if (updateColumnAndValues.length <= 0) {
1,072!
491
            throw new UpdateValuesMissingError()
×
492
        }
×
493

904✔
494
        // get a table name and all column database names
904✔
495
        const whereExpression = this.createWhereExpression()
904✔
496
        const returningExpression = this.createReturningExpression("update")
904✔
497

904✔
498
        if (returningExpression === "") {
958✔
499
            return `UPDATE ${this.getTableName(
742✔
500
                this.getMainTableName(),
742✔
501
            )} SET ${updateColumnAndValues.join(", ")}${whereExpression}` // todo: how do we replace aliases in where to nothing?
742✔
502
        }
742✔
503
        if (this.connection.driver.options.type === "mssql") {
302!
504
            return `UPDATE ${this.getTableName(
36✔
505
                this.getMainTableName(),
36✔
506
            )} SET ${updateColumnAndValues.join(
36✔
507
                ", ",
36✔
508
            )} OUTPUT ${returningExpression}${whereExpression}`
36✔
509
        }
36✔
510
        return `UPDATE ${this.getTableName(
126!
511
            this.getMainTableName(),
126✔
512
        )} SET ${updateColumnAndValues.join(
126✔
513
            ", ",
126✔
514
        )}${whereExpression} RETURNING ${returningExpression}`
126✔
515
    }
126✔
516

26✔
517
    /**
26✔
518
     * Creates "ORDER BY" part of SQL query.
26✔
519
     */
26✔
520
    protected createOrderByExpression() {
26✔
521
        const orderBys = this.expressionMap.orderBys
904✔
522
        if (Object.keys(orderBys).length > 0)
904✔
523
            return (
904!
524
                " ORDER BY " +
×
525
                Object.keys(orderBys)
×
526
                    .map((columnName) => {
×
527
                        if (typeof orderBys[columnName] === "string") {
×
528
                            return (
×
529
                                this.replacePropertyNames(columnName) +
×
530
                                " " +
×
531
                                orderBys[columnName]
×
532
                            )
×
533
                        } else {
×
534
                            return (
×
535
                                this.replacePropertyNames(columnName) +
×
536
                                " " +
×
537
                                (orderBys[columnName] as any).order +
×
538
                                " " +
×
539
                                (orderBys[columnName] as any).nulls
×
540
                            )
×
541
                        }
×
542
                    })
×
543
                    .join(", ")
×
544
            )
×
545

904✔
546
        return ""
904✔
547
    }
904✔
548

26✔
549
    /**
26✔
550
     * Creates "LIMIT" parts of SQL query.
26✔
551
     */
26✔
552
    protected createLimitExpression(): string {
26✔
553
        const limit: number | undefined = this.expressionMap.limit
904✔
554

904✔
555
        if (limit) {
904✔
556
            if (DriverUtils.isMySQLFamily(this.connection.driver)) {
56!
557
                return " LIMIT " + limit
16✔
558
            } else {
56!
559
                throw new LimitOnUpdateNotSupportedError()
40✔
560
            }
40✔
561
        }
56✔
562

848✔
563
        return ""
848✔
564
    }
848✔
565
}
26✔
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