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

typeorm / typeorm / 14796576772

02 May 2025 01:52PM UTC coverage: 45.367% (-30.9%) from 76.309%
14796576772

Pull #11434

github

web-flow
Merge ec4ce2d00 into fadad1a74
Pull Request #11434: feat: release PR releases using pkg.pr.new

5216 of 12761 branches covered (40.87%)

Branch coverage included in aggregate %.

11439 of 23951 relevant lines covered (47.76%)

15712.55 hits per line

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

74.84
/src/util/OrmUtils.ts
1
import { ObjectLiteral } from "../common/ObjectLiteral"
2

3
export class OrmUtils {
4✔
4
    // -------------------------------------------------------------------------
5
    // Public methods
6
    // -------------------------------------------------------------------------
7

8
    /**
9
     * Chunks array into pieces.
10
     */
11
    static chunk<T>(array: T[], size: number): T[][] {
12
        return Array.from(Array(Math.ceil(array.length / size)), (_, i) => {
×
13
            return array.slice(i * size, i * size + size)
×
14
        })
15
    }
16

17
    static splitClassesAndStrings<T>(
18
        classesAndStrings: (string | T)[],
19
    ): [T[], string[]] {
20
        return [
5,452✔
21
            classesAndStrings.filter(
22
                (cls): cls is T => typeof cls !== "string",
2,316✔
23
            ),
24
            classesAndStrings.filter(
25
                (str): str is string => typeof str === "string",
2,316✔
26
            ),
27
        ]
28
    }
29

30
    static groupBy<T, R>(
31
        array: T[],
32
        propertyCallback: (item: T) => R,
33
    ): { id: R; items: T[] }[] {
34
        return array.reduce((groupedArray, value) => {
×
35
            const key = propertyCallback(value)
×
36
            let grouped = groupedArray.find((i) => i.id === key)
×
37
            if (!grouped) {
×
38
                grouped = { id: key, items: [] }
×
39
                groupedArray.push(grouped)
×
40
            }
41
            grouped.items.push(value)
×
42
            return groupedArray
×
43
        }, [] as Array<{ id: R; items: T[] }>)
44
    }
45

46
    static uniq<T>(array: T[], criteria?: (item: T) => any): T[]
47
    static uniq<T, K extends keyof T>(array: T[], property: K): T[]
48
    static uniq<T, K extends keyof T>(
49
        array: T[],
50
        criteriaOrProperty?: ((item: T) => any) | K,
51
    ): T[] {
52
        return array.reduce((uniqueArray, item) => {
2,942✔
53
            let found: boolean = false
2,181✔
54
            if (typeof criteriaOrProperty === "function") {
2,181!
55
                const itemValue = criteriaOrProperty(item)
2,181✔
56
                found = !!uniqueArray.find(
2,181✔
57
                    (uniqueItem) =>
58
                        criteriaOrProperty(uniqueItem) === itemValue,
1,616✔
59
                )
60
            } else if (typeof criteriaOrProperty === "string") {
×
61
                found = !!uniqueArray.find(
×
62
                    (uniqueItem) =>
63
                        uniqueItem[criteriaOrProperty] ===
×
64
                        item[criteriaOrProperty],
65
                )
66
            } else {
67
                found = uniqueArray.indexOf(item) !== -1
×
68
            }
69

70
            if (!found) uniqueArray.push(item)
2,181✔
71

72
            return uniqueArray
2,181✔
73
        }, [] as T[])
74
    }
75

76
    // Checks if it's an object made by Object.create(null), {} or new Object()
77
    private static isPlainObject(item: any) {
78
        if (item === null || item === undefined) {
904,572✔
79
            return false
2,139✔
80
        }
81

82
        return !item.constructor || item.constructor === Object
902,433✔
83
    }
84

85
    private static mergeArrayKey(
86
        target: any,
87
        key: number,
88
        value: any,
89
        memo: Map<any, any>,
90
    ) {
91
        // Have we seen this before?  Prevent infinite recursion.
92
        if (memo.has(value)) {
81!
93
            target[key] = memo.get(value)
×
94
            return
×
95
        }
96

97
        if (value instanceof Promise) {
81!
98
            // Skip promises entirely.
99
            // This is a hold-over from the old code & is because we don't want to pull in
100
            // the lazy fields.  Ideally we'd remove these promises via another function first
101
            // but for now we have to do it here.
102
            return
×
103
        }
104

105
        if (!this.isPlainObject(value) && !Array.isArray(value)) {
81✔
106
            target[key] = value
81✔
107
            return
81✔
108
        }
109

110
        if (!target[key]) {
×
111
            target[key] = Array.isArray(value) ? [] : {}
×
112
        }
113

114
        memo.set(value, target[key])
×
115
        this.merge(target[key], value, memo)
×
116
        memo.delete(value)
×
117
    }
118

119
    private static mergeObjectKey(
120
        target: any,
121
        key: string,
122
        value: any,
123
        memo: Map<any, any>,
124
    ) {
125
        // Have we seen this before?  Prevent infinite recursion.
126
        if (memo.has(value)) {
301,954✔
127
            Object.assign(target, { [key]: memo.get(value) })
16✔
128
            return
16✔
129
        }
130

131
        if (value instanceof Promise) {
301,938✔
132
            // Skip promises entirely.
133
            // This is a hold-over from the old code & is because we don't want to pull in
134
            // the lazy fields.  Ideally we'd remove these promises via another function first
135
            // but for now we have to do it here.
136
            return
16✔
137
        }
138

139
        if (!this.isPlainObject(value) && !Array.isArray(value)) {
301,922✔
140
            Object.assign(target, { [key]: value })
286,482✔
141
            return
286,482✔
142
        }
143

144
        if (!target[key]) {
15,440✔
145
            Object.assign(target, { [key]: Array.isArray(value) ? [] : {} })
9,341✔
146
        }
147

148
        memo.set(value, target[key])
15,440✔
149
        this.merge(target[key], value, memo)
15,440✔
150
        memo.delete(value)
15,440✔
151
    }
152

153
    private static merge(
154
        target: any,
155
        source: any,
156
        memo: Map<any, any> = new Map(),
285,880✔
157
    ): any {
158
        if (this.isPlainObject(target) && this.isPlainObject(source)) {
301,320✔
159
            for (const key of Object.keys(source)) {
301,249✔
160
                if (key === "__proto__") continue
301,954!
161
                this.mergeObjectKey(target, key, source[key], memo)
301,954✔
162
            }
163
        }
164

165
        if (Array.isArray(target) && Array.isArray(source)) {
301,320✔
166
            for (let key = 0; key < source.length; key++) {
31✔
167
                this.mergeArrayKey(target, key, source[key], memo)
81✔
168
            }
169
        }
170
    }
171

172
    /**
173
     * Deep Object.assign.
174
     */
175
    static mergeDeep(target: any, ...sources: any[]): any {
176
        if (!sources.length) {
285,520!
177
            return target
×
178
        }
179

180
        for (const source of sources) {
285,520✔
181
            OrmUtils.merge(target, source)
285,880✔
182
        }
183

184
        return target
285,520✔
185
    }
186

187
    /**
188
     * Deep compare objects.
189
     *
190
     * @see http://stackoverflow.com/a/1144249
191
     */
192
    static deepCompare(...args: any[]): boolean {
193
        let i: any, l: any, leftChain: any, rightChain: any
194

195
        if (arguments.length < 1) {
463!
196
            return true // Die silently? Don't know how to handle such case, please help...
×
197
            // throw "Need two or more arguments to compare";
198
        }
199

200
        for (i = 1, l = arguments.length; i < l; i++) {
463✔
201
            leftChain = [] // Todo: this can be cached
463✔
202
            rightChain = []
463✔
203

204
            if (
463✔
205
                !this.compare2Objects(
206
                    leftChain,
207
                    rightChain,
208
                    arguments[0],
209
                    arguments[i],
210
                )
211
            ) {
212
                return false
172✔
213
            }
214
        }
215

216
        return true
291✔
217
    }
218

219
    /**
220
     * Gets deeper value of object.
221
     */
222
    static deepValue(obj: ObjectLiteral, path: string) {
223
        const segments = path.split(".")
48✔
224
        for (let i = 0, len = segments.length; i < len; i++) {
48✔
225
            obj = obj[segments[i]]
48✔
226
        }
227
        return obj
48✔
228
    }
229

230
    static replaceEmptyObjectsWithBooleans(obj: any) {
231
        for (const key in obj) {
24✔
232
            if (obj[key] && typeof obj[key] === "object") {
24✔
233
                if (Object.keys(obj[key]).length === 0) {
24✔
234
                    obj[key] = true
20✔
235
                } else {
236
                    this.replaceEmptyObjectsWithBooleans(obj[key])
4✔
237
                }
238
            }
239
        }
240
    }
241

242
    static propertyPathsToTruthyObject(paths: string[]) {
243
        const obj: any = {}
20✔
244
        for (const path of paths) {
20✔
245
            const props = path.split(".")
24✔
246
            if (!props.length) continue
24!
247

248
            if (!obj[props[0]] || obj[props[0]] === true) {
24✔
249
                obj[props[0]] = {}
20✔
250
            }
251
            let recursiveChild = obj[props[0]]
24✔
252
            for (const [key, prop] of props.entries()) {
24✔
253
                if (key === 0) continue
28✔
254

255
                if (recursiveChild[prop]) {
4!
256
                    recursiveChild = recursiveChild[prop]
×
257
                } else if (key === props.length - 1) {
4!
258
                    recursiveChild[prop] = {}
4✔
259
                    recursiveChild = null
4✔
260
                } else {
261
                    recursiveChild[prop] = {}
×
262
                    recursiveChild = recursiveChild[prop]
×
263
                }
264
            }
265
        }
266
        this.replaceEmptyObjectsWithBooleans(obj)
20✔
267
        return obj
20✔
268
    }
269

270
    /**
271
     * Check if two entity-id-maps are the same
272
     */
273
    static compareIds(
274
        firstId: ObjectLiteral | undefined,
275
        secondId: ObjectLiteral | undefined,
276
    ): boolean {
277
        if (
2,203✔
278
            firstId === undefined ||
8,704✔
279
            firstId === null ||
280
            secondId === undefined ||
281
            secondId === null
282
        )
283
            return false
60✔
284

285
        // Optimized version for the common case
286
        if (
2,143✔
287
            ((typeof firstId.id === "string" &&
9,650!
288
                typeof secondId.id === "string") ||
289
                (typeof firstId.id === "number" &&
290
                    typeof secondId.id === "number")) &&
291
            Object.keys(firstId).length === 1 &&
292
            Object.keys(secondId).length === 1
293
        ) {
294
            return firstId.id === secondId.id
1,680✔
295
        }
296

297
        return OrmUtils.deepCompare(firstId, secondId)
463✔
298
    }
299

300
    /**
301
     * Transforms given value into boolean value.
302
     */
303
    static toBoolean(value: any): boolean {
304
        if (typeof value === "boolean") return value
36!
305

306
        if (typeof value === "string") return value === "true" || value === "1"
36!
307

308
        if (typeof value === "number") return value > 0
36!
309

310
        return false
36✔
311
    }
312

313
    /**
314
     * Composes an object from the given array of keys and values.
315
     */
316
    static zipObject(keys: any[], values: any[]): ObjectLiteral {
317
        return keys.reduce((object, column, index) => {
×
318
            object[column] = values[index]
×
319
            return object
×
320
        }, {} as ObjectLiteral)
321
    }
322

323
    /**
324
     * Compares two arrays.
325
     */
326
    static isArraysEqual(arr1: any[], arr2: any[]): boolean {
327
        if (arr1.length !== arr2.length) return false
83✔
328
        return arr1.every((element) => {
82✔
329
            return arr2.indexOf(element) !== -1
152✔
330
        })
331
    }
332

333
    static areMutuallyExclusive<T>(...lists: T[][]): boolean {
334
        const haveSharedObjects = lists.some((list) => {
×
335
            const otherLists = lists.filter((otherList) => otherList !== list)
×
336
            return list.some((item) =>
×
337
                otherLists.some((otherList) => otherList.includes(item)),
×
338
            )
339
        })
340
        return !haveSharedObjects
×
341
    }
342

343
    /**
344
     * Parses the CHECK constraint on the specified column and returns
345
     * all values allowed by the constraint or undefined if the constraint
346
     * is not present.
347
     */
348
    static parseSqlCheckExpression(
349
        sql: string,
350
        columnName: string,
351
    ): string[] | undefined {
352
        const enumMatch = sql.match(
2,019✔
353
            new RegExp(
354
                `"${columnName}" varchar CHECK\\s*\\(\\s*"${columnName}"\\s+IN\\s*`,
355
            ),
356
        )
357

358
        if (enumMatch && enumMatch.index) {
2,019✔
359
            const afterMatch = sql.substring(
23✔
360
                enumMatch.index + enumMatch[0].length,
361
            )
362

363
            // This is an enum
364
            // all enum values stored as a comma separated list
365
            const chars = afterMatch
23✔
366

367
            /**
368
             * * When outside quotes: empty string
369
             * * When inside single quotes: `'`
370
             */
371
            let currentQuotes = ""
23✔
372
            let nextValue = ""
23✔
373
            const enumValues: string[] = []
23✔
374
            for (let idx = 0; idx < chars.length; idx++) {
23✔
375
                const char = chars[idx]
1,742✔
376
                switch (char) {
1,742✔
377
                    case ",":
378
                        if (currentQuotes == "") {
72✔
379
                            enumValues.push(nextValue)
56✔
380
                            nextValue = ""
56✔
381
                        } else {
382
                            nextValue += char
16✔
383
                        }
384
                        break
72✔
385
                    case "'":
386
                        if (currentQuotes == char) {
186✔
387
                            const isNextCharQuote = chars[idx + 1] === char
107✔
388
                            if (isNextCharQuote) {
107✔
389
                                // double quote in sql should be treated as a
390
                                // single quote that's part of the quoted string
391
                                nextValue += char
28✔
392
                                idx += 1 // skip that next quote
28✔
393
                            } else {
394
                                currentQuotes = ""
79✔
395
                            }
396
                        } else {
397
                            currentQuotes = char
79✔
398
                        }
399
                        break
186✔
400
                    case ")":
401
                        if (currentQuotes == "") {
31✔
402
                            enumValues.push(nextValue)
23✔
403
                            return enumValues
23✔
404
                        } else {
405
                            nextValue += char
8✔
406
                        }
407
                        break
8✔
408
                    default:
409
                        if (currentQuotes != "") {
1,453✔
410
                            nextValue += char
254✔
411
                        }
412
                }
413
            }
414
        }
415
        return undefined
1,996✔
416
    }
417

418
    // -------------------------------------------------------------------------
419
    // Private methods
420
    // -------------------------------------------------------------------------
421

422
    private static compare2Objects(
423
        leftChain: any,
424
        rightChain: any,
425
        x: any,
426
        y: any,
427
    ) {
428
        let p
429

430
        // remember that NaN === NaN returns false
431
        // and isNaN(undefined) returns true
432
        if (Number.isNaN(x) && Number.isNaN(y)) return true
630!
433

434
        // Compare primitives and functions.
435
        // Check if both arguments link to the same object.
436
        // Especially useful on the step where we compare prototypes
437
        if (x === y) return true
630!
438

439
        // Unequal, but either is null or undefined (use case: jsonb comparison)
440
        // PR #3776, todo: add tests
441
        if (x === null || y === null || x === undefined || y === undefined)
630!
442
            return false
×
443

444
        // Fix the buffer compare bug.
445
        // See: https://github.com/typeorm/typeorm/issues/3654
446
        if (
630!
447
            (typeof x.equals === "function" ||
1,260!
448
                typeof x.equals === "function") &&
449
            x.equals(y)
450
        )
451
            return true
×
452

453
        // Works in case when functions are created in constructor.
454
        // Comparing dates is a common scenario. Another built-ins?
455
        // We can even handle functions passed across iframes
456
        if (
630✔
457
            (typeof x === "function" && typeof y === "function") ||
3,156!
458
            (x instanceof Date && y instanceof Date) ||
459
            (x instanceof RegExp && y instanceof RegExp) ||
460
            (typeof x === "string" && typeof y === "string") ||
461
            (typeof x === "number" && typeof y === "number")
462
        )
463
            return x.toString() === y.toString()
1✔
464

465
        // At last checking prototypes as good as we can
466
        if (!(typeof x === "object" && typeof y === "object")) return false
629✔
467

468
        if (
621!
469
            Object.prototype.isPrototypeOf.call(x, y) ||
1,242✔
470
            Object.prototype.isPrototypeOf.call(y, x)
471
        )
472
            return false
×
473

474
        if (x.constructor !== y.constructor) return false
621!
475

476
        if (x.prototype !== y.prototype) return false
621!
477

478
        // Check for infinitive linking loops
479
        if (leftChain.indexOf(x) > -1 || rightChain.indexOf(y) > -1)
621!
480
            return false
×
481

482
        // Quick checking of one object being a subset of another.
483
        // todo: cache the structure of arguments[0] for performance
484
        for (p in y) {
621✔
485
            if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
1,124!
486
                return false
×
487
            } else if (typeof y[p] !== typeof x[p]) {
1,124✔
488
                return false
20✔
489
            }
490
        }
491

492
        for (p in x) {
601✔
493
            if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
1,072!
494
                return false
×
495
            } else if (typeof y[p] !== typeof x[p]) {
1,072!
496
                return false
×
497
            }
498

499
            switch (typeof x[p]) {
1,072✔
500
                case "object":
501
                case "function":
502
                    leftChain.push(x)
167✔
503
                    rightChain.push(y)
167✔
504

505
                    if (
167!
506
                        !this.compare2Objects(leftChain, rightChain, x[p], y[p])
507
                    ) {
508
                        return false
×
509
                    }
510

511
                    leftChain.pop()
167✔
512
                    rightChain.pop()
167✔
513
                    break
167✔
514

515
                default:
516
                    if (x[p] !== y[p]) {
905✔
517
                        return false
144✔
518
                    }
519
                    break
761✔
520
            }
521
        }
522

523
        return true
457✔
524
    }
525
}
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

© 2025 Coveralls, Inc