Summary
Multiple route handlers load project data twice per request. The first load happens via checkUserAccess() (which internally calls #load()), and the second happens when a utility function like findLayerById(), findPageById(), or ProjectFactory.loadAsUser() internally calls Project.getById() again.
Root Cause
Utility functions always fetch the project internally with no way to accept a pre-loaded project object. When a route handler has already loaded the project for access control, the subsequent utility call duplicates that database query.
Proposed Fix
Add an optional project parameter to utility functions so callers can pass a pre-loaded project when available. Callers without a pre-loaded project continue to work unchanged by omitting the parameter.
Confirmed Double-Fetch Routes (11)
layer/index.js
| Method |
Route |
Second fetch via |
| PUT |
/:layerId |
findLayerById() → Project.getById() |
page/index.js
| Method |
Route |
Second fetch via |
| PUT |
/:pageId |
findPageById() → Project.getById() |
line/index.js
| Method |
Route |
Second fetch via |
| POST |
/ |
findPageById() → Project.getById() |
| PUT |
/:lineId |
findPageById() → Project.getById() |
| PATCH |
/:lineId/text |
findPageById() → Project.getById() |
| PATCH |
/:lineId/bounds |
findPageById() → Project.getById() |
project/projectReadRouter.js
| Method |
Route |
Second fetch via |
| GET |
/:id |
ProjectFactory.loadAsUser() |
| GET |
/:id/manifest |
ProjectFactory.loadAsUser() |
project/customMetadataRouter.js
| Method |
Route |
Second fetch via |
| GET |
/:id/custom |
database.findOne() |
| POST |
/:id/custom |
database.findOne() |
| PUT |
/:id/custom |
database.findOne() |
Potential Double-Fetches (5, depends on ProjectFactory internals)
project/projectCopyRouter.js
- POST
/:projectId/copy — via ProjectFactory.copyProject()
- POST
/:projectId/copy-without-annotations — via ProjectFactory.cloneWithoutAnnotations()
- POST
/:projectId/copy-with-group — via ProjectFactory.cloneWithGroup()
- POST
/:projectId/copy-with-customizations — via ProjectFactory.cloneWithCustomizations()
project/projectReadRouter.js
- GET
/:id/deploymentStatus — via ProjectFactory.checkManifestUploadAndDeployment()
Summary
Multiple route handlers load project data twice per request. The first load happens via
checkUserAccess()(which internally calls#load()), and the second happens when a utility function likefindLayerById(),findPageById(), orProjectFactory.loadAsUser()internally callsProject.getById()again.Root Cause
Utility functions always fetch the project internally with no way to accept a pre-loaded project object. When a route handler has already loaded the project for access control, the subsequent utility call duplicates that database query.
Proposed Fix
Add an optional
projectparameter to utility functions so callers can pass a pre-loaded project when available. Callers without a pre-loaded project continue to work unchanged by omitting the parameter.Confirmed Double-Fetch Routes (11)
layer/index.js/:layerIdfindLayerById()→Project.getById()page/index.js/:pageIdfindPageById()→Project.getById()line/index.js/findPageById()→Project.getById()/:lineIdfindPageById()→Project.getById()/:lineId/textfindPageById()→Project.getById()/:lineId/boundsfindPageById()→Project.getById()project/projectReadRouter.js/:idProjectFactory.loadAsUser()/:id/manifestProjectFactory.loadAsUser()project/customMetadataRouter.js/:id/customdatabase.findOne()/:id/customdatabase.findOne()/:id/customdatabase.findOne()Potential Double-Fetches (5, depends on ProjectFactory internals)
project/projectCopyRouter.js/:projectId/copy— viaProjectFactory.copyProject()/:projectId/copy-without-annotations— viaProjectFactory.cloneWithoutAnnotations()/:projectId/copy-with-group— viaProjectFactory.cloneWithGroup()/:projectId/copy-with-customizations— viaProjectFactory.cloneWithCustomizations()project/projectReadRouter.js/:id/deploymentStatus— viaProjectFactory.checkManifestUploadAndDeployment()