@@ -128,3 +128,110 @@ export function applyMetadataOperations(
128128
129129 return { newMetadata, unappliedOperations } ;
130130}
131+
132+ /**
133+ * Collapses metadata operations to reduce payload size and avoid 413 "Request Entity Too Large" errors.
134+ *
135+ * When there are many operations queued up (e.g., 10k increment operations), sending them all
136+ * individually can result in request payloads exceeding the server's 1MB limit. This function
137+ * intelligently combines operations where possible to reduce the payload size:
138+ *
139+ * - **Increment operations**: Multiple increments on the same key are summed into a single increment
140+ * - Example: increment("counter", 1) + increment("counter", 2) → increment("counter", 3)
141+ *
142+ * - **Set operations**: Multiple sets on the same key keep only the last one (since later sets override earlier ones)
143+ * - Example: set("status", "processing") + set("status", "done") → set("status", "done")
144+ *
145+ * - **Delete operations**: Multiple deletes on the same key keep only one (duplicates are redundant)
146+ * - Example: del("temp") + del("temp") → del("temp")
147+ *
148+ * - **Append, remove, and update operations**: Preserved as-is to maintain correctness since order matters
149+ *
150+ * @param operations Array of metadata change operations to collapse
151+ * @returns Collapsed array with fewer operations that produce the same final result
152+ *
153+ * @example
154+ * ```typescript
155+ * const operations = [
156+ * { type: "increment", key: "counter", value: 1 },
157+ * { type: "increment", key: "counter", value: 2 },
158+ * { type: "set", key: "status", value: "processing" },
159+ * { type: "set", key: "status", value: "done" }
160+ * ];
161+ *
162+ * const collapsed = collapseOperations(operations);
163+ * // Result: [
164+ * // { type: "increment", key: "counter", value: 3 },
165+ * // { type: "set", key: "status", value: "done" }
166+ * // ]
167+ * ```
168+ */
169+ export function collapseOperations (
170+ operations : RunMetadataChangeOperation [ ]
171+ ) : RunMetadataChangeOperation [ ] {
172+ if ( operations . length === 0 ) {
173+ return operations ;
174+ }
175+
176+ // Maps to track collapsible operations
177+ const incrementsByKey = new Map < string , number > ( ) ;
178+ const setsByKey = new Map < string , RunMetadataChangeOperation > ( ) ;
179+ const deletesByKey = new Set < string > ( ) ;
180+ const preservedOperations : RunMetadataChangeOperation [ ] = [ ] ;
181+
182+ // Process operations in order
183+ for ( const operation of operations ) {
184+ switch ( operation . type ) {
185+ case "increment" : {
186+ const currentIncrement = incrementsByKey . get ( operation . key ) || 0 ;
187+ incrementsByKey . set ( operation . key , currentIncrement + operation . value ) ;
188+ break ;
189+ }
190+ case "set" : {
191+ // Keep only the last set operation for each key
192+ setsByKey . set ( operation . key , operation ) ;
193+ break ;
194+ }
195+ case "delete" : {
196+ // Keep only one delete operation per key
197+ deletesByKey . add ( operation . key ) ;
198+ break ;
199+ }
200+ case "append" :
201+ case "remove" :
202+ case "update" : {
203+ // Preserve these operations as-is to maintain correctness
204+ preservedOperations . push ( operation ) ;
205+ break ;
206+ }
207+ default : {
208+ // Handle any future operation types by preserving them
209+ preservedOperations . push ( operation ) ;
210+ break ;
211+ }
212+ }
213+ }
214+
215+ // Build the collapsed operations array
216+ const collapsedOperations : RunMetadataChangeOperation [ ] = [ ] ;
217+
218+ // Add collapsed increment operations
219+ for ( const [ key , value ] of incrementsByKey ) {
220+ collapsedOperations . push ( { type : "increment" , key, value } ) ;
221+ }
222+
223+ // Add collapsed set operations
224+ for ( const operation of setsByKey . values ( ) ) {
225+ collapsedOperations . push ( operation ) ;
226+ }
227+
228+ // Add collapsed delete operations
229+ for ( const key of deletesByKey ) {
230+ collapsedOperations . push ( { type : "delete" , key } ) ;
231+ }
232+
233+ // Add preserved operations
234+ collapsedOperations . push ( ...preservedOperations ) ;
235+
236+ return collapsedOperations ;
237+ }
0 commit comments