Skip to content

Firebase Contextual Errors#9837

Draft
maneesht wants to merge 6 commits intomainfrom
mtewani/add-firebase-context
Draft

Firebase Contextual Errors#9837
maneesht wants to merge 6 commits intomainfrom
mtewani/add-firebase-context

Conversation

@maneesht
Copy link
Copy Markdown
Contributor

Hey there! So you want to contribute to a Firebase SDK?
Before you file this pull request, please read these guidelines:

Discussion

  • Read the contribution guidelines (CONTRIBUTING.md).
  • If this has been discussed in an issue, make sure to link to the issue here.
    If not, go file an issue about this before creating a pull request to discuss.

Testing

  • Make sure all existing tests in the repository pass after your change.
  • If you fixed a bug or added a feature, add a new test to cover your code.

API Changes

  • Changes that affect the public API will require internal review. Before making a
    PR that changes the public API, we would suggest first proposing your change in an
    issue so that we can discuss it together.

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 13, 2026

⚠️ No Changeset found

Latest commit: abd258d

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements a contextual error enrichment system for the Firebase JS SDK, primarily focusing on Firestore. It allows errors to carry operational metadata such as resource paths, operation types, and authentication state to improve debuggability. Key changes include refactoring error classes to support generic custom data, capturing ID tokens at request time, and implementing observers to wrap errors with context. The PR also adds a documentation guide and updates internal scripts. Review feedback identified several critical issues, including an accidental IDE import of the TypeScript compiler API in browser code, a circular dependency introduced in the utility package, and the loss of original stack traces during error enrichment. Additionally, the reviewer noted the presence of debug logs, unresolved TODOs, and a regression in script error handling that should be resolved.

import { logDebug, logWarn } from '../../util/log';
import { Rejecter, Resolver } from '../../util/promise';
import { StringMap } from '../../util/types';
import { HighlightSpanKind } from 'typescript';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This import appears to be an accidental IDE auto-import. HighlightSpanKind is part of the TypeScript compiler API and should not be imported in production browser code.

