···1717 UpdateResult,
1818 WithId,
1919 BulkWriteOptions,
2020+ UpdateFilter,
2021} from "mongodb";
2122import { ObjectId } from "mongodb";
2223import type { Schema, Infer, Input } from "../types.ts";
2323-import { parse, parsePartial, parseReplace } from "./validation.ts";
2424+import { parse, parsePartial, parseReplace, applyDefaultsForUpsert } from "./validation.ts";
24252526/**
2627 * Core CRUD operations for the Model class
···125126/**
126127 * Update multiple documents matching the query
127128 *
129129+ * Case handling:
130130+ * - If upsert: false (or undefined) → Normal update, no defaults applied
131131+ * - If upsert: true → Defaults added to $setOnInsert for new document creation
132132+ *
128133 * @param collection - MongoDB collection
129134 * @param schema - Zod schema for validation
130135 * @param query - MongoDB query filter
131136 * @param data - Partial data to update
132132- * @param options - Update options (including session for transactions)
137137+ * @param options - Update options (including session for transactions and upsert flag)
133138 * @returns Update result
134139 */
135140export async function update<T extends Schema>(
···140145 options?: UpdateOptions
141146): Promise<UpdateResult<Infer<T>>> {
142147 const validatedData = parsePartial(schema, data);
143143- return await collection.updateMany(
144144- query,
145145- { $set: validatedData as Partial<Infer<T>> },
146146- options
147147- );
148148+ let updateDoc: UpdateFilter<Infer<T>> = { $set: validatedData as Partial<Infer<T>> };
149149+150150+ // If this is an upsert, apply defaults using $setOnInsert
151151+ if (options?.upsert) {
152152+ updateDoc = applyDefaultsForUpsert(schema, query, updateDoc);
153153+ }
154154+155155+ return await collection.updateMany(query, updateDoc, options);
148156}
149157150158/**
151159 * Update a single document matching the query
152160 *
161161+ * Case handling:
162162+ * - If upsert: false (or undefined) → Normal update, no defaults applied
163163+ * - If upsert: true → Defaults added to $setOnInsert for new document creation
164164+ *
153165 * @param collection - MongoDB collection
154166 * @param schema - Zod schema for validation
155167 * @param query - MongoDB query filter
156168 * @param data - Partial data to update
157157- * @param options - Update options (including session for transactions)
169169+ * @param options - Update options (including session for transactions and upsert flag)
158170 * @returns Update result
159171 */
160172export async function updateOne<T extends Schema>(
···165177 options?: UpdateOptions
166178): Promise<UpdateResult<Infer<T>>> {
167179 const validatedData = parsePartial(schema, data);
168168- return await collection.updateOne(
169169- query,
170170- { $set: validatedData as Partial<Infer<T>> },
171171- options
172172- );
180180+ let updateDoc: UpdateFilter<Infer<T>> = { $set: validatedData as Partial<Infer<T>> };
181181+182182+ // If this is an upsert, apply defaults using $setOnInsert
183183+ if (options?.upsert) {
184184+ updateDoc = applyDefaultsForUpsert(schema, query, updateDoc);
185185+ }
186186+187187+ return await collection.updateOne(query, updateDoc, options);
173188}
174189175190/**
176191 * Replace a single document matching the query
177192 *
193193+ * Case handling:
194194+ * - If upsert: false (or undefined) → Normal replace on existing doc, no additional defaults
195195+ * - If upsert: true → Defaults applied via parse() since we're passing a full document
196196+ *
197197+ * Note: For replace operations, defaults are automatically applied by the schema's
198198+ * parse() function which treats missing fields as candidates for defaults. This works
199199+ * for both regular replaces and upsert-creates since we're providing a full document.
200200+ *
178201 * @param collection - MongoDB collection
179202 * @param schema - Zod schema for validation
180203 * @param query - MongoDB query filter
181204 * @param data - Complete document data for replacement
182182- * @param options - Replace options (including session for transactions)
205205+ * @param options - Replace options (including session for transactions and upsert flag)
183206 * @returns Update result
184207 */
185208export async function replaceOne<T extends Schema>(
···189212 data: Input<T>,
190213 options?: ReplaceOptions
191214): Promise<UpdateResult<Infer<T>>> {
215215+ // parseReplace will apply all schema defaults to missing fields
216216+ // This works correctly for both regular replaces and upsert-created documents
192217 const validatedData = parseReplace(schema, data);
218218+193219 // Remove _id from validatedData for replaceOne (it will use the query's _id)
194220 const { _id, ...withoutId } = validatedData as Infer<T> & { _id?: unknown };
195221 return await collection.replaceOne(
+192-2
model/validation.ts
···11import type { z } from "@zod/zod";
22import type { Schema, Infer, Input } from "../types.ts";
33import { ValidationError, AsyncValidationError } from "../errors.ts";
44+import type { Document, UpdateFilter, Filter } from "mongodb";
4556/**
67 * Validate data for insert operations using Zod schema
···2829/**
2930 * Validate partial data for update operations using Zod schema
3031 *
3232+ * Important: This function only validates the fields that are provided in the data object.
3333+ * Unlike parse(), this function does NOT apply defaults for missing fields because
3434+ * in an update context, missing fields should remain unchanged in the database.
3535+ *
3136 * @param schema - Zod schema to validate against
3237 * @param data - Partial data to validate
3333- * @returns Validated and typed partial data
3838+ * @returns Validated and typed partial data (only fields present in input)
3439 * @throws {ValidationError} If validation fails
3540 * @throws {AsyncValidationError} If async validation is detected
3641 */
···3843 schema: T,
3944 data: Partial<z.infer<T>>,
4045): Partial<z.infer<T>> {
4646+ // Get the list of fields actually provided in the input
4747+ const inputKeys = Object.keys(data);
4848+4149 const result = schema.partial().safeParse(data);
42504351 // Check for async validation
···4856 if (!result.success) {
4957 throw new ValidationError(result.error.issues, "update");
5058 }
5151- return result.data as Partial<z.infer<T>>;
5959+6060+ // Filter the result to only include fields that were in the input
6161+ // This prevents defaults from being applied to fields that weren't provided
6262+ const filtered: Record<string, unknown> = {};
6363+ for (const key of inputKeys) {
6464+ if (key in result.data) {
6565+ filtered[key] = (result.data as Record<string, unknown>)[key];
6666+ }
6767+ }
6868+6969+ return filtered as Partial<z.infer<T>>;
5270}
53715472/**
···7391 }
7492 return result.data as Infer<T>;
7593}
9494+9595+/**
9696+ * Extract default values from a Zod schema
9797+ * This parses an empty object through the schema to get all defaults applied
9898+ *
9999+ * @param schema - Zod schema to extract defaults from
100100+ * @returns Object containing all default values from the schema
101101+ */
102102+export function extractDefaults<T extends Schema>(schema: T): Partial<Infer<T>> {
103103+ try {
104104+ // Make all fields optional, then parse empty object to trigger defaults
105105+ // This allows us to see which fields get default values
106106+ const partialSchema = schema.partial();
107107+ const result = partialSchema.safeParse({});
108108+109109+ if (result instanceof Promise) {
110110+ // Cannot extract defaults from async schemas
111111+ return {};
112112+ }
113113+114114+ // If successful, the result contains all fields that have defaults
115115+ // Only include fields that were actually added (have values)
116116+ if (!result.success) {
117117+ return {};
118118+ }
119119+120120+ // Filter to only include fields that got values from defaults
121121+ // (not undefined, which indicates no default)
122122+ const defaults: Record<string, unknown> = {};
123123+ const data = result.data as Record<string, unknown>;
124124+125125+ for (const [key, value] of Object.entries(data)) {
126126+ if (value !== undefined) {
127127+ defaults[key] = value;
128128+ }
129129+ }
130130+131131+ return defaults as Partial<Infer<T>>;
132132+ } catch {
133133+ return {};
134134+ }
135135+}
136136+137137+/**
138138+ * Get all field paths mentioned in an update filter object
139139+ * This includes fields in $set, $unset, $inc, $push, etc.
140140+ *
141141+ * @param update - MongoDB update filter
142142+ * @returns Set of field paths that are being modified
143143+ */
144144+function getModifiedFields(update: UpdateFilter<Document>): Set<string> {
145145+ const fields = new Set<string>();
146146+147147+ // Operators that modify fields
148148+ const operators = [
149149+ '$set', '$unset', '$inc', '$mul', '$rename', '$min', '$max',
150150+ '$currentDate', '$push', '$pull', '$addToSet', '$pop', '$bit',
151151+ '$setOnInsert',
152152+ ];
153153+154154+ for (const op of operators) {
155155+ if (update[op] && typeof update[op] === 'object') {
156156+ // Add all field names from this operator
157157+ for (const field of Object.keys(update[op] as Document)) {
158158+ fields.add(field);
159159+ }
160160+ }
161161+ }
162162+163163+ return fields;
164164+}
165165+166166+/**
167167+ * Get field paths that are fixed by equality clauses in a query filter.
168168+ * Only equality-style predicates become part of the inserted document during upsert.
169169+ */
170170+function getEqualityFields(filter: Filter<Document>): Set<string> {
171171+ const fields = new Set<string>();
172172+173173+ const collect = (node: Record<string, unknown>) => {
174174+ for (const [key, value] of Object.entries(node)) {
175175+ if (key.startsWith("$")) {
176176+ if (Array.isArray(value)) {
177177+ for (const item of value) {
178178+ if (item && typeof item === "object" && !Array.isArray(item)) {
179179+ collect(item as Record<string, unknown>);
180180+ }
181181+ }
182182+ } else if (value && typeof value === "object") {
183183+ collect(value as Record<string, unknown>);
184184+ }
185185+ continue;
186186+ }
187187+188188+ if (value && typeof value === "object" && !Array.isArray(value)) {
189189+ const objectValue = value as Record<string, unknown>;
190190+ const keys = Object.keys(objectValue);
191191+ const hasOperator = keys.some((k) => k.startsWith("$"));
192192+193193+ if (hasOperator) {
194194+ if (Object.prototype.hasOwnProperty.call(objectValue, "$eq")) {
195195+ fields.add(key);
196196+ }
197197+ } else {
198198+ fields.add(key);
199199+ }
200200+ } else {
201201+ fields.add(key);
202202+ }
203203+ }
204204+ };
205205+206206+ collect(filter as Record<string, unknown>);
207207+ return fields;
208208+}
209209+210210+/**
211211+ * Apply schema defaults to an update operation using $setOnInsert
212212+ *
213213+ * This is used for upsert operations to ensure defaults are applied when
214214+ * a new document is created, but not when updating an existing document.
215215+ *
216216+ * For each default field:
217217+ * - If the field is NOT mentioned in any update operator ($set, $inc, etc.)
218218+ * - If the field is NOT fixed by an equality clause in the query filter
219219+ * - Add it to $setOnInsert so it's only applied on insert
220220+ *
221221+ * @param schema - Zod schema with defaults
222222+ * @param query - MongoDB query filter
223223+ * @param update - MongoDB update filter
224224+ * @returns Modified update filter with defaults in $setOnInsert
225225+ */
226226+export function applyDefaultsForUpsert<T extends Schema>(
227227+ schema: T,
228228+ query: Filter<Infer<T>>,
229229+ update: UpdateFilter<Infer<T>>
230230+): UpdateFilter<Infer<T>> {
231231+ // Extract defaults from schema
232232+ const defaults = extractDefaults(schema);
233233+234234+ // If no defaults, return update unchanged
235235+ if (Object.keys(defaults).length === 0) {
236236+ return update;
237237+ }
238238+239239+ // Get fields that are already being modified
240240+ const modifiedFields = getModifiedFields(update as UpdateFilter<Document>);
241241+ const filterEqualityFields = getEqualityFields(query as Filter<Document>);
242242+243243+ // Build $setOnInsert with defaults for unmodified fields
244244+ const setOnInsert: Partial<Infer<T>> = {};
245245+246246+ for (const [field, value] of Object.entries(defaults)) {
247247+ // Only add default if field is not already being modified or fixed by filter equality
248248+ if (!modifiedFields.has(field) && !filterEqualityFields.has(field)) {
249249+ setOnInsert[field as keyof Infer<T>] = value as Infer<T>[keyof Infer<T>];
250250+ }
251251+ }
252252+253253+ // If there are defaults to add, merge them into $setOnInsert
254254+ if (Object.keys(setOnInsert).length > 0) {
255255+ return {
256256+ ...update,
257257+ $setOnInsert: {
258258+ ...(update.$setOnInsert || {}),
259259+ ...setOnInsert
260260+ } as Partial<Infer<T>>
261261+ };
262262+ }
263263+264264+ return update;
265265+}
+352
tests/defaults_test.ts
···11+import { assertEquals, assertExists } from "@std/assert";
22+import { z } from "@zod/zod";
33+import { connect, disconnect, Model, type Input } from "../mod.ts";
44+import { applyDefaultsForUpsert } from "../model/validation.ts";
55+import { MongoMemoryServer } from "mongodb-memory-server-core";
66+77+/**
88+ * Test suite for default value handling in different operation types
99+ *
1010+ * This tests the three main cases:
1111+ * 1. Plain inserts - defaults applied directly
1212+ * 2. Updates without upsert - defaults NOT applied
1313+ * 3. Upserts that create - defaults applied via $setOnInsert
1414+ * 4. Upserts that match - $setOnInsert ignored (correct behavior)
1515+ * 5. Replace with upsert - defaults applied on creation
1616+ */
1717+1818+// Schema with defaults for testing
1919+const productSchema = z.object({
2020+ name: z.string(),
2121+ price: z.number().min(0),
2222+ category: z.string().default("general"),
2323+ inStock: z.boolean().default(true),
2424+ createdAt: z.date().default(() => new Date("2024-01-01T00:00:00Z")),
2525+ tags: z.array(z.string()).default([]),
2626+});
2727+2828+type Product = z.infer<typeof productSchema>;
2929+type ProductInsert = Input<typeof productSchema>;
3030+3131+let ProductModel: Model<typeof productSchema>;
3232+let mongoServer: MongoMemoryServer;
3333+3434+Deno.test.beforeAll(async () => {
3535+ mongoServer = await MongoMemoryServer.create();
3636+ const uri = mongoServer.getUri();
3737+ await connect(uri, "test_defaults_db");
3838+ ProductModel = new Model("test_products_defaults", productSchema);
3939+});
4040+4141+Deno.test.beforeEach(async () => {
4242+ await ProductModel.delete({});
4343+});
4444+4545+Deno.test.afterAll(async () => {
4646+ await ProductModel.delete({});
4747+ await disconnect();
4848+ await mongoServer.stop();
4949+});
5050+5151+Deno.test({
5252+ name: "Defaults: Case 1 - Plain insert applies defaults",
5353+ async fn() {
5454+ // Insert without providing optional fields with defaults
5555+ const result = await ProductModel.insertOne({
5656+ name: "Widget",
5757+ price: 29.99,
5858+ // category, inStock, createdAt, tags not provided - should use defaults
5959+ });
6060+6161+ assertExists(result.insertedId);
6262+6363+ // Verify defaults were applied
6464+ const product = await ProductModel.findById(result.insertedId);
6565+ assertExists(product);
6666+6767+ assertEquals(product.name, "Widget");
6868+ assertEquals(product.price, 29.99);
6969+ assertEquals(product.category, "general"); // default
7070+ assertEquals(product.inStock, true); // default
7171+ assertExists(product.createdAt); // default function called
7272+ assertEquals(product.tags, []); // default empty array
7373+ },
7474+ sanitizeResources: false,
7575+ sanitizeOps: false,
7676+});
7777+7878+Deno.test({
7979+ name: "Defaults: Case 2 - Update without upsert does NOT apply defaults",
8080+ async fn() {
8181+ // First create a document without defaults (simulate old data)
8282+ const insertResult = await ProductModel.insertOne({
8383+ name: "Gadget",
8484+ price: 19.99,
8585+ category: "electronics",
8686+ inStock: false,
8787+ createdAt: new Date("2023-01-01"),
8888+ tags: ["test"],
8989+ });
9090+9191+ assertExists(insertResult.insertedId);
9292+9393+ // Now update it - defaults should NOT be applied
9494+ await ProductModel.updateOne(
9595+ { _id: insertResult.insertedId },
9696+ { price: 24.99 }
9797+ // No upsert flag
9898+ );
9999+100100+ const updated = await ProductModel.findById(insertResult.insertedId);
101101+ assertExists(updated);
102102+103103+ assertEquals(updated.price, 24.99); // updated
104104+ assertEquals(updated.category, "electronics"); // unchanged
105105+ assertEquals(updated.inStock, false); // unchanged
106106+ assertEquals(updated.tags, ["test"]); // unchanged
107107+ },
108108+ sanitizeResources: false,
109109+ sanitizeOps: false,
110110+});
111111+112112+Deno.test({
113113+ name: "Defaults: Case 3 - Upsert that creates applies defaults via $setOnInsert",
114114+ async fn() {
115115+ // Upsert with a query that won't match - will create new document
116116+ const result = await ProductModel.updateOne(
117117+ { name: "NonExistent" },
118118+ { price: 39.99 },
119119+ { upsert: true }
120120+ );
121121+122122+ assertEquals(result.upsertedCount, 1);
123123+ assertExists(result.upsertedId);
124124+125125+ // Verify the created document has defaults applied
126126+ const product = await ProductModel.findOne({ name: "NonExistent" });
127127+ assertExists(product);
128128+129129+ assertEquals(product.price, 39.99); // from $set
130130+ assertEquals(product.name, "NonExistent"); // from query
131131+ assertEquals(product.category, "general"); // default via $setOnInsert
132132+ assertEquals(product.inStock, true); // default via $setOnInsert
133133+ assertExists(product.createdAt); // default via $setOnInsert
134134+ assertEquals(product.tags, []); // default via $setOnInsert
135135+ },
136136+ sanitizeResources: false,
137137+ sanitizeOps: false,
138138+});
139139+140140+Deno.test({
141141+ name: "Defaults: Case 4 - Upsert that matches does NOT apply defaults",
142142+ async fn() {
143143+ // Create a document first with explicit non-default values
144144+ const insertResult = await ProductModel.insertOne({
145145+ name: "ExistingProduct",
146146+ price: 49.99,
147147+ category: "premium",
148148+ inStock: false,
149149+ createdAt: new Date("2023-06-01"),
150150+ tags: ["premium", "featured"],
151151+ });
152152+153153+ assertExists(insertResult.insertedId);
154154+155155+ // Upsert with matching query - should update, not insert
156156+ const result = await ProductModel.updateOne(
157157+ { name: "ExistingProduct" },
158158+ { price: 44.99 },
159159+ { upsert: true }
160160+ );
161161+162162+ assertEquals(result.matchedCount, 1);
163163+ assertEquals(result.modifiedCount, 1);
164164+ assertEquals(result.upsertedCount, 0); // No insert happened
165165+166166+ // Verify defaults were NOT applied (existing values preserved)
167167+ const product = await ProductModel.findOne({ name: "ExistingProduct" });
168168+ assertExists(product);
169169+170170+ assertEquals(product.price, 44.99); // updated via $set
171171+ assertEquals(product.category, "premium"); // preserved (not overwritten with default)
172172+ assertEquals(product.inStock, false); // preserved
173173+ assertEquals(product.tags, ["premium", "featured"]); // preserved
174174+ },
175175+ sanitizeResources: false,
176176+ sanitizeOps: false,
177177+});
178178+179179+Deno.test({
180180+ name: "Defaults: Case 5 - Replace without upsert uses defaults from parse",
181181+ async fn() {
182182+ // Create initial document
183183+ const insertResult = await ProductModel.insertOne({
184184+ name: "ReplaceMe",
185185+ price: 10.0,
186186+ category: "old",
187187+ inStock: true,
188188+ createdAt: new Date("2020-01-01"),
189189+ tags: ["old"],
190190+ });
191191+192192+ assertExists(insertResult.insertedId);
193193+194194+ // Replace with partial data - defaults should fill in missing fields
195195+ await ProductModel.replaceOne(
196196+ { _id: insertResult.insertedId },
197197+ {
198198+ name: "Replaced",
199199+ price: 15.0,
200200+ // category, inStock, createdAt, tags not provided - defaults should apply
201201+ }
202202+ );
203203+204204+ const product = await ProductModel.findById(insertResult.insertedId);
205205+ assertExists(product);
206206+207207+ assertEquals(product.name, "Replaced");
208208+ assertEquals(product.price, 15.0);
209209+ assertEquals(product.category, "general"); // default applied
210210+ assertEquals(product.inStock, true); // default applied
211211+ assertExists(product.createdAt); // default applied
212212+ assertEquals(product.tags, []); // default applied
213213+ },
214214+ sanitizeResources: false,
215215+ sanitizeOps: false,
216216+});
217217+218218+Deno.test({
219219+ name: "Defaults: Case 6 - Replace with upsert (creates) applies defaults",
220220+ async fn() {
221221+ // Replace with upsert on non-existent document
222222+ const result = await ProductModel.replaceOne(
223223+ { name: "NewViaReplace" },
224224+ {
225225+ name: "NewViaReplace",
226226+ price: 99.99,
227227+ // Missing optional fields - defaults should apply
228228+ },
229229+ { upsert: true }
230230+ );
231231+232232+ assertEquals(result.upsertedCount, 1);
233233+ assertExists(result.upsertedId);
234234+235235+ const product = await ProductModel.findOne({ name: "NewViaReplace" });
236236+ assertExists(product);
237237+238238+ assertEquals(product.name, "NewViaReplace");
239239+ assertEquals(product.price, 99.99);
240240+ assertEquals(product.category, "general"); // default
241241+ assertEquals(product.inStock, true); // default
242242+ assertExists(product.createdAt); // default
243243+ assertEquals(product.tags, []); // default
244244+ },
245245+ sanitizeResources: false,
246246+ sanitizeOps: false,
247247+});
248248+249249+Deno.test({
250250+ name: "Defaults: Upsert only applies defaults to unmodified fields",
251251+ async fn() {
252252+ // Upsert where we explicitly set some fields that have defaults
253253+ const result = await ProductModel.updateOne(
254254+ { name: "CustomDefaults" },
255255+ {
256256+ price: 25.0,
257257+ category: "custom", // Explicitly setting a field that has a default
258258+ // inStock not set - should get default
259259+ },
260260+ { upsert: true }
261261+ );
262262+263263+ assertEquals(result.upsertedCount, 1);
264264+265265+ const product = await ProductModel.findOne({ name: "CustomDefaults" });
266266+ assertExists(product);
267267+268268+ assertEquals(product.name, "CustomDefaults"); // from query
269269+ assertEquals(product.price, 25.0); // from $set
270270+ assertEquals(product.category, "custom"); // from $set (NOT default)
271271+ assertEquals(product.inStock, true); // default via $setOnInsert
272272+ assertExists(product.createdAt); // default via $setOnInsert
273273+ assertEquals(product.tags, []); // default via $setOnInsert
274274+ },
275275+ sanitizeResources: false,
276276+ sanitizeOps: false,
277277+});
278278+279279+Deno.test({
280280+ name: "Defaults: insertMany applies defaults to all documents",
281281+ async fn() {
282282+ const result = await ProductModel.insertMany([
283283+ { name: "Bulk1", price: 10 },
284284+ { name: "Bulk2", price: 20, category: "special" },
285285+ { name: "Bulk3", price: 30 },
286286+ ]);
287287+288288+ assertEquals(Object.keys(result.insertedIds).length, 3);
289289+290290+ const products = await ProductModel.find({});
291291+ assertEquals(products.length, 3);
292292+293293+ // All should have defaults where not provided
294294+ for (const product of products) {
295295+ assertExists(product.createdAt);
296296+ assertEquals(product.inStock, true);
297297+ assertEquals(product.tags, []);
298298+299299+ if (product.name === "Bulk2") {
300300+ assertEquals(product.category, "special");
301301+ } else {
302302+ assertEquals(product.category, "general");
303303+ }
304304+ }
305305+ },
306306+ sanitizeResources: false,
307307+ sanitizeOps: false,
308308+});
309309+310310+Deno.test({
311311+ name: "Defaults: applyDefaultsForUpsert preserves existing $setOnInsert values",
312312+ fn() {
313313+ const schema = z.object({
314314+ name: z.string(),
315315+ flag: z.boolean().default(true),
316316+ count: z.number().default(0),
317317+ });
318318+319319+ const update = {
320320+ $set: { name: "test" },
321321+ $setOnInsert: { flag: false },
322322+ };
323323+324324+ const result = applyDefaultsForUpsert(schema, {}, update);
325325+326326+ assertEquals(result.$setOnInsert?.flag, false);
327327+ assertEquals(result.$setOnInsert?.count, 0);
328328+ },
329329+ sanitizeResources: false,
330330+ sanitizeOps: false,
331331+});
332332+333333+Deno.test({
334334+ name: "Defaults: applyDefaultsForUpsert keeps query equality fields untouched",
335335+ fn() {
336336+ const schema = z.object({
337337+ status: z.string().default("pending"),
338338+ flag: z.boolean().default(true),
339339+ name: z.string(),
340340+ });
341341+342342+ const query = { status: "queued" };
343343+ const update = { $set: { name: "upsert-test" } };
344344+345345+ const result = applyDefaultsForUpsert(schema, query, update);
346346+347347+ assertEquals(result.$setOnInsert?.status, undefined);
348348+ assertEquals(result.$setOnInsert?.flag, true);
349349+ },
350350+ sanitizeResources: false,
351351+ sanitizeOps: false,
352352+});