Thin MongoDB ODM built for Standard Schema
mongodb zod deno

fix default logic for updates

knotbin.com 903dfaab e0183687

verified
+584 -16
+40 -14
model/core.ts
··· 17 17 UpdateResult, 18 18 WithId, 19 19 BulkWriteOptions, 20 + UpdateFilter, 20 21 } from "mongodb"; 21 22 import { ObjectId } from "mongodb"; 22 23 import type { Schema, Infer, Input } from "../types.ts"; 23 - import { parse, parsePartial, parseReplace } from "./validation.ts"; 24 + import { parse, parsePartial, parseReplace, applyDefaultsForUpsert } from "./validation.ts"; 24 25 25 26 /** 26 27 * Core CRUD operations for the Model class ··· 125 126 /** 126 127 * Update multiple documents matching the query 127 128 * 129 + * Case handling: 130 + * - If upsert: false (or undefined) → Normal update, no defaults applied 131 + * - If upsert: true → Defaults added to $setOnInsert for new document creation 132 + * 128 133 * @param collection - MongoDB collection 129 134 * @param schema - Zod schema for validation 130 135 * @param query - MongoDB query filter 131 136 * @param data - Partial data to update 132 - * @param options - Update options (including session for transactions) 137 + * @param options - Update options (including session for transactions and upsert flag) 133 138 * @returns Update result 134 139 */ 135 140 export async function update<T extends Schema>( ··· 140 145 options?: UpdateOptions 141 146 ): Promise<UpdateResult<Infer<T>>> { 142 147 const validatedData = parsePartial(schema, data); 143 - return await collection.updateMany( 144 - query, 145 - { $set: validatedData as Partial<Infer<T>> }, 146 - options 147 - ); 148 + let updateDoc: UpdateFilter<Infer<T>> = { $set: validatedData as Partial<Infer<T>> }; 149 + 150 + // If this is an upsert, apply defaults using $setOnInsert 151 + if (options?.upsert) { 152 + updateDoc = applyDefaultsForUpsert(schema, query, updateDoc); 153 + } 154 + 155 + return await collection.updateMany(query, updateDoc, options); 148 156 } 149 157 150 158 /** 151 159 * Update a single document matching the query 152 160 * 161 + * Case handling: 162 + * - If upsert: false (or undefined) → Normal update, no defaults applied 163 + * - If upsert: true → Defaults added to $setOnInsert for new document creation 164 + * 153 165 * @param collection - MongoDB collection 154 166 * @param schema - Zod schema for validation 155 167 * @param query - MongoDB query filter 156 168 * @param data - Partial data to update 157 - * @param options - Update options (including session for transactions) 169 + * @param options - Update options (including session for transactions and upsert flag) 158 170 * @returns Update result 159 171 */ 160 172 export async function updateOne<T extends Schema>( ··· 165 177 options?: UpdateOptions 166 178 ): Promise<UpdateResult<Infer<T>>> { 167 179 const validatedData = parsePartial(schema, data); 168 - return await collection.updateOne( 169 - query, 170 - { $set: validatedData as Partial<Infer<T>> }, 171 - options 172 - ); 180 + let updateDoc: UpdateFilter<Infer<T>> = { $set: validatedData as Partial<Infer<T>> }; 181 + 182 + // If this is an upsert, apply defaults using $setOnInsert 183 + if (options?.upsert) { 184 + updateDoc = applyDefaultsForUpsert(schema, query, updateDoc); 185 + } 186 + 187 + return await collection.updateOne(query, updateDoc, options); 173 188 } 174 189 175 190 /** 176 191 * Replace a single document matching the query 177 192 * 193 + * Case handling: 194 + * - If upsert: false (or undefined) → Normal replace on existing doc, no additional defaults 195 + * - If upsert: true → Defaults applied via parse() since we're passing a full document 196 + * 197 + * Note: For replace operations, defaults are automatically applied by the schema's 198 + * parse() function which treats missing fields as candidates for defaults. This works 199 + * for both regular replaces and upsert-creates since we're providing a full document. 200 + * 178 201 * @param collection - MongoDB collection 179 202 * @param schema - Zod schema for validation 180 203 * @param query - MongoDB query filter 181 204 * @param data - Complete document data for replacement 182 - * @param options - Replace options (including session for transactions) 205 + * @param options - Replace options (including session for transactions and upsert flag) 183 206 * @returns Update result 184 207 */ 185 208 export async function replaceOne<T extends Schema>( ··· 189 212 data: Input<T>, 190 213 options?: ReplaceOptions 191 214 ): Promise<UpdateResult<Infer<T>>> { 215 + // parseReplace will apply all schema defaults to missing fields 216 + // This works correctly for both regular replaces and upsert-created documents 192 217 const validatedData = parseReplace(schema, data); 218 + 193 219 // Remove _id from validatedData for replaceOne (it will use the query's _id) 194 220 const { _id, ...withoutId } = validatedData as Infer<T> & { _id?: unknown }; 195 221 return await collection.replaceOne(
+192 -2
model/validation.ts
··· 1 1 import type { z } from "@zod/zod"; 2 2 import type { Schema, Infer, Input } from "../types.ts"; 3 3 import { ValidationError, AsyncValidationError } from "../errors.ts"; 4 + import type { Document, UpdateFilter, Filter } from "mongodb"; 4 5 5 6 /** 6 7 * Validate data for insert operations using Zod schema ··· 28 29 /** 29 30 * Validate partial data for update operations using Zod schema 30 31 * 32 + * Important: This function only validates the fields that are provided in the data object. 33 + * Unlike parse(), this function does NOT apply defaults for missing fields because 34 + * in an update context, missing fields should remain unchanged in the database. 35 + * 31 36 * @param schema - Zod schema to validate against 32 37 * @param data - Partial data to validate 33 - * @returns Validated and typed partial data 38 + * @returns Validated and typed partial data (only fields present in input) 34 39 * @throws {ValidationError} If validation fails 35 40 * @throws {AsyncValidationError} If async validation is detected 36 41 */ ··· 38 43 schema: T, 39 44 data: Partial<z.infer<T>>, 40 45 ): Partial<z.infer<T>> { 46 + // Get the list of fields actually provided in the input 47 + const inputKeys = Object.keys(data); 48 + 41 49 const result = schema.partial().safeParse(data); 42 50 43 51 // Check for async validation ··· 48 56 if (!result.success) { 49 57 throw new ValidationError(result.error.issues, "update"); 50 58 } 51 - return result.data as Partial<z.infer<T>>; 59 + 60 + // Filter the result to only include fields that were in the input 61 + // This prevents defaults from being applied to fields that weren't provided 62 + const filtered: Record<string, unknown> = {}; 63 + for (const key of inputKeys) { 64 + if (key in result.data) { 65 + filtered[key] = (result.data as Record<string, unknown>)[key]; 66 + } 67 + } 68 + 69 + return filtered as Partial<z.infer<T>>; 52 70 } 53 71 54 72 /** ··· 73 91 } 74 92 return result.data as Infer<T>; 75 93 } 94 + 95 + /** 96 + * Extract default values from a Zod schema 97 + * This parses an empty object through the schema to get all defaults applied 98 + * 99 + * @param schema - Zod schema to extract defaults from 100 + * @returns Object containing all default values from the schema 101 + */ 102 + export function extractDefaults<T extends Schema>(schema: T): Partial<Infer<T>> { 103 + try { 104 + // Make all fields optional, then parse empty object to trigger defaults 105 + // This allows us to see which fields get default values 106 + const partialSchema = schema.partial(); 107 + const result = partialSchema.safeParse({}); 108 + 109 + if (result instanceof Promise) { 110 + // Cannot extract defaults from async schemas 111 + return {}; 112 + } 113 + 114 + // If successful, the result contains all fields that have defaults 115 + // Only include fields that were actually added (have values) 116 + if (!result.success) { 117 + return {}; 118 + } 119 + 120 + // Filter to only include fields that got values from defaults 121 + // (not undefined, which indicates no default) 122 + const defaults: Record<string, unknown> = {}; 123 + const data = result.data as Record<string, unknown>; 124 + 125 + for (const [key, value] of Object.entries(data)) { 126 + if (value !== undefined) { 127 + defaults[key] = value; 128 + } 129 + } 130 + 131 + return defaults as Partial<Infer<T>>; 132 + } catch { 133 + return {}; 134 + } 135 + } 136 + 137 + /** 138 + * Get all field paths mentioned in an update filter object 139 + * This includes fields in $set, $unset, $inc, $push, etc. 140 + * 141 + * @param update - MongoDB update filter 142 + * @returns Set of field paths that are being modified 143 + */ 144 + function getModifiedFields(update: UpdateFilter<Document>): Set<string> { 145 + const fields = new Set<string>(); 146 + 147 + // Operators that modify fields 148 + const operators = [ 149 + '$set', '$unset', '$inc', '$mul', '$rename', '$min', '$max', 150 + '$currentDate', '$push', '$pull', '$addToSet', '$pop', '$bit', 151 + '$setOnInsert', 152 + ]; 153 + 154 + for (const op of operators) { 155 + if (update[op] && typeof update[op] === 'object') { 156 + // Add all field names from this operator 157 + for (const field of Object.keys(update[op] as Document)) { 158 + fields.add(field); 159 + } 160 + } 161 + } 162 + 163 + return fields; 164 + } 165 + 166 + /** 167 + * Get field paths that are fixed by equality clauses in a query filter. 168 + * Only equality-style predicates become part of the inserted document during upsert. 169 + */ 170 + function getEqualityFields(filter: Filter<Document>): Set<string> { 171 + const fields = new Set<string>(); 172 + 173 + const collect = (node: Record<string, unknown>) => { 174 + for (const [key, value] of Object.entries(node)) { 175 + if (key.startsWith("$")) { 176 + if (Array.isArray(value)) { 177 + for (const item of value) { 178 + if (item && typeof item === "object" && !Array.isArray(item)) { 179 + collect(item as Record<string, unknown>); 180 + } 181 + } 182 + } else if (value && typeof value === "object") { 183 + collect(value as Record<string, unknown>); 184 + } 185 + continue; 186 + } 187 + 188 + if (value && typeof value === "object" && !Array.isArray(value)) { 189 + const objectValue = value as Record<string, unknown>; 190 + const keys = Object.keys(objectValue); 191 + const hasOperator = keys.some((k) => k.startsWith("$")); 192 + 193 + if (hasOperator) { 194 + if (Object.prototype.hasOwnProperty.call(objectValue, "$eq")) { 195 + fields.add(key); 196 + } 197 + } else { 198 + fields.add(key); 199 + } 200 + } else { 201 + fields.add(key); 202 + } 203 + } 204 + }; 205 + 206 + collect(filter as Record<string, unknown>); 207 + return fields; 208 + } 209 + 210 + /** 211 + * Apply schema defaults to an update operation using $setOnInsert 212 + * 213 + * This is used for upsert operations to ensure defaults are applied when 214 + * a new document is created, but not when updating an existing document. 215 + * 216 + * For each default field: 217 + * - If the field is NOT mentioned in any update operator ($set, $inc, etc.) 218 + * - If the field is NOT fixed by an equality clause in the query filter 219 + * - Add it to $setOnInsert so it's only applied on insert 220 + * 221 + * @param schema - Zod schema with defaults 222 + * @param query - MongoDB query filter 223 + * @param update - MongoDB update filter 224 + * @returns Modified update filter with defaults in $setOnInsert 225 + */ 226 + export function applyDefaultsForUpsert<T extends Schema>( 227 + schema: T, 228 + query: Filter<Infer<T>>, 229 + update: UpdateFilter<Infer<T>> 230 + ): UpdateFilter<Infer<T>> { 231 + // Extract defaults from schema 232 + const defaults = extractDefaults(schema); 233 + 234 + // If no defaults, return update unchanged 235 + if (Object.keys(defaults).length === 0) { 236 + return update; 237 + } 238 + 239 + // Get fields that are already being modified 240 + const modifiedFields = getModifiedFields(update as UpdateFilter<Document>); 241 + const filterEqualityFields = getEqualityFields(query as Filter<Document>); 242 + 243 + // Build $setOnInsert with defaults for unmodified fields 244 + const setOnInsert: Partial<Infer<T>> = {}; 245 + 246 + for (const [field, value] of Object.entries(defaults)) { 247 + // Only add default if field is not already being modified or fixed by filter equality 248 + if (!modifiedFields.has(field) && !filterEqualityFields.has(field)) { 249 + setOnInsert[field as keyof Infer<T>] = value as Infer<T>[keyof Infer<T>]; 250 + } 251 + } 252 + 253 + // If there are defaults to add, merge them into $setOnInsert 254 + if (Object.keys(setOnInsert).length > 0) { 255 + return { 256 + ...update, 257 + $setOnInsert: { 258 + ...(update.$setOnInsert || {}), 259 + ...setOnInsert 260 + } as Partial<Infer<T>> 261 + }; 262 + } 263 + 264 + return update; 265 + }
+352
tests/defaults_test.ts
··· 1 + import { assertEquals, assertExists } from "@std/assert"; 2 + import { z } from "@zod/zod"; 3 + import { connect, disconnect, Model, type Input } from "../mod.ts"; 4 + import { applyDefaultsForUpsert } from "../model/validation.ts"; 5 + import { MongoMemoryServer } from "mongodb-memory-server-core"; 6 + 7 + /** 8 + * Test suite for default value handling in different operation types 9 + * 10 + * This tests the three main cases: 11 + * 1. Plain inserts - defaults applied directly 12 + * 2. Updates without upsert - defaults NOT applied 13 + * 3. Upserts that create - defaults applied via $setOnInsert 14 + * 4. Upserts that match - $setOnInsert ignored (correct behavior) 15 + * 5. Replace with upsert - defaults applied on creation 16 + */ 17 + 18 + // Schema with defaults for testing 19 + const productSchema = z.object({ 20 + name: z.string(), 21 + price: z.number().min(0), 22 + category: z.string().default("general"), 23 + inStock: z.boolean().default(true), 24 + createdAt: z.date().default(() => new Date("2024-01-01T00:00:00Z")), 25 + tags: z.array(z.string()).default([]), 26 + }); 27 + 28 + type Product = z.infer<typeof productSchema>; 29 + type ProductInsert = Input<typeof productSchema>; 30 + 31 + let ProductModel: Model<typeof productSchema>; 32 + let mongoServer: MongoMemoryServer; 33 + 34 + Deno.test.beforeAll(async () => { 35 + mongoServer = await MongoMemoryServer.create(); 36 + const uri = mongoServer.getUri(); 37 + await connect(uri, "test_defaults_db"); 38 + ProductModel = new Model("test_products_defaults", productSchema); 39 + }); 40 + 41 + Deno.test.beforeEach(async () => { 42 + await ProductModel.delete({}); 43 + }); 44 + 45 + Deno.test.afterAll(async () => { 46 + await ProductModel.delete({}); 47 + await disconnect(); 48 + await mongoServer.stop(); 49 + }); 50 + 51 + Deno.test({ 52 + name: "Defaults: Case 1 - Plain insert applies defaults", 53 + async fn() { 54 + // Insert without providing optional fields with defaults 55 + const result = await ProductModel.insertOne({ 56 + name: "Widget", 57 + price: 29.99, 58 + // category, inStock, createdAt, tags not provided - should use defaults 59 + }); 60 + 61 + assertExists(result.insertedId); 62 + 63 + // Verify defaults were applied 64 + const product = await ProductModel.findById(result.insertedId); 65 + assertExists(product); 66 + 67 + assertEquals(product.name, "Widget"); 68 + assertEquals(product.price, 29.99); 69 + assertEquals(product.category, "general"); // default 70 + assertEquals(product.inStock, true); // default 71 + assertExists(product.createdAt); // default function called 72 + assertEquals(product.tags, []); // default empty array 73 + }, 74 + sanitizeResources: false, 75 + sanitizeOps: false, 76 + }); 77 + 78 + Deno.test({ 79 + name: "Defaults: Case 2 - Update without upsert does NOT apply defaults", 80 + async fn() { 81 + // First create a document without defaults (simulate old data) 82 + const insertResult = await ProductModel.insertOne({ 83 + name: "Gadget", 84 + price: 19.99, 85 + category: "electronics", 86 + inStock: false, 87 + createdAt: new Date("2023-01-01"), 88 + tags: ["test"], 89 + }); 90 + 91 + assertExists(insertResult.insertedId); 92 + 93 + // Now update it - defaults should NOT be applied 94 + await ProductModel.updateOne( 95 + { _id: insertResult.insertedId }, 96 + { price: 24.99 } 97 + // No upsert flag 98 + ); 99 + 100 + const updated = await ProductModel.findById(insertResult.insertedId); 101 + assertExists(updated); 102 + 103 + assertEquals(updated.price, 24.99); // updated 104 + assertEquals(updated.category, "electronics"); // unchanged 105 + assertEquals(updated.inStock, false); // unchanged 106 + assertEquals(updated.tags, ["test"]); // unchanged 107 + }, 108 + sanitizeResources: false, 109 + sanitizeOps: false, 110 + }); 111 + 112 + Deno.test({ 113 + name: "Defaults: Case 3 - Upsert that creates applies defaults via $setOnInsert", 114 + async fn() { 115 + // Upsert with a query that won't match - will create new document 116 + const result = await ProductModel.updateOne( 117 + { name: "NonExistent" }, 118 + { price: 39.99 }, 119 + { upsert: true } 120 + ); 121 + 122 + assertEquals(result.upsertedCount, 1); 123 + assertExists(result.upsertedId); 124 + 125 + // Verify the created document has defaults applied 126 + const product = await ProductModel.findOne({ name: "NonExistent" }); 127 + assertExists(product); 128 + 129 + assertEquals(product.price, 39.99); // from $set 130 + assertEquals(product.name, "NonExistent"); // from query 131 + assertEquals(product.category, "general"); // default via $setOnInsert 132 + assertEquals(product.inStock, true); // default via $setOnInsert 133 + assertExists(product.createdAt); // default via $setOnInsert 134 + assertEquals(product.tags, []); // default via $setOnInsert 135 + }, 136 + sanitizeResources: false, 137 + sanitizeOps: false, 138 + }); 139 + 140 + Deno.test({ 141 + name: "Defaults: Case 4 - Upsert that matches does NOT apply defaults", 142 + async fn() { 143 + // Create a document first with explicit non-default values 144 + const insertResult = await ProductModel.insertOne({ 145 + name: "ExistingProduct", 146 + price: 49.99, 147 + category: "premium", 148 + inStock: false, 149 + createdAt: new Date("2023-06-01"), 150 + tags: ["premium", "featured"], 151 + }); 152 + 153 + assertExists(insertResult.insertedId); 154 + 155 + // Upsert with matching query - should update, not insert 156 + const result = await ProductModel.updateOne( 157 + { name: "ExistingProduct" }, 158 + { price: 44.99 }, 159 + { upsert: true } 160 + ); 161 + 162 + assertEquals(result.matchedCount, 1); 163 + assertEquals(result.modifiedCount, 1); 164 + assertEquals(result.upsertedCount, 0); // No insert happened 165 + 166 + // Verify defaults were NOT applied (existing values preserved) 167 + const product = await ProductModel.findOne({ name: "ExistingProduct" }); 168 + assertExists(product); 169 + 170 + assertEquals(product.price, 44.99); // updated via $set 171 + assertEquals(product.category, "premium"); // preserved (not overwritten with default) 172 + assertEquals(product.inStock, false); // preserved 173 + assertEquals(product.tags, ["premium", "featured"]); // preserved 174 + }, 175 + sanitizeResources: false, 176 + sanitizeOps: false, 177 + }); 178 + 179 + Deno.test({ 180 + name: "Defaults: Case 5 - Replace without upsert uses defaults from parse", 181 + async fn() { 182 + // Create initial document 183 + const insertResult = await ProductModel.insertOne({ 184 + name: "ReplaceMe", 185 + price: 10.0, 186 + category: "old", 187 + inStock: true, 188 + createdAt: new Date("2020-01-01"), 189 + tags: ["old"], 190 + }); 191 + 192 + assertExists(insertResult.insertedId); 193 + 194 + // Replace with partial data - defaults should fill in missing fields 195 + await ProductModel.replaceOne( 196 + { _id: insertResult.insertedId }, 197 + { 198 + name: "Replaced", 199 + price: 15.0, 200 + // category, inStock, createdAt, tags not provided - defaults should apply 201 + } 202 + ); 203 + 204 + const product = await ProductModel.findById(insertResult.insertedId); 205 + assertExists(product); 206 + 207 + assertEquals(product.name, "Replaced"); 208 + assertEquals(product.price, 15.0); 209 + assertEquals(product.category, "general"); // default applied 210 + assertEquals(product.inStock, true); // default applied 211 + assertExists(product.createdAt); // default applied 212 + assertEquals(product.tags, []); // default applied 213 + }, 214 + sanitizeResources: false, 215 + sanitizeOps: false, 216 + }); 217 + 218 + Deno.test({ 219 + name: "Defaults: Case 6 - Replace with upsert (creates) applies defaults", 220 + async fn() { 221 + // Replace with upsert on non-existent document 222 + const result = await ProductModel.replaceOne( 223 + { name: "NewViaReplace" }, 224 + { 225 + name: "NewViaReplace", 226 + price: 99.99, 227 + // Missing optional fields - defaults should apply 228 + }, 229 + { upsert: true } 230 + ); 231 + 232 + assertEquals(result.upsertedCount, 1); 233 + assertExists(result.upsertedId); 234 + 235 + const product = await ProductModel.findOne({ name: "NewViaReplace" }); 236 + assertExists(product); 237 + 238 + assertEquals(product.name, "NewViaReplace"); 239 + assertEquals(product.price, 99.99); 240 + assertEquals(product.category, "general"); // default 241 + assertEquals(product.inStock, true); // default 242 + assertExists(product.createdAt); // default 243 + assertEquals(product.tags, []); // default 244 + }, 245 + sanitizeResources: false, 246 + sanitizeOps: false, 247 + }); 248 + 249 + Deno.test({ 250 + name: "Defaults: Upsert only applies defaults to unmodified fields", 251 + async fn() { 252 + // Upsert where we explicitly set some fields that have defaults 253 + const result = await ProductModel.updateOne( 254 + { name: "CustomDefaults" }, 255 + { 256 + price: 25.0, 257 + category: "custom", // Explicitly setting a field that has a default 258 + // inStock not set - should get default 259 + }, 260 + { upsert: true } 261 + ); 262 + 263 + assertEquals(result.upsertedCount, 1); 264 + 265 + const product = await ProductModel.findOne({ name: "CustomDefaults" }); 266 + assertExists(product); 267 + 268 + assertEquals(product.name, "CustomDefaults"); // from query 269 + assertEquals(product.price, 25.0); // from $set 270 + assertEquals(product.category, "custom"); // from $set (NOT default) 271 + assertEquals(product.inStock, true); // default via $setOnInsert 272 + assertExists(product.createdAt); // default via $setOnInsert 273 + assertEquals(product.tags, []); // default via $setOnInsert 274 + }, 275 + sanitizeResources: false, 276 + sanitizeOps: false, 277 + }); 278 + 279 + Deno.test({ 280 + name: "Defaults: insertMany applies defaults to all documents", 281 + async fn() { 282 + const result = await ProductModel.insertMany([ 283 + { name: "Bulk1", price: 10 }, 284 + { name: "Bulk2", price: 20, category: "special" }, 285 + { name: "Bulk3", price: 30 }, 286 + ]); 287 + 288 + assertEquals(Object.keys(result.insertedIds).length, 3); 289 + 290 + const products = await ProductModel.find({}); 291 + assertEquals(products.length, 3); 292 + 293 + // All should have defaults where not provided 294 + for (const product of products) { 295 + assertExists(product.createdAt); 296 + assertEquals(product.inStock, true); 297 + assertEquals(product.tags, []); 298 + 299 + if (product.name === "Bulk2") { 300 + assertEquals(product.category, "special"); 301 + } else { 302 + assertEquals(product.category, "general"); 303 + } 304 + } 305 + }, 306 + sanitizeResources: false, 307 + sanitizeOps: false, 308 + }); 309 + 310 + Deno.test({ 311 + name: "Defaults: applyDefaultsForUpsert preserves existing $setOnInsert values", 312 + fn() { 313 + const schema = z.object({ 314 + name: z.string(), 315 + flag: z.boolean().default(true), 316 + count: z.number().default(0), 317 + }); 318 + 319 + const update = { 320 + $set: { name: "test" }, 321 + $setOnInsert: { flag: false }, 322 + }; 323 + 324 + const result = applyDefaultsForUpsert(schema, {}, update); 325 + 326 + assertEquals(result.$setOnInsert?.flag, false); 327 + assertEquals(result.$setOnInsert?.count, 0); 328 + }, 329 + sanitizeResources: false, 330 + sanitizeOps: false, 331 + }); 332 + 333 + Deno.test({ 334 + name: "Defaults: applyDefaultsForUpsert keeps query equality fields untouched", 335 + fn() { 336 + const schema = z.object({ 337 + status: z.string().default("pending"), 338 + flag: z.boolean().default(true), 339 + name: z.string(), 340 + }); 341 + 342 + const query = { status: "queued" }; 343 + const update = { $set: { name: "upsert-test" } }; 344 + 345 + const result = applyDefaultsForUpsert(schema, query, update); 346 + 347 + assertEquals(result.$setOnInsert?.status, undefined); 348 + assertEquals(result.$setOnInsert?.flag, true); 349 + }, 350 + sanitizeResources: false, 351 + sanitizeOps: false, 352 + });