Comment thread packages/util/package.json Outdated
"dependencies": {
"tslib": "^2.1.0"
"tslib": "^2.1.0",
"@firebase/auth": "1.12.2"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Adding @firebase/auth as a dependency to @firebase/util is problematic as it likely creates a circular dependency (since auth depends on util). Additionally, util is intended to be a low-level package and should not depend on high-level SDKs. The current changes in util/src/errors.ts use internal JWT utilities and do not appear to require the full auth package.

Comment on lines +253 to +262
copyWithAuthInfo(idToken: string | null): FirestoreError {
if (idToken === null) {
return this;
}
const authInfo = parseIdTokenToAuthInfo(idToken);
return new FirestoreError(this.code, this.message, {
...this.customData,
authInfo
});
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The copyWithAuthInfo method creates a new FirestoreError instance, which causes the original stack trace to be lost. To maintain debuggability, the stack trace should be preserved by copying it from the current instance to the new one.

  copyWithAuthInfo(idToken: string | null): FirestoreError {
    if (idToken === null) {
      return this;
    }
    const authInfo = parseIdTokenToAuthInfo(idToken);
    const error = new FirestoreError(this.code, this.message, {
      ...this.customData,
      authInfo
    });
    if (this.stack) {
      Object.defineProperty(error, 'stack', {
        value: this.stack,
        writable: false,
        configurable: true
      });
    }
    return error;
  }

Comment on lines +288 to +292
const newFirestoreErr = new FirestoreError(
firestoreErr.code,
firestoreErr.message,
customData
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

In firestoreToContextualError, creating a new FirestoreError loses the original stack trace of the input err. The stack trace should be explicitly preserved to ensure that error reports remain useful for debugging.

Suggested change
const newFirestoreErr = new FirestoreError(
firestoreErr.code,
firestoreErr.message,
customData
);
const newFirestoreErr = new FirestoreError(
firestoreErr.code,
firestoreErr.message,
customData
);
if (firestoreErr.stack) {
Object.defineProperty(newFirestoreErr, 'stack', {
value: firestoreErr.stack,
writable: false,
configurable: true
});
}

}
// Mark closed so no further events are propagated
closed = true;
console.log('CALL ON CLOSE');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Debug logging (console.log) should be removed before merging.

Comment on lines +785 to 790
// TODO: Make sure that the error used here parses FirestoreError.
// TODO: Make sure that the stack trace is correct.
processUserCallback(syncEngineImpl, batchId, error);
// Attach the fact that it was a write here.
// syncEngineImpl.
triggerPendingWritesCallbacks(syncEngineImpl, batchId);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This section contains unresolved TODOs and an incomplete comment (// syncEngineImpl.). These should be addressed or removed to ensure the implementation is complete and maintainable.

* See the License for the specific language governing permissions and
* limitations under the License.
*/var __awaiter=this&&this.__awaiter||function(thisArg,_arguments,P,generator){function adopt(value){return value instanceof P?value:new P((function(resolve){resolve(value)}))}return new(P||(P=Promise))((function(resolve,reject){function fulfilled(value){try{step(generator.next(value))}catch(e){reject(e)}}function rejected(value){try{step(generator["throw"](value))}catch(e){reject(e)}}function step(result){result.done?resolve(result.value):adopt(result.value).then(fulfilled,rejected)}step((generator=generator.apply(thisArg,_arguments||[])).next())}))};var __generator=this&&this.__generator||function(thisArg,body){var _={label:0,sent:function(){if(t[0]&1)throw t[1];return t[1]},trys:[],ops:[]},f,y,t,g;return g={next:verb(0),throw:verb(1),return:verb(2)},typeof Symbol==="function"&&(g[Symbol.iterator]=function(){return this}),g;function verb(n){return function(v){return step([n,v])}}function step(op){if(f)throw new TypeError("Generator is already executing.");while(g&&(g=0,op[0]&&(_=0)),_)try{if(f=1,y&&(t=op[0]&2?y["return"]:op[0]?y["throw"]||((t=y["return"])&&t.call(y),0):y.next)&&!(t=t.call(y,op[1])).done)return t;if(y=0,t)op=[op[0]&2,t.value];switch(op[0]){case 0:case 1:t=op;break;case 4:_.label++;return{value:op[1],done:false};case 5:_.label++;y=op[1];op=[0];continue;case 7:op=_.ops.pop();_.trys.pop();continue;default:if(!(t=_.trys,t=t.length>0&&t[t.length-1])&&(op[0]===6||op[0]===2)){_=0;continue}if(op[0]===3&&(!t||op[1]>t[0]&&op[1]<t[3])){_.label=op[1];break}if(op[0]===6&&_.label<t[1]){_.label=t[1];t=op;break}if(t&&_.label<t[2]){_.label=t[2];_.ops.push(op);break}if(t[2])_.ops.pop();_.trys.pop();continue}op=body.call(thisArg,_)}catch(e){op=[6,e];y=0}finally{f=t=0}if(op[0]&5)throw op[1];return{value:op[0]?op[1]:void 0,done:true}}};Object.defineProperty(exports,"__esModule",{value:true});var fs=require("fs");var path=require("path");var ts=require("typescript");var yargs_1=require("yargs");var helpers_1=require("yargs/helpers");var isVerbose=false;function log(message){if(isVerbose){console.log(message)}}var targetFunctionNames=new Set(["fail","hardAssert"]);function getTsFilesRecursive(dirPath){var tsFiles=[];try{var entries=fs.readdirSync(dirPath,{withFileTypes:true});for(var _i=0,entries_1=entries;_i<entries_1.length;_i++){var entry=entries_1[_i];var fullPath=path.join(dirPath,entry.name);if(entry.isDirectory()){if(entry.name==="node_modules"){continue}tsFiles=tsFiles.concat(getTsFilesRecursive(fullPath))}else if(entry.isFile()&&entry.name.endsWith(".ts")){if(!entry.name.endsWith(".d.ts")){tsFiles.push(fullPath)}}}}catch(error){console.error("Error reading directory ".concat(dirPath,": ").concat(error.message))}return tsFiles}function findFunctionCalls(filePaths){var foundCalls=[];var _loop_1=function(filePath){try{var sourceText=fs.readFileSync(filePath,"utf8");var sourceFile_1=ts.createSourceFile(path.basename(filePath),sourceText,ts.ScriptTarget.ESNext,true,ts.ScriptKind.Unknown);var visit_1=function(node){if(ts.isCallExpression(node)){var functionName=null;var expression=node.expression;if(ts.isIdentifier(expression)){functionName=expression.text}if(functionName&&targetFunctionNames.has(functionName)){var _a=ts.getLineAndCharacterOfPosition(sourceFile_1,node.getStart()),line=_a.line,character=_a.character;var argsText_1=[];var errorMessage_1;var assertionId_1;if(node.arguments&&node.arguments.length>0){node.arguments.forEach((function(arg){argsText_1.push(arg.getText(sourceFile_1));if(ts.isStringLiteral(arg)){errorMessage_1=arg.getText(sourceFile_1)}else if(ts.isNumericLiteral(arg)){assertionId_1=parseInt(arg.getText(sourceFile_1),10)}}))}foundCalls.push({fileName:filePath,functionName:functionName,line:line+1,character:character+1,argumentsText:argsText_1,errorMessage:errorMessage_1,assertionId:assertionId_1!==null&&assertionId_1!==void 0?assertionId_1:-1})}}ts.forEachChild(node,visit_1)};visit_1(sourceFile_1)}catch(error){console.error("Error processing file ".concat(filePath,": ").concat(error.message))}};for(var _i=0,filePaths_1=filePaths;_i<filePaths_1.length;_i++){var filePath=filePaths_1[_i];_loop_1(filePath)}return foundCalls}function handleList(occurrences){if(occurrences.length===0){log("No assertion ids found.");return}occurrences.sort((function(a,b){return a.assertionId-b.assertionId})).forEach((function(call){console.log("ID: ".concat(call.assertionId,"; MESSAGE: ").concat(call.errorMessage,"; SOURCE: '").concat(call.functionName,"' call at ").concat(path.relative(process.cwd(),call.fileName),":").concat(call.line,":").concat(call.character))}))}function handleFind(occurrences,targetId){var target=typeof targetId==="number"?targetId:targetId.toString();var foundLocations=occurrences.filter((function(o){return String(o.assertionId)===String(target)}));if(foundLocations.length===0){log('Assertion id "'.concat(targetId,'" not found.'));process.exit(1)}handleList(foundLocations)}function handleCheck(occurrences){if(occurrences.length===0){log("No assertion ids found to check for duplicates.");return}var idCounts={};occurrences.forEach((function(occ){var codeStr=String(occ.assertionId);if(!idCounts[codeStr]){idCounts[codeStr]=[]}idCounts[codeStr].push(occ)}));var duplicatesFound=false;log("Checking for duplicate assertion id usage:");Object.entries(idCounts).forEach((function(_a){var code=_a[0],locations=_a[1];if(locations.length>1){duplicatesFound=true;console.error('\nDuplicate assertion id "'.concat(code,'" found at ').concat(locations.length," locations:"));locations.forEach((function(loc){var relativePath=path.relative(process.cwd(),loc.fileName);console.error("- ".concat(relativePath,":").concat(loc.line,":").concat(loc.character))}))}}));if(!duplicatesFound){log("No duplicate assertion ids found.")}else{process.exit(1)}}function handleNew(occurrences){var maxCode=0;occurrences.forEach((function(occ){if(occ.assertionId>maxCode){maxCode=occ.assertionId}}));if(occurrences.length===0){log("0");return}var newCode=maxCode+1;console.log(newCode)}function main(){return __awaiter(this,void 0,void 0,(function(){var argv,targetDirectory,stats,filesToScan,allOccurrences;return __generator(this,(function(_a){argv=(0,yargs_1.default)((0,helpers_1.hideBin)(process.argv)).usage("Usage: $0 [options]").option("dir",{alias:"D",describe:"Directory to scan recursively for TS files",type:"string",demandOption:true}).option("verbose",{alias:"V",describe:"verbose",type:"boolean"}).option("find",{alias:"F",describe:"Find locations of a specific {assertionId}",type:"string",nargs:1}).option("list",{alias:"L",describe:"List all unique assertion ids found (default action)",type:"boolean"}).option("new",{alias:"N",describe:"Suggest a new assertion id based on existing ones",type:"boolean"}).option("check",{alias:"C",describe:"Check for duplicate usage of assertion ids",type:"boolean"}).check((function(argv){var options=[argv.F,argv.L,argv.N,argv.C].filter(Boolean).length;if(options>1){throw new Error("Options -F, -L, -N, -C are mutually exclusive.")}return true})).help().alias("help","h").strict().parse();targetDirectory=path.resolve(argv["dir"]);isVerbose=!!argv["verbose"];try{stats=fs.statSync(targetDirectory);if(!stats.isDirectory()){console.error("Error: Provided path is not a directory: ".concat(targetDirectory));process.exit(1)}}catch(error){console.error("Error accessing directory ".concat(targetDirectory,": ").concat(error.message));process.exit(1)}log("Scanning directory: ".concat(targetDirectory));filesToScan=getTsFilesRecursive(targetDirectory);if(filesToScan.length===0){log("No relevant .ts or .tsx files found.");process.exit(0)}log("Found ".concat(filesToScan.length," files. Analyzing for assertion ids..."));allOccurrences=findFunctionCalls(filesToScan);log("Scan complete. Found ".concat(allOccurrences.length," potential assertion id occurrences."));if(argv["find"]){handleFind(allOccurrences,argv["find"])}else if(argv["new"]){handleNew(allOccurrences)}else if(argv["check"]){handleCheck(allOccurrences)}else{handleList(allOccurrences)}return[2]}))}))}main().catch((function(error){console.error("\nAn unexpected error occurred:");console.error(error);process.exit(1)})); No newline at end of file
*/var __awaiter=this&&this.__awaiter||function(thisArg,_arguments,P,generator){function adopt(value){return value instanceof P?value:new P((function(resolve){resolve(value)}))}return new(P||(P=Promise))((function(resolve,reject){function fulfilled(value){try{step(generator.next(value))}catch(e){reject(e)}}function rejected(value){try{step(generator["throw"](value))}catch(e){reject(e)}}function step(result){result.done?resolve(result.value):adopt(result.value).then(fulfilled,rejected)}step((generator=generator.apply(thisArg,_arguments||[])).next())}))};var __generator=this&&this.__generator||function(thisArg,body){var _={label:0,sent:function(){if(t[0]&1)throw t[1];return t[1]},trys:[],ops:[]},f,y,t,g;return g={next:verb(0),throw:verb(1),return:verb(2)},typeof Symbol==="function"&&(g[Symbol.iterator]=function(){return this}),g;function verb(n){return function(v){return step([n,v])}}function step(op){if(f)throw new TypeError("Generator is already executing.");while(g&&(g=0,op[0]&&(_=0)),_)try{if(f=1,y&&(t=op[0]&2?y["return"]:op[0]?y["throw"]||((t=y["return"])&&t.call(y),0):y.next)&&!(t=t.call(y,op[1])).done)return t;if(y=0,t)op=[op[0]&2,t.value];switch(op[0]){case 0:case 1:t=op;break;case 4:_.label++;return{value:op[1],done:false};case 5:_.label++;y=op[1];op=[0];continue;case 7:op=_.ops.pop();_.trys.pop();continue;default:if(!(t=_.trys,t=t.length>0&&t[t.length-1])&&(op[0]===6||op[0]===2)){_=0;continue}if(op[0]===3&&(!t||op[1]>t[0]&&op[1]<t[3])){_.label=op[1];break}if(op[0]===6&&_.label<t[1]){_.label=t[1];t=op;break}if(t&&_.label<t[2]){_.label=t[2];_.ops.push(op);break}if(t[2])_.ops.pop();_.trys.pop();continue}op=body.call(thisArg,_)}catch(e){op=[6,e];y=0}finally{f=t=0}if(op[0]&5)throw op[1];return{value:op[0]?op[1]:void 0,done:true}}};Object.defineProperty(exports,"__esModule",{value:true});var fs=require("fs");var node_crypto_1=require("node:crypto");var path=require("path");var ts=require("typescript");var yargs_1=require("yargs");var helpers_1=require("yargs/helpers");var isVerbose=false;function log(message){if(isVerbose){console.log(message)}}var targetFunctionNames=new Set(["fail","hardAssert"]);function getTsFilesRecursive(dirPath){var tsFiles=[];try{var entries=fs.readdirSync(dirPath,{withFileTypes:true});for(var _i=0,entries_1=entries;_i<entries_1.length;_i++){var entry=entries_1[_i];var fullPath=path.join(dirPath,entry.name);if(entry.isDirectory()){if(entry.name==="node_modules"){continue}tsFiles=tsFiles.concat(getTsFilesRecursive(fullPath))}else if(entry.isFile()&&entry.name.endsWith(".ts")){if(!entry.name.endsWith(".d.ts")){tsFiles.push(fullPath)}}}}catch(error){console.error("Error reading directory ".concat(dirPath,": ").concat(error.message));throw error}return tsFiles}function findFunctionCalls(filePaths){var foundCalls=[];var _loop_1=function(filePath){var sourceText=fs.readFileSync(filePath,"utf8");var sourceFile=ts.createSourceFile(path.basename(filePath),sourceText,ts.ScriptTarget.ESNext,true,ts.ScriptKind.Unknown);var visit=function(node){if(ts.isCallExpression(node)){var functionName=null;var expression=node.expression;if(ts.isIdentifier(expression)){functionName=expression.text}if(functionName&&targetFunctionNames.has(functionName)){var _a=ts.getLineAndCharacterOfPosition(sourceFile,node.getStart()),line=_a.line,character=_a.character;var argsText_1=[];var errorMessage_1;var assertionId_1;if(node.arguments&&node.arguments.length>0){node.arguments.forEach((function(arg){argsText_1.push(arg.getText(sourceFile));if(ts.isStringLiteral(arg)){errorMessage_1=arg.getText(sourceFile)}else if(ts.isNumericLiteral(arg)){assertionId_1=arg.getText(sourceFile)}}))}foundCalls.push({fileName:filePath,functionName:functionName,line:line+1,character:character+1,argumentsText:argsText_1,errorMessage:errorMessage_1,assertionId:assertionId_1!==null&&assertionId_1!==void 0?assertionId_1:"INVALID"})}}ts.forEachChild(node,visit)};visit(sourceFile)};for(var _i=0,filePaths_1=filePaths;_i<filePaths_1.length;_i++){var filePath=filePaths_1[_i];_loop_1(filePath)}return foundCalls}function handleList(occurrences){if(occurrences.length===0){log("No assertion ids found.");return}occurrences.sort((function(a,b){return a.assertionId.localeCompare(b.assertionId)})).forEach((function(call){console.log("ID: ".concat(call.assertionId,"; MESSAGE: ").concat(call.errorMessage,"; SOURCE: '").concat(call.functionName,"' call at ").concat(path.relative(process.cwd(),call.fileName),":").concat(call.line,":").concat(call.character))}))}function find(occurrences,targetId){var target=typeof targetId==="number"?targetId.toString(16):targetId;return occurrences.filter((function(o){return String(o.assertionId)===String(target)}))}function handleFind(occurrences,targetId){var foundLocations=find(occurrences,targetId);if(foundLocations.length===0){log('Assertion id "'.concat(targetId,'" not found.'));process.exit(1)}handleList(foundLocations)}function handleCheck(occurrences){if(occurrences.length===0){log("No assertion ids found to check for duplicates.");return}var idCounts={};occurrences.forEach((function(occ){var codeStr=String(occ.assertionId);if(!idCounts[codeStr]){idCounts[codeStr]=[]}idCounts[codeStr].push(occ);if(!/^0x[0-9a-f]{4}$/.test(occ.assertionId)){console.error("Invalid assertion ID '".concat(occ.assertionId,"'. Must match /^0x[0-9a-f]{4}$/"));var relativePath=path.relative(process.cwd(),occ.fileName);console.error("- at '".concat(relativePath,":").concat(occ.line,":").concat(occ.character))}}));var duplicatesFound=false;log("Checking for duplicate assertion id usage:");Object.entries(idCounts).forEach((function(_a){var code=_a[0],locations=_a[1];if(locations.length>1){duplicatesFound=true;console.error('\nDuplicate assertion id "'.concat(code,'" found at ').concat(locations.length," locations:"));locations.forEach((function(loc){var relativePath=path.relative(process.cwd(),loc.fileName);console.error("- ".concat(relativePath,":").concat(loc.line,":").concat(loc.character))}))}}));if(!duplicatesFound){log("No duplicate assertion ids found.")}else{process.exit(1)}}function randomId(){var randomBytes=new Uint8Array(2);(0,node_crypto_1.getRandomValues)(randomBytes);return"0x"+Array.from(randomBytes).map((function(byte){return byte.toString(16).padStart(2,"0")})).join("")}function handleNew(occurrences){var newCode=randomId();while(find(occurrences,newCode).length>0){newCode=randomId()}console.log(newCode)}function main(){return __awaiter(this,void 0,void 0,(function(){var argv,targetDirectory,stats,filesToScan,allOccurrences;return __generator(this,(function(_a){argv=(0,yargs_1.default)((0,helpers_1.hideBin)(process.argv)).usage("Usage: $0 [options]").option("dir",{alias:"D",describe:"Directory to scan recursively for TS files",type:"string",demandOption:true}).option("verbose",{alias:"V",describe:"verbose",type:"boolean"}).option("find",{alias:"F",describe:"Find locations of a specific {assertionId}",type:"string",nargs:1}).option("list",{alias:"L",describe:"List all unique assertion ids found (default action)",type:"boolean"}).option("new",{alias:"N",describe:"Suggest a new assertion id based on existing ones",type:"boolean"}).option("check",{alias:"C",describe:"Check for duplicate usage of assertion ids",type:"boolean"}).check((function(argv){var options=[argv.F,argv.L,argv.N,argv.C].filter(Boolean).length;if(options>1){throw new Error("Options -F, -L, -N, -C are mutually exclusive.")}return true})).help().alias("help","h").strict().parse();targetDirectory=path.resolve(argv["dir"]);isVerbose=!!argv["verbose"];try{stats=fs.statSync(targetDirectory);if(!stats.isDirectory()){console.error("Error: Provided path is not a directory: ".concat(targetDirectory));process.exit(1)}}catch(error){console.error("Error accessing directory ".concat(targetDirectory,": ").concat(error.message));process.exit(1)}log("Scanning directory: ".concat(targetDirectory));filesToScan=getTsFilesRecursive(targetDirectory);if(filesToScan.length===0){log("No relevant .ts or .tsx files found.");process.exit(0)}log("Found ".concat(filesToScan.length," files. Analyzing for assertion ids..."));allOccurrences=findFunctionCalls(filesToScan);log("Scan complete. Found ".concat(allOccurrences.length," potential assertion id occurrences."));if(argv["find"]){handleFind(allOccurrences,argv["find"])}else if(argv["new"]){handleNew(allOccurrences)}else if(argv["check"]){handleCheck(allOccurrences)}else{handleList(allOccurrences)}return[2]}))}))}void main(); No newline at end of file
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The error handling for the async main function was removed (previously handled by .catch()). This can lead to silent failures or unhandled promise rejections if the script encounters an error. Please restore the error handling logic.

return syncEngineWrite(
syncEngine,
mutations,
mutations.length === 1 ? wrappedDeferred : deferred
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Contextual error enrichment is currently disabled for batch writes containing more than one mutation (mutations.length === 1 ? wrappedDeferred : deferred). This limits the effectiveness of the feature for transactions and bulk operations. As noted in the adjacent comment, this gap should be addressed to provide context even for multi-mutation batches.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant