@@ -128,3 +128,109 @@ export function applyMetadataOperations(
128
128
129
129
return { newMetadata, unappliedOperations } ;
130
130
}
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