From f2a29d5228fd18a9a71a3dbd6ca67bb42750c86f Mon Sep 17 00:00:00 2001 From: KernelDeimos Date: Mon, 28 Jul 2025 17:41:46 -0400 Subject: [PATCH] fix: prevent entity mutation during upsert In EntityStoreService, update and upsert both make use of the detect_identifier utility function to get an identifier to pass through to (eventually) SQLES. For `update`, the `id` object is passed as the argument. Properties are removed from this object when "redundant identifiers" are extracted to create the update predicate. For `upsert`, the `entity` object is passed and `id` is ignored. It is suspected that this was done for reasons of improved developer experience for developers using the API. The mutation behavior of detect_identifier caused issues when trying to modify a property that can also be used as an identifier, without having another identifier available. (i.e. need to get UID of a subdomain to change the `subdomain` property instead of just using `subdomain` to identify it). This commit modifies the default behavior of detect_identifier to not remove properties, then adds a flag to preserve the previous behavior which is set TRUE by `update` since it's widelely used by puter.js and has higher impact for potential regressions. --- src/backend/src/om/IdentifierUtil.js | 6 +++--- src/backend/src/services/EntityStoreService.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/backend/src/om/IdentifierUtil.js b/src/backend/src/om/IdentifierUtil.js index fcecbce6c..58b8e05d1 100644 --- a/src/backend/src/om/IdentifierUtil.js +++ b/src/backend/src/om/IdentifierUtil.js @@ -26,7 +26,7 @@ class IdentifierUtil extends AdvancedBase { new WeakConstructorFeature(), ] - async detect_identifier (object) { + async detect_identifier (object, allow_mutation = false) { const redundant_identifiers = this.om.redundant_identifiers ?? []; let match_found = null; @@ -60,9 +60,9 @@ class IdentifierUtil extends AdvancedBase { await object.get(key) : object[key], })); if ( object instanceof Entity ) { - await object.del(key); + if ( allow_mutation ) await object.del(key); } else { - delete object[key]; + if ( allow_mutation ) delete object[key]; } } let predicate = new And({ children: key_eqs }); diff --git a/src/backend/src/services/EntityStoreService.js b/src/backend/src/services/EntityStoreService.js index 334d09ca8..2bae2f92b 100644 --- a/src/backend/src/services/EntityStoreService.js +++ b/src/backend/src/services/EntityStoreService.js @@ -194,7 +194,7 @@ class EntityStoreService extends BaseService { om: this.om, }); - const predicate = await idu.detect_identifier(id ?? {}); + const predicate = await idu.detect_identifier(id ?? {}, true); if ( predicate ) { const maybe_entity = await this.select({ predicate, limit: 1 }); if ( maybe_entity.length ) {