@@ -128,3 +128,109 @@ 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+ // Build the collapsed operations array
215+ const collapsedOperations : RunMetadataChangeOperation [ ] = [ ] ;
216+
217+ // Add collapsed increment operations
218+ for ( const [ key , value ] of incrementsByKey ) {
219+ collapsedOperations . push ( { type : "increment" , key, value } ) ;
220+ }
221+
222+ // Add collapsed set operations
223+ for ( const operation of setsByKey . values ( ) ) {
224+ collapsedOperations . push ( operation ) ;
225+ }
226+
227+ // Add collapsed delete operations
228+ for ( const key of deletesByKey ) {
229+ collapsedOperations . push ( { type : "delete" , key } ) ;
230+ }
231+
232+ // Add preserved operations
233+ collapsedOperations . push ( ...preservedOperations ) ;
234+
235+ return collapsedOperations ;
236+ }
0 commit comments