Skip to content

Commit d6e17ce

Browse files
committed
Use structuredClone for deep cloning
Replace numerous JSON.parse(JSON.stringify(...)) and utils.cloneObject calls with native structuredClone across controllers and utils for safer, more reliable deep copies. Remove the now-unused cloneObject helper from utils.js and update configureRerumOptions to use structuredClone. Also tweak clampNonNegativeInt to treat non-positive values (<= 0) as fallback, switch history HEAD handling to res.status(200).end(), and add safeBody usage in delete to avoid re-parsing request body.
1 parent b6db442 commit d6e17ce

13 files changed

Lines changed: 42 additions & 48 deletions

File tree

controllers/bulk.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ const bulkCreate = async function (req, res, next) {
3333
// Each item must be valid JSON, but can't be an array.
3434
if(Array.isArray(d) || typeof d !== "object") return d
3535
try {
36-
JSON.parse(JSON.stringify(d))
36+
structuredClone(d)
3737
} catch (err) {
3838
return d
3939
}
@@ -120,7 +120,7 @@ const bulkUpdate = async function (req, res, next) {
120120
// Each item must be valid JSON, but can't be an array.
121121
if(Array.isArray(d) || typeof d !== "object") return d
122122
try {
123-
JSON.parse(JSON.stringify(d))
123+
structuredClone(d)
124124
} catch (err) {
125125
return d
126126
}

controllers/crud.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ const create = async function (req, res, next) {
3737
let generatorAgent = getAgentClaim(req, next)
3838
if (!generatorAgent) return
3939
let context = req.body["@context"] ? { "@context": req.body["@context"] } : {}
40-
let provided = utils.cloneObject(req.body)
40+
let provided = structuredClone(req.body)
4141
let rerumProp = { "__rerum": utils.configureRerumOptions(generatorAgent, provided, false, false)["__rerum"] }
4242
if(slug){
4343
rerumProp.__rerum.slug = slug
@@ -55,7 +55,7 @@ const create = async function (req, res, next) {
5555
let result = await db.insertOne(newObject)
5656
res.set(utils.configureWebAnnoHeadersFor(newObject))
5757
newObject = idNegotiation(newObject)
58-
newObject.new_obj_state = utils.cloneObject(newObject)
58+
newObject.new_obj_state = structuredClone(newObject)
5959
res.location(newObject[_contextid(newObject["@context"]) ? "id":"@id"])
6060
res.status(201)
6161
res.json(newObject)

controllers/delete.js

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ import { getAgentClaim, parseDocumentID, getAllVersions, getAllDescendants } fro
2323
const deleteObj = async function(req, res, next) {
2424
let id
2525
let err = { message: `` }
26+
const safeBody = structuredClone(req.body)
2627
try {
27-
id = req.params["_id"] ?? parseDocumentID(JSON.parse(JSON.stringify(req.body))["@id"]) ?? parseDocumentID(JSON.parse(JSON.stringify(req.body))["id"])
28+
id = req.params["_id"] ?? parseDocumentID(safeBody?.["@id"]) ?? parseDocumentID(safeBody?.["id"])
2829
} catch(error){
2930
return next(utils.createExpressError(error))
3031
}
@@ -37,7 +38,7 @@ const deleteObj = async function(req, res, next) {
3738
return next(utils.createExpressError(error))
3839
}
3940
if (null !== originalObject) {
40-
let safe_original = JSON.parse(JSON.stringify(originalObject))
41+
let safe_original = structuredClone(originalObject)
4142
if (utils.isDeleted(safe_original)) {
4243
err = Object.assign(err, {
4344
message: `The object you are trying to delete is already deleted. ${err.message}`,
@@ -61,7 +62,7 @@ const deleteObj = async function(req, res, next) {
6162
}
6263
let preserveID = safe_original["@id"]
6364
let deletedFlag = {} //The __deleted flag is a JSONObject
64-
deletedFlag["object"] = JSON.parse(JSON.stringify(originalObject))
65+
deletedFlag["object"] = structuredClone(originalObject)
6566
deletedFlag["deletor"] = agentRequestingDelete
6667
deletedFlag["time"] = new Date(Date.now()).toISOString().replace("Z", "")
6768
let deletedObject = {
@@ -125,7 +126,7 @@ async function healHistoryTree(obj) {
125126
const nextIdForQuery = parseDocumentID(nextID)
126127
const objToUpdate = await db.findOne({"$or":[{"_id": nextIdForQuery}, {"__rerum.slug": nextIdForQuery}]})
127128
if (null !== objToUpdate) {
128-
let fixHistory = JSON.parse(JSON.stringify(objToUpdate))
129+
let fixHistory = structuredClone(objToUpdate)
129130
if (objToDeleteisRoot) {
130131
//This means this next object must become root.
131132
//Strictly, all history trees must have num(root) > 0.
@@ -158,7 +159,7 @@ async function healHistoryTree(obj) {
158159
let previousIdForQuery = parseDocumentID(previous_id)
159160
const objToUpdate2 = await db.findOne({"$or":[{"_id": previousIdForQuery}, {"__rerum.slug": previousIdForQuery}]})
160161
if (null !== objToUpdate2) {
161-
let fixHistory2 = JSON.parse(JSON.stringify(objToUpdate2))
162+
let fixHistory2 = structuredClone(objToUpdate2)
162163
let origNextArray = fixHistory2["__rerum"]["history"]["next"]
163164
let newNextArray = [...origNextArray]
164165
newNextArray = newNextArray.filter(id => id !== obj["@id"])
@@ -197,7 +198,7 @@ async function newTreePrime(obj) {
197198
// fail silently
198199
}
199200
for (const d of descendants) {
200-
let objWithUpdate = JSON.parse(JSON.stringify(d))
201+
let objWithUpdate = structuredClone(d)
201202
objWithUpdate["__rerum"]["history"]["prime"] = primeID
202203
let result = await db.replaceOne({ "_id": d["_id"] }, objWithUpdate)
203204
if (result.modifiedCount === 0) {

controllers/gog.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,7 @@ const expand = async function(primitiveEntity, GENERATOR=undefined, CREATOR=unde
387387
})
388388

389389
// Combine the Annotation bodies with the primitive object
390-
let expandedEntity = JSON.parse(JSON.stringify(primitiveEntity))
390+
let expandedEntity = structuredClone(primitiveEntity)
391391
for(const anno of matches){
392392
const body = anno.body
393393
let keys = Object.keys(body)

controllers/history.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ const queryHeadRequest = async function (req, res, next) {
118118
if (matches.length) {
119119
const size = Buffer.byteLength(JSON.stringify(matches))
120120
res.set("Content-Length", size)
121-
res.sendStatus(200)
121+
res.status(200).end()
122122
return
123123
}
124124
let err = {

controllers/overwrite.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { _contextid, ObjectID, getAgentClaim, parseDocumentID, idNegotiation } f
1919
const overwrite = async function (req, res, next) {
2020
let err = { message: `` }
2121
res.set("Content-Type", "application/json; charset=utf-8")
22-
let objectReceived = JSON.parse(JSON.stringify(req.body))
22+
let objectReceived = structuredClone(req.body)
2323
let agentRequestingOverwrite = getAgentClaim(req, next)
2424
if (!agentRequestingOverwrite) return
2525
const receivedID = objectReceived["@id"] ?? objectReceived.id
@@ -93,7 +93,7 @@ const overwrite = async function (req, res, next) {
9393
res.set('Current-Overwritten-Version', rerumProp["__rerum"].isOverwritten)
9494
res.set(utils.configureWebAnnoHeadersFor(newObject))
9595
newObject = idNegotiation(newObject)
96-
newObject.new_obj_state = JSON.parse(JSON.stringify(newObject))
96+
newObject.new_obj_state = structuredClone(newObject)
9797
res.location(newObject[_contextid(newObject["@context"]) ? "id":"@id"])
9898
res.json(newObject)
9999
return

controllers/patchSet.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { _contextid, ObjectID, getAgentClaim, parseDocumentID, idNegotiation, al
2121
const patchSet = async function (req, res, next) {
2222
let err = { message: `` }
2323
res.set("Content-Type", "application/json; charset=utf-8")
24-
let objectReceived = utils.cloneObject(req.body)
24+
let objectReceived = structuredClone(req.body)
2525
let originalContext
2626
let patchedObject = {}
2727
let generatorAgent = getAgentClaim(req, next)
@@ -50,7 +50,7 @@ const patchSet = async function (req, res, next) {
5050
})
5151
}
5252
else {
53-
patchedObject = utils.cloneObject(originalObject)
53+
patchedObject = structuredClone(originalObject)
5454
if(_contextid(originalObject["@context"])) {
5555
// If the original object has a context that needs id protected, make sure you don't set it.
5656
delete objectReceived.id
@@ -73,7 +73,7 @@ const patchSet = async function (req, res, next) {
7373
//Just hand back the object. The resulting of setting nothing is the object from the request body.
7474
res.set(utils.configureWebAnnoHeadersFor(originalObject))
7575
originalObject = idNegotiation(originalObject)
76-
originalObject.new_obj_state = utils.cloneObject(originalObject)
76+
originalObject.new_obj_state = structuredClone(originalObject)
7777
res.location(originalObject[_contextid(originalObject["@context"]) ? "id":"@id"])
7878
res.status(200)
7979
res.json(originalObject)
@@ -93,7 +93,7 @@ const patchSet = async function (req, res, next) {
9393
//Success, the original object has been updated.
9494
res.set(utils.configureWebAnnoHeadersFor(newObject))
9595
newObject = idNegotiation(newObject)
96-
newObject.new_obj_state = utils.cloneObject(newObject)
96+
newObject.new_obj_state = structuredClone(newObject)
9797
res.location(newObject[_contextid(newObject["@context"]) ? "id":"@id"])
9898
res.status(200)
9999
res.json(newObject)

controllers/patchUnset.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { _contextid, ObjectID, getAgentClaim, parseDocumentID, idNegotiation, al
2121
const patchUnset = async function (req, res, next) {
2222
let err = { message: `` }
2323
res.set("Content-Type", "application/json; charset=utf-8")
24-
let objectReceived = utils.cloneObject(req.body)
24+
let objectReceived = structuredClone(req.body)
2525
let patchedObject = {}
2626
let generatorAgent = getAgentClaim(req, next)
2727
if (!generatorAgent) return
@@ -49,7 +49,7 @@ const patchUnset = async function (req, res, next) {
4949
})
5050
}
5151
else {
52-
patchedObject = utils.cloneObject(originalObject)
52+
patchedObject = structuredClone(originalObject)
5353
delete objectReceived._id //can't unset this
5454
delete objectReceived.__rerum //can't unset this
5555
delete objectReceived["@id"] //can't unset this
@@ -75,7 +75,7 @@ const patchUnset = async function (req, res, next) {
7575
//Just hand back the object. The resulting of unsetting nothing is the object.
7676
res.set(utils.configureWebAnnoHeadersFor(originalObject))
7777
originalObject = idNegotiation(originalObject)
78-
originalObject.new_obj_state = utils.cloneObject(originalObject)
78+
originalObject.new_obj_state = structuredClone(originalObject)
7979
res.location(originalObject[_contextid(originalObject["@context"]) ? "id":"@id"])
8080
res.status(200)
8181
res.json(originalObject)
@@ -97,7 +97,7 @@ const patchUnset = async function (req, res, next) {
9797
//Success, the original object has been updated.
9898
res.set(utils.configureWebAnnoHeadersFor(newObject))
9999
newObject = idNegotiation(newObject)
100-
newObject.new_obj_state = utils.cloneObject(newObject)
100+
newObject.new_obj_state = structuredClone(newObject)
101101
res.location(newObject[_contextid(newObject["@context"]) ? "id":"@id"])
102102
res.status(200)
103103
res.json(newObject)

controllers/patchUpdate.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { _contextid, ObjectID, getAgentClaim, parseDocumentID, idNegotiation, al
2020
const patchUpdate = async function (req, res, next) {
2121
let err = { message: `` }
2222
res.set("Content-Type", "application/json; charset=utf-8")
23-
let objectReceived = utils.cloneObject(req.body)
23+
let objectReceived = structuredClone(req.body)
2424
let patchedObject = {}
2525
let generatorAgent = getAgentClaim(req, next)
2626
if (!generatorAgent) return
@@ -48,7 +48,7 @@ const patchUpdate = async function (req, res, next) {
4848
})
4949
}
5050
else {
51-
patchedObject = utils.cloneObject(originalObject)
51+
patchedObject = structuredClone(originalObject)
5252
delete objectReceived.__rerum //can't patch this
5353
delete objectReceived._id //can't patch this
5454
delete objectReceived["@id"] //can't patch this
@@ -74,7 +74,7 @@ const patchUpdate = async function (req, res, next) {
7474
//Just hand back the object. The resulting of patching nothing is the object unchanged.
7575
res.set(utils.configureWebAnnoHeadersFor(originalObject))
7676
originalObject = idNegotiation(originalObject)
77-
originalObject.new_obj_state = utils.cloneObject(originalObject)
77+
originalObject.new_obj_state = structuredClone(originalObject)
7878
res.location(originalObject[_contextid(originalObject["@context"]) ? "id":"@id"])
7979
res.status(200)
8080
res.json(originalObject)
@@ -96,7 +96,7 @@ const patchUpdate = async function (req, res, next) {
9696
//Success, the original object has been updated.
9797
res.set(utils.configureWebAnnoHeadersFor(newObject))
9898
newObject = idNegotiation(newObject)
99-
newObject.new_obj_state = utils.cloneObject(newObject)
99+
newObject.new_obj_state = structuredClone(newObject)
100100
res.location(newObject[_contextid(newObject["@context"]) ? "id":"@id"])
101101
res.status(200)
102102
res.json(newObject)

controllers/putUpdate.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { _contextid, ObjectID, getAgentClaim, parseDocumentID, idNegotiation, al
2222
const putUpdate = async function (req, res, next) {
2323
let err = { message: `` }
2424
res.set("Content-Type", "application/json; charset=utf-8")
25-
let objectReceived = utils.cloneObject(req.body)
25+
let objectReceived = structuredClone(req.body)
2626
let generatorAgent = getAgentClaim(req, next)
2727
if (!generatorAgent) return
2828
const idReceived = objectReceived["@id"] ?? objectReceived.id
@@ -69,7 +69,7 @@ const putUpdate = async function (req, res, next) {
6969
//Success, the original object has been updated.
7070
res.set(utils.configureWebAnnoHeadersFor(newObject))
7171
newObject = idNegotiation(newObject)
72-
newObject.new_obj_state = utils.cloneObject(newObject)
72+
newObject.new_obj_state = structuredClone(newObject)
7373
res.location(newObject[_contextid(newObject["@context"]) ? "id":"@id"])
7474
res.status(200)
7575
res.json(newObject)
@@ -107,7 +107,7 @@ const putUpdate = async function (req, res, next) {
107107
async function _import(req, res, next) {
108108
let err = { message: `` }
109109
res.set("Content-Type", "application/json; charset=utf-8")
110-
let objectReceived = utils.cloneObject(req.body)
110+
let objectReceived = structuredClone(req.body)
111111
let generatorAgent = getAgentClaim(req, next)
112112
if (!generatorAgent) return
113113
const id = ObjectID()
@@ -125,7 +125,7 @@ async function _import(req, res, next) {
125125
let result = await db.insertOne(newObject)
126126
res.set(utils.configureWebAnnoHeadersFor(newObject))
127127
newObject = idNegotiation(newObject)
128-
newObject.new_obj_state = utils.cloneObject(newObject)
128+
newObject.new_obj_state = structuredClone(newObject)
129129
res.location(newObject[_contextid(newObject["@context"]) ? "id":"@id"])
130130
res.status(200)
131131
res.json(newObject)

0 commit comments

Comments
 (0)