Skip to content

Commit 0889d31

Browse files
authored
fix(gatsby): Sanitize length on objects (#34253)
Co-authored-by: LekoArts <[email protected]> Fix #26565
1 parent c935f05 commit 0889d31

File tree

3 files changed

+69
-26
lines changed

3 files changed

+69
-26
lines changed

packages/gatsby/src/redux/actions/public.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const {
1515
const { splitComponentPath } = require(`gatsby-core-utils/parse-component-path`)
1616
const { hasNodeChanged } = require(`../../utils/nodes`)
1717
const { getNode, getDataStore } = require(`../../datastore`)
18-
import sanitizeNode from "../../utils/sanitize-node"
18+
import { sanitizeNode } from "../../utils/sanitize-node"
1919
const { store } = require(`../index`)
2020
const { validateComponent } = require(`../../utils/validate-component`)
2121
import { nodeSchema } from "../../joi-schemas/joi"

packages/gatsby/src/utils/__tests__/sanitize-node.ts

+21-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import sanitizeNode from "../sanitize-node"
1+
import { sanitizeNode } from "../sanitize-node"
22

33
describe(`node sanitization`, () => {
44
let testNode
@@ -90,4 +90,24 @@ describe(`node sanitization`, () => {
9090
// should be same instance
9191
expect(result).toBe(testNodeWithoutUnserializableData)
9292
})
93+
94+
it(`keeps length field but not OOM`, () => {
95+
const testNodeWithLength = {
96+
id: `id2`,
97+
parent: ``,
98+
children: [],
99+
length: 81185414,
100+
foo: `bar`,
101+
internal: {
102+
type: `Test`,
103+
contentDigest: `digest1`,
104+
owner: `test`,
105+
counter: 0,
106+
},
107+
fields: [],
108+
}
109+
const result = sanitizeNode(testNodeWithLength)
110+
// @ts-ignore - Just for tests
111+
expect(result.length).toBeDefined()
112+
})
93113
})

packages/gatsby/src/utils/sanitize-node.ts

+47-24
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@ import _ from "lodash"
33
import type { IGatsbyNode } from "../redux/types"
44
import type { GatsbyIterable } from "../datastore/common/iterable"
55

6-
type data = IGatsbyNode | GatsbyIterable<IGatsbyNode>
6+
type Data = IGatsbyNode | GatsbyIterable<IGatsbyNode>
7+
8+
type OmitUndefined = (data: Data) => Partial<Data>
79

810
/**
911
* @param {Object|Array} data
1012
* @returns {Object|Array} data without undefined values
1113
*/
12-
type omitUndefined = (data: data) => Partial<data>
13-
14-
const omitUndefined: omitUndefined = data => {
14+
const omitUndefined: OmitUndefined = data => {
1515
const isPlainObject = _.isPlainObject(data)
1616
if (isPlainObject) {
1717
return _.pickBy(data, p => p !== undefined)
@@ -20,12 +20,12 @@ const omitUndefined: omitUndefined = data => {
2020
return (data as GatsbyIterable<IGatsbyNode>).filter(p => p !== undefined)
2121
}
2222

23+
type isTypeSupported = (data: Data) => boolean
24+
2325
/**
2426
* @param {*} data
25-
* @return {boolean}
27+
* @return {boolean} Boolean if type is supported
2628
*/
27-
type isTypeSupported = (data: data) => boolean
28-
2929
const isTypeSupported: isTypeSupported = data => {
3030
if (data === null) {
3131
return true
@@ -41,42 +41,67 @@ const isTypeSupported: isTypeSupported = data => {
4141
return isSupported
4242
}
4343

44+
type sanitizeNode = (
45+
data: Data,
46+
isNode?: boolean,
47+
path?: Set<unknown>
48+
) => Data | undefined
49+
4450
/**
4551
* Make data serializable
4652
* @param {(Object|Array)} data to sanitize
4753
* @param {boolean} isNode = true
4854
* @param {Set<string>} path = new Set
4955
*/
50-
51-
type sanitizeNode = (
52-
data: data,
53-
isNode?: boolean,
54-
path?: Set<unknown>
55-
) => data | undefined
56-
57-
const sanitizeNode: sanitizeNode = (data, isNode = true, path = new Set()) => {
56+
export const sanitizeNode: sanitizeNode = (
57+
data,
58+
isNode = true,
59+
path = new Set()
60+
) => {
5861
const isPlainObject = _.isPlainObject(data)
62+
const isArray = _.isArray(data)
5963

60-
if (isPlainObject || _.isArray(data)) {
64+
if (isPlainObject || isArray) {
6165
if (path.has(data)) return data
6266
path.add(data)
6367

64-
const returnData = isPlainObject ? {} : []
68+
const returnData = isPlainObject
69+
? ({} as IGatsbyNode)
70+
: ([] as Array<IGatsbyNode>)
6571
let anyFieldChanged = false
66-
_.each(data, (o, key) => {
72+
73+
// _.each is a "Collection" method and thus objects with "length" property are iterated as arrays
74+
const hasLengthProperty = isPlainObject
75+
? Object.prototype.hasOwnProperty.call(data, `length`)
76+
: false
77+
let lengthProperty
78+
if (hasLengthProperty) {
79+
lengthProperty = (data as IGatsbyNode).length
80+
delete (data as IGatsbyNode).length
81+
}
82+
83+
_.each(data, (value, key) => {
6784
if (isNode && key === `internal`) {
68-
returnData[key] = o
85+
returnData[key] = value
6986
return
7087
}
71-
returnData[key] = sanitizeNode(o as data, false, path)
88+
returnData[key] = sanitizeNode(value as Data, false, path)
7289

73-
if (returnData[key] !== o) {
90+
if (returnData[key] !== value) {
7491
anyFieldChanged = true
7592
}
7693
})
7794

95+
if (hasLengthProperty) {
96+
;(data as IGatsbyNode).length = lengthProperty
97+
returnData.length = sanitizeNode(lengthProperty as Data, false, path)
98+
if (returnData.length !== lengthProperty) {
99+
anyFieldChanged = true
100+
}
101+
}
102+
78103
if (anyFieldChanged) {
79-
data = omitUndefined(returnData as data) as data
104+
data = omitUndefined(returnData as Data) as Data
80105
}
81106

82107
// arrays and plain objects are supported - no need to to sanitize
@@ -89,5 +114,3 @@ const sanitizeNode: sanitizeNode = (data, isNode = true, path = new Set()) => {
89114
return data
90115
}
91116
}
92-
93-
export default sanitizeNode

0 commit comments

Comments
 (0)