@@ -162,8 +162,31 @@ export async function handleDataRequest(deps: DomainHandlerDeps, path: string, m
162162 // PATCH /data/:object/:id
163163 if ( parts . length === 2 && m === 'PATCH' ) {
164164 const id = parts [ 1 ] ;
165+ // [#23462] The optimistic-concurrency token, read the way
166+ // `RestServer`'s twin of this route reads it: the body's
167+ // `expectedVersion`, else `If-Match`, and forwarded to `updateData`,
168+ // which refuses an update whose version moved (`409
169+ // CONCURRENT_UPDATE`). This branch read neither and handed the
170+ // whole body over as the patch, so through the `@objectstack/hono`
171+ // catch-all a stale `If-Match` update was APPLIED, and a body
172+ // `expectedVersion` reached the engine as a field it refused as
173+ // unknown. The key is stripped from the patch whatever its value,
174+ // and a token is forwarded only when truthy: the twin's own
175+ // statements, so both doors send `updateData` the same thing.
176+ const bodyVersion = ( body && typeof body === 'object' ) ? body . expectedVersion : undefined ;
177+ const expectedVersion = bodyVersion ?? requestIfMatch ( _context . request ?. headers ) ;
178+ let data = body ;
179+ if ( body && typeof body === 'object' && 'expectedVersion' in body ) {
180+ const { expectedVersion : _token , ...patch } = body ;
181+ data = patch ;
182+ }
165183 // Spec: returns UpdateDataResponse = { object, id, record }
166- const result = await actionExec . callData ( deps , _context , 'update' , { object : objectName , id, data : body } , _context . dataDriver , _context . environmentId , _context . executionContext ) ;
184+ const result = await actionExec . callData ( deps , _context , 'update' , {
185+ object : objectName ,
186+ id,
187+ data,
188+ ...( expectedVersion ? { expectedVersion : String ( expectedVersion ) } : { } ) ,
189+ } , _context . dataDriver , _context . environmentId , _context . executionContext ) ;
167190 return { handled : true , response : deps . success ( result ) } ;
168191 }
169192
0 commit comments