From 53603f3bc1d5bec48a087c8dd00d5883c6f76c5a Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Wed, 12 Jun 2024 11:42:02 -0600 Subject: [PATCH 01/18] Implement solution to replace error messages with error codes, instead of removing error messages. --- packages/firestore/scripts/build-bundle.js | 2 +- packages/firestore/scripts/remove-asserts.js | 17 ---- packages/firestore/scripts/remove-asserts.ts | 87 +++++++++++++++---- packages/firestore/src/api/credentials.ts | 15 ++-- packages/firestore/src/api/snapshot.ts | 2 +- packages/firestore/src/core/filter.ts | 2 +- packages/firestore/src/core/query.ts | 2 +- .../firestore/src/core/sync_engine_impl.ts | 6 +- packages/firestore/src/core/transaction.ts | 4 +- packages/firestore/src/core/view.ts | 2 +- packages/firestore/src/core/view_snapshot.ts | 10 +-- .../src/index/firestore_index_value_writer.ts | 2 +- .../firestore/src/lite-api/transaction.ts | 6 +- .../src/lite-api/user_data_reader.ts | 2 +- .../src/lite-api/user_data_writer.ts | 7 +- .../src/local/encoded_resource_path.ts | 9 +- .../local/indexeddb_mutation_batch_impl.ts | 4 +- .../src/local/indexeddb_mutation_queue.ts | 39 ++++++--- .../src/local/indexeddb_schema_converter.ts | 3 +- .../firestore/src/local/local_serializer.ts | 2 +- .../src/local/shared_client_state.ts | 3 +- packages/firestore/src/model/mutation.ts | 7 +- .../firestore/src/model/mutation_batch.ts | 9 +- packages/firestore/src/model/normalize.ts | 4 +- packages/firestore/src/model/path.ts | 4 +- packages/firestore/src/model/values.ts | 14 +-- .../platform/browser/webchannel_connection.ts | 13 +-- packages/firestore/src/remote/datastore.ts | 2 +- packages/firestore/src/remote/rpc_error.ts | 6 +- packages/firestore/src/remote/serializer.ts | 36 ++++---- packages/firestore/src/remote/watch_change.ts | 15 ++-- packages/firestore/src/util/assert.ts | 28 ++++-- .../firestore/src/util/async_queue_impl.ts | 4 +- .../firestore/src/util/input_validation.ts | 2 +- packages/firestore/src/util/sorted_map.ts | 10 ++- 35 files changed, 241 insertions(+), 139 deletions(-) delete mode 100644 packages/firestore/scripts/remove-asserts.js diff --git a/packages/firestore/scripts/build-bundle.js b/packages/firestore/scripts/build-bundle.js index e7d1fdf5145..51501eaa313 100644 --- a/packages/firestore/scripts/build-bundle.js +++ b/packages/firestore/scripts/build-bundle.js @@ -14,4 +14,4 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 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(_)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]0&&t[t.length-1])&&(op[0]===6||op[0]===2)){_=0;continue}if(op[0]===3&&(!t||op[1]>t[0]&&op[1]=0){var method=declaration.name.text;if(method==="debugAssert"){updatedNode=ts.createOmittedExpression()}else if(method==="hardAssert"){updatedNode=ts.createCall(declaration.name,undefined,[node.arguments[0]])}else if(method==="fail"){updatedNode=ts.createCall(declaration.name,undefined,[])}}}}if(updatedNode){ts.setSourceMapRange(updatedNode,ts.getSourceMapRange(node));return updatedNode}else{return node}};return RemoveAsserts}(); diff --git a/packages/firestore/scripts/remove-asserts.ts b/packages/firestore/scripts/remove-asserts.ts index f195f7bd2ab..916e3253533 100644 --- a/packages/firestore/scripts/remove-asserts.ts +++ b/packages/firestore/scripts/remove-asserts.ts @@ -15,10 +15,15 @@ * limitations under the License. */ +import { createHash } from 'crypto'; +import { existsSync, readFileSync, writeFileSync } from 'fs'; +import { join } from 'path'; + import * as ts from 'typescript'; // Location of file that includes the asserts const ASSERT_LOCATION = 'packages/firestore/src/util/assert.ts'; +const ERROR_CODE_LOCATION = '../dist/error_codes.json'; export function removeAsserts( program: ts.Program @@ -31,7 +36,8 @@ export function removeAsserts( /** * Transformer that removes all "debugAssert" statements from the SDK and - * removes the custom message for fail() and hardAssert(). + * replaces the custom message for fail() and hardAssert() with shorter + * error codes */ class RemoveAsserts { constructor(private readonly typeChecker: ts.TypeChecker) {} @@ -63,22 +69,37 @@ class RemoveAsserts { declaration.getSourceFile().fileName.indexOf(ASSERT_LOCATION) >= 0 ) { const method = declaration.name!.text; + if (method === 'debugAssert') { updatedNode = ts.createOmittedExpression(); - } else if (method === 'hardAssert') { - // Remove the log message but keep the assertion - updatedNode = ts.createCall( - declaration.name!, - /*typeArgs*/ undefined, - [node.arguments[0]] - ); - } else if (method === 'fail') { - // Remove the log message - updatedNode = ts.createCall( - declaration.name!, - /*typeArgs*/ undefined, - [] - ); + } else if ((method === 'hardAssert') || (method === 'fail')) { + const messageIndex = (method === 'hardAssert') ? 1 : 0; + if ((node.arguments.length > messageIndex) && (node.arguments[messageIndex].kind === ts.SyntaxKind.StringLiteral)) { + const stringLiteral: ts.StringLiteral = node.arguments[messageIndex] as ts.StringLiteral; + const errorMessage = RemoveAsserts.trimErrorMessage(stringLiteral.getFullText()); + const errorCode = RemoveAsserts.errorCode(errorMessage); + + RemoveAsserts.saveErrorCode(errorCode, errorMessage); + const newArguments = [...node.arguments]; + newArguments[messageIndex] = ts.createLiteral(errorCode); + + // Replace the call with the full error message to a + // build with an error code + updatedNode = ts.createCall( + declaration.name!, + /*typeArgs*/ undefined, + newArguments + ); + } else { + const newArguments = [...node.arguments]; + newArguments[messageIndex] = ts.createLiteral('Unexpected error'); + // Remove the log message but keep the assertion + updatedNode = ts.createCall( + declaration.name!, + /*typeArgs*/ undefined, + newArguments + ); + } } } } @@ -91,4 +112,40 @@ class RemoveAsserts { return node; } } + + static trimErrorMessage(errorMessage: string): string { + return errorMessage.substring( + errorMessage.indexOf("'") + 1, + errorMessage.lastIndexOf("'")); + } + + static errorCode(errorMessage: string): string { + // Create a sha256 hash from the parameter names and types. + const hash = createHash('sha256'); + hash.update(errorMessage); + + // Use the first 7 characters of the hash for a more compact code. + const paramHash = hash.digest('hex').substring(0, 7); + + return paramHash; + } + + static saveErrorCode(errorCode: string, errorMessage: string): void { + const errorCodes = RemoveAsserts.getErrorCodes(); + errorCodes[errorCode] = errorMessage; + RemoveAsserts.saveErrorCodes(errorCodes); + } + + static getErrorCodes(): Record { + const path = join(module.path, ERROR_CODE_LOCATION); + if (!existsSync(path)){ + return {}; + } + return JSON.parse(readFileSync(path, 'utf-8')); + } + + static saveErrorCodes(errorCodes: Record): void { + const path = join(module.path, ERROR_CODE_LOCATION); + writeFileSync(path, JSON.stringify(errorCodes, undefined, 4), {encoding: "utf-8", }); + } } diff --git a/packages/firestore/src/api/credentials.ts b/packages/firestore/src/api/credentials.ts index 662439a04bb..47a21bcdd4d 100644 --- a/packages/firestore/src/api/credentials.ts +++ b/packages/firestore/src/api/credentials.ts @@ -206,7 +206,8 @@ export class LiteAuthCredentialsProvider implements CredentialsProvider { if (tokenData) { hardAssert( typeof tokenData.accessToken === 'string', - 'Invalid tokenData returned from getToken():' + tokenData + 'Invalid tokenData returned from getToken()', + { tokenData } ); return new OAuthToken( tokenData.accessToken, @@ -350,7 +351,8 @@ export class FirebaseAuthCredentialsProvider if (tokenData) { hardAssert( typeof tokenData.accessToken === 'string', - 'Invalid tokenData returned from getToken():' + tokenData + 'Invalid tokenData returned from getToken()', + { tokenData } ); return new OAuthToken(tokenData.accessToken, this.currentUser); } else { @@ -378,7 +380,8 @@ export class FirebaseAuthCredentialsProvider const currentUid = this.auth && this.auth.getUid(); hardAssert( currentUid === null || typeof currentUid === 'string', - 'Received invalid UID: ' + currentUid + 'Received invalid UID', + { currentUid } ); return new User(currentUid); } @@ -564,7 +567,8 @@ export class FirebaseAppCheckTokenProvider if (tokenResult) { hardAssert( typeof tokenResult.token === 'string', - 'Invalid tokenResult returned from getToken():' + tokenResult + 'Invalid tokenResult returned from getToken()', + { tokenResult } ); this.latestAppCheckToken = tokenResult.token; return new AppCheckToken(tokenResult.token); @@ -625,7 +629,8 @@ export class LiteAppCheckTokenProvider implements CredentialsProvider { if (tokenResult) { hardAssert( typeof tokenResult.token === 'string', - 'Invalid tokenResult returned from getToken():' + tokenResult + 'Invalid tokenResult returned from getToken()', + { tokenResult } ); return new AppCheckToken(tokenResult.token); } else { diff --git a/packages/firestore/src/api/snapshot.ts b/packages/firestore/src/api/snapshot.ts index 29e1616b61c..088c80cb65d 100644 --- a/packages/firestore/src/api/snapshot.ts +++ b/packages/firestore/src/api/snapshot.ts @@ -749,7 +749,7 @@ export function resultChangeType(type: ChangeType): DocumentChangeType { case ChangeType.Removed: return 'removed'; default: - return fail('Unknown change type: ' + type); + return fail('Unknown change type', { type }); } } diff --git a/packages/firestore/src/core/filter.ts b/packages/firestore/src/core/filter.ts index 06e2740c315..528471c2ca3 100644 --- a/packages/firestore/src/core/filter.ts +++ b/packages/firestore/src/core/filter.ts @@ -168,7 +168,7 @@ export class FieldFilter extends Filter { case Operator.GREATER_THAN_OR_EQUAL: return comparison >= 0; default: - return fail('Unknown FieldFilter operator: ' + this.op); + return fail('Unknown FieldFilter operator', { operator: this.op }); } } diff --git a/packages/firestore/src/core/query.ts b/packages/firestore/src/core/query.ts index b13296ad7ee..3be80872e1e 100644 --- a/packages/firestore/src/core/query.ts +++ b/packages/firestore/src/core/query.ts @@ -578,6 +578,6 @@ export function compareDocs( case Direction.DESCENDING: return -1 * comparison; default: - return fail('Unknown direction: ' + orderBy.dir); + return fail('Unknown direction', { direction: orderBy.dir }); } } diff --git a/packages/firestore/src/core/sync_engine_impl.ts b/packages/firestore/src/core/sync_engine_impl.ts index bba07f4f4bc..1678f5b000c 100644 --- a/packages/firestore/src/core/sync_engine_impl.ts +++ b/packages/firestore/src/core/sync_engine_impl.ts @@ -992,7 +992,7 @@ function updateTrackedLimbos( removeLimboTarget(syncEngineImpl, limboChange.key); } } else { - fail('Unknown limbo change: ' + JSON.stringify(limboChange)); + fail('Unknown limbo change', { limboChange }); } } } @@ -1309,7 +1309,7 @@ export async function syncEngineApplyBatchState( batchId ); } else { - fail(`Unknown batchState: ${batchState}`); + fail(`Unknown batchState`, { batchState }); } await syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngineImpl, documents); @@ -1552,7 +1552,7 @@ export async function syncEngineApplyTargetState( break; } default: - fail('Unexpected target state: ' + state); + fail('Unexpected target state', state); } } } diff --git a/packages/firestore/src/core/transaction.ts b/packages/firestore/src/core/transaction.ts index 471c64e13bd..fce866ca9c0 100644 --- a/packages/firestore/src/core/transaction.ts +++ b/packages/firestore/src/core/transaction.ts @@ -124,7 +124,9 @@ export class Transaction { // Represent a deleted doc using SnapshotVersion.min(). docVersion = SnapshotVersion.min(); } else { - throw fail('Document in a transaction was a ' + doc.constructor.name); + throw fail('Document in a transaction was a ', { + documentName: doc.constructor.name + }); } const existingVersion = this.readVersions.get(doc.key.toString()); diff --git a/packages/firestore/src/core/view.ts b/packages/firestore/src/core/view.ts index b0a07bd783c..facdd603b15 100644 --- a/packages/firestore/src/core/view.ts +++ b/packages/firestore/src/core/view.ts @@ -505,7 +505,7 @@ function compareChangeType(c1: ChangeType, c2: ChangeType): number { case ChangeType.Removed: return 0; default: - return fail('Unknown ChangeType: ' + change); + return fail('Unknown ChangeType', { change }); } }; diff --git a/packages/firestore/src/core/view_snapshot.ts b/packages/firestore/src/core/view_snapshot.ts index f15c5ccb409..b05a3f98dd5 100644 --- a/packages/firestore/src/core/view_snapshot.ts +++ b/packages/firestore/src/core/view_snapshot.ts @@ -117,12 +117,10 @@ export class DocumentChangeSet { // Removed->Modified // Metadata->Added // Removed->Metadata - fail( - 'unsupported combination of changes: ' + - JSON.stringify(change) + - ' after ' + - JSON.stringify(oldChange) - ); + fail('unsupported combination of changes: `change` after `oldChange`', { + change, + oldChange + }); } } diff --git a/packages/firestore/src/index/firestore_index_value_writer.ts b/packages/firestore/src/index/firestore_index_value_writer.ts index 9d5d86ab640..8c403168d4e 100644 --- a/packages/firestore/src/index/firestore_index_value_writer.ts +++ b/packages/firestore/src/index/firestore_index_value_writer.ts @@ -129,7 +129,7 @@ export class FirestoreIndexValueWriter { this.writeIndexArray(indexValue.arrayValue!, encoder); this.writeTruncationMarker(encoder); } else { - fail('unknown index value type ' + indexValue); + fail('unknown index value type', { indexValue }); } } diff --git a/packages/firestore/src/lite-api/transaction.ts b/packages/firestore/src/lite-api/transaction.ts index 40bb70460e8..7bf9c0caa1c 100644 --- a/packages/firestore/src/lite-api/transaction.ts +++ b/packages/firestore/src/lite-api/transaction.ts @@ -114,9 +114,9 @@ export class Transaction { ref.converter ); } else { - throw fail( - `BatchGetDocumentsRequest returned unexpected document: ${doc}` - ); + throw fail('BatchGetDocumentsRequest returned unexpected document', { + doc + }); } }); } diff --git a/packages/firestore/src/lite-api/user_data_reader.ts b/packages/firestore/src/lite-api/user_data_reader.ts index 008eb49809c..f9f27b536e3 100644 --- a/packages/firestore/src/lite-api/user_data_reader.ts +++ b/packages/firestore/src/lite-api/user_data_reader.ts @@ -169,7 +169,7 @@ function isWrite(dataSource: UserDataSource): boolean { case UserDataSource.ArrayArgument: return false; default: - throw fail(`Unexpected case for UserDataSource: ${dataSource}`); + throw fail('Unexpected case for UserDataSource', { dataSource }); } } diff --git a/packages/firestore/src/lite-api/user_data_writer.ts b/packages/firestore/src/lite-api/user_data_writer.ts index 5536b5c8b42..222d1c8b28b 100644 --- a/packages/firestore/src/lite-api/user_data_writer.ts +++ b/packages/firestore/src/lite-api/user_data_writer.ts @@ -86,7 +86,9 @@ export abstract class AbstractUserDataWriter { case TypeOrder.ObjectValue: return this.convertObject(value.mapValue!, serverTimestampBehavior); default: - throw fail('Invalid value type: ' + JSON.stringify(value)); + throw fail('Invalid value type', { + value + }); } } @@ -157,7 +159,8 @@ export abstract class AbstractUserDataWriter { const resourcePath = ResourcePath.fromString(name); hardAssert( isValidResourceName(resourcePath), - 'ReferenceValue is not valid ' + name + 'ReferenceValue is not valid', + { name } ); const databaseId = new DatabaseId(resourcePath.get(1), resourcePath.get(3)); const key = new DocumentKey(resourcePath.popFirst(5)); diff --git a/packages/firestore/src/local/encoded_resource_path.ts b/packages/firestore/src/local/encoded_resource_path.ts index b52b8bd6805..a720be534f4 100644 --- a/packages/firestore/src/local/encoded_resource_path.ts +++ b/packages/firestore/src/local/encoded_resource_path.ts @@ -118,11 +118,12 @@ export function decodeResourcePath(path: EncodedResourcePath): ResourcePath { // Event the empty path must encode as a path of at least length 2. A path // with exactly 2 must be the empty path. const length = path.length; - hardAssert(length >= 2, 'Invalid path ' + path); + hardAssert(length >= 2, 'Invalid path', { path }); if (length === 2) { hardAssert( path.charAt(0) === escapeChar && path.charAt(1) === encodedSeparatorChar, - 'Non-empty path ' + path + ' had length 2' + 'Non-empty path had length 2', + { path } ); return ResourcePath.emptyPath(); } @@ -139,7 +140,7 @@ export function decodeResourcePath(path: EncodedResourcePath): ResourcePath { // there must be an end to this segment. const end = path.indexOf(escapeChar, start); if (end < 0 || end > lastReasonableEscapeIndex) { - fail('Invalid encoded resource path: "' + path + '"'); + fail('Invalid encoded resource path', { path }); } const next = path.charAt(end + 1); @@ -167,7 +168,7 @@ export function decodeResourcePath(path: EncodedResourcePath): ResourcePath { segmentBuilder += path.substring(start, end + 1); break; default: - fail('Invalid encoded resource path: "' + path + '"'); + fail('Invalid encoded resource path', { path }); } start = end + 2; diff --git a/packages/firestore/src/local/indexeddb_mutation_batch_impl.ts b/packages/firestore/src/local/indexeddb_mutation_batch_impl.ts index 16b157accb2..f8c62c76aab 100644 --- a/packages/firestore/src/local/indexeddb_mutation_batch_impl.ts +++ b/packages/firestore/src/local/indexeddb_mutation_batch_impl.ts @@ -64,8 +64,8 @@ export function removeMutationBatch( removePromise.next(() => { hardAssert( numDeleted === 1, - 'Dangling document-mutation reference found: Missing batch ' + - batch.batchId + 'Dangling document-mutation reference found: Missing batch', + { batchId: batch.batchId } ); }) ); diff --git a/packages/firestore/src/local/indexeddb_mutation_queue.ts b/packages/firestore/src/local/indexeddb_mutation_queue.ts index 0aedf650769..a9a2a5808be 100644 --- a/packages/firestore/src/local/indexeddb_mutation_queue.ts +++ b/packages/firestore/src/local/indexeddb_mutation_queue.ts @@ -206,7 +206,11 @@ export class IndexedDbMutationQueue implements MutationQueue { if (dbBatch) { hardAssert( dbBatch.userId === this.userId, - `Unexpected user '${dbBatch.userId}' for mutation batch ${batchId}` + `Unexpected user for mutation batch`, + { + userId: dbBatch.userId, + batchId + } ); return fromDbMutationBatch(this.serializer, dbBatch); } @@ -257,7 +261,8 @@ export class IndexedDbMutationQueue implements MutationQueue { if (dbBatch.userId === this.userId) { hardAssert( dbBatch.batchId >= nextBatchId, - 'Should have found mutation after ' + nextBatchId + 'Should have found mutation after `nextBatchId`', + { nextBatchId } ); foundBatch = fromDbMutationBatch(this.serializer, dbBatch); } @@ -336,15 +341,20 @@ export class IndexedDbMutationQueue implements MutationQueue { .next(mutation => { if (!mutation) { throw fail( - 'Dangling document-mutation reference found: ' + - indexKey + - ' which points to ' + + 'Dangling document-mutation reference found: `indexKey` which points to `batchId`', + { + indexKey, batchId + } ); } hardAssert( mutation.userId === this.userId, - `Unexpected user '${mutation.userId}' for mutation batch ${batchId}` + `Unexpected user for mutation batch`, + { + userId: mutation.userId, + batchId + } ); results.push(fromDbMutationBatch(this.serializer, mutation)); }); @@ -468,14 +478,16 @@ export class IndexedDbMutationQueue implements MutationQueue { .next(mutation => { if (mutation === null) { throw fail( - 'Dangling document-mutation reference found, ' + - 'which points to ' + + 'Dangling document-mutation reference found, which points to `batchId`', + { batchId + } ); } hardAssert( mutation.userId === this.userId, - `Unexpected user '${mutation.userId}' for mutation batch ${batchId}` + `Unexpected user for mutation batch`, + { userId: mutation.userId, batchId } ); results.push(fromDbMutationBatch(this.serializer, mutation)); }) @@ -549,9 +561,12 @@ export class IndexedDbMutationQueue implements MutationQueue { .next(() => { hardAssert( danglingMutationReferences.length === 0, - 'Document leak -- detected dangling mutation references when queue is empty. ' + - 'Dangling keys: ' + - danglingMutationReferences.map(p => p.canonicalString()) + 'Document leak -- detected dangling mutation references when queue is empty.', + { + danglingKeys: danglingMutationReferences.map(p => + p.canonicalString() + ) + } ); }); }); diff --git a/packages/firestore/src/local/indexeddb_schema_converter.ts b/packages/firestore/src/local/indexeddb_schema_converter.ts index b65a63a627f..1f7ab0a9758 100644 --- a/packages/firestore/src/local/indexeddb_schema_converter.ts +++ b/packages/firestore/src/local/indexeddb_schema_converter.ts @@ -318,7 +318,8 @@ export class SchemaConverter implements SimpleDbSchemaConverter { (dbBatch: DbMutationBatch) => { hardAssert( dbBatch.userId === queue.userId, - `Cannot process batch ${dbBatch.batchId} from unexpected user` + `Cannot process batch from unexpected user`, + { batchId: dbBatch.batchId } ); const batch = fromDbMutationBatch(this.serializer, dbBatch); diff --git a/packages/firestore/src/local/local_serializer.ts b/packages/firestore/src/local/local_serializer.ts index b8916608711..06eabbc3281 100644 --- a/packages/firestore/src/local/local_serializer.ts +++ b/packages/firestore/src/local/local_serializer.ts @@ -138,7 +138,7 @@ export function toDbRemoteDocument( version: toDbTimestamp(document.version) }; } else { - return fail('Unexpected Document ' + document); + return fail('Unexpected Document', { document }); } return remoteDoc; } diff --git a/packages/firestore/src/local/shared_client_state.ts b/packages/firestore/src/local/shared_client_state.ts index 0d0e430c12f..4b82ff0e602 100644 --- a/packages/firestore/src/local/shared_client_state.ts +++ b/packages/firestore/src/local/shared_client_state.ts @@ -1075,7 +1075,8 @@ function fromWebStorageSequenceNumber( const parsed = JSON.parse(seqString); hardAssert( typeof parsed === 'number', - 'Found non-numeric sequence number' + 'Found non-numeric sequence number', + { seqString } ); sequenceNumber = parsed; } catch (e) { diff --git a/packages/firestore/src/model/mutation.ts b/packages/firestore/src/model/mutation.ts index 119e9b9731b..9e9a265b69e 100644 --- a/packages/firestore/src/model/mutation.ts +++ b/packages/firestore/src/model/mutation.ts @@ -623,8 +623,11 @@ function serverTransformResults( const transformResults = new Map(); hardAssert( fieldTransforms.length === serverTransformResults.length, - `server transform result count (${serverTransformResults.length}) ` + - `should match field transform count (${fieldTransforms.length})` + 'server transform result count should match field transform count', + { + serverTransformResultCount: serverTransformResults.length, + fieldTransformCount: fieldTransforms.length + } ); for (let i = 0; i < serverTransformResults.length; i++) { diff --git a/packages/firestore/src/model/mutation_batch.ts b/packages/firestore/src/model/mutation_batch.ts index 56d5f4d2cd3..d4780cd7f29 100644 --- a/packages/firestore/src/model/mutation_batch.ts +++ b/packages/firestore/src/model/mutation_batch.ts @@ -219,10 +219,11 @@ export class MutationBatchResult { ): MutationBatchResult { hardAssert( batch.mutations.length === results.length, - 'Mutations sent ' + - batch.mutations.length + - ' must equal results received ' + - results.length + 'Mutations sent must equal results received', + { + mutationsSent: batch.mutations.length, + resultsReceived: results.length + } ); let versionMap = documentVersionMap(); diff --git a/packages/firestore/src/model/normalize.ts b/packages/firestore/src/model/normalize.ts index 2061601d23e..50df6aabf76 100644 --- a/packages/firestore/src/model/normalize.ts +++ b/packages/firestore/src/model/normalize.ts @@ -44,7 +44,9 @@ export function normalizeTimestamp(date: Timestamp): { // Parse the nanos right out of the string. let nanos = 0; const fraction = ISO_TIMESTAMP_REG_EXP.exec(date); - hardAssert(!!fraction, 'invalid timestamp: ' + date); + hardAssert(!!fraction, 'invalid timestamp', { + timestamp: date + }); if (fraction[1]) { // Pad the fraction out to 9 digits (nanos). let nanoStr = fraction[1]; diff --git a/packages/firestore/src/model/path.ts b/packages/firestore/src/model/path.ts index 4a4095ccfb1..e88d4f53caa 100644 --- a/packages/firestore/src/model/path.ts +++ b/packages/firestore/src/model/path.ts @@ -32,13 +32,13 @@ abstract class BasePath> { if (offset === undefined) { offset = 0; } else if (offset > segments.length) { - fail('offset ' + offset + ' out of range ' + segments.length); + fail('offset out of range', { offset, range: segments.length }); } if (length === undefined) { length = segments.length - offset; } else if (length > segments.length - offset) { - fail('length ' + length + ' out of range ' + (segments.length - offset)); + fail('length out of range', { length, range: segments.length - offset }); } this.segments = segments; this.offset = offset; diff --git a/packages/firestore/src/model/values.ts b/packages/firestore/src/model/values.ts index 3466c2c1ee6..2753e10ac79 100644 --- a/packages/firestore/src/model/values.ts +++ b/packages/firestore/src/model/values.ts @@ -82,7 +82,7 @@ export function typeOrder(value: Value): TypeOrder { } return TypeOrder.ObjectValue; } else { - return fail('Invalid value type: ' + JSON.stringify(value)); + return fail('Invalid value type', { value }); } } @@ -128,7 +128,7 @@ export function valueEquals(left: Value, right: Value): boolean { case TypeOrder.MaxValue: return true; default: - return fail('Unexpected value type: ' + JSON.stringify(left)); + return fail('Unexpected value type', { left }); } } @@ -255,7 +255,7 @@ export function valueCompare(left: Value, right: Value): number { case TypeOrder.ObjectValue: return compareMaps(left.mapValue!, right.mapValue!); default: - throw fail('Invalid value type: ' + leftType); + throw fail('Invalid value type', { leftType }); } } @@ -416,7 +416,7 @@ function canonifyValue(value: Value): string { } else if ('mapValue' in value) { return canonifyMap(value.mapValue!); } else { - return fail('Invalid value type: ' + JSON.stringify(value)); + return fail('Invalid value type', { value }); } } @@ -507,7 +507,7 @@ export function estimateByteSize(value: Value): number { case TypeOrder.ObjectValue: return estimateMapByteSize(value.mapValue!); default: - throw fail('Invalid value type: ' + JSON.stringify(value)); + throw fail('Invalid value type', { value }); } } @@ -647,7 +647,7 @@ export function valuesGetLowerBound(value: Value): Value { } else if ('mapValue' in value) { return { mapValue: {} }; } else { - return fail('Invalid value type: ' + JSON.stringify(value)); + return fail('Invalid value type', { value }); } } @@ -674,7 +674,7 @@ export function valuesGetUpperBound(value: Value): Value { } else if ('mapValue' in value) { return MAX_VALUE; } else { - return fail('Invalid value type: ' + JSON.stringify(value)); + return fail('Invalid value type', { value }); } } diff --git a/packages/firestore/src/platform/browser/webchannel_connection.ts b/packages/firestore/src/platform/browser/webchannel_connection.ts index aa790f0ac2b..5d47d251ec0 100644 --- a/packages/firestore/src/platform/browser/webchannel_connection.ts +++ b/packages/firestore/src/platform/browser/webchannel_connection.ts @@ -143,12 +143,13 @@ export class WebChannelConnection extends RestConnection { break; default: fail( - `RPC '${rpcName}' ${streamId} ` + - 'failed with unanticipated webchannel error: ' + - xhr.getLastErrorCode() + - ': ' + - xhr.getLastError() + - ', giving up.' + 'RPC failed with unanticipated webchannel error. Giving up.', + { + rpcName, + streamId, + lastErrorCode: xhr.getLastErrorCode(), + lastError: xhr.getLastError() + } ); } } finally { diff --git a/packages/firestore/src/remote/datastore.ts b/packages/firestore/src/remote/datastore.ts index ac47f0cb931..1e11a254679 100644 --- a/packages/firestore/src/remote/datastore.ts +++ b/packages/firestore/src/remote/datastore.ts @@ -228,7 +228,7 @@ export async function invokeBatchGetDocumentsRpc( const result: Document[] = []; keys.forEach(key => { const doc = docs.get(key.toString()); - hardAssert(!!doc, 'Missing entity in write response for ' + key); + hardAssert(!!doc, 'Missing entity in write response for `key`', { key }); result.push(doc); }); return result; diff --git a/packages/firestore/src/remote/rpc_error.ts b/packages/firestore/src/remote/rpc_error.ts index cbacb6c59ca..2c071d7bf1e 100644 --- a/packages/firestore/src/remote/rpc_error.ts +++ b/packages/firestore/src/remote/rpc_error.ts @@ -83,7 +83,7 @@ export function isPermanentError(code: Code): boolean { case Code.DATA_LOSS: return true; default: - return fail('Unknown status code: ' + code); + return fail('Unknown status code', { code }); } } @@ -171,7 +171,7 @@ export function mapCodeFromRpcCode(code: number | undefined): Code { case RpcCode.DATA_LOSS: return Code.DATA_LOSS; default: - return fail('Unknown status code: ' + code); + return fail('Unknown status code', { code }); } } @@ -220,7 +220,7 @@ export function mapRpcCodeFromCode(code: Code | undefined): number { case Code.DATA_LOSS: return RpcCode.DATA_LOSS; default: - return fail('Unknown status code: ' + code); + return fail('Unknown status code', { code }); } } diff --git a/packages/firestore/src/remote/serializer.ts b/packages/firestore/src/remote/serializer.ts index 811c2ac4df6..3892357ff1d 100644 --- a/packages/firestore/src/remote/serializer.ts +++ b/packages/firestore/src/remote/serializer.ts @@ -306,7 +306,8 @@ function fromResourceName(name: string): ResourcePath { const resource = ResourcePath.fromString(name); hardAssert( isValidResourceName(resource), - 'Tried to deserialize invalid key ' + resource.toString() + 'Tried to deserialize invalid key', + { key: resource.toString() } ); return resource; } @@ -389,7 +390,8 @@ function extractLocalPathFromResourceName( ): ResourcePath { hardAssert( resourceName.length > 4 && resourceName.get(4) === 'documents', - 'tried to deserialize invalid key ' + resourceName.toString() + 'tried to deserialize invalid key', + { key: resourceName.toString() } ); return resourceName.popFirst(5); } @@ -493,7 +495,7 @@ export function fromBatchGetDocumentsResponse( } else if ('missing' in result) { return fromMissing(serializer, result); } - return fail('invalid batch get response: ' + JSON.stringify(result)); + return fail('invalid batch get response', { result }); } export function fromWatchChange( @@ -578,7 +580,7 @@ export function fromWatchChange( const targetId = filter.targetId; watchChange = new ExistenceFilterChange(targetId, existenceFilter); } else { - return fail('Unknown change type ' + JSON.stringify(change)); + return fail('Unknown change type', { change }); } return watchChange; } @@ -597,7 +599,7 @@ function fromWatchTargetChangeState( } else if (state === 'RESET') { return WatchTargetChangeState.Reset; } else { - return fail('Got unexpected TargetChange.state: ' + state); + return fail('Got unexpected TargetChange.state', { state }); } } @@ -641,7 +643,7 @@ export function toMutation( verify: toName(serializer, mutation.key) }; } else { - return fail('Unknown mutation type ' + mutation.type); + return fail('Unknown mutation type', { mutationType: mutation.type }); } if (mutation.fieldTransforms.length > 0) { @@ -697,7 +699,7 @@ export function fromMutation( const key = fromName(serializer, proto.verify); return new VerifyMutation(key, precondition); } else { - return fail('unknown mutation proto: ' + JSON.stringify(proto)); + return fail('unknown mutation proto', { proto }); } } @@ -793,7 +795,7 @@ function toFieldTransform( increment: transform.operand }; } else { - throw fail('Unknown transform: ' + fieldTransform.transform); + throw fail('Unknown transform', { transform: fieldTransform.transform }); } } @@ -805,7 +807,8 @@ function fromFieldTransform( if ('setToServerValue' in proto) { hardAssert( proto.setToServerValue === 'REQUEST_TIME', - 'Unknown server value transform proto: ' + JSON.stringify(proto) + 'Unknown server value transform proto', + { proto } ); transform = new ServerTimestampTransform(); } else if ('appendMissingElements' in proto) { @@ -820,7 +823,7 @@ function fromFieldTransform( proto.increment! ); } else { - fail('Unknown transform proto: ' + JSON.stringify(proto)); + fail('Unknown transform proto', { proto }); } const fieldPath = FieldPath.fromServerFormat(proto.fieldPath!); return new FieldTransform(fieldPath, transform!); @@ -837,10 +840,9 @@ export function fromDocumentsTarget( documentsTarget: ProtoDocumentsTarget ): Target { const count = documentsTarget.documents!.length; - hardAssert( - count === 1, - 'DocumentsTarget contained other than 1 document: ' + count - ); + hardAssert(count === 1, 'DocumentsTarget contained other than 1 document', { + count + }); const name = documentsTarget.documents![0]; return queryToTarget(newQueryForPath(fromQueryPath(name))); } @@ -1045,7 +1047,7 @@ export function toLabel(purpose: TargetPurpose): string | null { case TargetPurpose.LimboResolution: return 'limbo-document'; default: - return fail('Unrecognized query purpose: ' + purpose); + return fail('Unrecognized query purpose', { purpose }); } } @@ -1116,7 +1118,7 @@ function fromFilter(filter: ProtoFilter): Filter { } else if (filter.compositeFilter !== undefined) { return fromCompositeFilter(filter); } else { - return fail('Unknown filter: ' + JSON.stringify(filter)); + return fail('Unknown filter', { filter }); } } @@ -1261,7 +1263,7 @@ export function toFilter(filter: Filter): ProtoFilter { } else if (filter instanceof CompositeFilter) { return toCompositeFilter(filter); } else { - return fail('Unrecognized filter type ' + JSON.stringify(filter)); + return fail('Unrecognized filter type', { filter }); } } diff --git a/packages/firestore/src/remote/watch_change.ts b/packages/firestore/src/remote/watch_change.ts index 38e10a23e35..564e028372e 100644 --- a/packages/firestore/src/remote/watch_change.ts +++ b/packages/firestore/src/remote/watch_change.ts @@ -203,7 +203,7 @@ class TargetState { removedDocuments = removedDocuments.add(key); break; default: - fail('Encountered invalid change type: ' + changeType); + fail('Encountered invalid change type', { changeType }); } }); @@ -242,10 +242,8 @@ class TargetState { this.pendingResponses -= 1; hardAssert( this.pendingResponses >= 0, - '`pendingResponses` is less than 0. Actual value: ' + - this.pendingResponses + - '. This indicates that the SDK received more target acks from the ' + - 'server than expected. The SDK should not continue to operate.' + '`pendingResponses` is less than 0. This indicates that the SDK received more target acks from the server than expected. The SDK should not continue to operate.', + { pendingResponses: this.pendingResponses } ); } @@ -377,7 +375,9 @@ export class WatchChangeAggregator { } break; default: - fail('Unknown target watch change state: ' + targetChange.state); + fail('Unknown target watch change state', { + state: targetChange.state + }); } }); } @@ -431,7 +431,8 @@ export class WatchChangeAggregator { } else { hardAssert( expectedCount === 1, - 'Single document existence filter with count: ' + expectedCount + 'Single document existence filter with count', + { expectedCount } ); } } else { diff --git a/packages/firestore/src/util/assert.ts b/packages/firestore/src/util/assert.ts index 6d65e6cd19b..fa2b27a98d7 100644 --- a/packages/firestore/src/util/assert.ts +++ b/packages/firestore/src/util/assert.ts @@ -27,11 +27,28 @@ import { logError } from './log'; * @example * let futureVar = fail('not implemented yet'); */ -export function fail(failure: string = 'Unexpected state'): never { +export function fail( + failure: string = 'Unexpected state', + context: unknown = undefined +): never { + _fail(failure, context); +} + +function _fail( + failure: string = 'Unexpected state', + context: unknown = undefined +): never { // Log the failure in addition to throw an exception, just in case the // exception is swallowed. - const message = - `FIRESTORE (${SDK_VERSION}) INTERNAL ASSERTION FAILED: ` + failure; + let message = `FIRESTORE (${SDK_VERSION}) INTERNAL ASSERTION FAILED: ${failure}`; + if (context !== undefined) { + try { + const stringContext = JSON.stringify(context); + message += ' CONTEXT: ' + stringContext; + } catch (e) { + message += ' CONTEXT: ' + context; + } + } logError(message); // NOTE: We don't use FirestoreError here because these are internal failures @@ -48,10 +65,11 @@ export function fail(failure: string = 'Unexpected state'): never { */ export function hardAssert( assertion: boolean, - message?: string + message?: string, + context?: unknown ): asserts assertion { if (!assertion) { - fail(message); + _fail(message, context); } } diff --git a/packages/firestore/src/util/async_queue_impl.ts b/packages/firestore/src/util/async_queue_impl.ts index 13bd7c79233..a142d48f8be 100644 --- a/packages/firestore/src/util/async_queue_impl.ts +++ b/packages/firestore/src/util/async_queue_impl.ts @@ -235,7 +235,9 @@ export class AsyncQueueImpl implements AsyncQueue { private verifyNotFailed(): void { if (this.failure) { - fail('AsyncQueue is already failed: ' + getMessageOrStack(this.failure)); + fail('AsyncQueue is already failed', { + messageOrStack: getMessageOrStack(this.failure) + }); } } diff --git a/packages/firestore/src/util/input_validation.ts b/packages/firestore/src/util/input_validation.ts index 37e349ce910..e8c45ae3ec6 100644 --- a/packages/firestore/src/util/input_validation.ts +++ b/packages/firestore/src/util/input_validation.ts @@ -128,7 +128,7 @@ export function valueDescription(input: unknown): string { } else if (typeof input === 'function') { return 'a function'; } else { - return fail('Unknown wrong type: ' + typeof input); + return fail('Unknown wrong type', { type: typeof input }); } } diff --git a/packages/firestore/src/util/sorted_map.ts b/packages/firestore/src/util/sorted_map.ts index a24cf8802ca..53bbe36b22e 100644 --- a/packages/firestore/src/util/sorted_map.ts +++ b/packages/firestore/src/util/sorted_map.ts @@ -511,10 +511,16 @@ export class LLRBNode { // leaves is equal on both sides. This function verifies that or asserts. protected check(): number { if (this.isRed() && this.left.isRed()) { - throw fail('Red node has red child(' + this.key + ',' + this.value + ')'); + throw fail('Red node has red child', { + key: this.key, + value: this.value + }); } if (this.right.isRed()) { - throw fail('Right child of (' + this.key + ',' + this.value + ') is red'); + throw fail('Right child of (`key`, `value`) is red', { + key: this.key, + value: this.value + }); } const blackDepth = (this.left as LLRBNode).check(); if (blackDepth !== (this.right as LLRBNode).check()) { From c7c099afbedc1feae7f83cc2aaf82662facd716a Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Wed, 12 Jun 2024 13:07:11 -0600 Subject: [PATCH 02/18] Try-catch on saveErrorCode --- packages/firestore/scripts/remove-asserts.js | 17 +++++++++++++++++ packages/firestore/scripts/remove-asserts.ts | 7 ++++++- packages/firestore/scripts/run-tests.js | 17 +++++++++++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 packages/firestore/scripts/remove-asserts.js create mode 100644 packages/firestore/scripts/run-tests.js diff --git a/packages/firestore/scripts/remove-asserts.js b/packages/firestore/scripts/remove-asserts.js new file mode 100644 index 00000000000..72553496098 --- /dev/null +++ b/packages/firestore/scripts/remove-asserts.js @@ -0,0 +1,17 @@ +"use strict"; +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */var __spreadArray=this&&this.__spreadArray||function(to,from,pack){if(pack||arguments.length===2)for(var i=0,l=from.length,ar;i=0){var method=declaration.name.text;if(method==="debugAssert"){updatedNode=ts.createOmittedExpression()}else if(method==="hardAssert"||method==="fail"){var messageIndex=method==="hardAssert"?1:0;if(node.arguments.length>messageIndex&&node.arguments[messageIndex].kind===ts.SyntaxKind.StringLiteral){var stringLiteral=node.arguments[messageIndex];var errorMessage=RemoveAsserts.trimErrorMessage(stringLiteral.getFullText());var errorCode=RemoveAsserts.errorCode(errorMessage);try{RemoveAsserts.saveErrorCode(errorCode,errorMessage)}catch(e){console.log("Failed to save error code "+JSON.stringify(e))}var newArguments=__spreadArray([],node.arguments,true);newArguments[messageIndex]=ts.createLiteral(errorCode);updatedNode=ts.createCall(declaration.name,undefined,newArguments)}else{var newArguments=__spreadArray([],node.arguments,true);newArguments[messageIndex]=ts.createLiteral("Unexpected error");updatedNode=ts.createCall(declaration.name,undefined,newArguments)}}}}}if(updatedNode){ts.setSourceMapRange(updatedNode,ts.getSourceMapRange(node));return updatedNode}else{return node}};RemoveAsserts.trimErrorMessage=function(errorMessage){return errorMessage.substring(errorMessage.indexOf("'")+1,errorMessage.lastIndexOf("'"))};RemoveAsserts.errorCode=function(errorMessage){var hash=(0,crypto_1.createHash)("sha256");hash.update(errorMessage);var paramHash=hash.digest("hex").substring(0,7);return paramHash};RemoveAsserts.saveErrorCode=function(errorCode,errorMessage){var errorCodes=RemoveAsserts.getErrorCodes();errorCodes[errorCode]=errorMessage;RemoveAsserts.saveErrorCodes(errorCodes)};RemoveAsserts.getErrorCodes=function(){var path=(0,path_1.join)(module.path,ERROR_CODE_LOCATION);if(!(0,fs_1.existsSync)(path)){return{}}return JSON.parse((0,fs_1.readFileSync)(path,"utf-8"))};RemoveAsserts.saveErrorCodes=function(errorCodes){var path=(0,path_1.join)(module.path,ERROR_CODE_LOCATION);(0,fs_1.writeFileSync)(path,JSON.stringify(errorCodes,undefined,4),{encoding:"utf-8"})};return RemoveAsserts}(); \ No newline at end of file diff --git a/packages/firestore/scripts/remove-asserts.ts b/packages/firestore/scripts/remove-asserts.ts index 916e3253533..0f028211ebc 100644 --- a/packages/firestore/scripts/remove-asserts.ts +++ b/packages/firestore/scripts/remove-asserts.ts @@ -79,7 +79,12 @@ class RemoveAsserts { const errorMessage = RemoveAsserts.trimErrorMessage(stringLiteral.getFullText()); const errorCode = RemoveAsserts.errorCode(errorMessage); - RemoveAsserts.saveErrorCode(errorCode, errorMessage); + try { + RemoveAsserts.saveErrorCode(errorCode, errorMessage); + } + catch (e) { + console.log('Failed to save error code ' + JSON.stringify(e)); + } const newArguments = [...node.arguments]; newArguments[messageIndex] = ts.createLiteral(errorCode); diff --git a/packages/firestore/scripts/run-tests.js b/packages/firestore/scripts/run-tests.js new file mode 100644 index 00000000000..88b9f05ee55 --- /dev/null +++ b/packages/firestore/scripts/run-tests.js @@ -0,0 +1,17 @@ +"use strict"; +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */exports.__esModule=true;var path_1=require("path");var child_process_promise_1=require("child-process-promise");var yargs=require("yargs");var argv=yargs.options({main:{type:"string",demandOption:true},platform:{type:"string",default:"node"},emulator:{type:"boolean"},persistence:{type:"boolean"},databaseId:{type:"string"}}).parseSync();var nyc=(0,path_1.resolve)(__dirname,"../../../node_modules/.bin/nyc");var mocha=(0,path_1.resolve)(__dirname,"../../../node_modules/.bin/mocha");var babel=(0,path_1.resolve)(__dirname,"../babel-register.js");process.env.NO_TS_NODE="true";process.env.TEST_PLATFORM=argv.platform;var args=["--reporter","lcovonly",mocha,"--require",babel,"--require",argv.main,"--config","../../config/mocharc.node.js"];if(argv.emulator){process.env.FIRESTORE_TARGET_BACKEND="emulator"}if(argv.persistence){process.env.USE_MOCK_PERSISTENCE="YES";args.push("--require","test/util/node_persistence.ts")}if(argv.databaseId){process.env.FIRESTORE_TARGET_DB_ID=argv.databaseId}args=args.concat(argv._);var childProcess=(0,child_process_promise_1.spawn)(nyc,args,{stdio:"inherit",cwd:process.cwd()}).childProcess;process.once("exit",(function(){return childProcess.kill()}));process.once("SIGINT",(function(){return childProcess.kill("SIGINT")}));process.once("SIGTERM",(function(){return childProcess.kill("SIGTERM")})); \ No newline at end of file From 39cf218b212271467c66f8486342b80969106d69 Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Wed, 12 Jun 2024 14:52:17 -0600 Subject: [PATCH 03/18] Replace string errorCodes with numeric errorIds to make the bundles smaller. --- packages/firestore/scripts/remove-asserts.js | 2 +- packages/firestore/scripts/remove-asserts.ts | 36 ++++++++++++++----- packages/firestore/src/util/assert.ts | 9 ++--- .../unit/index/ordered_code_writer.test.ts | 4 ++- 4 files changed, 35 insertions(+), 16 deletions(-) diff --git a/packages/firestore/scripts/remove-asserts.js b/packages/firestore/scripts/remove-asserts.js index 72553496098..257ee8412b1 100644 --- a/packages/firestore/scripts/remove-asserts.js +++ b/packages/firestore/scripts/remove-asserts.js @@ -14,4 +14,4 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - */var __spreadArray=this&&this.__spreadArray||function(to,from,pack){if(pack||arguments.length===2)for(var i=0,l=from.length,ar;i=0){var method=declaration.name.text;if(method==="debugAssert"){updatedNode=ts.createOmittedExpression()}else if(method==="hardAssert"||method==="fail"){var messageIndex=method==="hardAssert"?1:0;if(node.arguments.length>messageIndex&&node.arguments[messageIndex].kind===ts.SyntaxKind.StringLiteral){var stringLiteral=node.arguments[messageIndex];var errorMessage=RemoveAsserts.trimErrorMessage(stringLiteral.getFullText());var errorCode=RemoveAsserts.errorCode(errorMessage);try{RemoveAsserts.saveErrorCode(errorCode,errorMessage)}catch(e){console.log("Failed to save error code "+JSON.stringify(e))}var newArguments=__spreadArray([],node.arguments,true);newArguments[messageIndex]=ts.createLiteral(errorCode);updatedNode=ts.createCall(declaration.name,undefined,newArguments)}else{var newArguments=__spreadArray([],node.arguments,true);newArguments[messageIndex]=ts.createLiteral("Unexpected error");updatedNode=ts.createCall(declaration.name,undefined,newArguments)}}}}}if(updatedNode){ts.setSourceMapRange(updatedNode,ts.getSourceMapRange(node));return updatedNode}else{return node}};RemoveAsserts.trimErrorMessage=function(errorMessage){return errorMessage.substring(errorMessage.indexOf("'")+1,errorMessage.lastIndexOf("'"))};RemoveAsserts.errorCode=function(errorMessage){var hash=(0,crypto_1.createHash)("sha256");hash.update(errorMessage);var paramHash=hash.digest("hex").substring(0,7);return paramHash};RemoveAsserts.saveErrorCode=function(errorCode,errorMessage){var errorCodes=RemoveAsserts.getErrorCodes();errorCodes[errorCode]=errorMessage;RemoveAsserts.saveErrorCodes(errorCodes)};RemoveAsserts.getErrorCodes=function(){var path=(0,path_1.join)(module.path,ERROR_CODE_LOCATION);if(!(0,fs_1.existsSync)(path)){return{}}return JSON.parse((0,fs_1.readFileSync)(path,"utf-8"))};RemoveAsserts.saveErrorCodes=function(errorCodes){var path=(0,path_1.join)(module.path,ERROR_CODE_LOCATION);(0,fs_1.writeFileSync)(path,JSON.stringify(errorCodes,undefined,4),{encoding:"utf-8"})};return RemoveAsserts}(); \ No newline at end of file + */var __spreadArray=this&&this.__spreadArray||function(to,from,pack){if(pack||arguments.length===2)for(var i=0,l=from.length,ar;i=0){var method=declaration.name.text;if(method==="debugAssert"){updatedNode=ts.createOmittedExpression()}else if(method==="hardAssert"||method==="fail"){var messageIndex=method==="hardAssert"?1:0;if(node.arguments.length>messageIndex&&node.arguments[messageIndex].kind===ts.SyntaxKind.StringLiteral){var stringLiteral=node.arguments[messageIndex];var errorMessage=RemoveAsserts.trimErrorMessage(stringLiteral.getFullText());var errorCode=RemoveAsserts.errorCode(errorMessage);var errorId=-1;try{errorId=RemoveAsserts.saveErrorCode(errorCode,errorMessage)}catch(e){console.log("Failed to save error code "+JSON.stringify(e))}var newArguments=__spreadArray([],node.arguments,true);newArguments[messageIndex]=ts.factory.createNumericLiteral(errorId);updatedNode=ts.factory.createCallExpression(declaration.name,undefined,newArguments)}else{var newArguments=__spreadArray([],node.arguments,true);newArguments[messageIndex]=ts.factory.createNumericLiteral(-1);updatedNode=ts.createCall(declaration.name,undefined,newArguments)}}}}}if(updatedNode){ts.setSourceMapRange(updatedNode,ts.getSourceMapRange(node));return updatedNode}else{return node}};RemoveAsserts.trimErrorMessage=function(errorMessage){return errorMessage.substring(errorMessage.indexOf("'")+1,errorMessage.lastIndexOf("'"))};RemoveAsserts.errorCode=function(errorMessage){var hash=(0,crypto_1.createHash)("sha256");hash.update(errorMessage);var paramHash=hash.digest("hex").substring(0,7);return paramHash};RemoveAsserts.saveErrorCode=function(errorCode,errorMessage){var errorCodes=RemoveAsserts.getErrorCodes();var existingErrorCode=errorCodes[errorCode];if(existingErrorCode)return existingErrorCode.id;var id=Object.keys(errorCodes).length;errorCodes[errorCode]={message:errorMessage,id:id};RemoveAsserts.saveErrorCodes(errorCodes);return id};RemoveAsserts.getErrorCodes=function(){var path=(0,path_1.join)(module.path,ERROR_CODE_LOCATION);if(!(0,fs_1.existsSync)(path)){return{}}return JSON.parse((0,fs_1.readFileSync)(path,"utf-8"))};RemoveAsserts.saveErrorCodes=function(errorCodes){var path=(0,path_1.join)(module.path,ERROR_CODE_LOCATION);(0,fs_1.writeFileSync)(path,JSON.stringify(errorCodes,undefined,4),{encoding:"utf-8"})};return RemoveAsserts}(); \ No newline at end of file diff --git a/packages/firestore/scripts/remove-asserts.ts b/packages/firestore/scripts/remove-asserts.ts index 0f028211ebc..55c3ce15678 100644 --- a/packages/firestore/scripts/remove-asserts.ts +++ b/packages/firestore/scripts/remove-asserts.ts @@ -79,25 +79,26 @@ class RemoveAsserts { const errorMessage = RemoveAsserts.trimErrorMessage(stringLiteral.getFullText()); const errorCode = RemoveAsserts.errorCode(errorMessage); + let errorId: number = -1; try { - RemoveAsserts.saveErrorCode(errorCode, errorMessage); + errorId = RemoveAsserts.saveErrorCode(errorCode, errorMessage); } catch (e) { console.log('Failed to save error code ' + JSON.stringify(e)); } const newArguments = [...node.arguments]; - newArguments[messageIndex] = ts.createLiteral(errorCode); + newArguments[messageIndex] = ts.factory.createNumericLiteral(errorId); // Replace the call with the full error message to a // build with an error code - updatedNode = ts.createCall( + updatedNode = ts.factory.createCallExpression( declaration.name!, /*typeArgs*/ undefined, newArguments ); } else { const newArguments = [...node.arguments]; - newArguments[messageIndex] = ts.createLiteral('Unexpected error'); + newArguments[messageIndex] = ts.factory.createNumericLiteral(-1); // Remove the log message but keep the assertion updatedNode = ts.createCall( declaration.name!, @@ -135,13 +136,27 @@ class RemoveAsserts { return paramHash; } - static saveErrorCode(errorCode: string, errorMessage: string): void { + + + static saveErrorCode(errorCode: string, errorMessage: string): number { const errorCodes = RemoveAsserts.getErrorCodes(); - errorCodes[errorCode] = errorMessage; + + const existingErrorCode: Error | undefined = errorCodes[errorCode]; + if (existingErrorCode) + {return existingErrorCode.id;} + + const id = Object.keys(errorCodes).length; + errorCodes[errorCode] = { + message: errorMessage, + id + }; + RemoveAsserts.saveErrorCodes(errorCodes); + + return id; } - static getErrorCodes(): Record { + static getErrorCodes(): Record { const path = join(module.path, ERROR_CODE_LOCATION); if (!existsSync(path)){ return {}; @@ -149,8 +164,13 @@ class RemoveAsserts { return JSON.parse(readFileSync(path, 'utf-8')); } - static saveErrorCodes(errorCodes: Record): void { + static saveErrorCodes(errorCodes: Record): void { const path = join(module.path, ERROR_CODE_LOCATION); writeFileSync(path, JSON.stringify(errorCodes, undefined, 4), {encoding: "utf-8", }); } } + +interface Error { + id: number, + message: string +}; diff --git a/packages/firestore/src/util/assert.ts b/packages/firestore/src/util/assert.ts index fa2b27a98d7..293dfc5417a 100644 --- a/packages/firestore/src/util/assert.ts +++ b/packages/firestore/src/util/assert.ts @@ -28,16 +28,13 @@ import { logError } from './log'; * let futureVar = fail('not implemented yet'); */ export function fail( - failure: string = 'Unexpected state', + failure: string | number, context: unknown = undefined ): never { _fail(failure, context); } -function _fail( - failure: string = 'Unexpected state', - context: unknown = undefined -): never { +function _fail(failure: string | number, context: unknown = undefined): never { // Log the failure in addition to throw an exception, just in case the // exception is swallowed. let message = `FIRESTORE (${SDK_VERSION}) INTERNAL ASSERTION FAILED: ${failure}`; @@ -65,7 +62,7 @@ function _fail( */ export function hardAssert( assertion: boolean, - message?: string, + message: string | number, context?: unknown ): asserts assertion { if (!assertion) { diff --git a/packages/firestore/test/unit/index/ordered_code_writer.test.ts b/packages/firestore/test/unit/index/ordered_code_writer.test.ts index 6d87ddb4849..d2393c66120 100644 --- a/packages/firestore/test/unit/index/ordered_code_writer.test.ts +++ b/packages/firestore/test/unit/index/ordered_code_writer.test.ts @@ -248,7 +248,9 @@ function getBytes(val: unknown): { asc: Uint8Array; desc: Uint8Array } { ascWriter.writeUtf8Ascending(val); descWriter.writeUtf8Descending(val); } else { - hardAssert(val instanceof Uint8Array); + hardAssert(val instanceof Uint8Array, 'val is not instance of Uint8Array', { + val + }); ascWriter.writeBytesAscending(ByteString.fromUint8Array(val)); descWriter.writeBytesDescending(ByteString.fromUint8Array(val)); } From 448257f220907751c1ec76a769438ad9c18644b7 Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Tue, 1 Apr 2025 16:51:19 -0600 Subject: [PATCH 04/18] Added fixed error code values to source --- packages/firestore/scripts/build-bundle.js | 2 +- packages/firestore/scripts/extract-api.js | 2 +- packages/firestore/scripts/remove-asserts.js | 2 +- packages/firestore/scripts/remove-asserts.ts | 98 +++---------------- .../firestore/scripts/rename-internals.js | 2 +- packages/firestore/scripts/run-tests.js | 2 +- packages/firestore/src/api/credentials.ts | 7 ++ packages/firestore/src/api/snapshot.ts | 2 +- .../firestore/src/core/component_provider.ts | 1 + packages/firestore/src/core/filter.ts | 6 +- packages/firestore/src/core/query.ts | 2 +- .../firestore/src/core/sync_engine_impl.ts | 9 +- packages/firestore/src/core/transaction.ts | 2 +- packages/firestore/src/core/view.ts | 2 +- packages/firestore/src/core/view_snapshot.ts | 12 ++- .../src/index/firestore_index_value_writer.ts | 2 +- .../firestore/src/lite-api/reference_impl.ts | 6 +- .../firestore/src/lite-api/transaction.ts | 15 ++- .../src/lite-api/user_data_reader.ts | 4 +- .../src/lite-api/user_data_writer.ts | 3 +- .../src/local/encoded_resource_path.ts | 7 +- .../src/local/indexeddb_index_manager.ts | 3 +- .../local/indexeddb_mutation_batch_impl.ts | 3 +- .../src/local/indexeddb_mutation_queue.ts | 14 ++- .../local/indexeddb_remote_document_cache.ts | 2 +- .../src/local/indexeddb_schema_converter.ts | 3 +- .../src/local/indexeddb_sentinels.ts | 2 +- .../src/local/indexeddb_target_cache.ts | 3 +- .../firestore/src/local/local_serializer.ts | 4 +- .../firestore/src/local/local_store_impl.ts | 7 +- .../src/local/memory_mutation_queue.ts | 1 + .../firestore/src/local/memory_persistence.ts | 5 +- .../src/local/memory_remote_document_cache.ts | 2 +- .../src/local/persistence_promise.ts | 2 +- .../src/local/shared_client_state.ts | 1 + packages/firestore/src/model/document.ts | 5 +- packages/firestore/src/model/mutation.ts | 1 + .../firestore/src/model/mutation_batch.ts | 1 + packages/firestore/src/model/normalize.ts | 8 +- packages/firestore/src/model/path.ts | 10 +- .../src/model/target_index_matcher.ts | 1 + packages/firestore/src/model/values.ts | 14 +-- .../platform/browser/webchannel_connection.ts | 7 +- .../src/platform/node/grpc_connection.ts | 1 + packages/firestore/src/remote/datastore.ts | 8 +- .../firestore/src/remote/persistent_stream.ts | 3 + packages/firestore/src/remote/rpc_error.ts | 8 +- packages/firestore/src/remote/serializer.ts | 63 ++++++++---- packages/firestore/src/remote/watch_change.ts | 6 +- packages/firestore/src/util/assert.ts | 60 ++++++++++-- .../firestore/src/util/async_queue_impl.ts | 2 +- .../firestore/src/util/input_validation.ts | 2 +- packages/firestore/src/util/logic_utils.ts | 10 ++ packages/firestore/src/util/sorted_map.ts | 16 +-- .../unit/index/ordered_code_writer.test.ts | 11 ++- .../unit/local/indexeddb_persistence.test.ts | 2 +- .../test/unit/local/simple_db.test.ts | 2 +- .../firestore/test/unit/specs/spec_builder.ts | 14 ++- .../test/unit/specs/spec_test_components.ts | 2 +- .../test/unit/specs/spec_test_runner.ts | 4 +- .../firestore/test/unit/util/assert.test.ts | 90 +++++++++++++++++ .../test/unit/util/async_queue.test.ts | 4 +- packages/firestore/test/util/helpers.ts | 2 + .../firestore/test/util/spec_test_helpers.ts | 7 +- packages/firestore/test/util/test_platform.ts | 2 +- 65 files changed, 406 insertions(+), 200 deletions(-) create mode 100644 packages/firestore/test/unit/util/assert.test.ts diff --git a/packages/firestore/scripts/build-bundle.js b/packages/firestore/scripts/build-bundle.js index 51501eaa313..f8ba283a5a8 100644 --- a/packages/firestore/scripts/build-bundle.js +++ b/packages/firestore/scripts/build-bundle.js @@ -14,4 +14,4 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 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(_)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]0&&t[t.length-1])&&(op[0]===6||op[0]===2)){_=0;continue}if(op[0]===3&&(!t||op[1]>t[0]&&op[1]=0){var method=declaration.name.text;if(method==="debugAssert"){updatedNode=ts.createOmittedExpression()}else if(method==="hardAssert"||method==="fail"){var messageIndex=method==="hardAssert"?1:0;if(node.arguments.length>messageIndex&&node.arguments[messageIndex].kind===ts.SyntaxKind.StringLiteral){var stringLiteral=node.arguments[messageIndex];var errorMessage=RemoveAsserts.trimErrorMessage(stringLiteral.getFullText());var errorCode=RemoveAsserts.errorCode(errorMessage);var errorId=-1;try{errorId=RemoveAsserts.saveErrorCode(errorCode,errorMessage)}catch(e){console.log("Failed to save error code "+JSON.stringify(e))}var newArguments=__spreadArray([],node.arguments,true);newArguments[messageIndex]=ts.factory.createNumericLiteral(errorId);updatedNode=ts.factory.createCallExpression(declaration.name,undefined,newArguments)}else{var newArguments=__spreadArray([],node.arguments,true);newArguments[messageIndex]=ts.factory.createNumericLiteral(-1);updatedNode=ts.createCall(declaration.name,undefined,newArguments)}}}}}if(updatedNode){ts.setSourceMapRange(updatedNode,ts.getSourceMapRange(node));return updatedNode}else{return node}};RemoveAsserts.trimErrorMessage=function(errorMessage){return errorMessage.substring(errorMessage.indexOf("'")+1,errorMessage.lastIndexOf("'"))};RemoveAsserts.errorCode=function(errorMessage){var hash=(0,crypto_1.createHash)("sha256");hash.update(errorMessage);var paramHash=hash.digest("hex").substring(0,7);return paramHash};RemoveAsserts.saveErrorCode=function(errorCode,errorMessage){var errorCodes=RemoveAsserts.getErrorCodes();var existingErrorCode=errorCodes[errorCode];if(existingErrorCode)return existingErrorCode.id;var id=Object.keys(errorCodes).length;errorCodes[errorCode]={message:errorMessage,id:id};RemoveAsserts.saveErrorCodes(errorCodes);return id};RemoveAsserts.getErrorCodes=function(){var path=(0,path_1.join)(module.path,ERROR_CODE_LOCATION);if(!(0,fs_1.existsSync)(path)){return{}}return JSON.parse((0,fs_1.readFileSync)(path,"utf-8"))};RemoveAsserts.saveErrorCodes=function(errorCodes){var path=(0,path_1.join)(module.path,ERROR_CODE_LOCATION);(0,fs_1.writeFileSync)(path,JSON.stringify(errorCodes,undefined,4),{encoding:"utf-8"})};return RemoveAsserts}(); \ No newline at end of file + */Object.defineProperty(exports,"__esModule",{value:true});exports.removeAsserts=removeAsserts;var ts=require("typescript");var ASSERT_LOCATION="packages/firestore/src/util/assert.ts";function removeAsserts(program){var removeAsserts=new RemoveAsserts(program.getTypeChecker());return function(context){return function(file){return removeAsserts.visitNodeAndChildren(file,context)}}}var RemoveAsserts=function(){function RemoveAsserts(typeChecker){this.typeChecker=typeChecker}RemoveAsserts.prototype.visitNodeAndChildren=function(node,context){var _this=this;return ts.visitEachChild(this.visitNode(node),(function(childNode){return _this.visitNodeAndChildren(childNode,context)}),context)};RemoveAsserts.prototype.visitNode=function(node){var updatedNode=null;if(ts.isCallExpression(node)){var signature=this.typeChecker.getResolvedSignature(node);if(signature&&signature.declaration&&signature.declaration.kind===ts.SyntaxKind.FunctionDeclaration){var declaration=signature.declaration;if(declaration&&declaration.getSourceFile().fileName.indexOf(ASSERT_LOCATION)>=0){var method=declaration.name.text;if(method==="debugAssert"){updatedNode=ts.factory.createOmittedExpression()}else if(method==="hardAssert"){updatedNode=ts.factory.createCallExpression(declaration.name,undefined,node.arguments.filter((function(value){return!ts.isStringLiteral(value)})))}else if(method==="fail"){updatedNode=ts.factory.createCallExpression(declaration.name,undefined,node.arguments.filter((function(value){return!ts.isStringLiteral(value)})))}}}}if(updatedNode){ts.setSourceMapRange(updatedNode,ts.getSourceMapRange(node));return updatedNode}else{return node}};return RemoveAsserts}(); \ No newline at end of file diff --git a/packages/firestore/scripts/remove-asserts.ts b/packages/firestore/scripts/remove-asserts.ts index c37a32ab77d..f55907ec152 100644 --- a/packages/firestore/scripts/remove-asserts.ts +++ b/packages/firestore/scripts/remove-asserts.ts @@ -31,8 +31,7 @@ export function removeAsserts( /** * Transformer that removes all "debugAssert" statements from the SDK and - * replaces the custom message for fail() and hardAssert() with shorter - * error codes + * removes the custom message for fail() and hardAssert(). */ class RemoveAsserts { constructor(private readonly typeChecker: ts.TypeChecker) {} @@ -64,43 +63,22 @@ class RemoveAsserts { declaration.getSourceFile().fileName.indexOf(ASSERT_LOCATION) >= 0 ) { const method = declaration.name!.text; - if (method === 'debugAssert') { updatedNode = ts.factory.createOmittedExpression(); - } else if ((method === 'hardAssert') || (method === 'fail')) { - const messageIndex = (method === 'hardAssert') ? 1 : 0; - if ((node.arguments.length > messageIndex) && (node.arguments[messageIndex].kind === ts.SyntaxKind.StringLiteral)) { - const stringLiteral: ts.StringLiteral = node.arguments[messageIndex] as ts.StringLiteral; - const errorMessage = RemoveAsserts.trimErrorMessage(stringLiteral.getFullText()); - const errorCode = RemoveAsserts.errorCode(errorMessage); - - let errorId: number = -1; - try { - errorId = RemoveAsserts.saveErrorCode(errorCode, errorMessage); - } - catch (e) { - console.log('Failed to save error code ' + JSON.stringify(e)); - } - const newArguments = [...node.arguments]; - newArguments[messageIndex] = ts.factory.createNumericLiteral(errorId); - - // Replace the call with the full error message to a - // build with an error code - updatedNode = ts.factory.createCallExpression( - declaration.name!, - /*typeArgs*/ undefined, - newArguments - ); - } else { - const newArguments = [...node.arguments]; - newArguments[messageIndex] = ts.factory.createNumericLiteral(-1); - // Remove the log message but keep the assertion - updatedNode = ts.factory.createCallExpression( - declaration.name!, - /*typeArgs*/ undefined, - newArguments - ); - } + } else if (method === 'hardAssert') { + // Remove the log message but keep the assertion + updatedNode = ts.factory.createCallExpression( + declaration.name!, + /*typeArgs*/ undefined, + node.arguments.filter(value => !ts.isStringLiteral(value)) + ); + } else if (method === 'fail') { + // Remove the log message + updatedNode = ts.factory.createCallExpression( + declaration.name!, + /*typeArgs*/ undefined, + node.arguments.filter(value => !ts.isStringLiteral(value)) + ); } } } @@ -113,48 +91,4 @@ class RemoveAsserts { return node; } } - - static trimErrorMessage(errorMessage: string): string { - return errorMessage.substring( - errorMessage.indexOf("'") + 1, - errorMessage.lastIndexOf("'")); - } - - static errorCode(errorMessage: string): string { - // Create a sha256 hash from the parameter names and types. - const hash = createHash('sha256'); - hash.update(errorMessage); - - // Use the first 7 characters of the hash for a more compact code. - const paramHash = hash.digest('hex').substring(0, 7); - - return paramHash; - } - - - - static saveErrorCode(errorCode: string, errorMessage: string): number { - const errorCodes = RemoveAsserts.getErrorCodes(); - - const existingErrorCode: Error | undefined = errorCodes[errorCode]; - if (existingErrorCode) - {return existingErrorCode.id;} - - const id = Object.keys(errorCodes).length; - errorCodes[errorCode] = { - message: errorMessage, - id - }; - - RemoveAsserts.saveErrorCodes(errorCodes); - - return id; - } - - static getErrorCodes(): Record { - const path = join(module.path, ERROR_CODE_LOCATION); - if (!existsSync(path)){ - return {}; - } - return JSON.parse(readFileSync(path, 'utf-8')); - } +} diff --git a/packages/firestore/scripts/rename-internals.js b/packages/firestore/scripts/rename-internals.js index fa8394a2261..4d5ebf1a838 100644 --- a/packages/firestore/scripts/rename-internals.js +++ b/packages/firestore/scripts/rename-internals.js @@ -14,4 +14,4 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - */exports.__esModule=true;exports.renameInternals=void 0;var ts=require("typescript");var ignoredIdentifiers=["undefined"];var RenameInternals=function(){function RenameInternals(publicApi,prefix){this.publicApi=publicApi;this.prefix=prefix}RenameInternals.prototype.visitNodeAndChildren=function(node,context){var _this=this;return ts.visitEachChild(this.visitNode(node),(function(childNode){return _this.visitNodeAndChildren(childNode,context)}),context)};RenameInternals.prototype.visitNode=function(node){if(ts.isIdentifier(node)){var name_1=node.escapedText.toString();if(!this.publicApi.has(name_1)&&ignoredIdentifiers.indexOf(node.escapedText.toString())===-1){var newIdentifier=ts.factory.createIdentifier(this.prefix+name_1);ts.setSourceMapRange(newIdentifier,ts.getSourceMapRange(node));return newIdentifier}}return node};return RenameInternals}();var DEFAULT_PREFIX="_";function renameInternals(program,config){var _a;var prefix=(_a=config.prefix)!==null&&_a!==void 0?_a:DEFAULT_PREFIX;var renamer=new RenameInternals(config.publicIdentifiers,prefix);return function(context){return function(file){return renamer.visitNodeAndChildren(file,context)}}}exports.renameInternals=renameInternals; \ No newline at end of file + */Object.defineProperty(exports,"__esModule",{value:true});exports.renameInternals=renameInternals;var ts=require("typescript");var ignoredIdentifiers=["undefined"];var RenameInternals=function(){function RenameInternals(publicApi,prefix){this.publicApi=publicApi;this.prefix=prefix}RenameInternals.prototype.visitNodeAndChildren=function(node,context){var _this=this;return ts.visitEachChild(this.visitNode(node),(function(childNode){return _this.visitNodeAndChildren(childNode,context)}),context)};RenameInternals.prototype.visitNode=function(node){if(ts.isIdentifier(node)){var name_1=node.escapedText.toString();if(!this.publicApi.has(name_1)&&ignoredIdentifiers.indexOf(node.escapedText.toString())===-1){var newIdentifier=ts.factory.createIdentifier(this.prefix+name_1);ts.setSourceMapRange(newIdentifier,ts.getSourceMapRange(node));return newIdentifier}}return node};return RenameInternals}();var DEFAULT_PREFIX="_";function renameInternals(program,config){var _a;var prefix=(_a=config.prefix)!==null&&_a!==void 0?_a:DEFAULT_PREFIX;var renamer=new RenameInternals(config.publicIdentifiers,prefix);return function(context){return function(file){return renamer.visitNodeAndChildren(file,context)}}} \ No newline at end of file diff --git a/packages/firestore/scripts/run-tests.js b/packages/firestore/scripts/run-tests.js index 88b9f05ee55..3a2e171b649 100644 --- a/packages/firestore/scripts/run-tests.js +++ b/packages/firestore/scripts/run-tests.js @@ -14,4 +14,4 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - */exports.__esModule=true;var path_1=require("path");var child_process_promise_1=require("child-process-promise");var yargs=require("yargs");var argv=yargs.options({main:{type:"string",demandOption:true},platform:{type:"string",default:"node"},emulator:{type:"boolean"},persistence:{type:"boolean"},databaseId:{type:"string"}}).parseSync();var nyc=(0,path_1.resolve)(__dirname,"../../../node_modules/.bin/nyc");var mocha=(0,path_1.resolve)(__dirname,"../../../node_modules/.bin/mocha");var babel=(0,path_1.resolve)(__dirname,"../babel-register.js");process.env.NO_TS_NODE="true";process.env.TEST_PLATFORM=argv.platform;var args=["--reporter","lcovonly",mocha,"--require",babel,"--require",argv.main,"--config","../../config/mocharc.node.js"];if(argv.emulator){process.env.FIRESTORE_TARGET_BACKEND="emulator"}if(argv.persistence){process.env.USE_MOCK_PERSISTENCE="YES";args.push("--require","test/util/node_persistence.ts")}if(argv.databaseId){process.env.FIRESTORE_TARGET_DB_ID=argv.databaseId}args=args.concat(argv._);var childProcess=(0,child_process_promise_1.spawn)(nyc,args,{stdio:"inherit",cwd:process.cwd()}).childProcess;process.once("exit",(function(){return childProcess.kill()}));process.once("SIGINT",(function(){return childProcess.kill("SIGINT")}));process.once("SIGTERM",(function(){return childProcess.kill("SIGTERM")})); \ No newline at end of file + */Object.defineProperty(exports,"__esModule",{value:true});var path_1=require("path");var child_process_promise_1=require("child-process-promise");var yargs=require("yargs");var argv=yargs.options({main:{type:"string",demandOption:true},platform:{type:"string",default:"node"},emulator:{type:"boolean"},persistence:{type:"boolean"},databaseId:{type:"string"}}).parseSync();var nyc=(0,path_1.resolve)(__dirname,"../../../node_modules/.bin/nyc");var mocha=(0,path_1.resolve)(__dirname,"../../../node_modules/.bin/mocha");var babel=(0,path_1.resolve)(__dirname,"../babel-register.js");process.env.NO_TS_NODE="true";process.env.TEST_PLATFORM=argv.platform;var args=["--reporter","lcovonly",mocha,"--require",babel,"--require",argv.main,"--config","../../config/mocharc.node.js"];if(argv.emulator){process.env.FIRESTORE_TARGET_BACKEND="emulator"}if(argv.persistence){process.env.USE_MOCK_PERSISTENCE="YES";args.push("--require","test/util/node_persistence.ts")}if(argv.databaseId){process.env.FIRESTORE_TARGET_DB_ID=argv.databaseId}args=args.concat(argv._);var childProcess=(0,child_process_promise_1.spawn)(nyc,args,{stdio:"inherit",cwd:process.cwd()}).childProcess;process.once("exit",(function(){return childProcess.kill()}));process.once("SIGINT",(function(){return childProcess.kill("SIGINT")}));process.once("SIGTERM",(function(){return childProcess.kill("SIGTERM")})); \ No newline at end of file diff --git a/packages/firestore/src/api/credentials.ts b/packages/firestore/src/api/credentials.ts index 40a18b5407a..00c9c80676a 100644 --- a/packages/firestore/src/api/credentials.ts +++ b/packages/firestore/src/api/credentials.ts @@ -207,6 +207,7 @@ export class LiteAuthCredentialsProvider implements CredentialsProvider { if (tokenData) { hardAssert( typeof tokenData.accessToken === 'string', + 0xa539b8a7, 'Invalid tokenData returned from getToken()', { tokenData } ); @@ -260,6 +261,7 @@ export class FirebaseAuthCredentialsProvider ): void { hardAssert( this.tokenListener === undefined, + 0xa539b8a6, 'Token listener already added' ); let lastTokenId = this.tokenCounter; @@ -358,6 +360,7 @@ export class FirebaseAuthCredentialsProvider if (tokenData) { hardAssert( typeof tokenData.accessToken === 'string', + 0x7c5d2af1, 'Invalid tokenData returned from getToken()', { tokenData } ); @@ -388,6 +391,7 @@ export class FirebaseAuthCredentialsProvider const currentUid = this.auth && this.auth.getUid(); hardAssert( currentUid === null || typeof currentUid === 'string', + 0x0807a470, 'Received invalid UID', { currentUid } ); @@ -516,6 +520,7 @@ export class FirebaseAppCheckTokenProvider ): void { hardAssert( this.tokenListener === undefined, + 0x0db82ad2, 'Token listener already added' ); @@ -591,6 +596,7 @@ export class FirebaseAppCheckTokenProvider if (tokenResult) { hardAssert( typeof tokenResult.token === 'string', + 0xae0e832b, 'Invalid tokenResult returned from getToken()', { tokenResult } ); @@ -663,6 +669,7 @@ export class LiteAppCheckTokenProvider implements CredentialsProvider { if (tokenResult) { hardAssert( typeof tokenResult.token === 'string', + 0x0d8ea95d, 'Invalid tokenResult returned from getToken()', { tokenResult } ); diff --git a/packages/firestore/src/api/snapshot.ts b/packages/firestore/src/api/snapshot.ts index 088c80cb65d..da23560ba3d 100644 --- a/packages/firestore/src/api/snapshot.ts +++ b/packages/firestore/src/api/snapshot.ts @@ -749,7 +749,7 @@ export function resultChangeType(type: ChangeType): DocumentChangeType { case ChangeType.Removed: return 'removed'; default: - return fail('Unknown change type', { type }); + return fail(0xf03d90cd, 'Unknown change type', { type }); } } diff --git a/packages/firestore/src/core/component_provider.ts b/packages/firestore/src/core/component_provider.ts index 8a63509232c..1d8a7067a23 100644 --- a/packages/firestore/src/core/component_provider.ts +++ b/packages/firestore/src/core/component_provider.ts @@ -197,6 +197,7 @@ export class LruGcMemoryOfflineComponentProvider extends MemoryOfflineComponentP ): Scheduler | null { hardAssert( this.persistence.referenceDelegate instanceof MemoryLruDelegate, + 0xb7434c0f, 'referenceDelegate is expected to be an instance of MemoryLruDelegate.' ); diff --git a/packages/firestore/src/core/filter.ts b/packages/firestore/src/core/filter.ts index 528471c2ca3..49eae42b3b3 100644 --- a/packages/firestore/src/core/filter.ts +++ b/packages/firestore/src/core/filter.ts @@ -168,7 +168,9 @@ export class FieldFilter extends Filter { case Operator.GREATER_THAN_OR_EQUAL: return comparison >= 0; default: - return fail('Unknown FieldFilter operator', { operator: this.op }); + return fail(0xb8a2aab3, 'Unknown FieldFilter operator', { + operator: this.op + }); } } @@ -315,7 +317,7 @@ export function filterEquals(f1: Filter, f2: Filter): boolean { } else if (f1 instanceof CompositeFilter) { return compositeFilterEquals(f1, f2); } else { - fail('Only FieldFilters and CompositeFilters can be compared'); + fail(0x4befcb45, 'Only FieldFilters and CompositeFilters can be compared'); } } diff --git a/packages/firestore/src/core/query.ts b/packages/firestore/src/core/query.ts index 3be80872e1e..72ecf71ce3b 100644 --- a/packages/firestore/src/core/query.ts +++ b/packages/firestore/src/core/query.ts @@ -578,6 +578,6 @@ export function compareDocs( case Direction.DESCENDING: return -1 * comparison; default: - return fail('Unknown direction', { direction: orderBy.dir }); + return fail(0x4d4e3851, 'Unknown direction', { direction: orderBy.dir }); } } diff --git a/packages/firestore/src/core/sync_engine_impl.ts b/packages/firestore/src/core/sync_engine_impl.ts index 34677971c51..ac0fcb8d30f 100644 --- a/packages/firestore/src/core/sync_engine_impl.ts +++ b/packages/firestore/src/core/sync_engine_impl.ts @@ -579,6 +579,7 @@ export async function syncEngineApplyRemoteEvent( targetChange.modifiedDocuments.size + targetChange.removedDocuments.size <= 1, + 0x5858a197, 'Limbo resolution for single document contains multiple changes.' ); if (targetChange.addedDocuments.size > 0) { @@ -586,11 +587,13 @@ export async function syncEngineApplyRemoteEvent( } else if (targetChange.modifiedDocuments.size > 0) { hardAssert( limboResolution.receivedDocument, + 0x390f2c8b, 'Received change for limbo target document without add.' ); } else if (targetChange.removedDocuments.size > 0) { hardAssert( limboResolution.receivedDocument, + 0xa4f3c30e, 'Received remove for limbo target document without add.' ); limboResolution.receivedDocument = false; @@ -994,7 +997,7 @@ function updateTrackedLimbos( removeLimboTarget(syncEngineImpl, limboChange.key); } } else { - fail('Unknown limbo change', { limboChange }); + fail(0x4d4e3850, 'Unknown limbo change', { limboChange }); } } } @@ -1317,7 +1320,7 @@ export async function syncEngineApplyBatchState( batchId ); } else { - fail(`Unknown batchState`, { batchState }); + fail(0x1a4018ac, `Unknown batchState`, { batchState }); } await syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngineImpl, documents); @@ -1560,7 +1563,7 @@ export async function syncEngineApplyTargetState( break; } default: - fail('Unexpected target state', state); + fail(0xfa9b528f, 'Unexpected target state', state); } } } diff --git a/packages/firestore/src/core/transaction.ts b/packages/firestore/src/core/transaction.ts index fce866ca9c0..1c2b7f01955 100644 --- a/packages/firestore/src/core/transaction.ts +++ b/packages/firestore/src/core/transaction.ts @@ -124,7 +124,7 @@ export class Transaction { // Represent a deleted doc using SnapshotVersion.min(). docVersion = SnapshotVersion.min(); } else { - throw fail('Document in a transaction was a ', { + throw fail(0xc542d4e0, 'Document in a transaction was a ', { documentName: doc.constructor.name }); } diff --git a/packages/firestore/src/core/view.ts b/packages/firestore/src/core/view.ts index facdd603b15..27d0ecc8da2 100644 --- a/packages/firestore/src/core/view.ts +++ b/packages/firestore/src/core/view.ts @@ -505,7 +505,7 @@ function compareChangeType(c1: ChangeType, c2: ChangeType): number { case ChangeType.Removed: return 0; default: - return fail('Unknown ChangeType', { change }); + return fail(0x4f35c22a, 'Unknown ChangeType', { change }); } }; diff --git a/packages/firestore/src/core/view_snapshot.ts b/packages/firestore/src/core/view_snapshot.ts index b05a3f98dd5..073ed930da7 100644 --- a/packages/firestore/src/core/view_snapshot.ts +++ b/packages/firestore/src/core/view_snapshot.ts @@ -117,10 +117,14 @@ export class DocumentChangeSet { // Removed->Modified // Metadata->Added // Removed->Metadata - fail('unsupported combination of changes: `change` after `oldChange`', { - change, - oldChange - }); + fail( + 0xf76d356d, + 'unsupported combination of changes: `change` after `oldChange`', + { + change, + oldChange + } + ); } } diff --git a/packages/firestore/src/index/firestore_index_value_writer.ts b/packages/firestore/src/index/firestore_index_value_writer.ts index 98ba6c1d126..5d952dad476 100644 --- a/packages/firestore/src/index/firestore_index_value_writer.ts +++ b/packages/firestore/src/index/firestore_index_value_writer.ts @@ -136,7 +136,7 @@ export class FirestoreIndexValueWriter { this.writeIndexArray(indexValue.arrayValue!, encoder); this.writeTruncationMarker(encoder); } else { - fail('unknown index value type', { indexValue }); + fail(0x4a4e16fa, 'unknown index value type', { indexValue }); } } diff --git a/packages/firestore/src/lite-api/reference_impl.ts b/packages/firestore/src/lite-api/reference_impl.ts index 6876ed0a877..13eeaf044d0 100644 --- a/packages/firestore/src/lite-api/reference_impl.ts +++ b/packages/firestore/src/lite-api/reference_impl.ts @@ -133,7 +133,11 @@ export function getDoc( return invokeBatchGetDocumentsRpc(datastore, [reference._key]).then( result => { - hardAssert(result.length === 1, 'Expected a single document result'); + hardAssert( + result.length === 1, + 0x3d02d4f4, + 'Expected a single document result' + ); const document = result[0]; return new DocumentSnapshot( reference.firestore, diff --git a/packages/firestore/src/lite-api/transaction.ts b/packages/firestore/src/lite-api/transaction.ts index 20a7f65107b..af804bc0eb7 100644 --- a/packages/firestore/src/lite-api/transaction.ts +++ b/packages/firestore/src/lite-api/transaction.ts @@ -94,7 +94,10 @@ export class Transaction { const userDataWriter = new LiteUserDataWriter(this._firestore); return this._transaction.lookup([ref._key]).then(docs => { if (!docs || docs.length !== 1) { - return fail('Mismatch in docs returned from document lookup.'); + return fail( + 0x5de91660, + 'Mismatch in docs returned from document lookup.' + ); } const doc = docs[0]; if (doc.isFoundDocument()) { @@ -114,9 +117,13 @@ export class Transaction { ref.converter ); } else { - throw fail('BatchGetDocumentsRequest returned unexpected document', { - doc - }); + throw fail( + 0x4801d4c1, + 'BatchGetDocumentsRequest returned unexpected document', + { + doc + } + ); } }); } diff --git a/packages/firestore/src/lite-api/user_data_reader.ts b/packages/firestore/src/lite-api/user_data_reader.ts index 5749ca3fc93..3e1b4e6c7ef 100644 --- a/packages/firestore/src/lite-api/user_data_reader.ts +++ b/packages/firestore/src/lite-api/user_data_reader.ts @@ -175,7 +175,9 @@ function isWrite(dataSource: UserDataSource): boolean { case UserDataSource.ArrayArgument: return false; default: - throw fail('Unexpected case for UserDataSource', { dataSource }); + throw fail(0x9c4bc6ee, 'Unexpected case for UserDataSource', { + dataSource + }); } } diff --git a/packages/firestore/src/lite-api/user_data_writer.ts b/packages/firestore/src/lite-api/user_data_writer.ts index cbc1130ccf9..1c082fb9381 100644 --- a/packages/firestore/src/lite-api/user_data_writer.ts +++ b/packages/firestore/src/lite-api/user_data_writer.ts @@ -89,7 +89,7 @@ export abstract class AbstractUserDataWriter { case TypeOrder.VectorValue: return this.convertVectorValue(value.mapValue!); default: - throw fail('Invalid value type', { + throw fail(0xf2a2c883, 'Invalid value type', { value }); } @@ -175,6 +175,7 @@ export abstract class AbstractUserDataWriter { const resourcePath = ResourcePath.fromString(name); hardAssert( isValidResourceName(resourcePath), + 0x25d8ba3b, 'ReferenceValue is not valid', { name } ); diff --git a/packages/firestore/src/local/encoded_resource_path.ts b/packages/firestore/src/local/encoded_resource_path.ts index a720be534f4..e0171e55105 100644 --- a/packages/firestore/src/local/encoded_resource_path.ts +++ b/packages/firestore/src/local/encoded_resource_path.ts @@ -118,10 +118,11 @@ export function decodeResourcePath(path: EncodedResourcePath): ResourcePath { // Event the empty path must encode as a path of at least length 2. A path // with exactly 2 must be the empty path. const length = path.length; - hardAssert(length >= 2, 'Invalid path', { path }); + hardAssert(length >= 2, 0xfb9852e0, 'Invalid path', { path }); if (length === 2) { hardAssert( path.charAt(0) === escapeChar && path.charAt(1) === encodedSeparatorChar, + 0xdb51aee5, 'Non-empty path had length 2', { path } ); @@ -140,7 +141,7 @@ export function decodeResourcePath(path: EncodedResourcePath): ResourcePath { // there must be an end to this segment. const end = path.indexOf(escapeChar, start); if (end < 0 || end > lastReasonableEscapeIndex) { - fail('Invalid encoded resource path', { path }); + fail(0xc553d051, 'Invalid encoded resource path', { path }); } const next = path.charAt(end + 1); @@ -168,7 +169,7 @@ export function decodeResourcePath(path: EncodedResourcePath): ResourcePath { segmentBuilder += path.substring(start, end + 1); break; default: - fail('Invalid encoded resource path', { path }); + fail(0xeeefd395, 'Invalid encoded resource path', { path }); } start = end + 2; diff --git a/packages/firestore/src/local/indexeddb_index_manager.ts b/packages/firestore/src/local/indexeddb_index_manager.ts index 04a380601b3..ab36c6d325c 100644 --- a/packages/firestore/src/local/indexeddb_index_manager.ts +++ b/packages/firestore/src/local/indexeddb_index_manager.ts @@ -1066,7 +1066,7 @@ export class IndexedDbIndexManager implements IndexManager { this.getSubTargets(target), (subTarget: Target) => this.getFieldIndex(transaction, subTarget).next(index => - index ? index : fail('Target cannot be served from index') + index ? index : fail(0xad8a2524, 'Target cannot be served from index') ) ).next(getMinOffsetFromFieldIndexes); } @@ -1118,6 +1118,7 @@ function indexStateStore( function getMinOffsetFromFieldIndexes(fieldIndexes: FieldIndex[]): IndexOffset { hardAssert( fieldIndexes.length !== 0, + 0x709979b6, 'Found empty index group when looking for least recent index offset.' ); diff --git a/packages/firestore/src/local/indexeddb_mutation_batch_impl.ts b/packages/firestore/src/local/indexeddb_mutation_batch_impl.ts index f8c62c76aab..845975ad162 100644 --- a/packages/firestore/src/local/indexeddb_mutation_batch_impl.ts +++ b/packages/firestore/src/local/indexeddb_mutation_batch_impl.ts @@ -64,6 +64,7 @@ export function removeMutationBatch( removePromise.next(() => { hardAssert( numDeleted === 1, + 0xb7deb8ed, 'Dangling document-mutation reference found: Missing batch', { batchId: batch.batchId } ); @@ -100,7 +101,7 @@ export function dbDocumentSize( } else if (doc.noDocument) { value = doc.noDocument; } else { - throw fail('Unknown remote document type'); + throw fail(0x398b459b, 'Unknown remote document type'); } return JSON.stringify(value).length; } diff --git a/packages/firestore/src/local/indexeddb_mutation_queue.ts b/packages/firestore/src/local/indexeddb_mutation_queue.ts index a9a2a5808be..f14e5b71d57 100644 --- a/packages/firestore/src/local/indexeddb_mutation_queue.ts +++ b/packages/firestore/src/local/indexeddb_mutation_queue.ts @@ -105,7 +105,11 @@ export class IndexedDbMutationQueue implements MutationQueue { // In particular, are there any reserved characters? are empty ids allowed? // For the moment store these together in the same mutations table assuming // that empty userIDs aren't allowed. - hardAssert(user.uid !== '', 'UserID must not be an empty string.'); + hardAssert( + user.uid !== '', + 0xfb8340e4, + 'UserID must not be an empty string.' + ); const userId = user.isAuthenticated() ? user.uid! : ''; return new IndexedDbMutationQueue( userId, @@ -154,6 +158,7 @@ export class IndexedDbMutationQueue implements MutationQueue { return mutationStore.add({} as any).next(batchId => { hardAssert( typeof batchId === 'number', + 0xbf7b47b3, 'Auto-generated key is not a number' ); @@ -206,6 +211,7 @@ export class IndexedDbMutationQueue implements MutationQueue { if (dbBatch) { hardAssert( dbBatch.userId === this.userId, + 0x00307f8d, `Unexpected user for mutation batch`, { userId: dbBatch.userId, @@ -261,6 +267,7 @@ export class IndexedDbMutationQueue implements MutationQueue { if (dbBatch.userId === this.userId) { hardAssert( dbBatch.batchId >= nextBatchId, + 0xb9a4d5e0, 'Should have found mutation after `nextBatchId`', { nextBatchId } ); @@ -341,6 +348,7 @@ export class IndexedDbMutationQueue implements MutationQueue { .next(mutation => { if (!mutation) { throw fail( + 0xf028f0ab, 'Dangling document-mutation reference found: `indexKey` which points to `batchId`', { indexKey, @@ -350,6 +358,7 @@ export class IndexedDbMutationQueue implements MutationQueue { } hardAssert( mutation.userId === this.userId, + 0x2907d360, `Unexpected user for mutation batch`, { userId: mutation.userId, @@ -478,6 +487,7 @@ export class IndexedDbMutationQueue implements MutationQueue { .next(mutation => { if (mutation === null) { throw fail( + 0x89caa3d9, 'Dangling document-mutation reference found, which points to `batchId`', { batchId @@ -486,6 +496,7 @@ export class IndexedDbMutationQueue implements MutationQueue { } hardAssert( mutation.userId === this.userId, + 0x2614151a, `Unexpected user for mutation batch`, { userId: mutation.userId, batchId } ); @@ -561,6 +572,7 @@ export class IndexedDbMutationQueue implements MutationQueue { .next(() => { hardAssert( danglingMutationReferences.length === 0, + 0xdd9079e3, 'Document leak -- detected dangling mutation references when queue is empty.', { danglingKeys: danglingMutationReferences.map(p => diff --git a/packages/firestore/src/local/indexeddb_remote_document_cache.ts b/packages/firestore/src/local/indexeddb_remote_document_cache.ts index 9b23c64fcf5..2ee1c354c4a 100644 --- a/packages/firestore/src/local/indexeddb_remote_document_cache.ts +++ b/packages/firestore/src/local/indexeddb_remote_document_cache.ts @@ -381,7 +381,7 @@ class IndexedDbRemoteDocumentCacheImpl implements IndexedDbRemoteDocumentCache { return documentGlobalStore(txn) .get(DbRemoteDocumentGlobalKey) .next(metadata => { - hardAssert(!!metadata, 'Missing document cache metadata'); + hardAssert(!!metadata, 0x4e35b51d, 'Missing document cache metadata'); return metadata!; }); } diff --git a/packages/firestore/src/local/indexeddb_schema_converter.ts b/packages/firestore/src/local/indexeddb_schema_converter.ts index c1720200aaf..55d8cae1f79 100644 --- a/packages/firestore/src/local/indexeddb_schema_converter.ts +++ b/packages/firestore/src/local/indexeddb_schema_converter.ts @@ -326,6 +326,7 @@ export class SchemaConverter implements SimpleDbSchemaConverter { (dbBatch: DbMutationBatch) => { hardAssert( dbBatch.userId === queue.userId, + 0x48daa634, `Cannot process batch from unexpected user`, { batchId: dbBatch.batchId } ); @@ -773,6 +774,6 @@ function extractKey(remoteDoc: DbRemoteDocumentLegacy): DocumentKey { } else if (remoteDoc.unknownDocument) { return DocumentKey.fromSegments(remoteDoc.unknownDocument.path); } else { - return fail('Unexpected DbRemoteDocument'); + return fail(0x8faf1dd8, 'Unexpected DbRemoteDocument'); } } diff --git a/packages/firestore/src/local/indexeddb_sentinels.ts b/packages/firestore/src/local/indexeddb_sentinels.ts index e1e3ead3aa2..30d063fbf75 100644 --- a/packages/firestore/src/local/indexeddb_sentinels.ts +++ b/packages/firestore/src/local/indexeddb_sentinels.ts @@ -450,6 +450,6 @@ export function getObjectStores(schemaVersion: number): string[] { } else if (schemaVersion === 11) { return V11_STORES; } else { - fail('Only schema version 11 and 12 and 13 are supported'); + fail(0xeb555e15, 'Only schema version 11 and 12 and 13 are supported'); } } diff --git a/packages/firestore/src/local/indexeddb_target_cache.ts b/packages/firestore/src/local/indexeddb_target_cache.ts index 9e93cc68838..dbaf3a44f9a 100644 --- a/packages/firestore/src/local/indexeddb_target_cache.ts +++ b/packages/firestore/src/local/indexeddb_target_cache.ts @@ -144,6 +144,7 @@ export class IndexedDbTargetCache implements TargetCache { .next(metadata => { hardAssert( metadata.targetCount > 0, + 0x1f81791d, 'Removing from an empty target cache' ); metadata.targetCount -= 1; @@ -197,7 +198,7 @@ export class IndexedDbTargetCache implements TargetCache { return globalTargetStore(transaction) .get(DbTargetGlobalKey) .next(metadata => { - hardAssert(metadata !== null, 'Missing metadata row.'); + hardAssert(metadata !== null, 0x0b4887a1, 'Missing metadata row.'); return metadata; }); } diff --git a/packages/firestore/src/local/local_serializer.ts b/packages/firestore/src/local/local_serializer.ts index 06eabbc3281..62cfe4ccb98 100644 --- a/packages/firestore/src/local/local_serializer.ts +++ b/packages/firestore/src/local/local_serializer.ts @@ -101,7 +101,7 @@ export function fromDbRemoteDocument( const version = fromDbTimestamp(remoteDoc.unknownDocument.version); doc = MutableDocument.newUnknownDocument(key, version); } else { - return fail('Unexpected DbRemoteDocument'); + return fail(0xdd85b182, 'Unexpected DbRemoteDocument'); } if (remoteDoc.readTime) { @@ -138,7 +138,7 @@ export function toDbRemoteDocument( version: toDbTimestamp(document.version) }; } else { - return fail('Unexpected Document', { document }); + return fail(0xe2301882, 'Unexpected Document', { document }); } return remoteDoc; } diff --git a/packages/firestore/src/local/local_store_impl.ts b/packages/firestore/src/local/local_store_impl.ts index 56f2b96f8d1..f56e79d7622 100644 --- a/packages/firestore/src/local/local_store_impl.ts +++ b/packages/firestore/src/local/local_store_impl.ts @@ -494,7 +494,11 @@ export function localStoreRejectBatch( return localStoreImpl.mutationQueue .lookupMutationBatch(txn, batchId) .next((batch: MutationBatch | null) => { - hardAssert(batch !== null, 'Attempt to reject nonexistent batch!'); + hardAssert( + batch !== null, + 0x90f9f22b, + 'Attempt to reject nonexistent batch!' + ); affectedKeys = batch.keys(); return localStoreImpl.mutationQueue.removeMutationBatch(txn, batch); }) @@ -1137,6 +1141,7 @@ function applyWriteToRemoteDocuments( const ackVersion = batchResult.docVersions.get(docKey); hardAssert( ackVersion !== null, + 0xbd9d3cef, 'ackVersions should contain every doc in the write.' ); if (doc.version.compareTo(ackVersion!) < 0) { diff --git a/packages/firestore/src/local/memory_mutation_queue.ts b/packages/firestore/src/local/memory_mutation_queue.ts index e3902cc96fc..4b9b14769cd 100644 --- a/packages/firestore/src/local/memory_mutation_queue.ts +++ b/packages/firestore/src/local/memory_mutation_queue.ts @@ -246,6 +246,7 @@ export class MemoryMutationQueue implements MutationQueue { const batchIndex = this.indexOfExistingBatchId(batch.batchId, 'removed'); hardAssert( batchIndex === 0, + 0xd6dbcbf2, 'Can only remove the first entry of the mutation queue' ); this.mutationQueue.shift(); diff --git a/packages/firestore/src/local/memory_persistence.ts b/packages/firestore/src/local/memory_persistence.ts index 30d4f2bd19a..fa42602d476 100644 --- a/packages/firestore/src/local/memory_persistence.ts +++ b/packages/firestore/src/local/memory_persistence.ts @@ -235,7 +235,10 @@ export class MemoryEagerDelegate implements MemoryReferenceDelegate { private get orphanedDocuments(): Set { if (!this._orphanedDocuments) { - throw fail('orphanedDocuments is only valid during a transaction.'); + throw fail( + 0xee44dcb1, + 'orphanedDocuments is only valid during a transaction.' + ); } else { return this._orphanedDocuments; } diff --git a/packages/firestore/src/local/memory_remote_document_cache.ts b/packages/firestore/src/local/memory_remote_document_cache.ts index 42a0010d4ac..51c40c5785c 100644 --- a/packages/firestore/src/local/memory_remote_document_cache.ts +++ b/packages/firestore/src/local/memory_remote_document_cache.ts @@ -219,7 +219,7 @@ class MemoryRemoteDocumentCacheImpl implements MemoryRemoteDocumentCache { ): PersistencePromise { // This method should only be called from the IndexBackfiller if persistence // is enabled. - fail('getAllFromCollectionGroup() is not supported.'); + fail(0x251c6adf, 'getAllFromCollectionGroup() is not supported.'); } forEachDocumentKey( diff --git a/packages/firestore/src/local/persistence_promise.ts b/packages/firestore/src/local/persistence_promise.ts index 4678650fa84..7fe0087314a 100644 --- a/packages/firestore/src/local/persistence_promise.ts +++ b/packages/firestore/src/local/persistence_promise.ts @@ -86,7 +86,7 @@ export class PersistencePromise { catchFn?: RejectedHandler ): PersistencePromise { if (this.callbackAttached) { - fail('Called next() or catch() twice for PersistencePromise'); + fail(0xe8303b04, 'Called next() or catch() twice for PersistencePromise'); } this.callbackAttached = true; if (this.isDone) { diff --git a/packages/firestore/src/local/shared_client_state.ts b/packages/firestore/src/local/shared_client_state.ts index ee5de933fa6..c95f9eb6d30 100644 --- a/packages/firestore/src/local/shared_client_state.ts +++ b/packages/firestore/src/local/shared_client_state.ts @@ -1085,6 +1085,7 @@ function fromWebStorageSequenceNumber( const parsed = JSON.parse(seqString); hardAssert( typeof parsed === 'number', + 0x77acb387, 'Found non-numeric sequence number', { seqString } ); diff --git a/packages/firestore/src/model/document.ts b/packages/firestore/src/model/document.ts index 830983aec43..a79f6335077 100644 --- a/packages/firestore/src/model/document.ts +++ b/packages/firestore/src/model/document.ts @@ -397,6 +397,9 @@ export function compareDocumentsByField( if (v1 !== null && v2 !== null) { return valueCompare(v1, v2); } else { - return fail("Trying to compare documents on fields that don't exist"); + return fail( + 0xa786ed58, + "Trying to compare documents on fields that don't exist" + ); } } diff --git a/packages/firestore/src/model/mutation.ts b/packages/firestore/src/model/mutation.ts index 9e9a265b69e..141a9e64b7e 100644 --- a/packages/firestore/src/model/mutation.ts +++ b/packages/firestore/src/model/mutation.ts @@ -623,6 +623,7 @@ function serverTransformResults( const transformResults = new Map(); hardAssert( fieldTransforms.length === serverTransformResults.length, + 0x7f904616, 'server transform result count should match field transform count', { serverTransformResultCount: serverTransformResults.length, diff --git a/packages/firestore/src/model/mutation_batch.ts b/packages/firestore/src/model/mutation_batch.ts index d4780cd7f29..b3df9797d9a 100644 --- a/packages/firestore/src/model/mutation_batch.ts +++ b/packages/firestore/src/model/mutation_batch.ts @@ -219,6 +219,7 @@ export class MutationBatchResult { ): MutationBatchResult { hardAssert( batch.mutations.length === results.length, + 0xe5da1b20, 'Mutations sent must equal results received', { mutationsSent: batch.mutations.length, diff --git a/packages/firestore/src/model/normalize.ts b/packages/firestore/src/model/normalize.ts index 50df6aabf76..069725e583e 100644 --- a/packages/firestore/src/model/normalize.ts +++ b/packages/firestore/src/model/normalize.ts @@ -32,7 +32,11 @@ export function normalizeTimestamp(date: Timestamp): { seconds: number; nanos: number; } { - hardAssert(!!date, 'Cannot normalize null or undefined timestamp.'); + hardAssert( + !!date, + 0x986a9b2d, + 'Cannot normalize null or undefined timestamp.' + ); // The json interface (for the browser) will return an iso timestamp string, // while the proto js library (for node) will return a @@ -44,7 +48,7 @@ export function normalizeTimestamp(date: Timestamp): { // Parse the nanos right out of the string. let nanos = 0; const fraction = ISO_TIMESTAMP_REG_EXP.exec(date); - hardAssert(!!fraction, 'invalid timestamp', { + hardAssert(!!fraction, 0xb5de9271, 'invalid timestamp', { timestamp: date }); if (fraction[1]) { diff --git a/packages/firestore/src/model/path.ts b/packages/firestore/src/model/path.ts index 5ed672438f1..474077b4391 100644 --- a/packages/firestore/src/model/path.ts +++ b/packages/firestore/src/model/path.ts @@ -35,13 +35,19 @@ abstract class BasePath> { if (offset === undefined) { offset = 0; } else if (offset > segments.length) { - fail('offset out of range', { offset, range: segments.length }); + fail(0x027dee8b, 'offset out of range', { + offset, + range: segments.length + }); } if (length === undefined) { length = segments.length - offset; } else if (length > segments.length - offset) { - fail('length out of range', { length, range: segments.length - offset }); + fail(0x06d25826, 'length out of range', { + length, + range: segments.length - offset + }); } this.segments = segments; this.offset = offset; diff --git a/packages/firestore/src/model/target_index_matcher.ts b/packages/firestore/src/model/target_index_matcher.ts index df80ffa419a..5582af1eb67 100644 --- a/packages/firestore/src/model/target_index_matcher.ts +++ b/packages/firestore/src/model/target_index_matcher.ts @@ -111,6 +111,7 @@ export class TargetIndexMatcher { servedByIndex(index: FieldIndex): boolean { hardAssert( index.collectionGroup === this.collectionId, + 0xc07f69ec, 'Collection IDs do not match' ); diff --git a/packages/firestore/src/model/values.ts b/packages/firestore/src/model/values.ts index ca989d9f7e9..0be91faba91 100644 --- a/packages/firestore/src/model/values.ts +++ b/packages/firestore/src/model/values.ts @@ -93,7 +93,7 @@ export function typeOrder(value: Value): TypeOrder { } return TypeOrder.ObjectValue; } else { - return fail('Invalid value type', { value }); + return fail(0x6e876576, 'Invalid value type', { value }); } } @@ -140,7 +140,7 @@ export function valueEquals(left: Value, right: Value): boolean { case TypeOrder.MaxValue: return true; default: - return fail('Unexpected value type', { left }); + return fail(0xcbf802b1, 'Unexpected value type', { left }); } } @@ -269,7 +269,7 @@ export function valueCompare(left: Value, right: Value): number { case TypeOrder.ObjectValue: return compareMaps(left.mapValue!, right.mapValue!); default: - throw fail('Invalid value type', { leftType }); + throw fail(0x5ae0c79a, 'Invalid value type', { leftType }); } } @@ -449,7 +449,7 @@ function canonifyValue(value: Value): string { } else if ('mapValue' in value) { return canonifyMap(value.mapValue!); } else { - return fail('Invalid value type', { value }); + return fail(0xee4d4cc0, 'Invalid value type', { value }); } } @@ -541,7 +541,7 @@ export function estimateByteSize(value: Value): number { case TypeOrder.ObjectValue: return estimateMapByteSize(value.mapValue!); default: - throw fail('Invalid value type', { value }); + throw fail(0x34ae10e9, 'Invalid value type', { value }); } } @@ -701,7 +701,7 @@ export function valuesGetLowerBound(value: Value): Value { } return { mapValue: {} }; } else { - return fail('Invalid value type', { value }); + return fail(0x8c66eca4, 'Invalid value type', { value }); } } @@ -731,7 +731,7 @@ export function valuesGetUpperBound(value: Value): Value { } return MAX_VALUE; } else { - return fail('Invalid value type', { value }); + return fail(0xf207a8af, 'Invalid value type', { value }); } } diff --git a/packages/firestore/src/platform/browser/webchannel_connection.ts b/packages/firestore/src/platform/browser/webchannel_connection.ts index b95cac92ef9..15ed2b1ea91 100644 --- a/packages/firestore/src/platform/browser/webchannel_connection.ts +++ b/packages/firestore/src/platform/browser/webchannel_connection.ts @@ -142,6 +142,7 @@ export class WebChannelConnection extends RestConnection { break; default: fail( + 0x235fa5fa, 'RPC failed with unanticipated webchannel error. Giving up.', { rpcName, @@ -352,7 +353,11 @@ export class WebChannelConnection extends RestConnection { msg => { if (!closed) { const msgData = msg.data[0]; - hardAssert(!!msgData, 'Got a webchannel message without data.'); + hardAssert( + !!msgData, + 0x3fddb347, + 'Got a webchannel message without data.' + ); // TODO(b/35143891): There is a bug in One Platform that caused errors // (and only errors) to be wrapped in an extra array. To be forward // compatible with the bug we need to check either condition. The latter diff --git a/packages/firestore/src/platform/node/grpc_connection.ts b/packages/firestore/src/platform/node/grpc_connection.ts index dec3137af76..fbdff0a70e4 100644 --- a/packages/firestore/src/platform/node/grpc_connection.ts +++ b/packages/firestore/src/platform/node/grpc_connection.ts @@ -48,6 +48,7 @@ function createMetadata( ): grpc.Metadata { hardAssert( authToken === null || authToken.type === 'OAuth', + 0x90486aa6, 'If provided, token must be OAuth' ); const metadata = new grpc.Metadata(); diff --git a/packages/firestore/src/remote/datastore.ts b/packages/firestore/src/remote/datastore.ts index 1e11a254679..f9fbf7636df 100644 --- a/packages/firestore/src/remote/datastore.ts +++ b/packages/firestore/src/remote/datastore.ts @@ -228,7 +228,12 @@ export async function invokeBatchGetDocumentsRpc( const result: Document[] = []; keys.forEach(key => { const doc = docs.get(key.toString()); - hardAssert(!!doc, 'Missing entity in write response for `key`', { key }); + hardAssert( + !!doc, + 0xd7c263c4, + 'Missing entity in write response for `key`', + { key } + ); result.push(doc); }); return result; @@ -290,6 +295,7 @@ export async function invokeRunAggregationQueryRpc( hardAssert( filteredResult.length === 1, + 0xfcd7682f, 'Aggregation fields are missing from result.' ); debugAssert( diff --git a/packages/firestore/src/remote/persistent_stream.ts b/packages/firestore/src/remote/persistent_stream.ts index f9f64bec7f6..685de537212 100644 --- a/packages/firestore/src/remote/persistent_stream.ts +++ b/packages/firestore/src/remote/persistent_stream.ts @@ -809,6 +809,7 @@ export class PersistentWriteStream extends PersistentStream< // Always capture the last stream token. hardAssert( !!responseProto.streamToken, + 0x7a5ae995, 'Got a write handshake response without a stream token' ); this.lastStreamToken = responseProto.streamToken; @@ -816,6 +817,7 @@ export class PersistentWriteStream extends PersistentStream< // The first response is always the handshake response hardAssert( !responseProto.writeResults || responseProto.writeResults.length === 0, + 0xda089d3f, 'Got mutation results for handshake' ); return this.listener!.onHandshakeComplete(); @@ -825,6 +827,7 @@ export class PersistentWriteStream extends PersistentStream< // Always capture the last stream token. hardAssert( !!responseProto.streamToken, + 0x31869253, 'Got a write response without a stream token' ); this.lastStreamToken = responseProto.streamToken; diff --git a/packages/firestore/src/remote/rpc_error.ts b/packages/firestore/src/remote/rpc_error.ts index 1ae2aa28a26..8185b17ec2c 100644 --- a/packages/firestore/src/remote/rpc_error.ts +++ b/packages/firestore/src/remote/rpc_error.ts @@ -58,7 +58,7 @@ enum RpcCode { export function isPermanentError(code: Code): boolean { switch (code) { case Code.OK: - return fail('Treated status OK as error'); + return fail(0xfdaa5013, 'Treated status OK as error'); case Code.CANCELLED: case Code.UNKNOWN: case Code.DEADLINE_EXCEEDED: @@ -83,7 +83,7 @@ export function isPermanentError(code: Code): boolean { case Code.DATA_LOSS: return true; default: - return fail('Unknown status code', { code }); + return fail(0x3c6b8863, 'Unknown status code', { code }); } } @@ -171,7 +171,7 @@ export function mapCodeFromRpcCode(code: number | undefined): Code { case RpcCode.DATA_LOSS: return Code.DATA_LOSS; default: - return fail('Unknown status code', { code }); + return fail(0x999b10f0, 'Unknown status code', { code }); } } @@ -220,7 +220,7 @@ export function mapRpcCodeFromCode(code: Code | undefined): number { case Code.DATA_LOSS: return RpcCode.DATA_LOSS; default: - return fail('Unknown status code', { code }); + return fail(0x3019a484, 'Unknown status code', { code }); } } diff --git a/packages/firestore/src/remote/serializer.ts b/packages/firestore/src/remote/serializer.ts index 3892357ff1d..5a7f03f468a 100644 --- a/packages/firestore/src/remote/serializer.ts +++ b/packages/firestore/src/remote/serializer.ts @@ -257,6 +257,7 @@ export function fromBytes( if (serializer.useProto3Json) { hardAssert( value === undefined || typeof value === 'string', + 0xe30bd3f9, 'value must be undefined or a string when using proto3 Json' ); return ByteString.fromBase64String(value ? value : ''); @@ -269,6 +270,7 @@ export function fromBytes( // does not indicate that it extends Uint8Array. value instanceof Buffer || value instanceof Uint8Array, + 0x3f413022, 'value must be undefined, Buffer, or Uint8Array' ); return ByteString.fromUint8Array(value ? value : new Uint8Array()); @@ -283,7 +285,11 @@ export function toVersion( } export function fromVersion(version: ProtoTimestamp): SnapshotVersion { - hardAssert(!!version, "Trying to deserialize version that isn't set"); + hardAssert( + !!version, + 0xc050aaa5, + "Trying to deserialize version that isn't set" + ); return SnapshotVersion.fromTimestamp(fromTimestamp(version)); } @@ -306,6 +312,7 @@ function fromResourceName(name: string): ResourcePath { const resource = ResourcePath.fromString(name); hardAssert( isValidResourceName(resource), + 0x27ce4509, 'Tried to deserialize invalid key', { key: resource.toString() } ); @@ -390,6 +397,7 @@ function extractLocalPathFromResourceName( ): ResourcePath { hardAssert( resourceName.length > 4 && resourceName.get(4) === 'documents', + 0x71a31a15, 'tried to deserialize invalid key', { key: resourceName.toString() } ); @@ -456,6 +464,7 @@ function fromFound( ): MutableDocument { hardAssert( !!doc.found, + 0xaa33452b, 'Tried to deserialize a found document from a missing document.' ); assertPresent(doc.found.name, 'doc.found.name'); @@ -475,10 +484,12 @@ function fromMissing( ): MutableDocument { hardAssert( !!result.missing, + 0x0f36533f, 'Tried to deserialize a missing document from a found document.' ); hardAssert( !!result.readTime, + 0x5995dd5b, 'Tried to deserialize a missing document without a read time.' ); const key = fromName(serializer, result.missing); @@ -495,7 +506,7 @@ export function fromBatchGetDocumentsResponse( } else if ('missing' in result) { return fromMissing(serializer, result); } - return fail('invalid batch get response', { result }); + return fail(0x1c42f830, 'invalid batch get response', { result }); } export function fromWatchChange( @@ -580,7 +591,7 @@ export function fromWatchChange( const targetId = filter.targetId; watchChange = new ExistenceFilterChange(targetId, existenceFilter); } else { - return fail('Unknown change type', { change }); + return fail(0x2d517680, 'Unknown change type', { change }); } return watchChange; } @@ -599,7 +610,7 @@ function fromWatchTargetChangeState( } else if (state === 'RESET') { return WatchTargetChangeState.Reset; } else { - return fail('Got unexpected TargetChange.state', { state }); + return fail(0x9991fe4b, 'Got unexpected TargetChange.state', { state }); } } @@ -643,7 +654,9 @@ export function toMutation( verify: toName(serializer, mutation.key) }; } else { - return fail('Unknown mutation type', { mutationType: mutation.type }); + return fail(0x40d7d046, 'Unknown mutation type', { + mutationType: mutation.type + }); } if (mutation.fieldTransforms.length > 0) { @@ -699,7 +712,7 @@ export function fromMutation( const key = fromName(serializer, proto.verify); return new VerifyMutation(key, precondition); } else { - return fail('unknown mutation proto', { proto }); + return fail(0x05b7ce18, 'unknown mutation proto', { proto }); } } @@ -715,7 +728,7 @@ function toPrecondition( } else if (precondition.exists !== undefined) { return { exists: precondition.exists }; } else { - return fail('Unknown precondition'); + return fail(0x6b69d5ca, 'Unknown precondition'); } } @@ -757,6 +770,7 @@ export function fromWriteResults( if (protos && protos.length > 0) { hardAssert( commitTime !== undefined, + 0x38116ed6, 'Received a write result without a commit time' ); return protos.map(proto => fromWriteResult(proto, commitTime)); @@ -795,7 +809,9 @@ function toFieldTransform( increment: transform.operand }; } else { - throw fail('Unknown transform', { transform: fieldTransform.transform }); + throw fail(0x51c28e85, 'Unknown transform', { + transform: fieldTransform.transform + }); } } @@ -807,6 +823,7 @@ function fromFieldTransform( if ('setToServerValue' in proto) { hardAssert( proto.setToServerValue === 'REQUEST_TIME', + 0x40f61500, 'Unknown server value transform proto', { proto } ); @@ -823,7 +840,7 @@ function fromFieldTransform( proto.increment! ); } else { - fail('Unknown transform proto', { proto }); + fail(0x40c8ed17, 'Unknown transform proto', { proto }); } const fieldPath = FieldPath.fromServerFormat(proto.fieldPath!); return new FieldTransform(fieldPath, transform!); @@ -840,9 +857,14 @@ export function fromDocumentsTarget( documentsTarget: ProtoDocumentsTarget ): Target { const count = documentsTarget.documents!.length; - hardAssert(count === 1, 'DocumentsTarget contained other than 1 document', { - count - }); + hardAssert( + count === 1, + 0x07ae6b67, + 'DocumentsTarget contained other than 1 document', + { + count + } + ); const name = documentsTarget.documents![0]; return queryToTarget(newQueryForPath(fromQueryPath(name))); } @@ -971,6 +993,7 @@ export function convertQueryTargetToQuery(target: ProtoQueryTarget): Query { if (fromCount > 0) { hardAssert( fromCount === 1, + 0xfe266d2c, 'StructuredQuery.from with more than one collection is not supported.' ); const from = query.from![0]; @@ -1047,7 +1070,7 @@ export function toLabel(purpose: TargetPurpose): string | null { case TargetPurpose.LimboResolution: return 'limbo-document'; default: - return fail('Unrecognized query purpose', { purpose }); + return fail(0x713b26a6, 'Unrecognized query purpose', { purpose }); } } @@ -1118,7 +1141,7 @@ function fromFilter(filter: ProtoFilter): Filter { } else if (filter.compositeFilter !== undefined) { return fromCompositeFilter(filter); } else { - return fail('Unknown filter', { filter }); + return fail(0x759192f9, 'Unknown filter', { filter }); } } @@ -1212,9 +1235,9 @@ export function fromOperatorName(op: ProtoFieldFilterOp): Operator { case 'ARRAY_CONTAINS_ANY': return Operator.ARRAY_CONTAINS_ANY; case 'OPERATOR_UNSPECIFIED': - return fail('Unspecified operator'); + return fail(0xe2fede39, 'Unspecified operator'); default: - return fail('Unknown operator'); + return fail(0xc54ab3b3, 'Unknown operator'); } } @@ -1227,7 +1250,7 @@ export function fromCompositeOperatorName( case 'OR': return CompositeOperator.OR; default: - return fail('Unknown operator'); + return fail(0x040233ad, 'Unknown operator'); } } @@ -1263,7 +1286,7 @@ export function toFilter(filter: Filter): ProtoFilter { } else if (filter instanceof CompositeFilter) { return toCompositeFilter(filter); } else { - return fail('Unrecognized filter type', { filter }); + return fail(0xd65d3916, 'Unrecognized filter type', { filter }); } } @@ -1348,9 +1371,9 @@ export function fromUnaryFilter(filter: ProtoFilter): Filter { nullValue: 'NULL_VALUE' }); case 'OPERATOR_UNSPECIFIED': - return fail('Unspecified filter'); + return fail(0xef814a9b, 'Unspecified filter'); default: - return fail('Unknown filter'); + return fail(0xed365cf0, 'Unknown filter'); } } diff --git a/packages/firestore/src/remote/watch_change.ts b/packages/firestore/src/remote/watch_change.ts index c042a94fbf5..4b5e8d3756d 100644 --- a/packages/firestore/src/remote/watch_change.ts +++ b/packages/firestore/src/remote/watch_change.ts @@ -203,7 +203,7 @@ class TargetState { removedDocuments = removedDocuments.add(key); break; default: - fail('Encountered invalid change type', { changeType }); + fail(0x9481155b, 'Encountered invalid change type', { changeType }); } }); @@ -242,6 +242,7 @@ class TargetState { this.pendingResponses -= 1; hardAssert( this.pendingResponses >= 0, + 0x0ca9c7ff, '`pendingResponses` is less than 0. This indicates that the SDK received more target acks from the server than expected. The SDK should not continue to operate.', { pendingResponses: this.pendingResponses } ); @@ -376,7 +377,7 @@ export class WatchChangeAggregator { } break; default: - fail('Unknown target watch change state', { + fail(0xddd63a72, 'Unknown target watch change state', { state: targetChange.state }); } @@ -432,6 +433,7 @@ export class WatchChangeAggregator { } else { hardAssert( expectedCount === 1, + 0x4e2c7755, 'Single document existence filter with count', { expectedCount } ); diff --git a/packages/firestore/src/util/assert.ts b/packages/firestore/src/util/assert.ts index 293dfc5417a..a7b24be42ac 100644 --- a/packages/firestore/src/util/assert.ts +++ b/packages/firestore/src/util/assert.ts @@ -28,16 +28,37 @@ import { logError } from './log'; * let futureVar = fail('not implemented yet'); */ export function fail( - failure: string | number, - context: unknown = undefined + code: number, + message: string, + context?: Record +): never; + +export function fail(code: number, context?: Record): never; + +export function fail( + code: number, + messageOrContext?: string | Record, + context?: Record ): never { - _fail(failure, context); + let message = 'Unexpected state'; + if (typeof messageOrContext === 'string') { + message = messageOrContext; + } else { + context = messageOrContext; + } + _fail(code, message, context); } -function _fail(failure: string | number, context: unknown = undefined): never { +function _fail( + code: number, + failure: string, + context?: Record +): never { // Log the failure in addition to throw an exception, just in case the // exception is swallowed. - let message = `FIRESTORE (${SDK_VERSION}) INTERNAL ASSERTION FAILED: ${failure}`; + let message = `FIRESTORE (${SDK_VERSION}) INTERNAL ASSERTION FAILED: ${failure} (CODE: ${code.toString( + 16 + )})`; if (context !== undefined) { try { const stringContext = JSON.stringify(context); @@ -62,11 +83,32 @@ function _fail(failure: string | number, context: unknown = undefined): never { */ export function hardAssert( assertion: boolean, - message: string | number, - context?: unknown + code: number, + message: string, + context?: Record +): asserts assertion; + +export function hardAssert( + assertion: boolean, + code: number, + context?: Record +): asserts assertion; + +export function hardAssert( + assertion: boolean, + code: number, + messageOrContext?: string | Record, + context?: Record ): asserts assertion { + let message = 'Unexpected state'; + if (typeof messageOrContext === 'string') { + message = messageOrContext; + } else { + context = messageOrContext; + } + if (!assertion) { - _fail(message, context); + _fail(code, message, context); } } @@ -85,7 +127,7 @@ export function debugAssert( message: string ): asserts assertion { if (!assertion) { - fail(message); + fail(0xdeb06a55e7, message); } } diff --git a/packages/firestore/src/util/async_queue_impl.ts b/packages/firestore/src/util/async_queue_impl.ts index f1dcb391dc2..ef365ee22a4 100644 --- a/packages/firestore/src/util/async_queue_impl.ts +++ b/packages/firestore/src/util/async_queue_impl.ts @@ -236,7 +236,7 @@ export class AsyncQueueImpl implements AsyncQueue { private verifyNotFailed(): void { if (this.failure) { - fail('AsyncQueue is already failed', { + fail(0xb815f382, 'AsyncQueue is already failed', { messageOrStack: getMessageOrStack(this.failure) }); } diff --git a/packages/firestore/src/util/input_validation.ts b/packages/firestore/src/util/input_validation.ts index e8c45ae3ec6..8dd49323c2e 100644 --- a/packages/firestore/src/util/input_validation.ts +++ b/packages/firestore/src/util/input_validation.ts @@ -128,7 +128,7 @@ export function valueDescription(input: unknown): string { } else if (typeof input === 'function') { return 'a function'; } else { - return fail('Unknown wrong type', { type: typeof input }); + return fail(0x30299470, 'Unknown wrong type', { type: typeof input }); } } diff --git a/packages/firestore/src/util/logic_utils.ts b/packages/firestore/src/util/logic_utils.ts index 3c3a6b19fd8..87e060585cd 100644 --- a/packages/firestore/src/util/logic_utils.ts +++ b/packages/firestore/src/util/logic_utils.ts @@ -44,6 +44,7 @@ import { hardAssert } from './assert'; export function computeInExpansion(filter: Filter): Filter { hardAssert( filter instanceof FieldFilter || filter instanceof CompositeFilter, + 0x4e2c7756, 'Only field filters and composite filters are accepted.' ); @@ -90,6 +91,7 @@ export function getDnfTerms(filter: CompositeFilter): Filter[] { hardAssert( isDisjunctiveNormalForm(result), + 0x1cdf2f11, 'computeDistributedNormalForm did not result in disjunctive normal form' ); @@ -157,6 +159,7 @@ function isDisjunctionOfFieldFiltersAndFlatConjunctions( export function computeDistributedNormalForm(filter: Filter): Filter { hardAssert( filter instanceof FieldFilter || filter instanceof CompositeFilter, + 0x84e214a9, 'Only field filters and composite filters are accepted.' ); @@ -182,14 +185,17 @@ export function computeDistributedNormalForm(filter: Filter): Filter { hardAssert( newFilter instanceof CompositeFilter, + 0xfbf20f0d, 'field filters are already in DNF form' ); hardAssert( compositeFilterIsConjunction(newFilter), + 0x9d3b3889, 'Disjunction of filters all of which are already in DNF form is itself in DNF form.' ); hardAssert( newFilter.filters.length > 1, + 0xe2477a92, 'Single-filter composite filters are already in DNF form.' ); @@ -201,10 +207,12 @@ export function computeDistributedNormalForm(filter: Filter): Filter { export function applyDistribution(lhs: Filter, rhs: Filter): Filter { hardAssert( lhs instanceof FieldFilter || lhs instanceof CompositeFilter, + 0x95f42f24, 'Only field filters and composite filters are accepted.' ); hardAssert( rhs instanceof FieldFilter || rhs instanceof CompositeFilter, + 0x6381d1d6, 'Only field filters and composite filters are accepted.' ); @@ -245,6 +253,7 @@ function applyDistributionCompositeFilters( ): Filter { hardAssert( lhs.filters.length > 0 && rhs.filters.length > 0, + 0xbb85c0b2, 'Found an empty composite filter' ); @@ -306,6 +315,7 @@ function applyDistributionFieldAndCompositeFilters( export function applyAssociation(filter: Filter): Filter { hardAssert( filter instanceof FieldFilter || filter instanceof CompositeFilter, + 0x2e4a312d, 'Only field filters and composite filters are accepted.' ); diff --git a/packages/firestore/src/util/sorted_map.ts b/packages/firestore/src/util/sorted_map.ts index 53bbe36b22e..0e8ea88f150 100644 --- a/packages/firestore/src/util/sorted_map.ts +++ b/packages/firestore/src/util/sorted_map.ts @@ -511,20 +511,20 @@ export class LLRBNode { // leaves is equal on both sides. This function verifies that or asserts. protected check(): number { if (this.isRed() && this.left.isRed()) { - throw fail('Red node has red child', { + throw fail(0xaad28107, 'Red node has red child', { key: this.key, value: this.value }); } if (this.right.isRed()) { - throw fail('Right child of (`key`, `value`) is red', { + throw fail(0x37210df9, 'Right child of (`key`, `value`) is red', { key: this.key, value: this.value }); } const blackDepth = (this.left as LLRBNode).check(); if (blackDepth !== (this.right as LLRBNode).check()) { - throw fail('Black depths differ'); + throw fail(0x6d2d0edf, 'Black depths differ'); } else { return blackDepth + (this.isRed() ? 0 : 1); } @@ -534,19 +534,19 @@ export class LLRBNode { // Represents an empty node (a leaf node in the Red-Black Tree). export class LLRBEmptyNode { get key(): never { - throw fail('LLRBEmptyNode has no key.'); + throw fail(0xe1a6d03e, 'LLRBEmptyNode has no key.'); } get value(): never { - throw fail('LLRBEmptyNode has no value.'); + throw fail(0x3f0d8793, 'LLRBEmptyNode has no value.'); } get color(): never { - throw fail('LLRBEmptyNode has no color.'); + throw fail(0x41575ede, 'LLRBEmptyNode has no color.'); } get left(): never { - throw fail('LLRBEmptyNode has no left child.'); + throw fail(0x741e2acf, 'LLRBEmptyNode has no left child.'); } get right(): never { - throw fail('LLRBEmptyNode has no right child.'); + throw fail(0x901e9907, 'LLRBEmptyNode has no right child.'); } size = 0; diff --git a/packages/firestore/test/unit/index/ordered_code_writer.test.ts b/packages/firestore/test/unit/index/ordered_code_writer.test.ts index d2393c66120..37f26befc00 100644 --- a/packages/firestore/test/unit/index/ordered_code_writer.test.ts +++ b/packages/firestore/test/unit/index/ordered_code_writer.test.ts @@ -248,9 +248,14 @@ function getBytes(val: unknown): { asc: Uint8Array; desc: Uint8Array } { ascWriter.writeUtf8Ascending(val); descWriter.writeUtf8Descending(val); } else { - hardAssert(val instanceof Uint8Array, 'val is not instance of Uint8Array', { - val - }); + hardAssert( + val instanceof Uint8Array, + 0xa10f7758, + 'val is not instance of Uint8Array', + { + val + } + ); ascWriter.writeBytesAscending(ByteString.fromUint8Array(val)); descWriter.writeBytesDescending(ByteString.fromUint8Array(val)); } diff --git a/packages/firestore/test/unit/local/indexeddb_persistence.test.ts b/packages/firestore/test/unit/local/indexeddb_persistence.test.ts index e44bb73e47b..e0a04b21d70 100644 --- a/packages/firestore/test/unit/local/indexeddb_persistence.test.ts +++ b/packages/firestore/test/unit/local/indexeddb_persistence.test.ts @@ -1609,6 +1609,6 @@ function toLegacyDbRemoteDocument( parentPath }; } else { - return fail('Unexpected Document ' + document); + return fail(0x6bb7d8aa, 'Unexpected Document ', { document }); } } diff --git a/packages/firestore/test/unit/local/simple_db.test.ts b/packages/firestore/test/unit/local/simple_db.test.ts index b2b7ed3f95a..ca1f2f911d4 100644 --- a/packages/firestore/test/unit/local/simple_db.test.ts +++ b/packages/firestore/test/unit/local/simple_db.test.ts @@ -363,7 +363,7 @@ describe('SimpleDb', () => { iterated.push(value); return PersistencePromise.reject(new Error('Expected error')); }) - .next(() => fail('Promise not rejected')) + .next(() => fail(0xb9b32f7f, 'Promise not rejected')) .catch(err => { expect(err.message).to.eq('Expected error'); expect(iterated).to.deep.equal([testData[0]]); diff --git a/packages/firestore/test/unit/specs/spec_builder.ts b/packages/firestore/test/unit/specs/spec_builder.ts index 80dcd6519de..5b5ae4e7af4 100644 --- a/packages/firestore/test/unit/specs/spec_builder.ts +++ b/packages/firestore/test/unit/specs/spec_builder.ts @@ -674,7 +674,7 @@ export class SpecBuilder { } else if (doc.isNoDocument()) { // Don't send any updates } else { - fail('Unknown parameter: ' + doc); + fail(0xd71cf527, 'Unknown parameter', { doc }); } this.watchCurrents(query, 'resume-token-' + version); this.watchSnapshots(version); @@ -1149,7 +1149,7 @@ export class SpecBuilder { userDataWriter.convertValue(filter.value) ] as SpecQueryFilter; } else { - return fail('Unknown filter: ' + filter); + return fail(0x0e51d502, 'Unknown filter', { filter }); } }); } @@ -1210,7 +1210,10 @@ export class SpecBuilder { targetPurpose?: TargetPurpose ): void { if (!(resume?.resumeToken || resume?.readTime) && resume?.expectedCount) { - fail('Expected count is present without a resume token or read time.'); + fail( + 0xc9a16b57, + 'Expected count is present without a resume token or read time.' + ); } if (this.activeTargets[targetId]) { @@ -1273,7 +1276,10 @@ export class SpecBuilder { if (queryTargetId && limboTargetId) { // TODO(dimond): add support for query for doc and limbo doc at the same // time? - fail('Found both query and limbo doc with target ID, not supported yet'); + fail( + 0x6e178880, + 'Found both query and limbo doc with target ID, not supported yet' + ); } const targetId = queryTargetId || limboTargetId; debugAssert( diff --git a/packages/firestore/test/unit/specs/spec_test_components.ts b/packages/firestore/test/unit/specs/spec_test_components.ts index 2a2e480de63..01e0fb30662 100644 --- a/packages/firestore/test/unit/specs/spec_test_components.ts +++ b/packages/firestore/test/unit/specs/spec_test_components.ts @@ -415,7 +415,7 @@ export class MockConnection implements Connection { } else if (request.removeTarget) { delete this.activeTargets[request.removeTarget]; } else { - fail('Invalid listen request'); + fail(0x782d0a75, 'Invalid listen request'); } }, closeFn: () => { diff --git a/packages/firestore/test/unit/specs/spec_test_runner.ts b/packages/firestore/test/unit/specs/spec_test_runner.ts index b34421d9e0a..bc625ca5bf0 100644 --- a/packages/firestore/test/unit/specs/spec_test_runner.ts +++ b/packages/firestore/test/unit/specs/spec_test_runner.ts @@ -477,7 +477,7 @@ abstract class TestRunner { ? this.doFailDatabase(step.failDatabase!) : this.doRecoverDatabase(); } else { - return fail('Unknown step: ' + JSON.stringify(step)); + return fail(0x6bb33efa, 'Unknown step: ' + JSON.stringify(step)); } } @@ -724,7 +724,7 @@ abstract class TestRunner { ); return this.doWatchEvent(change); } else { - return fail('Either doc or docs must be set'); + return fail(0xdcc33300, 'Either doc or docs must be set'); } } diff --git a/packages/firestore/test/unit/util/assert.test.ts b/packages/firestore/test/unit/util/assert.test.ts new file mode 100644 index 00000000000..a640433d78e --- /dev/null +++ b/packages/firestore/test/unit/util/assert.test.ts @@ -0,0 +1,90 @@ +/** + * @license + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expect } from 'chai'; + +import { fail, hardAssert } from '../../../src/util/assert'; + +describe('hardAssert', () => { + it('includes the error code as hex', () => { + expect(() => hardAssert(false, 0x12345678, 'a message here')).to.throw( + '12345678' + ); + }); + + it('includes the context', () => { + expect(() => + hardAssert(false, 0x12345678, 'a message here', { foo: 'bar baz' }) + ).to.throw('bar baz'); + }); + + it('includes the message', () => { + expect(() => + hardAssert(false, 0x12345678, 'a message here', { foo: 'bar baz' }) + ).to.throw('a message here'); + }); + + describe('without message', () => { + it('includes the error code as hex', () => { + expect(() => hardAssert(false, 0x12345678)).to.throw('12345678'); + }); + + it('includes the context', () => { + expect(() => hardAssert(false, 0x12345678, { foo: 'bar baz' })).to.throw( + 'bar baz' + ); + }); + it('includes a default message', () => { + expect(() => hardAssert(false, 0x12345678, { foo: 'bar baz' })).to.throw( + 'Unexpected state' + ); + }); + }); +}); + +describe('fail', () => { + it('includes the error code as hex', () => { + expect(() => fail(0x12345678, 'a message here')).to.throw('12345678'); + }); + + it('includes the context', () => { + expect(() => + fail(0x12345678, 'a message here', { foo: 'bar baz' }) + ).to.throw('bar baz'); + }); + + it('includes the message', () => { + expect(() => + fail(0x12345678, 'a message here', { foo: 'bar baz' }) + ).to.throw('a message here'); + }); + + describe('without message', () => { + it('includes the error code as hex', () => { + expect(() => fail(0x12345678)).to.throw('12345678'); + }); + + it('includes the context', () => { + expect(() => fail(0x12345678, { foo: 'bar baz' })).to.throw('bar baz'); + }); + it('includes a default message', () => { + expect(() => fail(0x12345678, { foo: 'bar baz' })).to.throw( + 'Unexpected state' + ); + }); + }); +}); diff --git a/packages/firestore/test/unit/util/async_queue.test.ts b/packages/firestore/test/unit/util/async_queue.test.ts index cc55879c88b..55f9f8547b1 100644 --- a/packages/firestore/test/unit/util/async_queue.test.ts +++ b/packages/firestore/test/unit/util/async_queue.test.ts @@ -139,7 +139,7 @@ describe('AsyncQueue', () => { Promise.reject('dummyOp should not be run'); expect(() => { queue.enqueueAndForget(dummyOp); - }).to.throw(/already failed:.*Simulated Error/); + }).to.throw(/already failed.*Simulated Error/); // Finally, restore log level. setLogLevel(oldLogLevel as unknown as LogLevelString); @@ -247,7 +247,7 @@ describe('AsyncQueue', () => { const deferred = new Deferred(); queue.enqueueRetryable(async () => { deferred.resolve(); - throw fail('Simulated test failure'); + throw fail(0x1576712c, 'Simulated test failure'); }); await deferred.promise; await expect( diff --git a/packages/firestore/test/util/helpers.ts b/packages/firestore/test/util/helpers.ts index c5865c3e0f7..c7b58870564 100644 --- a/packages/firestore/test/util/helpers.ts +++ b/packages/firestore/test/util/helpers.ts @@ -867,11 +867,13 @@ export function expectEqual(left: any, right: any, message?: string): void { message = message || ''; if (typeof left.isEqual !== 'function') { return fail( + 0x800468bf, JSON.stringify(left) + ' does not support isEqual (left) ' + message ); } if (typeof right.isEqual !== 'function') { return fail( + 0xebc9184f, JSON.stringify(right) + ' does not support isEqual (right) ' + message ); } diff --git a/packages/firestore/test/util/spec_test_helpers.ts b/packages/firestore/test/util/spec_test_helpers.ts index 7e2f77da438..09b8960c735 100644 --- a/packages/firestore/test/util/spec_test_helpers.ts +++ b/packages/firestore/test/util/spec_test_helpers.ts @@ -100,7 +100,10 @@ export function encodeWatchChange( } }; } - return fail('Unrecognized watch change: ' + JSON.stringify(watchChange)); + return fail( + 0xf8e51f87, + 'Unrecognized watch change: ' + JSON.stringify(watchChange) + ); } function encodeTargetChangeTargetChangeType( @@ -118,6 +121,6 @@ function encodeTargetChangeTargetChangeType( case WatchTargetChangeState.Reset: return 'RESET'; default: - return fail('Unknown WatchTargetChangeState: ' + state); + return fail(0x368b5cb4, 'Unknown WatchTargetChangeState: ' + state); } } diff --git a/packages/firestore/test/util/test_platform.ts b/packages/firestore/test/util/test_platform.ts index 7803a2fb3c6..1f1a9dd0d33 100644 --- a/packages/firestore/test/util/test_platform.ts +++ b/packages/firestore/test/util/test_platform.ts @@ -64,7 +64,7 @@ export class FakeWindow implements WindowLike { // listeners. break; default: - fail(`MockWindow doesn't support events of type '${type}'`); + fail(0xe53d2167, `MockWindow doesn't support events of type '${type}'`); } } From 70dcaa755179893e4aa01fb18a300098a32a5934 Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Wed, 2 Apr 2025 12:27:18 -0600 Subject: [PATCH 05/18] Removed 4 characters from each error code --- packages/firestore/src/api/credentials.ts | 14 ++--- packages/firestore/src/api/snapshot.ts | 2 +- .../firestore/src/core/component_provider.ts | 2 +- packages/firestore/src/core/filter.ts | 4 +- packages/firestore/src/core/query.ts | 2 +- .../firestore/src/core/sync_engine_impl.ts | 12 ++-- packages/firestore/src/core/transaction.ts | 2 +- packages/firestore/src/core/view.ts | 2 +- packages/firestore/src/core/view_snapshot.ts | 2 +- .../src/index/firestore_index_value_writer.ts | 2 +- .../firestore/src/lite-api/reference_impl.ts | 2 +- .../firestore/src/lite-api/transaction.ts | 7 +-- .../src/lite-api/user_data_reader.ts | 2 +- .../src/lite-api/user_data_writer.ts | 4 +- .../src/local/encoded_resource_path.ts | 8 +-- .../src/local/indexeddb_index_manager.ts | 4 +- .../local/indexeddb_mutation_batch_impl.ts | 4 +- .../src/local/indexeddb_mutation_queue.ts | 22 +++---- .../local/indexeddb_remote_document_cache.ts | 2 +- .../src/local/indexeddb_schema_converter.ts | 4 +- .../src/local/indexeddb_sentinels.ts | 2 +- .../src/local/indexeddb_target_cache.ts | 4 +- .../firestore/src/local/local_serializer.ts | 4 +- .../firestore/src/local/local_store_impl.ts | 4 +- .../src/local/memory_mutation_queue.ts | 2 +- .../firestore/src/local/memory_persistence.ts | 2 +- .../src/local/memory_remote_document_cache.ts | 2 +- .../src/local/persistence_promise.ts | 2 +- .../src/local/shared_client_state.ts | 2 +- packages/firestore/src/model/document.ts | 2 +- packages/firestore/src/model/mutation.ts | 2 +- .../firestore/src/model/mutation_batch.ts | 2 +- packages/firestore/src/model/normalize.ts | 8 +-- packages/firestore/src/model/path.ts | 4 +- .../src/model/target_index_matcher.ts | 2 +- packages/firestore/src/model/values.ts | 14 ++--- .../platform/browser/webchannel_connection.ts | 4 +- .../src/platform/node/grpc_connection.ts | 2 +- packages/firestore/src/remote/datastore.ts | 11 ++-- .../firestore/src/remote/persistent_stream.ts | 6 +- packages/firestore/src/remote/rpc_error.ts | 8 +-- packages/firestore/src/remote/serializer.ts | 60 +++++++++---------- packages/firestore/src/remote/watch_change.ts | 8 +-- packages/firestore/src/util/assert.ts | 2 +- .../firestore/src/util/async_queue_impl.ts | 2 +- .../firestore/src/util/input_validation.ts | 2 +- packages/firestore/src/util/logic_utils.ts | 20 +++---- packages/firestore/src/util/sorted_map.ts | 16 ++--- .../unit/index/ordered_code_writer.test.ts | 2 +- .../unit/local/indexeddb_persistence.test.ts | 2 +- .../test/unit/local/simple_db.test.ts | 2 +- .../firestore/test/unit/specs/spec_builder.ts | 8 +-- .../test/unit/specs/spec_test_components.ts | 2 +- .../test/unit/specs/spec_test_runner.ts | 4 +- .../firestore/test/unit/util/assert.test.ts | 32 +++++----- .../test/unit/util/async_queue.test.ts | 2 +- packages/firestore/test/util/helpers.ts | 4 +- .../firestore/test/util/spec_test_helpers.ts | 4 +- packages/firestore/test/util/test_platform.ts | 2 +- 59 files changed, 174 insertions(+), 192 deletions(-) diff --git a/packages/firestore/src/api/credentials.ts b/packages/firestore/src/api/credentials.ts index 00c9c80676a..69d8ebc3f05 100644 --- a/packages/firestore/src/api/credentials.ts +++ b/packages/firestore/src/api/credentials.ts @@ -207,7 +207,7 @@ export class LiteAuthCredentialsProvider implements CredentialsProvider { if (tokenData) { hardAssert( typeof tokenData.accessToken === 'string', - 0xa539b8a7, + 0xa539, 'Invalid tokenData returned from getToken()', { tokenData } ); @@ -261,7 +261,7 @@ export class FirebaseAuthCredentialsProvider ): void { hardAssert( this.tokenListener === undefined, - 0xa539b8a6, + 0xa539, 'Token listener already added' ); let lastTokenId = this.tokenCounter; @@ -360,7 +360,7 @@ export class FirebaseAuthCredentialsProvider if (tokenData) { hardAssert( typeof tokenData.accessToken === 'string', - 0x7c5d2af1, + 0x7c5d, 'Invalid tokenData returned from getToken()', { tokenData } ); @@ -391,7 +391,7 @@ export class FirebaseAuthCredentialsProvider const currentUid = this.auth && this.auth.getUid(); hardAssert( currentUid === null || typeof currentUid === 'string', - 0x0807a470, + 0x0807, 'Received invalid UID', { currentUid } ); @@ -520,7 +520,7 @@ export class FirebaseAppCheckTokenProvider ): void { hardAssert( this.tokenListener === undefined, - 0x0db82ad2, + 0x0db8, 'Token listener already added' ); @@ -596,7 +596,7 @@ export class FirebaseAppCheckTokenProvider if (tokenResult) { hardAssert( typeof tokenResult.token === 'string', - 0xae0e832b, + 0xae0e, 'Invalid tokenResult returned from getToken()', { tokenResult } ); @@ -669,7 +669,7 @@ export class LiteAppCheckTokenProvider implements CredentialsProvider { if (tokenResult) { hardAssert( typeof tokenResult.token === 'string', - 0x0d8ea95d, + 0x0d8e, 'Invalid tokenResult returned from getToken()', { tokenResult } ); diff --git a/packages/firestore/src/api/snapshot.ts b/packages/firestore/src/api/snapshot.ts index da23560ba3d..669ac26cafe 100644 --- a/packages/firestore/src/api/snapshot.ts +++ b/packages/firestore/src/api/snapshot.ts @@ -749,7 +749,7 @@ export function resultChangeType(type: ChangeType): DocumentChangeType { case ChangeType.Removed: return 'removed'; default: - return fail(0xf03d90cd, 'Unknown change type', { type }); + return fail(0xf03d, 'Unknown change type', { type }); } } diff --git a/packages/firestore/src/core/component_provider.ts b/packages/firestore/src/core/component_provider.ts index 1d8a7067a23..183a1046cc9 100644 --- a/packages/firestore/src/core/component_provider.ts +++ b/packages/firestore/src/core/component_provider.ts @@ -197,7 +197,7 @@ export class LruGcMemoryOfflineComponentProvider extends MemoryOfflineComponentP ): Scheduler | null { hardAssert( this.persistence.referenceDelegate instanceof MemoryLruDelegate, - 0xb7434c0f, + 0xb743, 'referenceDelegate is expected to be an instance of MemoryLruDelegate.' ); diff --git a/packages/firestore/src/core/filter.ts b/packages/firestore/src/core/filter.ts index 49eae42b3b3..d8907387057 100644 --- a/packages/firestore/src/core/filter.ts +++ b/packages/firestore/src/core/filter.ts @@ -168,7 +168,7 @@ export class FieldFilter extends Filter { case Operator.GREATER_THAN_OR_EQUAL: return comparison >= 0; default: - return fail(0xb8a2aab3, 'Unknown FieldFilter operator', { + return fail(0xb8a2, 'Unknown FieldFilter operator', { operator: this.op }); } @@ -317,7 +317,7 @@ export function filterEquals(f1: Filter, f2: Filter): boolean { } else if (f1 instanceof CompositeFilter) { return compositeFilterEquals(f1, f2); } else { - fail(0x4befcb45, 'Only FieldFilters and CompositeFilters can be compared'); + fail(0x4bef, 'Only FieldFilters and CompositeFilters can be compared'); } } diff --git a/packages/firestore/src/core/query.ts b/packages/firestore/src/core/query.ts index 72ecf71ce3b..ca875bbb14d 100644 --- a/packages/firestore/src/core/query.ts +++ b/packages/firestore/src/core/query.ts @@ -578,6 +578,6 @@ export function compareDocs( case Direction.DESCENDING: return -1 * comparison; default: - return fail(0x4d4e3851, 'Unknown direction', { direction: orderBy.dir }); + return fail(0x4d4e, 'Unknown direction', { direction: orderBy.dir }); } } diff --git a/packages/firestore/src/core/sync_engine_impl.ts b/packages/firestore/src/core/sync_engine_impl.ts index ac0fcb8d30f..404d4663a47 100644 --- a/packages/firestore/src/core/sync_engine_impl.ts +++ b/packages/firestore/src/core/sync_engine_impl.ts @@ -579,7 +579,7 @@ export async function syncEngineApplyRemoteEvent( targetChange.modifiedDocuments.size + targetChange.removedDocuments.size <= 1, - 0x5858a197, + 0x5858, 'Limbo resolution for single document contains multiple changes.' ); if (targetChange.addedDocuments.size > 0) { @@ -587,13 +587,13 @@ export async function syncEngineApplyRemoteEvent( } else if (targetChange.modifiedDocuments.size > 0) { hardAssert( limboResolution.receivedDocument, - 0x390f2c8b, + 0x390f, 'Received change for limbo target document without add.' ); } else if (targetChange.removedDocuments.size > 0) { hardAssert( limboResolution.receivedDocument, - 0xa4f3c30e, + 0xa4f3, 'Received remove for limbo target document without add.' ); limboResolution.receivedDocument = false; @@ -997,7 +997,7 @@ function updateTrackedLimbos( removeLimboTarget(syncEngineImpl, limboChange.key); } } else { - fail(0x4d4e3850, 'Unknown limbo change', { limboChange }); + fail(0x4d4f, 'Unknown limbo change', { limboChange }); } } } @@ -1320,7 +1320,7 @@ export async function syncEngineApplyBatchState( batchId ); } else { - fail(0x1a4018ac, `Unknown batchState`, { batchState }); + fail(0x1a40, `Unknown batchState`, { batchState }); } await syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngineImpl, documents); @@ -1563,7 +1563,7 @@ export async function syncEngineApplyTargetState( break; } default: - fail(0xfa9b528f, 'Unexpected target state', state); + fail(0xfa9b, 'Unexpected target state', state); } } } diff --git a/packages/firestore/src/core/transaction.ts b/packages/firestore/src/core/transaction.ts index 1c2b7f01955..d3dc6ee8a89 100644 --- a/packages/firestore/src/core/transaction.ts +++ b/packages/firestore/src/core/transaction.ts @@ -124,7 +124,7 @@ export class Transaction { // Represent a deleted doc using SnapshotVersion.min(). docVersion = SnapshotVersion.min(); } else { - throw fail(0xc542d4e0, 'Document in a transaction was a ', { + throw fail(0xc542, 'Document in a transaction was a ', { documentName: doc.constructor.name }); } diff --git a/packages/firestore/src/core/view.ts b/packages/firestore/src/core/view.ts index 27d0ecc8da2..e0909de938f 100644 --- a/packages/firestore/src/core/view.ts +++ b/packages/firestore/src/core/view.ts @@ -505,7 +505,7 @@ function compareChangeType(c1: ChangeType, c2: ChangeType): number { case ChangeType.Removed: return 0; default: - return fail(0x4f35c22a, 'Unknown ChangeType', { change }); + return fail(0x4f35, 'Unknown ChangeType', { change }); } }; diff --git a/packages/firestore/src/core/view_snapshot.ts b/packages/firestore/src/core/view_snapshot.ts index 073ed930da7..3bb6e8ae134 100644 --- a/packages/firestore/src/core/view_snapshot.ts +++ b/packages/firestore/src/core/view_snapshot.ts @@ -118,7 +118,7 @@ export class DocumentChangeSet { // Metadata->Added // Removed->Metadata fail( - 0xf76d356d, + 0xf76d, 'unsupported combination of changes: `change` after `oldChange`', { change, diff --git a/packages/firestore/src/index/firestore_index_value_writer.ts b/packages/firestore/src/index/firestore_index_value_writer.ts index 5d952dad476..b76ca7a930a 100644 --- a/packages/firestore/src/index/firestore_index_value_writer.ts +++ b/packages/firestore/src/index/firestore_index_value_writer.ts @@ -136,7 +136,7 @@ export class FirestoreIndexValueWriter { this.writeIndexArray(indexValue.arrayValue!, encoder); this.writeTruncationMarker(encoder); } else { - fail(0x4a4e16fa, 'unknown index value type', { indexValue }); + fail(0x4a4e, 'unknown index value type', { indexValue }); } } diff --git a/packages/firestore/src/lite-api/reference_impl.ts b/packages/firestore/src/lite-api/reference_impl.ts index 13eeaf044d0..6d92ccab479 100644 --- a/packages/firestore/src/lite-api/reference_impl.ts +++ b/packages/firestore/src/lite-api/reference_impl.ts @@ -135,7 +135,7 @@ export function getDoc( result => { hardAssert( result.length === 1, - 0x3d02d4f4, + 0x3d02, 'Expected a single document result' ); const document = result[0]; diff --git a/packages/firestore/src/lite-api/transaction.ts b/packages/firestore/src/lite-api/transaction.ts index af804bc0eb7..9a405feebea 100644 --- a/packages/firestore/src/lite-api/transaction.ts +++ b/packages/firestore/src/lite-api/transaction.ts @@ -94,10 +94,7 @@ export class Transaction { const userDataWriter = new LiteUserDataWriter(this._firestore); return this._transaction.lookup([ref._key]).then(docs => { if (!docs || docs.length !== 1) { - return fail( - 0x5de91660, - 'Mismatch in docs returned from document lookup.' - ); + return fail(0x5de9, 'Mismatch in docs returned from document lookup.'); } const doc = docs[0]; if (doc.isFoundDocument()) { @@ -118,7 +115,7 @@ export class Transaction { ); } else { throw fail( - 0x4801d4c1, + 0x4801, 'BatchGetDocumentsRequest returned unexpected document', { doc diff --git a/packages/firestore/src/lite-api/user_data_reader.ts b/packages/firestore/src/lite-api/user_data_reader.ts index 3e1b4e6c7ef..aa5f9eeb5bf 100644 --- a/packages/firestore/src/lite-api/user_data_reader.ts +++ b/packages/firestore/src/lite-api/user_data_reader.ts @@ -175,7 +175,7 @@ function isWrite(dataSource: UserDataSource): boolean { case UserDataSource.ArrayArgument: return false; default: - throw fail(0x9c4bc6ee, 'Unexpected case for UserDataSource', { + throw fail(0x9c4b, 'Unexpected case for UserDataSource', { dataSource }); } diff --git a/packages/firestore/src/lite-api/user_data_writer.ts b/packages/firestore/src/lite-api/user_data_writer.ts index 1c082fb9381..070c71c7832 100644 --- a/packages/firestore/src/lite-api/user_data_writer.ts +++ b/packages/firestore/src/lite-api/user_data_writer.ts @@ -89,7 +89,7 @@ export abstract class AbstractUserDataWriter { case TypeOrder.VectorValue: return this.convertVectorValue(value.mapValue!); default: - throw fail(0xf2a2c883, 'Invalid value type', { + throw fail(0xf2a2, 'Invalid value type', { value }); } @@ -175,7 +175,7 @@ export abstract class AbstractUserDataWriter { const resourcePath = ResourcePath.fromString(name); hardAssert( isValidResourceName(resourcePath), - 0x25d8ba3b, + 0x25d8, 'ReferenceValue is not valid', { name } ); diff --git a/packages/firestore/src/local/encoded_resource_path.ts b/packages/firestore/src/local/encoded_resource_path.ts index e0171e55105..497a65fdf8c 100644 --- a/packages/firestore/src/local/encoded_resource_path.ts +++ b/packages/firestore/src/local/encoded_resource_path.ts @@ -118,11 +118,11 @@ export function decodeResourcePath(path: EncodedResourcePath): ResourcePath { // Event the empty path must encode as a path of at least length 2. A path // with exactly 2 must be the empty path. const length = path.length; - hardAssert(length >= 2, 0xfb9852e0, 'Invalid path', { path }); + hardAssert(length >= 2, 0xfb98, 'Invalid path', { path }); if (length === 2) { hardAssert( path.charAt(0) === escapeChar && path.charAt(1) === encodedSeparatorChar, - 0xdb51aee5, + 0xdb51, 'Non-empty path had length 2', { path } ); @@ -141,7 +141,7 @@ export function decodeResourcePath(path: EncodedResourcePath): ResourcePath { // there must be an end to this segment. const end = path.indexOf(escapeChar, start); if (end < 0 || end > lastReasonableEscapeIndex) { - fail(0xc553d051, 'Invalid encoded resource path', { path }); + fail(0xc553, 'Invalid encoded resource path', { path }); } const next = path.charAt(end + 1); @@ -169,7 +169,7 @@ export function decodeResourcePath(path: EncodedResourcePath): ResourcePath { segmentBuilder += path.substring(start, end + 1); break; default: - fail(0xeeefd395, 'Invalid encoded resource path', { path }); + fail(0xeeef, 'Invalid encoded resource path', { path }); } start = end + 2; diff --git a/packages/firestore/src/local/indexeddb_index_manager.ts b/packages/firestore/src/local/indexeddb_index_manager.ts index ab36c6d325c..d2b8bc47163 100644 --- a/packages/firestore/src/local/indexeddb_index_manager.ts +++ b/packages/firestore/src/local/indexeddb_index_manager.ts @@ -1066,7 +1066,7 @@ export class IndexedDbIndexManager implements IndexManager { this.getSubTargets(target), (subTarget: Target) => this.getFieldIndex(transaction, subTarget).next(index => - index ? index : fail(0xad8a2524, 'Target cannot be served from index') + index ? index : fail(0xad8a, 'Target cannot be served from index') ) ).next(getMinOffsetFromFieldIndexes); } @@ -1118,7 +1118,7 @@ function indexStateStore( function getMinOffsetFromFieldIndexes(fieldIndexes: FieldIndex[]): IndexOffset { hardAssert( fieldIndexes.length !== 0, - 0x709979b6, + 0x7099, 'Found empty index group when looking for least recent index offset.' ); diff --git a/packages/firestore/src/local/indexeddb_mutation_batch_impl.ts b/packages/firestore/src/local/indexeddb_mutation_batch_impl.ts index 845975ad162..64b6df20b7e 100644 --- a/packages/firestore/src/local/indexeddb_mutation_batch_impl.ts +++ b/packages/firestore/src/local/indexeddb_mutation_batch_impl.ts @@ -64,7 +64,7 @@ export function removeMutationBatch( removePromise.next(() => { hardAssert( numDeleted === 1, - 0xb7deb8ed, + 0xb7de, 'Dangling document-mutation reference found: Missing batch', { batchId: batch.batchId } ); @@ -101,7 +101,7 @@ export function dbDocumentSize( } else if (doc.noDocument) { value = doc.noDocument; } else { - throw fail(0x398b459b, 'Unknown remote document type'); + throw fail(0x398b, 'Unknown remote document type'); } return JSON.stringify(value).length; } diff --git a/packages/firestore/src/local/indexeddb_mutation_queue.ts b/packages/firestore/src/local/indexeddb_mutation_queue.ts index f14e5b71d57..bcdafa6aa36 100644 --- a/packages/firestore/src/local/indexeddb_mutation_queue.ts +++ b/packages/firestore/src/local/indexeddb_mutation_queue.ts @@ -105,11 +105,7 @@ export class IndexedDbMutationQueue implements MutationQueue { // In particular, are there any reserved characters? are empty ids allowed? // For the moment store these together in the same mutations table assuming // that empty userIDs aren't allowed. - hardAssert( - user.uid !== '', - 0xfb8340e4, - 'UserID must not be an empty string.' - ); + hardAssert(user.uid !== '', 0xfb83, 'UserID must not be an empty string.'); const userId = user.isAuthenticated() ? user.uid! : ''; return new IndexedDbMutationQueue( userId, @@ -158,7 +154,7 @@ export class IndexedDbMutationQueue implements MutationQueue { return mutationStore.add({} as any).next(batchId => { hardAssert( typeof batchId === 'number', - 0xbf7b47b3, + 0xbf7b, 'Auto-generated key is not a number' ); @@ -211,7 +207,7 @@ export class IndexedDbMutationQueue implements MutationQueue { if (dbBatch) { hardAssert( dbBatch.userId === this.userId, - 0x00307f8d, + 0x0030, `Unexpected user for mutation batch`, { userId: dbBatch.userId, @@ -267,7 +263,7 @@ export class IndexedDbMutationQueue implements MutationQueue { if (dbBatch.userId === this.userId) { hardAssert( dbBatch.batchId >= nextBatchId, - 0xb9a4d5e0, + 0xb9a4, 'Should have found mutation after `nextBatchId`', { nextBatchId } ); @@ -348,7 +344,7 @@ export class IndexedDbMutationQueue implements MutationQueue { .next(mutation => { if (!mutation) { throw fail( - 0xf028f0ab, + 0xf028, 'Dangling document-mutation reference found: `indexKey` which points to `batchId`', { indexKey, @@ -358,7 +354,7 @@ export class IndexedDbMutationQueue implements MutationQueue { } hardAssert( mutation.userId === this.userId, - 0x2907d360, + 0x2907, `Unexpected user for mutation batch`, { userId: mutation.userId, @@ -487,7 +483,7 @@ export class IndexedDbMutationQueue implements MutationQueue { .next(mutation => { if (mutation === null) { throw fail( - 0x89caa3d9, + 0x89ca, 'Dangling document-mutation reference found, which points to `batchId`', { batchId @@ -496,7 +492,7 @@ export class IndexedDbMutationQueue implements MutationQueue { } hardAssert( mutation.userId === this.userId, - 0x2614151a, + 0x2614, `Unexpected user for mutation batch`, { userId: mutation.userId, batchId } ); @@ -572,7 +568,7 @@ export class IndexedDbMutationQueue implements MutationQueue { .next(() => { hardAssert( danglingMutationReferences.length === 0, - 0xdd9079e3, + 0xdd90, 'Document leak -- detected dangling mutation references when queue is empty.', { danglingKeys: danglingMutationReferences.map(p => diff --git a/packages/firestore/src/local/indexeddb_remote_document_cache.ts b/packages/firestore/src/local/indexeddb_remote_document_cache.ts index 2ee1c354c4a..fffe935c4f9 100644 --- a/packages/firestore/src/local/indexeddb_remote_document_cache.ts +++ b/packages/firestore/src/local/indexeddb_remote_document_cache.ts @@ -381,7 +381,7 @@ class IndexedDbRemoteDocumentCacheImpl implements IndexedDbRemoteDocumentCache { return documentGlobalStore(txn) .get(DbRemoteDocumentGlobalKey) .next(metadata => { - hardAssert(!!metadata, 0x4e35b51d, 'Missing document cache metadata'); + hardAssert(!!metadata, 0x4e35, 'Missing document cache metadata'); return metadata!; }); } diff --git a/packages/firestore/src/local/indexeddb_schema_converter.ts b/packages/firestore/src/local/indexeddb_schema_converter.ts index 55d8cae1f79..7446ae7ae20 100644 --- a/packages/firestore/src/local/indexeddb_schema_converter.ts +++ b/packages/firestore/src/local/indexeddb_schema_converter.ts @@ -326,7 +326,7 @@ export class SchemaConverter implements SimpleDbSchemaConverter { (dbBatch: DbMutationBatch) => { hardAssert( dbBatch.userId === queue.userId, - 0x48daa634, + 0x48da, `Cannot process batch from unexpected user`, { batchId: dbBatch.batchId } ); @@ -774,6 +774,6 @@ function extractKey(remoteDoc: DbRemoteDocumentLegacy): DocumentKey { } else if (remoteDoc.unknownDocument) { return DocumentKey.fromSegments(remoteDoc.unknownDocument.path); } else { - return fail(0x8faf1dd8, 'Unexpected DbRemoteDocument'); + return fail(0x8faf, 'Unexpected DbRemoteDocument'); } } diff --git a/packages/firestore/src/local/indexeddb_sentinels.ts b/packages/firestore/src/local/indexeddb_sentinels.ts index 30d063fbf75..cb6ebcb664a 100644 --- a/packages/firestore/src/local/indexeddb_sentinels.ts +++ b/packages/firestore/src/local/indexeddb_sentinels.ts @@ -450,6 +450,6 @@ export function getObjectStores(schemaVersion: number): string[] { } else if (schemaVersion === 11) { return V11_STORES; } else { - fail(0xeb555e15, 'Only schema version 11 and 12 and 13 are supported'); + fail(0xeb55, 'Only schema version 11 and 12 and 13 are supported'); } } diff --git a/packages/firestore/src/local/indexeddb_target_cache.ts b/packages/firestore/src/local/indexeddb_target_cache.ts index dbaf3a44f9a..1d5ed8f0c8b 100644 --- a/packages/firestore/src/local/indexeddb_target_cache.ts +++ b/packages/firestore/src/local/indexeddb_target_cache.ts @@ -144,7 +144,7 @@ export class IndexedDbTargetCache implements TargetCache { .next(metadata => { hardAssert( metadata.targetCount > 0, - 0x1f81791d, + 0x1f81, 'Removing from an empty target cache' ); metadata.targetCount -= 1; @@ -198,7 +198,7 @@ export class IndexedDbTargetCache implements TargetCache { return globalTargetStore(transaction) .get(DbTargetGlobalKey) .next(metadata => { - hardAssert(metadata !== null, 0x0b4887a1, 'Missing metadata row.'); + hardAssert(metadata !== null, 0x0b48, 'Missing metadata row.'); return metadata; }); } diff --git a/packages/firestore/src/local/local_serializer.ts b/packages/firestore/src/local/local_serializer.ts index 62cfe4ccb98..bb1658caa52 100644 --- a/packages/firestore/src/local/local_serializer.ts +++ b/packages/firestore/src/local/local_serializer.ts @@ -101,7 +101,7 @@ export function fromDbRemoteDocument( const version = fromDbTimestamp(remoteDoc.unknownDocument.version); doc = MutableDocument.newUnknownDocument(key, version); } else { - return fail(0xdd85b182, 'Unexpected DbRemoteDocument'); + return fail(0xdd85, 'Unexpected DbRemoteDocument'); } if (remoteDoc.readTime) { @@ -138,7 +138,7 @@ export function toDbRemoteDocument( version: toDbTimestamp(document.version) }; } else { - return fail(0xe2301882, 'Unexpected Document', { document }); + return fail(0xe230, 'Unexpected Document', { document }); } return remoteDoc; } diff --git a/packages/firestore/src/local/local_store_impl.ts b/packages/firestore/src/local/local_store_impl.ts index f56e79d7622..31d2a46c326 100644 --- a/packages/firestore/src/local/local_store_impl.ts +++ b/packages/firestore/src/local/local_store_impl.ts @@ -496,7 +496,7 @@ export function localStoreRejectBatch( .next((batch: MutationBatch | null) => { hardAssert( batch !== null, - 0x90f9f22b, + 0x90f9, 'Attempt to reject nonexistent batch!' ); affectedKeys = batch.keys(); @@ -1141,7 +1141,7 @@ function applyWriteToRemoteDocuments( const ackVersion = batchResult.docVersions.get(docKey); hardAssert( ackVersion !== null, - 0xbd9d3cef, + 0xbd9d, 'ackVersions should contain every doc in the write.' ); if (doc.version.compareTo(ackVersion!) < 0) { diff --git a/packages/firestore/src/local/memory_mutation_queue.ts b/packages/firestore/src/local/memory_mutation_queue.ts index 4b9b14769cd..f136fb7ad15 100644 --- a/packages/firestore/src/local/memory_mutation_queue.ts +++ b/packages/firestore/src/local/memory_mutation_queue.ts @@ -246,7 +246,7 @@ export class MemoryMutationQueue implements MutationQueue { const batchIndex = this.indexOfExistingBatchId(batch.batchId, 'removed'); hardAssert( batchIndex === 0, - 0xd6dbcbf2, + 0xd6db, 'Can only remove the first entry of the mutation queue' ); this.mutationQueue.shift(); diff --git a/packages/firestore/src/local/memory_persistence.ts b/packages/firestore/src/local/memory_persistence.ts index fa42602d476..fcb6db42059 100644 --- a/packages/firestore/src/local/memory_persistence.ts +++ b/packages/firestore/src/local/memory_persistence.ts @@ -236,7 +236,7 @@ export class MemoryEagerDelegate implements MemoryReferenceDelegate { private get orphanedDocuments(): Set { if (!this._orphanedDocuments) { throw fail( - 0xee44dcb1, + 0xee44, 'orphanedDocuments is only valid during a transaction.' ); } else { diff --git a/packages/firestore/src/local/memory_remote_document_cache.ts b/packages/firestore/src/local/memory_remote_document_cache.ts index 51c40c5785c..0daf80b6a19 100644 --- a/packages/firestore/src/local/memory_remote_document_cache.ts +++ b/packages/firestore/src/local/memory_remote_document_cache.ts @@ -219,7 +219,7 @@ class MemoryRemoteDocumentCacheImpl implements MemoryRemoteDocumentCache { ): PersistencePromise { // This method should only be called from the IndexBackfiller if persistence // is enabled. - fail(0x251c6adf, 'getAllFromCollectionGroup() is not supported.'); + fail(0x251c, 'getAllFromCollectionGroup() is not supported.'); } forEachDocumentKey( diff --git a/packages/firestore/src/local/persistence_promise.ts b/packages/firestore/src/local/persistence_promise.ts index 7fe0087314a..812cc0fca85 100644 --- a/packages/firestore/src/local/persistence_promise.ts +++ b/packages/firestore/src/local/persistence_promise.ts @@ -86,7 +86,7 @@ export class PersistencePromise { catchFn?: RejectedHandler ): PersistencePromise { if (this.callbackAttached) { - fail(0xe8303b04, 'Called next() or catch() twice for PersistencePromise'); + fail(0xe830, 'Called next() or catch() twice for PersistencePromise'); } this.callbackAttached = true; if (this.isDone) { diff --git a/packages/firestore/src/local/shared_client_state.ts b/packages/firestore/src/local/shared_client_state.ts index c95f9eb6d30..1000e63a0f6 100644 --- a/packages/firestore/src/local/shared_client_state.ts +++ b/packages/firestore/src/local/shared_client_state.ts @@ -1085,7 +1085,7 @@ function fromWebStorageSequenceNumber( const parsed = JSON.parse(seqString); hardAssert( typeof parsed === 'number', - 0x77acb387, + 0x77ac, 'Found non-numeric sequence number', { seqString } ); diff --git a/packages/firestore/src/model/document.ts b/packages/firestore/src/model/document.ts index a79f6335077..ac454704776 100644 --- a/packages/firestore/src/model/document.ts +++ b/packages/firestore/src/model/document.ts @@ -398,7 +398,7 @@ export function compareDocumentsByField( return valueCompare(v1, v2); } else { return fail( - 0xa786ed58, + 0xa786, "Trying to compare documents on fields that don't exist" ); } diff --git a/packages/firestore/src/model/mutation.ts b/packages/firestore/src/model/mutation.ts index 141a9e64b7e..0bcd1345b01 100644 --- a/packages/firestore/src/model/mutation.ts +++ b/packages/firestore/src/model/mutation.ts @@ -623,7 +623,7 @@ function serverTransformResults( const transformResults = new Map(); hardAssert( fieldTransforms.length === serverTransformResults.length, - 0x7f904616, + 0x7f90, 'server transform result count should match field transform count', { serverTransformResultCount: serverTransformResults.length, diff --git a/packages/firestore/src/model/mutation_batch.ts b/packages/firestore/src/model/mutation_batch.ts index b3df9797d9a..703623da01a 100644 --- a/packages/firestore/src/model/mutation_batch.ts +++ b/packages/firestore/src/model/mutation_batch.ts @@ -219,7 +219,7 @@ export class MutationBatchResult { ): MutationBatchResult { hardAssert( batch.mutations.length === results.length, - 0xe5da1b20, + 0xe5da, 'Mutations sent must equal results received', { mutationsSent: batch.mutations.length, diff --git a/packages/firestore/src/model/normalize.ts b/packages/firestore/src/model/normalize.ts index 069725e583e..986eeed1e48 100644 --- a/packages/firestore/src/model/normalize.ts +++ b/packages/firestore/src/model/normalize.ts @@ -32,11 +32,7 @@ export function normalizeTimestamp(date: Timestamp): { seconds: number; nanos: number; } { - hardAssert( - !!date, - 0x986a9b2d, - 'Cannot normalize null or undefined timestamp.' - ); + hardAssert(!!date, 0x986a, 'Cannot normalize null or undefined timestamp.'); // The json interface (for the browser) will return an iso timestamp string, // while the proto js library (for node) will return a @@ -48,7 +44,7 @@ export function normalizeTimestamp(date: Timestamp): { // Parse the nanos right out of the string. let nanos = 0; const fraction = ISO_TIMESTAMP_REG_EXP.exec(date); - hardAssert(!!fraction, 0xb5de9271, 'invalid timestamp', { + hardAssert(!!fraction, 0xb5de, 'invalid timestamp', { timestamp: date }); if (fraction[1]) { diff --git a/packages/firestore/src/model/path.ts b/packages/firestore/src/model/path.ts index 474077b4391..0f4581da8d8 100644 --- a/packages/firestore/src/model/path.ts +++ b/packages/firestore/src/model/path.ts @@ -35,7 +35,7 @@ abstract class BasePath> { if (offset === undefined) { offset = 0; } else if (offset > segments.length) { - fail(0x027dee8b, 'offset out of range', { + fail(0x027d, 'offset out of range', { offset, range: segments.length }); @@ -44,7 +44,7 @@ abstract class BasePath> { if (length === undefined) { length = segments.length - offset; } else if (length > segments.length - offset) { - fail(0x06d25826, 'length out of range', { + fail(0x06d2, 'length out of range', { length, range: segments.length - offset }); diff --git a/packages/firestore/src/model/target_index_matcher.ts b/packages/firestore/src/model/target_index_matcher.ts index 5582af1eb67..407eae337c7 100644 --- a/packages/firestore/src/model/target_index_matcher.ts +++ b/packages/firestore/src/model/target_index_matcher.ts @@ -111,7 +111,7 @@ export class TargetIndexMatcher { servedByIndex(index: FieldIndex): boolean { hardAssert( index.collectionGroup === this.collectionId, - 0xc07f69ec, + 0xc07f, 'Collection IDs do not match' ); diff --git a/packages/firestore/src/model/values.ts b/packages/firestore/src/model/values.ts index 0be91faba91..1ef54a98ad6 100644 --- a/packages/firestore/src/model/values.ts +++ b/packages/firestore/src/model/values.ts @@ -93,7 +93,7 @@ export function typeOrder(value: Value): TypeOrder { } return TypeOrder.ObjectValue; } else { - return fail(0x6e876576, 'Invalid value type', { value }); + return fail(0x6e87, 'Invalid value type', { value }); } } @@ -140,7 +140,7 @@ export function valueEquals(left: Value, right: Value): boolean { case TypeOrder.MaxValue: return true; default: - return fail(0xcbf802b1, 'Unexpected value type', { left }); + return fail(0xcbf8, 'Unexpected value type', { left }); } } @@ -269,7 +269,7 @@ export function valueCompare(left: Value, right: Value): number { case TypeOrder.ObjectValue: return compareMaps(left.mapValue!, right.mapValue!); default: - throw fail(0x5ae0c79a, 'Invalid value type', { leftType }); + throw fail(0x5ae0, 'Invalid value type', { leftType }); } } @@ -449,7 +449,7 @@ function canonifyValue(value: Value): string { } else if ('mapValue' in value) { return canonifyMap(value.mapValue!); } else { - return fail(0xee4d4cc0, 'Invalid value type', { value }); + return fail(0xee4d, 'Invalid value type', { value }); } } @@ -541,7 +541,7 @@ export function estimateByteSize(value: Value): number { case TypeOrder.ObjectValue: return estimateMapByteSize(value.mapValue!); default: - throw fail(0x34ae10e9, 'Invalid value type', { value }); + throw fail(0x34ae, 'Invalid value type', { value }); } } @@ -701,7 +701,7 @@ export function valuesGetLowerBound(value: Value): Value { } return { mapValue: {} }; } else { - return fail(0x8c66eca4, 'Invalid value type', { value }); + return fail(0x8c66, 'Invalid value type', { value }); } } @@ -731,7 +731,7 @@ export function valuesGetUpperBound(value: Value): Value { } return MAX_VALUE; } else { - return fail(0xf207a8af, 'Invalid value type', { value }); + return fail(0xf207, 'Invalid value type', { value }); } } diff --git a/packages/firestore/src/platform/browser/webchannel_connection.ts b/packages/firestore/src/platform/browser/webchannel_connection.ts index 15ed2b1ea91..ce20eef2049 100644 --- a/packages/firestore/src/platform/browser/webchannel_connection.ts +++ b/packages/firestore/src/platform/browser/webchannel_connection.ts @@ -142,7 +142,7 @@ export class WebChannelConnection extends RestConnection { break; default: fail( - 0x235fa5fa, + 0x235f, 'RPC failed with unanticipated webchannel error. Giving up.', { rpcName, @@ -355,7 +355,7 @@ export class WebChannelConnection extends RestConnection { const msgData = msg.data[0]; hardAssert( !!msgData, - 0x3fddb347, + 0x3fdd, 'Got a webchannel message without data.' ); // TODO(b/35143891): There is a bug in One Platform that caused errors diff --git a/packages/firestore/src/platform/node/grpc_connection.ts b/packages/firestore/src/platform/node/grpc_connection.ts index fbdff0a70e4..d50a3149416 100644 --- a/packages/firestore/src/platform/node/grpc_connection.ts +++ b/packages/firestore/src/platform/node/grpc_connection.ts @@ -48,7 +48,7 @@ function createMetadata( ): grpc.Metadata { hardAssert( authToken === null || authToken.type === 'OAuth', - 0x90486aa6, + 0x9048, 'If provided, token must be OAuth' ); const metadata = new grpc.Metadata(); diff --git a/packages/firestore/src/remote/datastore.ts b/packages/firestore/src/remote/datastore.ts index f9fbf7636df..f790ede0d5c 100644 --- a/packages/firestore/src/remote/datastore.ts +++ b/packages/firestore/src/remote/datastore.ts @@ -228,12 +228,9 @@ export async function invokeBatchGetDocumentsRpc( const result: Document[] = []; keys.forEach(key => { const doc = docs.get(key.toString()); - hardAssert( - !!doc, - 0xd7c263c4, - 'Missing entity in write response for `key`', - { key } - ); + hardAssert(!!doc, 0xd7c2, 'Missing entity in write response for `key`', { + key + }); result.push(doc); }); return result; @@ -295,7 +292,7 @@ export async function invokeRunAggregationQueryRpc( hardAssert( filteredResult.length === 1, - 0xfcd7682f, + 0xfcd7, 'Aggregation fields are missing from result.' ); debugAssert( diff --git a/packages/firestore/src/remote/persistent_stream.ts b/packages/firestore/src/remote/persistent_stream.ts index 685de537212..4f3b91652ad 100644 --- a/packages/firestore/src/remote/persistent_stream.ts +++ b/packages/firestore/src/remote/persistent_stream.ts @@ -809,7 +809,7 @@ export class PersistentWriteStream extends PersistentStream< // Always capture the last stream token. hardAssert( !!responseProto.streamToken, - 0x7a5ae995, + 0x7a5a, 'Got a write handshake response without a stream token' ); this.lastStreamToken = responseProto.streamToken; @@ -817,7 +817,7 @@ export class PersistentWriteStream extends PersistentStream< // The first response is always the handshake response hardAssert( !responseProto.writeResults || responseProto.writeResults.length === 0, - 0xda089d3f, + 0xda08, 'Got mutation results for handshake' ); return this.listener!.onHandshakeComplete(); @@ -827,7 +827,7 @@ export class PersistentWriteStream extends PersistentStream< // Always capture the last stream token. hardAssert( !!responseProto.streamToken, - 0x31869253, + 0x3186, 'Got a write response without a stream token' ); this.lastStreamToken = responseProto.streamToken; diff --git a/packages/firestore/src/remote/rpc_error.ts b/packages/firestore/src/remote/rpc_error.ts index 8185b17ec2c..2efee40f223 100644 --- a/packages/firestore/src/remote/rpc_error.ts +++ b/packages/firestore/src/remote/rpc_error.ts @@ -58,7 +58,7 @@ enum RpcCode { export function isPermanentError(code: Code): boolean { switch (code) { case Code.OK: - return fail(0xfdaa5013, 'Treated status OK as error'); + return fail(0xfdaa, 'Treated status OK as error'); case Code.CANCELLED: case Code.UNKNOWN: case Code.DEADLINE_EXCEEDED: @@ -83,7 +83,7 @@ export function isPermanentError(code: Code): boolean { case Code.DATA_LOSS: return true; default: - return fail(0x3c6b8863, 'Unknown status code', { code }); + return fail(0x3c6b, 'Unknown status code', { code }); } } @@ -171,7 +171,7 @@ export function mapCodeFromRpcCode(code: number | undefined): Code { case RpcCode.DATA_LOSS: return Code.DATA_LOSS; default: - return fail(0x999b10f0, 'Unknown status code', { code }); + return fail(0x999b, 'Unknown status code', { code }); } } @@ -220,7 +220,7 @@ export function mapRpcCodeFromCode(code: Code | undefined): number { case Code.DATA_LOSS: return RpcCode.DATA_LOSS; default: - return fail(0x3019a484, 'Unknown status code', { code }); + return fail(0x3019, 'Unknown status code', { code }); } } diff --git a/packages/firestore/src/remote/serializer.ts b/packages/firestore/src/remote/serializer.ts index 5a7f03f468a..aabdb263c1a 100644 --- a/packages/firestore/src/remote/serializer.ts +++ b/packages/firestore/src/remote/serializer.ts @@ -257,7 +257,7 @@ export function fromBytes( if (serializer.useProto3Json) { hardAssert( value === undefined || typeof value === 'string', - 0xe30bd3f9, + 0xe30b, 'value must be undefined or a string when using proto3 Json' ); return ByteString.fromBase64String(value ? value : ''); @@ -270,7 +270,7 @@ export function fromBytes( // does not indicate that it extends Uint8Array. value instanceof Buffer || value instanceof Uint8Array, - 0x3f413022, + 0x3f41, 'value must be undefined, Buffer, or Uint8Array' ); return ByteString.fromUint8Array(value ? value : new Uint8Array()); @@ -285,11 +285,7 @@ export function toVersion( } export function fromVersion(version: ProtoTimestamp): SnapshotVersion { - hardAssert( - !!version, - 0xc050aaa5, - "Trying to deserialize version that isn't set" - ); + hardAssert(!!version, 0xc050, "Trying to deserialize version that isn't set"); return SnapshotVersion.fromTimestamp(fromTimestamp(version)); } @@ -312,7 +308,7 @@ function fromResourceName(name: string): ResourcePath { const resource = ResourcePath.fromString(name); hardAssert( isValidResourceName(resource), - 0x27ce4509, + 0x27ce, 'Tried to deserialize invalid key', { key: resource.toString() } ); @@ -397,7 +393,7 @@ function extractLocalPathFromResourceName( ): ResourcePath { hardAssert( resourceName.length > 4 && resourceName.get(4) === 'documents', - 0x71a31a15, + 0x71a3, 'tried to deserialize invalid key', { key: resourceName.toString() } ); @@ -464,7 +460,7 @@ function fromFound( ): MutableDocument { hardAssert( !!doc.found, - 0xaa33452b, + 0xaa33, 'Tried to deserialize a found document from a missing document.' ); assertPresent(doc.found.name, 'doc.found.name'); @@ -484,12 +480,12 @@ function fromMissing( ): MutableDocument { hardAssert( !!result.missing, - 0x0f36533f, + 0x0f36, 'Tried to deserialize a missing document from a found document.' ); hardAssert( !!result.readTime, - 0x5995dd5b, + 0x5995, 'Tried to deserialize a missing document without a read time.' ); const key = fromName(serializer, result.missing); @@ -506,7 +502,7 @@ export function fromBatchGetDocumentsResponse( } else if ('missing' in result) { return fromMissing(serializer, result); } - return fail(0x1c42f830, 'invalid batch get response', { result }); + return fail(0x1c42, 'invalid batch get response', { result }); } export function fromWatchChange( @@ -591,7 +587,7 @@ export function fromWatchChange( const targetId = filter.targetId; watchChange = new ExistenceFilterChange(targetId, existenceFilter); } else { - return fail(0x2d517680, 'Unknown change type', { change }); + return fail(0x2d51, 'Unknown change type', { change }); } return watchChange; } @@ -610,7 +606,7 @@ function fromWatchTargetChangeState( } else if (state === 'RESET') { return WatchTargetChangeState.Reset; } else { - return fail(0x9991fe4b, 'Got unexpected TargetChange.state', { state }); + return fail(0x9991, 'Got unexpected TargetChange.state', { state }); } } @@ -654,7 +650,7 @@ export function toMutation( verify: toName(serializer, mutation.key) }; } else { - return fail(0x40d7d046, 'Unknown mutation type', { + return fail(0x40d7, 'Unknown mutation type', { mutationType: mutation.type }); } @@ -712,7 +708,7 @@ export function fromMutation( const key = fromName(serializer, proto.verify); return new VerifyMutation(key, precondition); } else { - return fail(0x05b7ce18, 'unknown mutation proto', { proto }); + return fail(0x05b7, 'unknown mutation proto', { proto }); } } @@ -728,7 +724,7 @@ function toPrecondition( } else if (precondition.exists !== undefined) { return { exists: precondition.exists }; } else { - return fail(0x6b69d5ca, 'Unknown precondition'); + return fail(0x6b69, 'Unknown precondition'); } } @@ -770,7 +766,7 @@ export function fromWriteResults( if (protos && protos.length > 0) { hardAssert( commitTime !== undefined, - 0x38116ed6, + 0x3811, 'Received a write result without a commit time' ); return protos.map(proto => fromWriteResult(proto, commitTime)); @@ -809,7 +805,7 @@ function toFieldTransform( increment: transform.operand }; } else { - throw fail(0x51c28e85, 'Unknown transform', { + throw fail(0x51c2, 'Unknown transform', { transform: fieldTransform.transform }); } @@ -823,7 +819,7 @@ function fromFieldTransform( if ('setToServerValue' in proto) { hardAssert( proto.setToServerValue === 'REQUEST_TIME', - 0x40f61500, + 0x40f6, 'Unknown server value transform proto', { proto } ); @@ -840,7 +836,7 @@ function fromFieldTransform( proto.increment! ); } else { - fail(0x40c8ed17, 'Unknown transform proto', { proto }); + fail(0x40c8, 'Unknown transform proto', { proto }); } const fieldPath = FieldPath.fromServerFormat(proto.fieldPath!); return new FieldTransform(fieldPath, transform!); @@ -859,7 +855,7 @@ export function fromDocumentsTarget( const count = documentsTarget.documents!.length; hardAssert( count === 1, - 0x07ae6b67, + 0x07ae, 'DocumentsTarget contained other than 1 document', { count @@ -993,7 +989,7 @@ export function convertQueryTargetToQuery(target: ProtoQueryTarget): Query { if (fromCount > 0) { hardAssert( fromCount === 1, - 0xfe266d2c, + 0xfe26, 'StructuredQuery.from with more than one collection is not supported.' ); const from = query.from![0]; @@ -1070,7 +1066,7 @@ export function toLabel(purpose: TargetPurpose): string | null { case TargetPurpose.LimboResolution: return 'limbo-document'; default: - return fail(0x713b26a6, 'Unrecognized query purpose', { purpose }); + return fail(0x713b, 'Unrecognized query purpose', { purpose }); } } @@ -1141,7 +1137,7 @@ function fromFilter(filter: ProtoFilter): Filter { } else if (filter.compositeFilter !== undefined) { return fromCompositeFilter(filter); } else { - return fail(0x759192f9, 'Unknown filter', { filter }); + return fail(0x7591, 'Unknown filter', { filter }); } } @@ -1235,9 +1231,9 @@ export function fromOperatorName(op: ProtoFieldFilterOp): Operator { case 'ARRAY_CONTAINS_ANY': return Operator.ARRAY_CONTAINS_ANY; case 'OPERATOR_UNSPECIFIED': - return fail(0xe2fede39, 'Unspecified operator'); + return fail(0xe2fe, 'Unspecified operator'); default: - return fail(0xc54ab3b3, 'Unknown operator'); + return fail(0xc54a, 'Unknown operator'); } } @@ -1250,7 +1246,7 @@ export function fromCompositeOperatorName( case 'OR': return CompositeOperator.OR; default: - return fail(0x040233ad, 'Unknown operator'); + return fail(0x0402, 'Unknown operator'); } } @@ -1286,7 +1282,7 @@ export function toFilter(filter: Filter): ProtoFilter { } else if (filter instanceof CompositeFilter) { return toCompositeFilter(filter); } else { - return fail(0xd65d3916, 'Unrecognized filter type', { filter }); + return fail(0xd65d, 'Unrecognized filter type', { filter }); } } @@ -1371,9 +1367,9 @@ export function fromUnaryFilter(filter: ProtoFilter): Filter { nullValue: 'NULL_VALUE' }); case 'OPERATOR_UNSPECIFIED': - return fail(0xef814a9b, 'Unspecified filter'); + return fail(0xef81, 'Unspecified filter'); default: - return fail(0xed365cf0, 'Unknown filter'); + return fail(0xed36, 'Unknown filter'); } } diff --git a/packages/firestore/src/remote/watch_change.ts b/packages/firestore/src/remote/watch_change.ts index 4b5e8d3756d..a656d8fdf6e 100644 --- a/packages/firestore/src/remote/watch_change.ts +++ b/packages/firestore/src/remote/watch_change.ts @@ -203,7 +203,7 @@ class TargetState { removedDocuments = removedDocuments.add(key); break; default: - fail(0x9481155b, 'Encountered invalid change type', { changeType }); + fail(0x9481, 'Encountered invalid change type', { changeType }); } }); @@ -242,7 +242,7 @@ class TargetState { this.pendingResponses -= 1; hardAssert( this.pendingResponses >= 0, - 0x0ca9c7ff, + 0x0ca9, '`pendingResponses` is less than 0. This indicates that the SDK received more target acks from the server than expected. The SDK should not continue to operate.', { pendingResponses: this.pendingResponses } ); @@ -377,7 +377,7 @@ export class WatchChangeAggregator { } break; default: - fail(0xddd63a72, 'Unknown target watch change state', { + fail(0xddd6, 'Unknown target watch change state', { state: targetChange.state }); } @@ -433,7 +433,7 @@ export class WatchChangeAggregator { } else { hardAssert( expectedCount === 1, - 0x4e2c7755, + 0x4e2d, 'Single document existence filter with count', { expectedCount } ); diff --git a/packages/firestore/src/util/assert.ts b/packages/firestore/src/util/assert.ts index a7b24be42ac..41fac5db241 100644 --- a/packages/firestore/src/util/assert.ts +++ b/packages/firestore/src/util/assert.ts @@ -127,7 +127,7 @@ export function debugAssert( message: string ): asserts assertion { if (!assertion) { - fail(0xdeb06a55e7, message); + fail(0xdeb0e7, message); } } diff --git a/packages/firestore/src/util/async_queue_impl.ts b/packages/firestore/src/util/async_queue_impl.ts index ef365ee22a4..f8c7a995761 100644 --- a/packages/firestore/src/util/async_queue_impl.ts +++ b/packages/firestore/src/util/async_queue_impl.ts @@ -236,7 +236,7 @@ export class AsyncQueueImpl implements AsyncQueue { private verifyNotFailed(): void { if (this.failure) { - fail(0xb815f382, 'AsyncQueue is already failed', { + fail(0xb815, 'AsyncQueue is already failed', { messageOrStack: getMessageOrStack(this.failure) }); } diff --git a/packages/firestore/src/util/input_validation.ts b/packages/firestore/src/util/input_validation.ts index 8dd49323c2e..7fd9967b5a0 100644 --- a/packages/firestore/src/util/input_validation.ts +++ b/packages/firestore/src/util/input_validation.ts @@ -128,7 +128,7 @@ export function valueDescription(input: unknown): string { } else if (typeof input === 'function') { return 'a function'; } else { - return fail(0x30299470, 'Unknown wrong type', { type: typeof input }); + return fail(0x3029, 'Unknown wrong type', { type: typeof input }); } } diff --git a/packages/firestore/src/util/logic_utils.ts b/packages/firestore/src/util/logic_utils.ts index 87e060585cd..b2167c385e9 100644 --- a/packages/firestore/src/util/logic_utils.ts +++ b/packages/firestore/src/util/logic_utils.ts @@ -44,7 +44,7 @@ import { hardAssert } from './assert'; export function computeInExpansion(filter: Filter): Filter { hardAssert( filter instanceof FieldFilter || filter instanceof CompositeFilter, - 0x4e2c7756, + 0x4e2c, 'Only field filters and composite filters are accepted.' ); @@ -91,7 +91,7 @@ export function getDnfTerms(filter: CompositeFilter): Filter[] { hardAssert( isDisjunctiveNormalForm(result), - 0x1cdf2f11, + 0x1cdf, 'computeDistributedNormalForm did not result in disjunctive normal form' ); @@ -159,7 +159,7 @@ function isDisjunctionOfFieldFiltersAndFlatConjunctions( export function computeDistributedNormalForm(filter: Filter): Filter { hardAssert( filter instanceof FieldFilter || filter instanceof CompositeFilter, - 0x84e214a9, + 0x84e2, 'Only field filters and composite filters are accepted.' ); @@ -185,17 +185,17 @@ export function computeDistributedNormalForm(filter: Filter): Filter { hardAssert( newFilter instanceof CompositeFilter, - 0xfbf20f0d, + 0xfbf2, 'field filters are already in DNF form' ); hardAssert( compositeFilterIsConjunction(newFilter), - 0x9d3b3889, + 0x9d3b, 'Disjunction of filters all of which are already in DNF form is itself in DNF form.' ); hardAssert( newFilter.filters.length > 1, - 0xe2477a92, + 0xe247, 'Single-filter composite filters are already in DNF form.' ); @@ -207,12 +207,12 @@ export function computeDistributedNormalForm(filter: Filter): Filter { export function applyDistribution(lhs: Filter, rhs: Filter): Filter { hardAssert( lhs instanceof FieldFilter || lhs instanceof CompositeFilter, - 0x95f42f24, + 0x95f4, 'Only field filters and composite filters are accepted.' ); hardAssert( rhs instanceof FieldFilter || rhs instanceof CompositeFilter, - 0x6381d1d6, + 0x6381, 'Only field filters and composite filters are accepted.' ); @@ -253,7 +253,7 @@ function applyDistributionCompositeFilters( ): Filter { hardAssert( lhs.filters.length > 0 && rhs.filters.length > 0, - 0xbb85c0b2, + 0xbb85, 'Found an empty composite filter' ); @@ -315,7 +315,7 @@ function applyDistributionFieldAndCompositeFilters( export function applyAssociation(filter: Filter): Filter { hardAssert( filter instanceof FieldFilter || filter instanceof CompositeFilter, - 0x2e4a312d, + 0x2e4a, 'Only field filters and composite filters are accepted.' ); diff --git a/packages/firestore/src/util/sorted_map.ts b/packages/firestore/src/util/sorted_map.ts index 0e8ea88f150..023354173d3 100644 --- a/packages/firestore/src/util/sorted_map.ts +++ b/packages/firestore/src/util/sorted_map.ts @@ -511,20 +511,20 @@ export class LLRBNode { // leaves is equal on both sides. This function verifies that or asserts. protected check(): number { if (this.isRed() && this.left.isRed()) { - throw fail(0xaad28107, 'Red node has red child', { + throw fail(0xaad2, 'Red node has red child', { key: this.key, value: this.value }); } if (this.right.isRed()) { - throw fail(0x37210df9, 'Right child of (`key`, `value`) is red', { + throw fail(0x3721, 'Right child of (`key`, `value`) is red', { key: this.key, value: this.value }); } const blackDepth = (this.left as LLRBNode).check(); if (blackDepth !== (this.right as LLRBNode).check()) { - throw fail(0x6d2d0edf, 'Black depths differ'); + throw fail(0x6d2d, 'Black depths differ'); } else { return blackDepth + (this.isRed() ? 0 : 1); } @@ -534,19 +534,19 @@ export class LLRBNode { // Represents an empty node (a leaf node in the Red-Black Tree). export class LLRBEmptyNode { get key(): never { - throw fail(0xe1a6d03e, 'LLRBEmptyNode has no key.'); + throw fail(0xe1a6, 'LLRBEmptyNode has no key.'); } get value(): never { - throw fail(0x3f0d8793, 'LLRBEmptyNode has no value.'); + throw fail(0x3f0d, 'LLRBEmptyNode has no value.'); } get color(): never { - throw fail(0x41575ede, 'LLRBEmptyNode has no color.'); + throw fail(0x4157, 'LLRBEmptyNode has no color.'); } get left(): never { - throw fail(0x741e2acf, 'LLRBEmptyNode has no left child.'); + throw fail(0x741e, 'LLRBEmptyNode has no left child.'); } get right(): never { - throw fail(0x901e9907, 'LLRBEmptyNode has no right child.'); + throw fail(0x901e, 'LLRBEmptyNode has no right child.'); } size = 0; diff --git a/packages/firestore/test/unit/index/ordered_code_writer.test.ts b/packages/firestore/test/unit/index/ordered_code_writer.test.ts index 37f26befc00..27956c730ee 100644 --- a/packages/firestore/test/unit/index/ordered_code_writer.test.ts +++ b/packages/firestore/test/unit/index/ordered_code_writer.test.ts @@ -250,7 +250,7 @@ function getBytes(val: unknown): { asc: Uint8Array; desc: Uint8Array } { } else { hardAssert( val instanceof Uint8Array, - 0xa10f7758, + 0xa10f, 'val is not instance of Uint8Array', { val diff --git a/packages/firestore/test/unit/local/indexeddb_persistence.test.ts b/packages/firestore/test/unit/local/indexeddb_persistence.test.ts index e0a04b21d70..1240c977cee 100644 --- a/packages/firestore/test/unit/local/indexeddb_persistence.test.ts +++ b/packages/firestore/test/unit/local/indexeddb_persistence.test.ts @@ -1609,6 +1609,6 @@ function toLegacyDbRemoteDocument( parentPath }; } else { - return fail(0x6bb7d8aa, 'Unexpected Document ', { document }); + return fail(0x6bb7, 'Unexpected Document ', { document }); } } diff --git a/packages/firestore/test/unit/local/simple_db.test.ts b/packages/firestore/test/unit/local/simple_db.test.ts index ca1f2f911d4..207e454fb5b 100644 --- a/packages/firestore/test/unit/local/simple_db.test.ts +++ b/packages/firestore/test/unit/local/simple_db.test.ts @@ -363,7 +363,7 @@ describe('SimpleDb', () => { iterated.push(value); return PersistencePromise.reject(new Error('Expected error')); }) - .next(() => fail(0xb9b32f7f, 'Promise not rejected')) + .next(() => fail(0xb9b3, 'Promise not rejected')) .catch(err => { expect(err.message).to.eq('Expected error'); expect(iterated).to.deep.equal([testData[0]]); diff --git a/packages/firestore/test/unit/specs/spec_builder.ts b/packages/firestore/test/unit/specs/spec_builder.ts index 5b5ae4e7af4..3e52c5873b9 100644 --- a/packages/firestore/test/unit/specs/spec_builder.ts +++ b/packages/firestore/test/unit/specs/spec_builder.ts @@ -674,7 +674,7 @@ export class SpecBuilder { } else if (doc.isNoDocument()) { // Don't send any updates } else { - fail(0xd71cf527, 'Unknown parameter', { doc }); + fail(0xd71c, 'Unknown parameter', { doc }); } this.watchCurrents(query, 'resume-token-' + version); this.watchSnapshots(version); @@ -1149,7 +1149,7 @@ export class SpecBuilder { userDataWriter.convertValue(filter.value) ] as SpecQueryFilter; } else { - return fail(0x0e51d502, 'Unknown filter', { filter }); + return fail(0x0e51, 'Unknown filter', { filter }); } }); } @@ -1211,7 +1211,7 @@ export class SpecBuilder { ): void { if (!(resume?.resumeToken || resume?.readTime) && resume?.expectedCount) { fail( - 0xc9a16b57, + 0xc9a1, 'Expected count is present without a resume token or read time.' ); } @@ -1277,7 +1277,7 @@ export class SpecBuilder { // TODO(dimond): add support for query for doc and limbo doc at the same // time? fail( - 0x6e178880, + 0x6e17, 'Found both query and limbo doc with target ID, not supported yet' ); } diff --git a/packages/firestore/test/unit/specs/spec_test_components.ts b/packages/firestore/test/unit/specs/spec_test_components.ts index 01e0fb30662..017afe1d924 100644 --- a/packages/firestore/test/unit/specs/spec_test_components.ts +++ b/packages/firestore/test/unit/specs/spec_test_components.ts @@ -415,7 +415,7 @@ export class MockConnection implements Connection { } else if (request.removeTarget) { delete this.activeTargets[request.removeTarget]; } else { - fail(0x782d0a75, 'Invalid listen request'); + fail(0x782d, 'Invalid listen request'); } }, closeFn: () => { diff --git a/packages/firestore/test/unit/specs/spec_test_runner.ts b/packages/firestore/test/unit/specs/spec_test_runner.ts index bc625ca5bf0..ee0af0b8bf8 100644 --- a/packages/firestore/test/unit/specs/spec_test_runner.ts +++ b/packages/firestore/test/unit/specs/spec_test_runner.ts @@ -477,7 +477,7 @@ abstract class TestRunner { ? this.doFailDatabase(step.failDatabase!) : this.doRecoverDatabase(); } else { - return fail(0x6bb33efa, 'Unknown step: ' + JSON.stringify(step)); + return fail(0x6bb3, 'Unknown step: ' + JSON.stringify(step)); } } @@ -724,7 +724,7 @@ abstract class TestRunner { ); return this.doWatchEvent(change); } else { - return fail(0xdcc33300, 'Either doc or docs must be set'); + return fail(0xdcc3, 'Either doc or docs must be set'); } } diff --git a/packages/firestore/test/unit/util/assert.test.ts b/packages/firestore/test/unit/util/assert.test.ts index a640433d78e..4c13f8db6b1 100644 --- a/packages/firestore/test/unit/util/assert.test.ts +++ b/packages/firestore/test/unit/util/assert.test.ts @@ -21,35 +21,35 @@ import { fail, hardAssert } from '../../../src/util/assert'; describe('hardAssert', () => { it('includes the error code as hex', () => { - expect(() => hardAssert(false, 0x12345678, 'a message here')).to.throw( + expect(() => hardAssert(false, 0x1234, 'a message here')).to.throw( '12345678' ); }); it('includes the context', () => { expect(() => - hardAssert(false, 0x12345678, 'a message here', { foo: 'bar baz' }) + hardAssert(false, 0x1234, 'a message here', { foo: 'bar baz' }) ).to.throw('bar baz'); }); it('includes the message', () => { expect(() => - hardAssert(false, 0x12345678, 'a message here', { foo: 'bar baz' }) + hardAssert(false, 0x1234, 'a message here', { foo: 'bar baz' }) ).to.throw('a message here'); }); describe('without message', () => { it('includes the error code as hex', () => { - expect(() => hardAssert(false, 0x12345678)).to.throw('12345678'); + expect(() => hardAssert(false, 0x1234)).to.throw('12345678'); }); it('includes the context', () => { - expect(() => hardAssert(false, 0x12345678, { foo: 'bar baz' })).to.throw( + expect(() => hardAssert(false, 0x1234, { foo: 'bar baz' })).to.throw( 'bar baz' ); }); it('includes a default message', () => { - expect(() => hardAssert(false, 0x12345678, { foo: 'bar baz' })).to.throw( + expect(() => hardAssert(false, 0x1234, { foo: 'bar baz' })).to.throw( 'Unexpected state' ); }); @@ -58,31 +58,31 @@ describe('hardAssert', () => { describe('fail', () => { it('includes the error code as hex', () => { - expect(() => fail(0x12345678, 'a message here')).to.throw('12345678'); + expect(() => fail(0x1234, 'a message here')).to.throw('12345678'); }); it('includes the context', () => { - expect(() => - fail(0x12345678, 'a message here', { foo: 'bar baz' }) - ).to.throw('bar baz'); + expect(() => fail(0x1234, 'a message here', { foo: 'bar baz' })).to.throw( + 'bar baz' + ); }); it('includes the message', () => { - expect(() => - fail(0x12345678, 'a message here', { foo: 'bar baz' }) - ).to.throw('a message here'); + expect(() => fail(0x1234, 'a message here', { foo: 'bar baz' })).to.throw( + 'a message here' + ); }); describe('without message', () => { it('includes the error code as hex', () => { - expect(() => fail(0x12345678)).to.throw('12345678'); + expect(() => fail(0x1234)).to.throw('12345678'); }); it('includes the context', () => { - expect(() => fail(0x12345678, { foo: 'bar baz' })).to.throw('bar baz'); + expect(() => fail(0x1234, { foo: 'bar baz' })).to.throw('bar baz'); }); it('includes a default message', () => { - expect(() => fail(0x12345678, { foo: 'bar baz' })).to.throw( + expect(() => fail(0x1234, { foo: 'bar baz' })).to.throw( 'Unexpected state' ); }); diff --git a/packages/firestore/test/unit/util/async_queue.test.ts b/packages/firestore/test/unit/util/async_queue.test.ts index 55f9f8547b1..48aa02fe298 100644 --- a/packages/firestore/test/unit/util/async_queue.test.ts +++ b/packages/firestore/test/unit/util/async_queue.test.ts @@ -247,7 +247,7 @@ describe('AsyncQueue', () => { const deferred = new Deferred(); queue.enqueueRetryable(async () => { deferred.resolve(); - throw fail(0x1576712c, 'Simulated test failure'); + throw fail(0x1576, 'Simulated test failure'); }); await deferred.promise; await expect( diff --git a/packages/firestore/test/util/helpers.ts b/packages/firestore/test/util/helpers.ts index c7b58870564..1ddaf174b19 100644 --- a/packages/firestore/test/util/helpers.ts +++ b/packages/firestore/test/util/helpers.ts @@ -867,13 +867,13 @@ export function expectEqual(left: any, right: any, message?: string): void { message = message || ''; if (typeof left.isEqual !== 'function') { return fail( - 0x800468bf, + 0x8004, JSON.stringify(left) + ' does not support isEqual (left) ' + message ); } if (typeof right.isEqual !== 'function') { return fail( - 0xebc9184f, + 0xebc9, JSON.stringify(right) + ' does not support isEqual (right) ' + message ); } diff --git a/packages/firestore/test/util/spec_test_helpers.ts b/packages/firestore/test/util/spec_test_helpers.ts index 09b8960c735..73378a361a3 100644 --- a/packages/firestore/test/util/spec_test_helpers.ts +++ b/packages/firestore/test/util/spec_test_helpers.ts @@ -101,7 +101,7 @@ export function encodeWatchChange( }; } return fail( - 0xf8e51f87, + 0xf8e5, 'Unrecognized watch change: ' + JSON.stringify(watchChange) ); } @@ -121,6 +121,6 @@ function encodeTargetChangeTargetChangeType( case WatchTargetChangeState.Reset: return 'RESET'; default: - return fail(0x368b5cb4, 'Unknown WatchTargetChangeState: ' + state); + return fail(0x368b, 'Unknown WatchTargetChangeState: ' + state); } } diff --git a/packages/firestore/test/util/test_platform.ts b/packages/firestore/test/util/test_platform.ts index 1f1a9dd0d33..a0eac93cbc9 100644 --- a/packages/firestore/test/util/test_platform.ts +++ b/packages/firestore/test/util/test_platform.ts @@ -64,7 +64,7 @@ export class FakeWindow implements WindowLike { // listeners. break; default: - fail(0xe53d2167, `MockWindow doesn't support events of type '${type}'`); + fail(0xe53d, `MockWindow doesn't support events of type '${type}'`); } } From a86219b6f1663f57b5dd09f29777d6b0a84524e3 Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Wed, 2 Apr 2025 12:30:11 -0600 Subject: [PATCH 06/18] Deduplicated an error code --- packages/firestore/src/api/credentials.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/firestore/src/api/credentials.ts b/packages/firestore/src/api/credentials.ts index 69d8ebc3f05..c1b10d61057 100644 --- a/packages/firestore/src/api/credentials.ts +++ b/packages/firestore/src/api/credentials.ts @@ -261,7 +261,7 @@ export class FirebaseAuthCredentialsProvider ): void { hardAssert( this.tokenListener === undefined, - 0xa539, + 0xa540, 'Token listener already added' ); let lastTokenId = this.tokenCounter; From 8b7c3f22996340318e75dc7c170360cd6c2ad01a Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Wed, 2 Apr 2025 12:51:47 -0600 Subject: [PATCH 07/18] Fix tests --- packages/firestore/test/unit/util/assert.test.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/firestore/test/unit/util/assert.test.ts b/packages/firestore/test/unit/util/assert.test.ts index 4c13f8db6b1..e865337a3a3 100644 --- a/packages/firestore/test/unit/util/assert.test.ts +++ b/packages/firestore/test/unit/util/assert.test.ts @@ -21,9 +21,7 @@ import { fail, hardAssert } from '../../../src/util/assert'; describe('hardAssert', () => { it('includes the error code as hex', () => { - expect(() => hardAssert(false, 0x1234, 'a message here')).to.throw( - '12345678' - ); + expect(() => hardAssert(false, 0x1234, 'a message here')).to.throw('1234'); }); it('includes the context', () => { @@ -40,7 +38,7 @@ describe('hardAssert', () => { describe('without message', () => { it('includes the error code as hex', () => { - expect(() => hardAssert(false, 0x1234)).to.throw('12345678'); + expect(() => hardAssert(false, 0x1234)).to.throw('1234'); }); it('includes the context', () => { @@ -58,7 +56,7 @@ describe('hardAssert', () => { describe('fail', () => { it('includes the error code as hex', () => { - expect(() => fail(0x1234, 'a message here')).to.throw('12345678'); + expect(() => fail(0x1234, 'a message here')).to.throw('1234'); }); it('includes the context', () => { @@ -75,7 +73,7 @@ describe('fail', () => { describe('without message', () => { it('includes the error code as hex', () => { - expect(() => fail(0x1234)).to.throw('12345678'); + expect(() => fail(0x1234)).to.throw('1234'); }); it('includes the context', () => { From 26cdf1732efc654ba6b92b4a45ce45dff9069867 Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Wed, 2 Apr 2025 18:25:24 -0600 Subject: [PATCH 08/18] Error codes numerically increasing starting at 0 with tools to create new and check --- packages/firestore/package.json | 8 +- packages/firestore/scripts/error-code-tool.js | 17 + packages/firestore/scripts/error-code-tool.ts | 367 ++++++++++++++++++ packages/firestore/src/api/credentials.ts | 14 +- packages/firestore/src/api/snapshot.ts | 2 +- .../firestore/src/core/component_provider.ts | 2 +- packages/firestore/src/core/filter.ts | 4 +- packages/firestore/src/core/query.ts | 2 +- .../firestore/src/core/sync_engine_impl.ts | 12 +- packages/firestore/src/core/transaction.ts | 2 +- packages/firestore/src/core/view.ts | 2 +- packages/firestore/src/core/view_snapshot.ts | 2 +- .../src/index/firestore_index_value_writer.ts | 2 +- .../firestore/src/lite-api/reference_impl.ts | 6 +- .../firestore/src/lite-api/transaction.ts | 12 +- .../src/lite-api/user_data_reader.ts | 2 +- .../src/lite-api/user_data_writer.ts | 4 +- .../src/local/encoded_resource_path.ts | 8 +- .../src/local/indexeddb_index_manager.ts | 4 +- .../local/indexeddb_mutation_batch_impl.ts | 4 +- .../src/local/indexeddb_mutation_queue.ts | 18 +- .../local/indexeddb_remote_document_cache.ts | 2 +- .../src/local/indexeddb_schema_converter.ts | 4 +- .../src/local/indexeddb_sentinels.ts | 2 +- .../src/local/indexeddb_target_cache.ts | 4 +- .../firestore/src/local/local_serializer.ts | 4 +- .../firestore/src/local/local_store_impl.ts | 4 +- .../src/local/memory_mutation_queue.ts | 2 +- .../firestore/src/local/memory_persistence.ts | 5 +- .../src/local/memory_remote_document_cache.ts | 2 +- .../src/local/persistence_promise.ts | 2 +- .../src/local/shared_client_state.ts | 2 +- packages/firestore/src/model/document.ts | 5 +- packages/firestore/src/model/mutation.ts | 2 +- .../firestore/src/model/mutation_batch.ts | 2 +- packages/firestore/src/model/normalize.ts | 4 +- packages/firestore/src/model/path.ts | 4 +- .../src/model/target_index_matcher.ts | 2 +- packages/firestore/src/model/values.ts | 14 +- .../platform/browser/webchannel_connection.ts | 8 +- .../src/platform/node/grpc_connection.ts | 2 +- packages/firestore/src/remote/datastore.ts | 4 +- .../firestore/src/remote/persistent_stream.ts | 6 +- packages/firestore/src/remote/rpc_error.ts | 8 +- packages/firestore/src/remote/serializer.ts | 56 +-- packages/firestore/src/remote/watch_change.ts | 8 +- packages/firestore/src/util/assert.ts | 2 +- .../firestore/src/util/async_queue_impl.ts | 2 +- .../firestore/src/util/input_validation.ts | 2 +- packages/firestore/src/util/logic_utils.ts | 20 +- packages/firestore/src/util/sorted_map.ts | 16 +- 51 files changed, 532 insertions(+), 162 deletions(-) create mode 100644 packages/firestore/scripts/error-code-tool.js create mode 100644 packages/firestore/scripts/error-code-tool.ts diff --git a/packages/firestore/package.json b/packages/firestore/package.json index d9cb6c263b5..284562742f5 100644 --- a/packages/firestore/package.json +++ b/packages/firestore/package.json @@ -27,7 +27,7 @@ "test:lite:browser": "karma start --lite", "test:lite:browser:nameddb": "karma start --lite --databaseId=test-db", "test:lite:browser:debug": "karma start --browsers=Chrome --lite --auto-watch", - "test": "run-s --npm-path npm lint test:all", + "test": "run-s --npm-path npm lint error-code:check test:all", "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:all:ci", "test:all:ci": "run-s --npm-path npm test:browser test:travis test:lite:browser test:browser:prod:nameddb test:lite:browser:nameddb", "test:all": "run-p --npm-path npm test:browser test:lite:browser test:travis test:minified test:browser:prod:nameddb test:lite:browser:nameddb", @@ -52,7 +52,11 @@ "api-report:api-json": "rm -rf temp && api-extractor run --local --verbose", "api-report": "run-s --npm-path npm api-report:main api-report:lite && yarn api-report:api-json", "doc": "api-documenter markdown --input temp --output docs", - "typings:public": "node ../../scripts/build/use_typings.js ./dist/index.d.ts" + "typings:public": "node ../../scripts/build/use_typings.js ./dist/index.d.ts", + "error-code:check": "ts-node scripts/error-code-tool.js --dir=src --check", + "error-code:generate": "ts-node scripts/error-code-tool.js --dir=src --new", + "error-code:list": "ts-node scripts/error-code-tool.js --dir=src --list", + "error-code:find": "ts-node scripts/error-code-tool.js --dir=src --find" }, "exports": { ".": { diff --git a/packages/firestore/scripts/error-code-tool.js b/packages/firestore/scripts/error-code-tool.js new file mode 100644 index 00000000000..609f45fe00b --- /dev/null +++ b/packages/firestore/scripts/error-code-tool.js @@ -0,0 +1,17 @@ +"use strict"; +/** + * @license + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * 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]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)){errorCode_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,errorCode:errorCode_1!==null&&errorCode_1!==void 0?errorCode_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;_i1){duplicatesFound=true;console.error('\nDuplicate error code "'.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 error codes found.")}else{process.exit(1)}}function handleNew(occurrences){var maxCode=0;occurrences.forEach((function(occ){if(occ.errorCode>maxCode){maxCode=occ.errorCode}}));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 {errorCode}",type:"string",nargs:1}).option("list",{alias:"L",describe:"List all unique error codes found (default action)",type:"boolean"}).option("new",{alias:"N",describe:"Suggest a new error code based on existing ones",type:"boolean"}).option("check",{alias:"C",describe:"Check for duplicate usage of error codes",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 error codes..."));allOccurrences=findFunctionCalls(filesToScan);log("Scan complete. Found ".concat(allOccurrences.length," potential error code 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 diff --git a/packages/firestore/scripts/error-code-tool.ts b/packages/firestore/scripts/error-code-tool.ts new file mode 100644 index 00000000000..10e6d9a087f --- /dev/null +++ b/packages/firestore/scripts/error-code-tool.ts @@ -0,0 +1,367 @@ +/** + * @license + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable no-console */ + +import * as fs from "fs"; +import * as path from "path"; + +import * as ts from "typescript"; +import yargs from "yargs"; +import { hideBin } from "yargs/helpers"; + +let isVerbose: boolean = false; + +function log(message: any): void { + if (isVerbose) { + console.log(message); + } +} + +// Define the names of the functions we are looking for +const targetFunctionNames: Set = new Set(["fail", "hardAssert"]); + +// Interface to store information about found call sites +interface CallSiteInfo { + fileName: string; + functionName: string; + line: number; + character: number; + argumentsText: string[]; // Added to store argument text + errorMessage: string | undefined; + errorCode: number; +} + +/** + * Recursively finds all files with .ts extensions in a directory. + * @param dirPath The absolute path to the directory to scan. + * @returns An array of absolute paths to the found TypeScript files. + */ +function getTsFilesRecursive(dirPath: string): string[] { + let tsFiles: string[] = []; + try { + const entries = fs.readdirSync(dirPath, { withFileTypes: true }); + + for (const entry of entries) { + const fullPath = path.join(dirPath, entry.name); + + if (entry.isDirectory()) { + // Ignore node_modules for performance and relevance + if (entry.name === 'node_modules') { + // log(`Skipping node_modules directory: ${fullPath}`); + continue; + } + // Recursively scan subdirectories + tsFiles = tsFiles.concat(getTsFilesRecursive(fullPath)); + } else if (entry.isFile() && (entry.name.endsWith(".ts"))) { + // Exclude declaration files (.d.ts) as they usually don't contain implementation + if (!entry.name.endsWith(".d.ts")) { + tsFiles.push(fullPath); + } + } + } + } catch (error: any) { + console.error(`Error reading directory ${dirPath}: ${error.message}`); + // Optionally, re-throw or handle differently + } + return tsFiles; +} + + +/** + * Analyzes TypeScript source files to find calls to specific functions. + * @param filePaths An array of absolute paths to the TypeScript files to scan. + * @returns An array of objects detailing the found call sites. + */ +function findFunctionCalls(filePaths: string[]): CallSiteInfo[] { + const foundCalls: CallSiteInfo[] = []; + + for (const filePath of filePaths) { + try { + // Read the file content + const sourceText = fs.readFileSync(filePath, "utf8"); + + // Create the SourceFile AST node + const sourceFile = ts.createSourceFile( + path.basename(filePath), // Use basename for AST node name + sourceText, + ts.ScriptTarget.ESNext, // Or your project's target + true, // Set parent pointers + ts.ScriptKind.Unknown // Detect TS vs TSX automatically + ); + + // Define the visitor function + const visit = (node: ts.Node) :void => { + // Check if the node is a CallExpression (e.g., myFunction(...)) + if (ts.isCallExpression(node)) { + let functionName: string | null = null; + const expression = node.expression; + + // Check if the call is directly to an identifier (e.g., fail()) + if (ts.isIdentifier(expression)) { + functionName = expression.text; + } + // Check if the call is to a property access (e.g., utils.fail(), this.hardAssert()) + else if (ts.isPropertyAccessExpression(expression)) { + // We only care about the final property name being called + functionName = expression.name.text; + } + // Add checks for other forms if needed + + // If we found a function name, and it's one we're looking for + if (functionName && targetFunctionNames.has(functionName)) { + // Get line and character number + const { line, character } = ts.getLineAndCharacterOfPosition( + sourceFile, + node.getStart() // Get start position of the call expression + ); + + // --- Extract Arguments --- + const argsText: string[] = []; + let errorMessage: string | undefined; + let errorCode: number | undefined; + if (node.arguments && node.arguments.length > 0) { + node.arguments.forEach((arg: ts.Expression) => { + // Get the source text of the argument node + argsText.push(arg.getText(sourceFile)); + + if (ts.isStringLiteral(arg)) { + errorMessage = arg.getText(sourceFile); + } + else if (ts.isNumericLiteral(arg)) { + errorCode = parseInt(arg.getText(sourceFile), 10); + } + }); + } + // --- End Extract Arguments --- + + // Store the information (add 1 to line/char for 1-based indexing) + foundCalls.push({ + fileName: filePath, // Store the full path + functionName, + line: line + 1, + character: character + 1, + argumentsText: argsText, // Store the extracted arguments, + errorMessage, + errorCode: errorCode ?? -1 + }); + } + } + + // Continue traversing down the AST + ts.forEachChild(node, visit); + }; + + // Start traversal from the root SourceFile node + visit(sourceFile); + + } catch (error: any) { + console.error(`Error processing file ${filePath}: ${error.message}`); + } + } // End loop through filePaths + + return foundCalls; +} + + +// --- Action Handlers --- + +function handleList(occurrences: CallSiteInfo[]): void { + if (occurrences.length === 0) { + log("No error codes found."); + return; + } + + occurrences.sort((a, b) => a.errorCode - b.errorCode).forEach((call) => { + console.log( + `CODE: ${call.errorCode}; MESSAGE: ${call.errorMessage}; SOURCE: '${call.functionName}' call at ${path.relative(process.cwd(), call.fileName)}:${call.line}:${call.character}` + ); + }); + +} + +function handleFind(occurrences: CallSiteInfo[], targetCode: string | number): void { + // Normalize target code for comparison if necessary (e.g., string vs number) + const target = typeof targetCode === 'number' ? targetCode : targetCode.toString(); + + const foundLocations = occurrences.filter(o => String(o.errorCode) === String(target)); // Compare as strings + + if (foundLocations.length === 0) { + log(`Error code "${targetCode}" not found.`); + process.exit(1); + } + + handleList(foundLocations); +} + +function handleCheck(occurrences: CallSiteInfo[]): void { + if (occurrences.length === 0) { + log("No error codes found to check for duplicates."); + return; + } + const codeCounts: { [code: string]: CallSiteInfo[] } = {}; + + occurrences.forEach(occ => { + const codeStr = String(occ.errorCode); // Use string representation as key + if (!codeCounts[codeStr]) { + codeCounts[codeStr] = []; + } + codeCounts[codeStr].push(occ); + }); + + let duplicatesFound = false; + log("Checking for duplicate error code usage:"); + Object.entries(codeCounts).forEach(([code, locations]) => { + if (locations.length > 1) { + duplicatesFound = true; + console.error(`\nDuplicate error code "${code}" found at ${locations.length} locations:`); + locations.forEach(loc => { + const relativePath = path.relative(process.cwd(), loc.fileName); + console.error(`- ${relativePath}:${loc.line}:${loc.character}`); + }); + } + }); + + if (!duplicatesFound) { + log("No duplicate error codes found."); + } + else { + process.exit(1); + } +} + +function handleNew(occurrences: CallSiteInfo[]): void { + // --- Simple Numeric Scheme: Find max numeric code and add 1 --- + let maxCode = 0; + + occurrences.forEach(occ => { + if (occ.errorCode > maxCode) { + maxCode = occ.errorCode; + } + }); + + if (occurrences.length === 0) { + log("0"); + return; + } + + const newCode = maxCode + 1; + console.log(newCode); +} + +// --- Main Execution --- +async function main(): Promise { + const argv = yargs(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 {errorCode}", + type: "string", + nargs: 1, + }) + .option("list", { + alias: "L", + describe: "List all unique error codes found (default action)", + type: "boolean", + }) + .option("new", { + alias: "N", + describe: "Suggest a new error code based on existing ones", + type: "boolean", + }) + .option("check", { + alias: "C", + describe: "Check for duplicate usage of error codes", + type: "boolean", + }) + .check((argv) => { + // Enforce mutual exclusivity among options *within* the scan command + const 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(); // Execute parsing + + // Extract directory path (safe due to demandOption) + const targetDirectory = path.resolve(argv['dir'] as string); + + // set verbosity + isVerbose = !!argv['verbose']; + + // Validate directory + try { + const stats = fs.statSync(targetDirectory); + if (!stats.isDirectory()) { + console.error(`Error: Provided path is not a directory: ${targetDirectory}`); + process.exit(1); + } + } catch (error: any) { + console.error(`Error accessing directory ${targetDirectory}: ${error.message}`); + process.exit(1); + } + + log(`Scanning directory: ${targetDirectory}`); + const filesToScan = getTsFilesRecursive(targetDirectory); + + if (filesToScan.length === 0) { + log("No relevant .ts or .tsx files found."); + process.exit(0); + } + log(`Found ${filesToScan.length} files. Analyzing for error codes...`); + + const allOccurrences = findFunctionCalls(filesToScan); + log(`Scan complete. Found ${allOccurrences.length} potential error code occurrences.`); + + // Determine action based on flags + if (argv['find']) { + handleFind(allOccurrences, argv['find']); + } else if (argv['new']) { + handleNew(allOccurrences); + } else if (argv['check']) { + handleCheck(allOccurrences); + } else { + // Default action is List (-L or no flag) + handleList(allOccurrences); + } +} + +// Run the main function +main().catch(error => { + console.error("\nAn unexpected error occurred:"); + console.error(error); + process.exit(1); +}); + + + diff --git a/packages/firestore/src/api/credentials.ts b/packages/firestore/src/api/credentials.ts index c1b10d61057..dd8a2d0d4d6 100644 --- a/packages/firestore/src/api/credentials.ts +++ b/packages/firestore/src/api/credentials.ts @@ -207,7 +207,7 @@ export class LiteAuthCredentialsProvider implements CredentialsProvider { if (tokenData) { hardAssert( typeof tokenData.accessToken === 'string', - 0xa539, + 92, 'Invalid tokenData returned from getToken()', { tokenData } ); @@ -261,7 +261,7 @@ export class FirebaseAuthCredentialsProvider ): void { hardAssert( this.tokenListener === undefined, - 0xa540, + 93, 'Token listener already added' ); let lastTokenId = this.tokenCounter; @@ -360,7 +360,7 @@ export class FirebaseAuthCredentialsProvider if (tokenData) { hardAssert( typeof tokenData.accessToken === 'string', - 0x7c5d, + 94, 'Invalid tokenData returned from getToken()', { tokenData } ); @@ -391,7 +391,7 @@ export class FirebaseAuthCredentialsProvider const currentUid = this.auth && this.auth.getUid(); hardAssert( currentUid === null || typeof currentUid === 'string', - 0x0807, + 95, 'Received invalid UID', { currentUid } ); @@ -520,7 +520,7 @@ export class FirebaseAppCheckTokenProvider ): void { hardAssert( this.tokenListener === undefined, - 0x0db8, + 96, 'Token listener already added' ); @@ -596,7 +596,7 @@ export class FirebaseAppCheckTokenProvider if (tokenResult) { hardAssert( typeof tokenResult.token === 'string', - 0xae0e, + 97, 'Invalid tokenResult returned from getToken()', { tokenResult } ); @@ -669,7 +669,7 @@ export class LiteAppCheckTokenProvider implements CredentialsProvider { if (tokenResult) { hardAssert( typeof tokenResult.token === 'string', - 0x0d8e, + 98, 'Invalid tokenResult returned from getToken()', { tokenResult } ); diff --git a/packages/firestore/src/api/snapshot.ts b/packages/firestore/src/api/snapshot.ts index 669ac26cafe..0e7f43c5642 100644 --- a/packages/firestore/src/api/snapshot.ts +++ b/packages/firestore/src/api/snapshot.ts @@ -749,7 +749,7 @@ export function resultChangeType(type: ChangeType): DocumentChangeType { case ChangeType.Removed: return 'removed'; default: - return fail(0xf03d, 'Unknown change type', { type }); + return fail(91, 'Unknown change type', { type }); } } diff --git a/packages/firestore/src/core/component_provider.ts b/packages/firestore/src/core/component_provider.ts index 183a1046cc9..3d1a3ffdc60 100644 --- a/packages/firestore/src/core/component_provider.ts +++ b/packages/firestore/src/core/component_provider.ts @@ -197,7 +197,7 @@ export class LruGcMemoryOfflineComponentProvider extends MemoryOfflineComponentP ): Scheduler | null { hardAssert( this.persistence.referenceDelegate instanceof MemoryLruDelegate, - 0xb743, + 17, 'referenceDelegate is expected to be an instance of MemoryLruDelegate.' ); diff --git a/packages/firestore/src/core/filter.ts b/packages/firestore/src/core/filter.ts index d8907387057..f7ecec783c2 100644 --- a/packages/firestore/src/core/filter.ts +++ b/packages/firestore/src/core/filter.ts @@ -168,7 +168,7 @@ export class FieldFilter extends Filter { case Operator.GREATER_THAN_OR_EQUAL: return comparison >= 0; default: - return fail(0xb8a2, 'Unknown FieldFilter operator', { + return fail(8, 'Unknown FieldFilter operator', { operator: this.op }); } @@ -317,7 +317,7 @@ export function filterEquals(f1: Filter, f2: Filter): boolean { } else if (f1 instanceof CompositeFilter) { return compositeFilterEquals(f1, f2); } else { - fail(0x4bef, 'Only FieldFilters and CompositeFilters can be compared'); + fail(9, 'Only FieldFilters and CompositeFilters can be compared'); } } diff --git a/packages/firestore/src/core/query.ts b/packages/firestore/src/core/query.ts index ca875bbb14d..97c378d4096 100644 --- a/packages/firestore/src/core/query.ts +++ b/packages/firestore/src/core/query.ts @@ -578,6 +578,6 @@ export function compareDocs( case Direction.DESCENDING: return -1 * comparison; default: - return fail(0x4d4e, 'Unknown direction', { direction: orderBy.dir }); + return fail(19, 'Unknown direction', { direction: orderBy.dir }); } } diff --git a/packages/firestore/src/core/sync_engine_impl.ts b/packages/firestore/src/core/sync_engine_impl.ts index 404d4663a47..7e430c5ddcb 100644 --- a/packages/firestore/src/core/sync_engine_impl.ts +++ b/packages/firestore/src/core/sync_engine_impl.ts @@ -579,7 +579,7 @@ export async function syncEngineApplyRemoteEvent( targetChange.modifiedDocuments.size + targetChange.removedDocuments.size <= 1, - 0x5858, + 10, 'Limbo resolution for single document contains multiple changes.' ); if (targetChange.addedDocuments.size > 0) { @@ -587,13 +587,13 @@ export async function syncEngineApplyRemoteEvent( } else if (targetChange.modifiedDocuments.size > 0) { hardAssert( limboResolution.receivedDocument, - 0x390f, + 11, 'Received change for limbo target document without add.' ); } else if (targetChange.removedDocuments.size > 0) { hardAssert( limboResolution.receivedDocument, - 0xa4f3, + 12, 'Received remove for limbo target document without add.' ); limboResolution.receivedDocument = false; @@ -997,7 +997,7 @@ function updateTrackedLimbos( removeLimboTarget(syncEngineImpl, limboChange.key); } } else { - fail(0x4d4f, 'Unknown limbo change', { limboChange }); + fail(13, 'Unknown limbo change', { limboChange }); } } } @@ -1320,7 +1320,7 @@ export async function syncEngineApplyBatchState( batchId ); } else { - fail(0x1a40, `Unknown batchState`, { batchState }); + fail(14, `Unknown batchState`, { batchState }); } await syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngineImpl, documents); @@ -1563,7 +1563,7 @@ export async function syncEngineApplyTargetState( break; } default: - fail(0xfa9b, 'Unexpected target state', state); + fail(15, 'Unexpected target state', state); } } } diff --git a/packages/firestore/src/core/transaction.ts b/packages/firestore/src/core/transaction.ts index d3dc6ee8a89..b7aea509601 100644 --- a/packages/firestore/src/core/transaction.ts +++ b/packages/firestore/src/core/transaction.ts @@ -124,7 +124,7 @@ export class Transaction { // Represent a deleted doc using SnapshotVersion.min(). docVersion = SnapshotVersion.min(); } else { - throw fail(0xc542, 'Document in a transaction was a ', { + throw fail(18, 'Document in a transaction was a ', { documentName: doc.constructor.name }); } diff --git a/packages/firestore/src/core/view.ts b/packages/firestore/src/core/view.ts index e0909de938f..eb5ef4a5a92 100644 --- a/packages/firestore/src/core/view.ts +++ b/packages/firestore/src/core/view.ts @@ -505,7 +505,7 @@ function compareChangeType(c1: ChangeType, c2: ChangeType): number { case ChangeType.Removed: return 0; default: - return fail(0x4f35, 'Unknown ChangeType', { change }); + return fail(16, 'Unknown ChangeType', { change }); } }; diff --git a/packages/firestore/src/core/view_snapshot.ts b/packages/firestore/src/core/view_snapshot.ts index 3bb6e8ae134..9f3d04cd458 100644 --- a/packages/firestore/src/core/view_snapshot.ts +++ b/packages/firestore/src/core/view_snapshot.ts @@ -118,7 +118,7 @@ export class DocumentChangeSet { // Metadata->Added // Removed->Metadata fail( - 0xf76d, + 7, 'unsupported combination of changes: `change` after `oldChange`', { change, diff --git a/packages/firestore/src/index/firestore_index_value_writer.ts b/packages/firestore/src/index/firestore_index_value_writer.ts index b76ca7a930a..b58c2a01b8c 100644 --- a/packages/firestore/src/index/firestore_index_value_writer.ts +++ b/packages/firestore/src/index/firestore_index_value_writer.ts @@ -136,7 +136,7 @@ export class FirestoreIndexValueWriter { this.writeIndexArray(indexValue.arrayValue!, encoder); this.writeTruncationMarker(encoder); } else { - fail(0x4a4e, 'unknown index value type', { indexValue }); + fail(99, 'unknown index value type', { indexValue }); } } diff --git a/packages/firestore/src/lite-api/reference_impl.ts b/packages/firestore/src/lite-api/reference_impl.ts index 6d92ccab479..10b1920322d 100644 --- a/packages/firestore/src/lite-api/reference_impl.ts +++ b/packages/firestore/src/lite-api/reference_impl.ts @@ -133,11 +133,7 @@ export function getDoc( return invokeBatchGetDocumentsRpc(datastore, [reference._key]).then( result => { - hardAssert( - result.length === 1, - 0x3d02, - 'Expected a single document result' - ); + hardAssert(result.length === 1, 1, 'Expected a single document result'); const document = result[0]; return new DocumentSnapshot( reference.firestore, diff --git a/packages/firestore/src/lite-api/transaction.ts b/packages/firestore/src/lite-api/transaction.ts index 9a405feebea..8b662bc6f15 100644 --- a/packages/firestore/src/lite-api/transaction.ts +++ b/packages/firestore/src/lite-api/transaction.ts @@ -94,7 +94,7 @@ export class Transaction { const userDataWriter = new LiteUserDataWriter(this._firestore); return this._transaction.lookup([ref._key]).then(docs => { if (!docs || docs.length !== 1) { - return fail(0x5de9, 'Mismatch in docs returned from document lookup.'); + return fail(2, 'Mismatch in docs returned from document lookup.'); } const doc = docs[0]; if (doc.isFoundDocument()) { @@ -114,13 +114,9 @@ export class Transaction { ref.converter ); } else { - throw fail( - 0x4801, - 'BatchGetDocumentsRequest returned unexpected document', - { - doc - } - ); + throw fail(3, 'BatchGetDocumentsRequest returned unexpected document', { + doc + }); } }); } diff --git a/packages/firestore/src/lite-api/user_data_reader.ts b/packages/firestore/src/lite-api/user_data_reader.ts index aa5f9eeb5bf..9ce6d59fbc9 100644 --- a/packages/firestore/src/lite-api/user_data_reader.ts +++ b/packages/firestore/src/lite-api/user_data_reader.ts @@ -175,7 +175,7 @@ function isWrite(dataSource: UserDataSource): boolean { case UserDataSource.ArrayArgument: return false; default: - throw fail(0x9c4b, 'Unexpected case for UserDataSource', { + throw fail(6, 'Unexpected case for UserDataSource', { dataSource }); } diff --git a/packages/firestore/src/lite-api/user_data_writer.ts b/packages/firestore/src/lite-api/user_data_writer.ts index 070c71c7832..1a83e523efd 100644 --- a/packages/firestore/src/lite-api/user_data_writer.ts +++ b/packages/firestore/src/lite-api/user_data_writer.ts @@ -89,7 +89,7 @@ export abstract class AbstractUserDataWriter { case TypeOrder.VectorValue: return this.convertVectorValue(value.mapValue!); default: - throw fail(0xf2a2, 'Invalid value type', { + throw fail(4, 'Invalid value type', { value }); } @@ -175,7 +175,7 @@ export abstract class AbstractUserDataWriter { const resourcePath = ResourcePath.fromString(name); hardAssert( isValidResourceName(resourcePath), - 0x25d8, + 5, 'ReferenceValue is not valid', { name } ); diff --git a/packages/firestore/src/local/encoded_resource_path.ts b/packages/firestore/src/local/encoded_resource_path.ts index 497a65fdf8c..00b135a1341 100644 --- a/packages/firestore/src/local/encoded_resource_path.ts +++ b/packages/firestore/src/local/encoded_resource_path.ts @@ -118,11 +118,11 @@ export function decodeResourcePath(path: EncodedResourcePath): ResourcePath { // Event the empty path must encode as a path of at least length 2. A path // with exactly 2 must be the empty path. const length = path.length; - hardAssert(length >= 2, 0xfb98, 'Invalid path', { path }); + hardAssert(length >= 2, 63, 'Invalid path', { path }); if (length === 2) { hardAssert( path.charAt(0) === escapeChar && path.charAt(1) === encodedSeparatorChar, - 0xdb51, + 64, 'Non-empty path had length 2', { path } ); @@ -141,7 +141,7 @@ export function decodeResourcePath(path: EncodedResourcePath): ResourcePath { // there must be an end to this segment. const end = path.indexOf(escapeChar, start); if (end < 0 || end > lastReasonableEscapeIndex) { - fail(0xc553, 'Invalid encoded resource path', { path }); + fail(65, 'Invalid encoded resource path', { path }); } const next = path.charAt(end + 1); @@ -169,7 +169,7 @@ export function decodeResourcePath(path: EncodedResourcePath): ResourcePath { segmentBuilder += path.substring(start, end + 1); break; default: - fail(0xeeef, 'Invalid encoded resource path', { path }); + fail(66, 'Invalid encoded resource path', { path }); } start = end + 2; diff --git a/packages/firestore/src/local/indexeddb_index_manager.ts b/packages/firestore/src/local/indexeddb_index_manager.ts index d2b8bc47163..c0702586120 100644 --- a/packages/firestore/src/local/indexeddb_index_manager.ts +++ b/packages/firestore/src/local/indexeddb_index_manager.ts @@ -1066,7 +1066,7 @@ export class IndexedDbIndexManager implements IndexManager { this.getSubTargets(target), (subTarget: Target) => this.getFieldIndex(transaction, subTarget).next(index => - index ? index : fail(0xad8a, 'Target cannot be served from index') + index ? index : fail(53, 'Target cannot be served from index') ) ).next(getMinOffsetFromFieldIndexes); } @@ -1118,7 +1118,7 @@ function indexStateStore( function getMinOffsetFromFieldIndexes(fieldIndexes: FieldIndex[]): IndexOffset { hardAssert( fieldIndexes.length !== 0, - 0x7099, + 54, 'Found empty index group when looking for least recent index offset.' ); diff --git a/packages/firestore/src/local/indexeddb_mutation_batch_impl.ts b/packages/firestore/src/local/indexeddb_mutation_batch_impl.ts index 64b6df20b7e..6b6e01877fb 100644 --- a/packages/firestore/src/local/indexeddb_mutation_batch_impl.ts +++ b/packages/firestore/src/local/indexeddb_mutation_batch_impl.ts @@ -64,7 +64,7 @@ export function removeMutationBatch( removePromise.next(() => { hardAssert( numDeleted === 1, - 0xb7de, + 55, 'Dangling document-mutation reference found: Missing batch', { batchId: batch.batchId } ); @@ -101,7 +101,7 @@ export function dbDocumentSize( } else if (doc.noDocument) { value = doc.noDocument; } else { - throw fail(0x398b, 'Unknown remote document type'); + throw fail(56, 'Unknown remote document type'); } return JSON.stringify(value).length; } diff --git a/packages/firestore/src/local/indexeddb_mutation_queue.ts b/packages/firestore/src/local/indexeddb_mutation_queue.ts index bcdafa6aa36..69ad7b1642e 100644 --- a/packages/firestore/src/local/indexeddb_mutation_queue.ts +++ b/packages/firestore/src/local/indexeddb_mutation_queue.ts @@ -105,7 +105,7 @@ export class IndexedDbMutationQueue implements MutationQueue { // In particular, are there any reserved characters? are empty ids allowed? // For the moment store these together in the same mutations table assuming // that empty userIDs aren't allowed. - hardAssert(user.uid !== '', 0xfb83, 'UserID must not be an empty string.'); + hardAssert(user.uid !== '', 67, 'UserID must not be an empty string.'); const userId = user.isAuthenticated() ? user.uid! : ''; return new IndexedDbMutationQueue( userId, @@ -154,7 +154,7 @@ export class IndexedDbMutationQueue implements MutationQueue { return mutationStore.add({} as any).next(batchId => { hardAssert( typeof batchId === 'number', - 0xbf7b, + 68, 'Auto-generated key is not a number' ); @@ -207,7 +207,7 @@ export class IndexedDbMutationQueue implements MutationQueue { if (dbBatch) { hardAssert( dbBatch.userId === this.userId, - 0x0030, + 69, `Unexpected user for mutation batch`, { userId: dbBatch.userId, @@ -263,7 +263,7 @@ export class IndexedDbMutationQueue implements MutationQueue { if (dbBatch.userId === this.userId) { hardAssert( dbBatch.batchId >= nextBatchId, - 0xb9a4, + 70, 'Should have found mutation after `nextBatchId`', { nextBatchId } ); @@ -344,7 +344,7 @@ export class IndexedDbMutationQueue implements MutationQueue { .next(mutation => { if (!mutation) { throw fail( - 0xf028, + 71, 'Dangling document-mutation reference found: `indexKey` which points to `batchId`', { indexKey, @@ -354,7 +354,7 @@ export class IndexedDbMutationQueue implements MutationQueue { } hardAssert( mutation.userId === this.userId, - 0x2907, + 72, `Unexpected user for mutation batch`, { userId: mutation.userId, @@ -483,7 +483,7 @@ export class IndexedDbMutationQueue implements MutationQueue { .next(mutation => { if (mutation === null) { throw fail( - 0x89ca, + 73, 'Dangling document-mutation reference found, which points to `batchId`', { batchId @@ -492,7 +492,7 @@ export class IndexedDbMutationQueue implements MutationQueue { } hardAssert( mutation.userId === this.userId, - 0x2614, + 74, `Unexpected user for mutation batch`, { userId: mutation.userId, batchId } ); @@ -568,7 +568,7 @@ export class IndexedDbMutationQueue implements MutationQueue { .next(() => { hardAssert( danglingMutationReferences.length === 0, - 0xdd90, + 75, 'Document leak -- detected dangling mutation references when queue is empty.', { danglingKeys: danglingMutationReferences.map(p => diff --git a/packages/firestore/src/local/indexeddb_remote_document_cache.ts b/packages/firestore/src/local/indexeddb_remote_document_cache.ts index fffe935c4f9..313c0dba67c 100644 --- a/packages/firestore/src/local/indexeddb_remote_document_cache.ts +++ b/packages/firestore/src/local/indexeddb_remote_document_cache.ts @@ -381,7 +381,7 @@ class IndexedDbRemoteDocumentCacheImpl implements IndexedDbRemoteDocumentCache { return documentGlobalStore(txn) .get(DbRemoteDocumentGlobalKey) .next(metadata => { - hardAssert(!!metadata, 0x4e35, 'Missing document cache metadata'); + hardAssert(!!metadata, 62, 'Missing document cache metadata'); return metadata!; }); } diff --git a/packages/firestore/src/local/indexeddb_schema_converter.ts b/packages/firestore/src/local/indexeddb_schema_converter.ts index 7446ae7ae20..8e6ab40a952 100644 --- a/packages/firestore/src/local/indexeddb_schema_converter.ts +++ b/packages/firestore/src/local/indexeddb_schema_converter.ts @@ -326,7 +326,7 @@ export class SchemaConverter implements SimpleDbSchemaConverter { (dbBatch: DbMutationBatch) => { hardAssert( dbBatch.userId === queue.userId, - 0x48da, + 48, `Cannot process batch from unexpected user`, { batchId: dbBatch.batchId } ); @@ -774,6 +774,6 @@ function extractKey(remoteDoc: DbRemoteDocumentLegacy): DocumentKey { } else if (remoteDoc.unknownDocument) { return DocumentKey.fromSegments(remoteDoc.unknownDocument.path); } else { - return fail(0x8faf, 'Unexpected DbRemoteDocument'); + return fail(49, 'Unexpected DbRemoteDocument'); } } diff --git a/packages/firestore/src/local/indexeddb_sentinels.ts b/packages/firestore/src/local/indexeddb_sentinels.ts index cb6ebcb664a..7bc8fa07cf8 100644 --- a/packages/firestore/src/local/indexeddb_sentinels.ts +++ b/packages/firestore/src/local/indexeddb_sentinels.ts @@ -450,6 +450,6 @@ export function getObjectStores(schemaVersion: number): string[] { } else if (schemaVersion === 11) { return V11_STORES; } else { - fail(0xeb55, 'Only schema version 11 and 12 and 13 are supported'); + fail(61, 'Only schema version 11 and 12 and 13 are supported'); } } diff --git a/packages/firestore/src/local/indexeddb_target_cache.ts b/packages/firestore/src/local/indexeddb_target_cache.ts index 1d5ed8f0c8b..2cd80695980 100644 --- a/packages/firestore/src/local/indexeddb_target_cache.ts +++ b/packages/firestore/src/local/indexeddb_target_cache.ts @@ -144,7 +144,7 @@ export class IndexedDbTargetCache implements TargetCache { .next(metadata => { hardAssert( metadata.targetCount > 0, - 0x1f81, + 59, 'Removing from an empty target cache' ); metadata.targetCount -= 1; @@ -198,7 +198,7 @@ export class IndexedDbTargetCache implements TargetCache { return globalTargetStore(transaction) .get(DbTargetGlobalKey) .next(metadata => { - hardAssert(metadata !== null, 0x0b48, 'Missing metadata row.'); + hardAssert(metadata !== null, 60, 'Missing metadata row.'); return metadata; }); } diff --git a/packages/firestore/src/local/local_serializer.ts b/packages/firestore/src/local/local_serializer.ts index bb1658caa52..a2ed0f36bc9 100644 --- a/packages/firestore/src/local/local_serializer.ts +++ b/packages/firestore/src/local/local_serializer.ts @@ -101,7 +101,7 @@ export function fromDbRemoteDocument( const version = fromDbTimestamp(remoteDoc.unknownDocument.version); doc = MutableDocument.newUnknownDocument(key, version); } else { - return fail(0xdd85, 'Unexpected DbRemoteDocument'); + return fail(45, 'Unexpected DbRemoteDocument'); } if (remoteDoc.readTime) { @@ -138,7 +138,7 @@ export function toDbRemoteDocument( version: toDbTimestamp(document.version) }; } else { - return fail(0xe230, 'Unexpected Document', { document }); + return fail(46, 'Unexpected Document', { document }); } return remoteDoc; } diff --git a/packages/firestore/src/local/local_store_impl.ts b/packages/firestore/src/local/local_store_impl.ts index 31d2a46c326..ca212ec66c0 100644 --- a/packages/firestore/src/local/local_store_impl.ts +++ b/packages/firestore/src/local/local_store_impl.ts @@ -496,7 +496,7 @@ export function localStoreRejectBatch( .next((batch: MutationBatch | null) => { hardAssert( batch !== null, - 0x90f9, + 50, 'Attempt to reject nonexistent batch!' ); affectedKeys = batch.keys(); @@ -1141,7 +1141,7 @@ function applyWriteToRemoteDocuments( const ackVersion = batchResult.docVersions.get(docKey); hardAssert( ackVersion !== null, - 0xbd9d, + 51, 'ackVersions should contain every doc in the write.' ); if (doc.version.compareTo(ackVersion!) < 0) { diff --git a/packages/firestore/src/local/memory_mutation_queue.ts b/packages/firestore/src/local/memory_mutation_queue.ts index f136fb7ad15..d5e265497c1 100644 --- a/packages/firestore/src/local/memory_mutation_queue.ts +++ b/packages/firestore/src/local/memory_mutation_queue.ts @@ -246,7 +246,7 @@ export class MemoryMutationQueue implements MutationQueue { const batchIndex = this.indexOfExistingBatchId(batch.batchId, 'removed'); hardAssert( batchIndex === 0, - 0xd6db, + 44, 'Can only remove the first entry of the mutation queue' ); this.mutationQueue.shift(); diff --git a/packages/firestore/src/local/memory_persistence.ts b/packages/firestore/src/local/memory_persistence.ts index fcb6db42059..96bb2d4a723 100644 --- a/packages/firestore/src/local/memory_persistence.ts +++ b/packages/firestore/src/local/memory_persistence.ts @@ -235,10 +235,7 @@ export class MemoryEagerDelegate implements MemoryReferenceDelegate { private get orphanedDocuments(): Set { if (!this._orphanedDocuments) { - throw fail( - 0xee44, - 'orphanedDocuments is only valid during a transaction.' - ); + throw fail(58, 'orphanedDocuments is only valid during a transaction.'); } else { return this._orphanedDocuments; } diff --git a/packages/firestore/src/local/memory_remote_document_cache.ts b/packages/firestore/src/local/memory_remote_document_cache.ts index 0daf80b6a19..edc695ed5d9 100644 --- a/packages/firestore/src/local/memory_remote_document_cache.ts +++ b/packages/firestore/src/local/memory_remote_document_cache.ts @@ -219,7 +219,7 @@ class MemoryRemoteDocumentCacheImpl implements MemoryRemoteDocumentCache { ): PersistencePromise { // This method should only be called from the IndexBackfiller if persistence // is enabled. - fail(0x251c, 'getAllFromCollectionGroup() is not supported.'); + fail(57, 'getAllFromCollectionGroup() is not supported.'); } forEachDocumentKey( diff --git a/packages/firestore/src/local/persistence_promise.ts b/packages/firestore/src/local/persistence_promise.ts index 812cc0fca85..1a9d4ffe6c1 100644 --- a/packages/firestore/src/local/persistence_promise.ts +++ b/packages/firestore/src/local/persistence_promise.ts @@ -86,7 +86,7 @@ export class PersistencePromise { catchFn?: RejectedHandler ): PersistencePromise { if (this.callbackAttached) { - fail(0xe830, 'Called next() or catch() twice for PersistencePromise'); + fail(52, 'Called next() or catch() twice for PersistencePromise'); } this.callbackAttached = true; if (this.isDone) { diff --git a/packages/firestore/src/local/shared_client_state.ts b/packages/firestore/src/local/shared_client_state.ts index 1000e63a0f6..19c22a633b8 100644 --- a/packages/firestore/src/local/shared_client_state.ts +++ b/packages/firestore/src/local/shared_client_state.ts @@ -1085,7 +1085,7 @@ function fromWebStorageSequenceNumber( const parsed = JSON.parse(seqString); hardAssert( typeof parsed === 'number', - 0x77ac, + 47, 'Found non-numeric sequence number', { seqString } ); diff --git a/packages/firestore/src/model/document.ts b/packages/firestore/src/model/document.ts index ac454704776..af946f2ea21 100644 --- a/packages/firestore/src/model/document.ts +++ b/packages/firestore/src/model/document.ts @@ -397,9 +397,6 @@ export function compareDocumentsByField( if (v1 !== null && v2 !== null) { return valueCompare(v1, v2); } else { - return fail( - 0xa786, - "Trying to compare documents on fields that don't exist" - ); + return fail(79, "Trying to compare documents on fields that don't exist"); } } diff --git a/packages/firestore/src/model/mutation.ts b/packages/firestore/src/model/mutation.ts index 0bcd1345b01..e7d0170c86f 100644 --- a/packages/firestore/src/model/mutation.ts +++ b/packages/firestore/src/model/mutation.ts @@ -623,7 +623,7 @@ function serverTransformResults( const transformResults = new Map(); hardAssert( fieldTransforms.length === serverTransformResults.length, - 0x7f90, + 83, 'server transform result count should match field transform count', { serverTransformResultCount: serverTransformResults.length, diff --git a/packages/firestore/src/model/mutation_batch.ts b/packages/firestore/src/model/mutation_batch.ts index 703623da01a..509ac454b7e 100644 --- a/packages/firestore/src/model/mutation_batch.ts +++ b/packages/firestore/src/model/mutation_batch.ts @@ -219,7 +219,7 @@ export class MutationBatchResult { ): MutationBatchResult { hardAssert( batch.mutations.length === results.length, - 0xe5da, + 80, 'Mutations sent must equal results received', { mutationsSent: batch.mutations.length, diff --git a/packages/firestore/src/model/normalize.ts b/packages/firestore/src/model/normalize.ts index 986eeed1e48..31731e58c4e 100644 --- a/packages/firestore/src/model/normalize.ts +++ b/packages/firestore/src/model/normalize.ts @@ -32,7 +32,7 @@ export function normalizeTimestamp(date: Timestamp): { seconds: number; nanos: number; } { - hardAssert(!!date, 0x986a, 'Cannot normalize null or undefined timestamp.'); + hardAssert(!!date, 81, 'Cannot normalize null or undefined timestamp.'); // The json interface (for the browser) will return an iso timestamp string, // while the proto js library (for node) will return a @@ -44,7 +44,7 @@ export function normalizeTimestamp(date: Timestamp): { // Parse the nanos right out of the string. let nanos = 0; const fraction = ISO_TIMESTAMP_REG_EXP.exec(date); - hardAssert(!!fraction, 0xb5de, 'invalid timestamp', { + hardAssert(!!fraction, 82, 'invalid timestamp', { timestamp: date }); if (fraction[1]) { diff --git a/packages/firestore/src/model/path.ts b/packages/firestore/src/model/path.ts index 0f4581da8d8..f30d65d0a9c 100644 --- a/packages/firestore/src/model/path.ts +++ b/packages/firestore/src/model/path.ts @@ -35,7 +35,7 @@ abstract class BasePath> { if (offset === undefined) { offset = 0; } else if (offset > segments.length) { - fail(0x027d, 'offset out of range', { + fail(76, 'offset out of range', { offset, range: segments.length }); @@ -44,7 +44,7 @@ abstract class BasePath> { if (length === undefined) { length = segments.length - offset; } else if (length > segments.length - offset) { - fail(0x06d2, 'length out of range', { + fail(77, 'length out of range', { length, range: segments.length - offset }); diff --git a/packages/firestore/src/model/target_index_matcher.ts b/packages/firestore/src/model/target_index_matcher.ts index 407eae337c7..73aae4ba0a0 100644 --- a/packages/firestore/src/model/target_index_matcher.ts +++ b/packages/firestore/src/model/target_index_matcher.ts @@ -111,7 +111,7 @@ export class TargetIndexMatcher { servedByIndex(index: FieldIndex): boolean { hardAssert( index.collectionGroup === this.collectionId, - 0xc07f, + 78, 'Collection IDs do not match' ); diff --git a/packages/firestore/src/model/values.ts b/packages/firestore/src/model/values.ts index 1ef54a98ad6..7f87e9c90bc 100644 --- a/packages/firestore/src/model/values.ts +++ b/packages/firestore/src/model/values.ts @@ -93,7 +93,7 @@ export function typeOrder(value: Value): TypeOrder { } return TypeOrder.ObjectValue; } else { - return fail(0x6e87, 'Invalid value type', { value }); + return fail(84, 'Invalid value type', { value }); } } @@ -140,7 +140,7 @@ export function valueEquals(left: Value, right: Value): boolean { case TypeOrder.MaxValue: return true; default: - return fail(0xcbf8, 'Unexpected value type', { left }); + return fail(85, 'Unexpected value type', { left }); } } @@ -269,7 +269,7 @@ export function valueCompare(left: Value, right: Value): number { case TypeOrder.ObjectValue: return compareMaps(left.mapValue!, right.mapValue!); default: - throw fail(0x5ae0, 'Invalid value type', { leftType }); + throw fail(86, 'Invalid value type', { leftType }); } } @@ -449,7 +449,7 @@ function canonifyValue(value: Value): string { } else if ('mapValue' in value) { return canonifyMap(value.mapValue!); } else { - return fail(0xee4d, 'Invalid value type', { value }); + return fail(87, 'Invalid value type', { value }); } } @@ -541,7 +541,7 @@ export function estimateByteSize(value: Value): number { case TypeOrder.ObjectValue: return estimateMapByteSize(value.mapValue!); default: - throw fail(0x34ae, 'Invalid value type', { value }); + throw fail(88, 'Invalid value type', { value }); } } @@ -701,7 +701,7 @@ export function valuesGetLowerBound(value: Value): Value { } return { mapValue: {} }; } else { - return fail(0x8c66, 'Invalid value type', { value }); + return fail(89, 'Invalid value type', { value }); } } @@ -731,7 +731,7 @@ export function valuesGetUpperBound(value: Value): Value { } return MAX_VALUE; } else { - return fail(0xf207, 'Invalid value type', { value }); + return fail(90, 'Invalid value type', { value }); } } diff --git a/packages/firestore/src/platform/browser/webchannel_connection.ts b/packages/firestore/src/platform/browser/webchannel_connection.ts index ce20eef2049..91038042c60 100644 --- a/packages/firestore/src/platform/browser/webchannel_connection.ts +++ b/packages/firestore/src/platform/browser/webchannel_connection.ts @@ -142,7 +142,7 @@ export class WebChannelConnection extends RestConnection { break; default: fail( - 0x235f, + 41, 'RPC failed with unanticipated webchannel error. Giving up.', { rpcName, @@ -353,11 +353,7 @@ export class WebChannelConnection extends RestConnection { msg => { if (!closed) { const msgData = msg.data[0]; - hardAssert( - !!msgData, - 0x3fdd, - 'Got a webchannel message without data.' - ); + hardAssert(!!msgData, 42, 'Got a webchannel message without data.'); // TODO(b/35143891): There is a bug in One Platform that caused errors // (and only errors) to be wrapped in an extra array. To be forward // compatible with the bug we need to check either condition. The latter diff --git a/packages/firestore/src/platform/node/grpc_connection.ts b/packages/firestore/src/platform/node/grpc_connection.ts index d50a3149416..d3be8385a8e 100644 --- a/packages/firestore/src/platform/node/grpc_connection.ts +++ b/packages/firestore/src/platform/node/grpc_connection.ts @@ -48,7 +48,7 @@ function createMetadata( ): grpc.Metadata { hardAssert( authToken === null || authToken.type === 'OAuth', - 0x9048, + 43, 'If provided, token must be OAuth' ); const metadata = new grpc.Metadata(); diff --git a/packages/firestore/src/remote/datastore.ts b/packages/firestore/src/remote/datastore.ts index f790ede0d5c..0bfdb9ac1aa 100644 --- a/packages/firestore/src/remote/datastore.ts +++ b/packages/firestore/src/remote/datastore.ts @@ -228,7 +228,7 @@ export async function invokeBatchGetDocumentsRpc( const result: Document[] = []; keys.forEach(key => { const doc = docs.get(key.toString()); - hardAssert(!!doc, 0xd7c2, 'Missing entity in write response for `key`', { + hardAssert(!!doc, 100, 'Missing entity in write response for `key`', { key }); result.push(doc); @@ -292,7 +292,7 @@ export async function invokeRunAggregationQueryRpc( hardAssert( filteredResult.length === 1, - 0xfcd7, + 101, 'Aggregation fields are missing from result.' ); debugAssert( diff --git a/packages/firestore/src/remote/persistent_stream.ts b/packages/firestore/src/remote/persistent_stream.ts index 4f3b91652ad..427a9c5571d 100644 --- a/packages/firestore/src/remote/persistent_stream.ts +++ b/packages/firestore/src/remote/persistent_stream.ts @@ -809,7 +809,7 @@ export class PersistentWriteStream extends PersistentStream< // Always capture the last stream token. hardAssert( !!responseProto.streamToken, - 0x7a5a, + 134, 'Got a write handshake response without a stream token' ); this.lastStreamToken = responseProto.streamToken; @@ -817,7 +817,7 @@ export class PersistentWriteStream extends PersistentStream< // The first response is always the handshake response hardAssert( !responseProto.writeResults || responseProto.writeResults.length === 0, - 0xda08, + 135, 'Got mutation results for handshake' ); return this.listener!.onHandshakeComplete(); @@ -827,7 +827,7 @@ export class PersistentWriteStream extends PersistentStream< // Always capture the last stream token. hardAssert( !!responseProto.streamToken, - 0x3186, + 136, 'Got a write response without a stream token' ); this.lastStreamToken = responseProto.streamToken; diff --git a/packages/firestore/src/remote/rpc_error.ts b/packages/firestore/src/remote/rpc_error.ts index 2efee40f223..bb0f49ed62e 100644 --- a/packages/firestore/src/remote/rpc_error.ts +++ b/packages/firestore/src/remote/rpc_error.ts @@ -58,7 +58,7 @@ enum RpcCode { export function isPermanentError(code: Code): boolean { switch (code) { case Code.OK: - return fail(0xfdaa, 'Treated status OK as error'); + return fail(137, 'Treated status OK as error'); case Code.CANCELLED: case Code.UNKNOWN: case Code.DEADLINE_EXCEEDED: @@ -83,7 +83,7 @@ export function isPermanentError(code: Code): boolean { case Code.DATA_LOSS: return true; default: - return fail(0x3c6b, 'Unknown status code', { code }); + return fail(138, 'Unknown status code', { code }); } } @@ -171,7 +171,7 @@ export function mapCodeFromRpcCode(code: number | undefined): Code { case RpcCode.DATA_LOSS: return Code.DATA_LOSS; default: - return fail(0x999b, 'Unknown status code', { code }); + return fail(139, 'Unknown status code', { code }); } } @@ -220,7 +220,7 @@ export function mapRpcCodeFromCode(code: Code | undefined): number { case Code.DATA_LOSS: return RpcCode.DATA_LOSS; default: - return fail(0x3019, 'Unknown status code', { code }); + return fail(140, 'Unknown status code', { code }); } } diff --git a/packages/firestore/src/remote/serializer.ts b/packages/firestore/src/remote/serializer.ts index aabdb263c1a..6e2a83b1aee 100644 --- a/packages/firestore/src/remote/serializer.ts +++ b/packages/firestore/src/remote/serializer.ts @@ -257,7 +257,7 @@ export function fromBytes( if (serializer.useProto3Json) { hardAssert( value === undefined || typeof value === 'string', - 0xe30b, + 106, 'value must be undefined or a string when using proto3 Json' ); return ByteString.fromBase64String(value ? value : ''); @@ -270,7 +270,7 @@ export function fromBytes( // does not indicate that it extends Uint8Array. value instanceof Buffer || value instanceof Uint8Array, - 0x3f41, + 107, 'value must be undefined, Buffer, or Uint8Array' ); return ByteString.fromUint8Array(value ? value : new Uint8Array()); @@ -285,7 +285,7 @@ export function toVersion( } export function fromVersion(version: ProtoTimestamp): SnapshotVersion { - hardAssert(!!version, 0xc050, "Trying to deserialize version that isn't set"); + hardAssert(!!version, 108, "Trying to deserialize version that isn't set"); return SnapshotVersion.fromTimestamp(fromTimestamp(version)); } @@ -308,7 +308,7 @@ function fromResourceName(name: string): ResourcePath { const resource = ResourcePath.fromString(name); hardAssert( isValidResourceName(resource), - 0x27ce, + 109, 'Tried to deserialize invalid key', { key: resource.toString() } ); @@ -393,7 +393,7 @@ function extractLocalPathFromResourceName( ): ResourcePath { hardAssert( resourceName.length > 4 && resourceName.get(4) === 'documents', - 0x71a3, + 110, 'tried to deserialize invalid key', { key: resourceName.toString() } ); @@ -460,7 +460,7 @@ function fromFound( ): MutableDocument { hardAssert( !!doc.found, - 0xaa33, + 111, 'Tried to deserialize a found document from a missing document.' ); assertPresent(doc.found.name, 'doc.found.name'); @@ -480,12 +480,12 @@ function fromMissing( ): MutableDocument { hardAssert( !!result.missing, - 0x0f36, + 112, 'Tried to deserialize a missing document from a found document.' ); hardAssert( !!result.readTime, - 0x5995, + 113, 'Tried to deserialize a missing document without a read time.' ); const key = fromName(serializer, result.missing); @@ -502,7 +502,7 @@ export function fromBatchGetDocumentsResponse( } else if ('missing' in result) { return fromMissing(serializer, result); } - return fail(0x1c42, 'invalid batch get response', { result }); + return fail(114, 'invalid batch get response', { result }); } export function fromWatchChange( @@ -587,7 +587,7 @@ export function fromWatchChange( const targetId = filter.targetId; watchChange = new ExistenceFilterChange(targetId, existenceFilter); } else { - return fail(0x2d51, 'Unknown change type', { change }); + return fail(115, 'Unknown change type', { change }); } return watchChange; } @@ -606,7 +606,7 @@ function fromWatchTargetChangeState( } else if (state === 'RESET') { return WatchTargetChangeState.Reset; } else { - return fail(0x9991, 'Got unexpected TargetChange.state', { state }); + return fail(116, 'Got unexpected TargetChange.state', { state }); } } @@ -650,7 +650,7 @@ export function toMutation( verify: toName(serializer, mutation.key) }; } else { - return fail(0x40d7, 'Unknown mutation type', { + return fail(117, 'Unknown mutation type', { mutationType: mutation.type }); } @@ -708,7 +708,7 @@ export function fromMutation( const key = fromName(serializer, proto.verify); return new VerifyMutation(key, precondition); } else { - return fail(0x05b7, 'unknown mutation proto', { proto }); + return fail(118, 'unknown mutation proto', { proto }); } } @@ -724,7 +724,7 @@ function toPrecondition( } else if (precondition.exists !== undefined) { return { exists: precondition.exists }; } else { - return fail(0x6b69, 'Unknown precondition'); + return fail(119, 'Unknown precondition'); } } @@ -766,7 +766,7 @@ export function fromWriteResults( if (protos && protos.length > 0) { hardAssert( commitTime !== undefined, - 0x3811, + 120, 'Received a write result without a commit time' ); return protos.map(proto => fromWriteResult(proto, commitTime)); @@ -805,7 +805,7 @@ function toFieldTransform( increment: transform.operand }; } else { - throw fail(0x51c2, 'Unknown transform', { + throw fail(121, 'Unknown transform', { transform: fieldTransform.transform }); } @@ -819,7 +819,7 @@ function fromFieldTransform( if ('setToServerValue' in proto) { hardAssert( proto.setToServerValue === 'REQUEST_TIME', - 0x40f6, + 122, 'Unknown server value transform proto', { proto } ); @@ -836,7 +836,7 @@ function fromFieldTransform( proto.increment! ); } else { - fail(0x40c8, 'Unknown transform proto', { proto }); + fail(123, 'Unknown transform proto', { proto }); } const fieldPath = FieldPath.fromServerFormat(proto.fieldPath!); return new FieldTransform(fieldPath, transform!); @@ -855,7 +855,7 @@ export function fromDocumentsTarget( const count = documentsTarget.documents!.length; hardAssert( count === 1, - 0x07ae, + 124, 'DocumentsTarget contained other than 1 document', { count @@ -989,7 +989,7 @@ export function convertQueryTargetToQuery(target: ProtoQueryTarget): Query { if (fromCount > 0) { hardAssert( fromCount === 1, - 0xfe26, + 125, 'StructuredQuery.from with more than one collection is not supported.' ); const from = query.from![0]; @@ -1066,7 +1066,7 @@ export function toLabel(purpose: TargetPurpose): string | null { case TargetPurpose.LimboResolution: return 'limbo-document'; default: - return fail(0x713b, 'Unrecognized query purpose', { purpose }); + return fail(126, 'Unrecognized query purpose', { purpose }); } } @@ -1137,7 +1137,7 @@ function fromFilter(filter: ProtoFilter): Filter { } else if (filter.compositeFilter !== undefined) { return fromCompositeFilter(filter); } else { - return fail(0x7591, 'Unknown filter', { filter }); + return fail(127, 'Unknown filter', { filter }); } } @@ -1231,9 +1231,9 @@ export function fromOperatorName(op: ProtoFieldFilterOp): Operator { case 'ARRAY_CONTAINS_ANY': return Operator.ARRAY_CONTAINS_ANY; case 'OPERATOR_UNSPECIFIED': - return fail(0xe2fe, 'Unspecified operator'); + return fail(128, 'Unspecified operator'); default: - return fail(0xc54a, 'Unknown operator'); + return fail(129, 'Unknown operator'); } } @@ -1246,7 +1246,7 @@ export function fromCompositeOperatorName( case 'OR': return CompositeOperator.OR; default: - return fail(0x0402, 'Unknown operator'); + return fail(130, 'Unknown operator'); } } @@ -1282,7 +1282,7 @@ export function toFilter(filter: Filter): ProtoFilter { } else if (filter instanceof CompositeFilter) { return toCompositeFilter(filter); } else { - return fail(0xd65d, 'Unrecognized filter type', { filter }); + return fail(131, 'Unrecognized filter type', { filter }); } } @@ -1367,9 +1367,9 @@ export function fromUnaryFilter(filter: ProtoFilter): Filter { nullValue: 'NULL_VALUE' }); case 'OPERATOR_UNSPECIFIED': - return fail(0xef81, 'Unspecified filter'); + return fail(132, 'Unspecified filter'); default: - return fail(0xed36, 'Unknown filter'); + return fail(133, 'Unknown filter'); } } diff --git a/packages/firestore/src/remote/watch_change.ts b/packages/firestore/src/remote/watch_change.ts index a656d8fdf6e..5cc6b2971a8 100644 --- a/packages/firestore/src/remote/watch_change.ts +++ b/packages/firestore/src/remote/watch_change.ts @@ -203,7 +203,7 @@ class TargetState { removedDocuments = removedDocuments.add(key); break; default: - fail(0x9481, 'Encountered invalid change type', { changeType }); + fail(102, 'Encountered invalid change type', { changeType }); } }); @@ -242,7 +242,7 @@ class TargetState { this.pendingResponses -= 1; hardAssert( this.pendingResponses >= 0, - 0x0ca9, + 103, '`pendingResponses` is less than 0. This indicates that the SDK received more target acks from the server than expected. The SDK should not continue to operate.', { pendingResponses: this.pendingResponses } ); @@ -377,7 +377,7 @@ export class WatchChangeAggregator { } break; default: - fail(0xddd6, 'Unknown target watch change state', { + fail(104, 'Unknown target watch change state', { state: targetChange.state }); } @@ -433,7 +433,7 @@ export class WatchChangeAggregator { } else { hardAssert( expectedCount === 1, - 0x4e2d, + 105, 'Single document existence filter with count', { expectedCount } ); diff --git a/packages/firestore/src/util/assert.ts b/packages/firestore/src/util/assert.ts index 41fac5db241..f1cac714a00 100644 --- a/packages/firestore/src/util/assert.ts +++ b/packages/firestore/src/util/assert.ts @@ -127,7 +127,7 @@ export function debugAssert( message: string ): asserts assertion { if (!assertion) { - fail(0xdeb0e7, message); + fail(21, message); } } diff --git a/packages/firestore/src/util/async_queue_impl.ts b/packages/firestore/src/util/async_queue_impl.ts index f8c7a995761..9379a0e41e6 100644 --- a/packages/firestore/src/util/async_queue_impl.ts +++ b/packages/firestore/src/util/async_queue_impl.ts @@ -236,7 +236,7 @@ export class AsyncQueueImpl implements AsyncQueue { private verifyNotFailed(): void { if (this.failure) { - fail(0xb815, 'AsyncQueue is already failed', { + fail(20, 'AsyncQueue is already failed', { messageOrStack: getMessageOrStack(this.failure) }); } diff --git a/packages/firestore/src/util/input_validation.ts b/packages/firestore/src/util/input_validation.ts index 7fd9967b5a0..ea77fbc1956 100644 --- a/packages/firestore/src/util/input_validation.ts +++ b/packages/firestore/src/util/input_validation.ts @@ -128,7 +128,7 @@ export function valueDescription(input: unknown): string { } else if (typeof input === 'function') { return 'a function'; } else { - return fail(0x3029, 'Unknown wrong type', { type: typeof input }); + return fail(30, 'Unknown wrong type', { type: typeof input }); } } diff --git a/packages/firestore/src/util/logic_utils.ts b/packages/firestore/src/util/logic_utils.ts index b2167c385e9..b40194f5ae5 100644 --- a/packages/firestore/src/util/logic_utils.ts +++ b/packages/firestore/src/util/logic_utils.ts @@ -44,7 +44,7 @@ import { hardAssert } from './assert'; export function computeInExpansion(filter: Filter): Filter { hardAssert( filter instanceof FieldFilter || filter instanceof CompositeFilter, - 0x4e2c, + 31, 'Only field filters and composite filters are accepted.' ); @@ -91,7 +91,7 @@ export function getDnfTerms(filter: CompositeFilter): Filter[] { hardAssert( isDisjunctiveNormalForm(result), - 0x1cdf, + 32, 'computeDistributedNormalForm did not result in disjunctive normal form' ); @@ -159,7 +159,7 @@ function isDisjunctionOfFieldFiltersAndFlatConjunctions( export function computeDistributedNormalForm(filter: Filter): Filter { hardAssert( filter instanceof FieldFilter || filter instanceof CompositeFilter, - 0x84e2, + 33, 'Only field filters and composite filters are accepted.' ); @@ -185,17 +185,17 @@ export function computeDistributedNormalForm(filter: Filter): Filter { hardAssert( newFilter instanceof CompositeFilter, - 0xfbf2, + 34, 'field filters are already in DNF form' ); hardAssert( compositeFilterIsConjunction(newFilter), - 0x9d3b, + 35, 'Disjunction of filters all of which are already in DNF form is itself in DNF form.' ); hardAssert( newFilter.filters.length > 1, - 0xe247, + 36, 'Single-filter composite filters are already in DNF form.' ); @@ -207,12 +207,12 @@ export function computeDistributedNormalForm(filter: Filter): Filter { export function applyDistribution(lhs: Filter, rhs: Filter): Filter { hardAssert( lhs instanceof FieldFilter || lhs instanceof CompositeFilter, - 0x95f4, + 37, 'Only field filters and composite filters are accepted.' ); hardAssert( rhs instanceof FieldFilter || rhs instanceof CompositeFilter, - 0x6381, + 38, 'Only field filters and composite filters are accepted.' ); @@ -253,7 +253,7 @@ function applyDistributionCompositeFilters( ): Filter { hardAssert( lhs.filters.length > 0 && rhs.filters.length > 0, - 0xbb85, + 39, 'Found an empty composite filter' ); @@ -315,7 +315,7 @@ function applyDistributionFieldAndCompositeFilters( export function applyAssociation(filter: Filter): Filter { hardAssert( filter instanceof FieldFilter || filter instanceof CompositeFilter, - 0x2e4a, + 40, 'Only field filters and composite filters are accepted.' ); diff --git a/packages/firestore/src/util/sorted_map.ts b/packages/firestore/src/util/sorted_map.ts index 023354173d3..9f05fba543e 100644 --- a/packages/firestore/src/util/sorted_map.ts +++ b/packages/firestore/src/util/sorted_map.ts @@ -511,20 +511,20 @@ export class LLRBNode { // leaves is equal on both sides. This function verifies that or asserts. protected check(): number { if (this.isRed() && this.left.isRed()) { - throw fail(0xaad2, 'Red node has red child', { + throw fail(22, 'Red node has red child', { key: this.key, value: this.value }); } if (this.right.isRed()) { - throw fail(0x3721, 'Right child of (`key`, `value`) is red', { + throw fail(23, 'Right child of (`key`, `value`) is red', { key: this.key, value: this.value }); } const blackDepth = (this.left as LLRBNode).check(); if (blackDepth !== (this.right as LLRBNode).check()) { - throw fail(0x6d2d, 'Black depths differ'); + throw fail(24, 'Black depths differ'); } else { return blackDepth + (this.isRed() ? 0 : 1); } @@ -534,19 +534,19 @@ export class LLRBNode { // Represents an empty node (a leaf node in the Red-Black Tree). export class LLRBEmptyNode { get key(): never { - throw fail(0xe1a6, 'LLRBEmptyNode has no key.'); + throw fail(25, 'LLRBEmptyNode has no key.'); } get value(): never { - throw fail(0x3f0d, 'LLRBEmptyNode has no value.'); + throw fail(26, 'LLRBEmptyNode has no value.'); } get color(): never { - throw fail(0x4157, 'LLRBEmptyNode has no color.'); + throw fail(27, 'LLRBEmptyNode has no color.'); } get left(): never { - throw fail(0x741e, 'LLRBEmptyNode has no left child.'); + throw fail(28, 'LLRBEmptyNode has no left child.'); } get right(): never { - throw fail(0x901e, 'LLRBEmptyNode has no right child.'); + throw fail(29, 'LLRBEmptyNode has no right child.'); } size = 0; From 8e28999749d494af616f668ba62c9b0d1f7daefb Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Wed, 2 Apr 2025 18:29:04 -0600 Subject: [PATCH 09/18] Instructions for using error-code-tool --- packages/firestore/src/util/assert.ts | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/packages/firestore/src/util/assert.ts b/packages/firestore/src/util/assert.ts index f1cac714a00..45b8d237daa 100644 --- a/packages/firestore/src/util/assert.ts +++ b/packages/firestore/src/util/assert.ts @@ -26,6 +26,9 @@ import { logError } from './log'; * Returns `never` and can be used in expressions: * @example * let futureVar = fail('not implemented yet'); + * + * @param code generate a new unique value with `yarn error-code:generate` + * Search for an existing value using `yarn error-code:find X` */ export function fail( code: number, @@ -33,6 +36,17 @@ export function fail( context?: Record ): never; +/** + * Unconditionally fails, throwing an Error with the given message. + * Messages are stripped in production builds. + * + * Returns `never` and can be used in expressions: + * @example + * let futureVar = fail('not implemented yet'); + * + * @param code generate a new unique value with `yarn error-code:generate` + * Search for an existing value using `yarn error-code:find X` + */ export function fail(code: number, context?: Record): never; export function fail( @@ -80,6 +94,9 @@ function _fail( * given message if it did. * * Messages are stripped in production builds. + * + * @param code generate a new unique value with `yarn error-code:generate`. + * Search for an existing value using `yarn error-code:find X` */ export function hardAssert( assertion: boolean, @@ -88,6 +105,15 @@ export function hardAssert( context?: Record ): asserts assertion; +/** + * Fails if the given assertion condition is false, throwing an Error with the + * given message if it did. + * + * Messages are stripped in production builds. + * + * @param code generate a new unique value with `yarn error-code:generate`. + * Search for an existing value using `yarn error-code:find X` + */ export function hardAssert( assertion: boolean, code: number, From 173344238abd4b042aee8fc2505625fed5f0f07a Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Thu, 3 Apr 2025 07:23:08 -0600 Subject: [PATCH 10/18] Create odd-wolves-sit.md --- .changeset/odd-wolves-sit.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/odd-wolves-sit.md diff --git a/.changeset/odd-wolves-sit.md b/.changeset/odd-wolves-sit.md new file mode 100644 index 00000000000..6cfc1590103 --- /dev/null +++ b/.changeset/odd-wolves-sit.md @@ -0,0 +1,5 @@ +--- +"@firebase/firestore": patch +--- + +Adding error codes and error context to internal assertion messages. From 3a1eaae5d8b234e7954ed5bffc5cf7d3f4327fd1 Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Thu, 3 Apr 2025 08:14:16 -0600 Subject: [PATCH 11/18] Rename error code to assertion id to deter customers from using this value programatically as an error code --- packages/firestore/package.json | 10 +-- .../firestore/scripts/assertion-id-tool.js | 17 +++++ ...rror-code-tool.ts => assertion-id-tool.ts} | 65 +++++++++---------- packages/firestore/scripts/error-code-tool.js | 17 ----- packages/firestore/src/util/assert.ts | 34 +++++----- 5 files changed, 68 insertions(+), 75 deletions(-) create mode 100644 packages/firestore/scripts/assertion-id-tool.js rename packages/firestore/scripts/{error-code-tool.ts => assertion-id-tool.ts} (81%) delete mode 100644 packages/firestore/scripts/error-code-tool.js diff --git a/packages/firestore/package.json b/packages/firestore/package.json index 284562742f5..21684e461fe 100644 --- a/packages/firestore/package.json +++ b/packages/firestore/package.json @@ -27,7 +27,7 @@ "test:lite:browser": "karma start --lite", "test:lite:browser:nameddb": "karma start --lite --databaseId=test-db", "test:lite:browser:debug": "karma start --browsers=Chrome --lite --auto-watch", - "test": "run-s --npm-path npm lint error-code:check test:all", + "test": "run-s --npm-path npm lint assertion-id:check test:all", "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:all:ci", "test:all:ci": "run-s --npm-path npm test:browser test:travis test:lite:browser test:browser:prod:nameddb test:lite:browser:nameddb", "test:all": "run-p --npm-path npm test:browser test:lite:browser test:travis test:minified test:browser:prod:nameddb test:lite:browser:nameddb", @@ -53,10 +53,10 @@ "api-report": "run-s --npm-path npm api-report:main api-report:lite && yarn api-report:api-json", "doc": "api-documenter markdown --input temp --output docs", "typings:public": "node ../../scripts/build/use_typings.js ./dist/index.d.ts", - "error-code:check": "ts-node scripts/error-code-tool.js --dir=src --check", - "error-code:generate": "ts-node scripts/error-code-tool.js --dir=src --new", - "error-code:list": "ts-node scripts/error-code-tool.js --dir=src --list", - "error-code:find": "ts-node scripts/error-code-tool.js --dir=src --find" + "assertion-id:check": "ts-node scripts/assertion-id-tool.ts --dir=src --check", + "assertion-id:generate": "ts-node scripts/assertion-id-tool.ts --dir=src --new", + "assertion-id:list": "ts-node scripts/assertion-id-tool.ts --dir=src --list", + "assertion-id:find": "ts-node scripts/assertion-id-tool.ts --dir=src --find" }, "exports": { ".": { diff --git a/packages/firestore/scripts/assertion-id-tool.js b/packages/firestore/scripts/assertion-id-tool.js new file mode 100644 index 00000000000..f29d88a1bf2 --- /dev/null +++ b/packages/firestore/scripts/assertion-id-tool.js @@ -0,0 +1,17 @@ +"use strict"; +/** + * @license + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * 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]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;_i1){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 diff --git a/packages/firestore/scripts/error-code-tool.ts b/packages/firestore/scripts/assertion-id-tool.ts similarity index 81% rename from packages/firestore/scripts/error-code-tool.ts rename to packages/firestore/scripts/assertion-id-tool.ts index 10e6d9a087f..1c8889d0d8c 100644 --- a/packages/firestore/scripts/error-code-tool.ts +++ b/packages/firestore/scripts/assertion-id-tool.ts @@ -44,7 +44,7 @@ interface CallSiteInfo { character: number; argumentsText: string[]; // Added to store argument text errorMessage: string | undefined; - errorCode: number; + assertionId: number; } /** @@ -77,7 +77,6 @@ function getTsFilesRecursive(dirPath: string): string[] { } } catch (error: any) { console.error(`Error reading directory ${dirPath}: ${error.message}`); - // Optionally, re-throw or handle differently } return tsFiles; } @@ -116,12 +115,6 @@ function findFunctionCalls(filePaths: string[]): CallSiteInfo[] { if (ts.isIdentifier(expression)) { functionName = expression.text; } - // Check if the call is to a property access (e.g., utils.fail(), this.hardAssert()) - else if (ts.isPropertyAccessExpression(expression)) { - // We only care about the final property name being called - functionName = expression.name.text; - } - // Add checks for other forms if needed // If we found a function name, and it's one we're looking for if (functionName && targetFunctionNames.has(functionName)) { @@ -134,7 +127,7 @@ function findFunctionCalls(filePaths: string[]): CallSiteInfo[] { // --- Extract Arguments --- const argsText: string[] = []; let errorMessage: string | undefined; - let errorCode: number | undefined; + let assertionId: number | undefined; if (node.arguments && node.arguments.length > 0) { node.arguments.forEach((arg: ts.Expression) => { // Get the source text of the argument node @@ -144,7 +137,7 @@ function findFunctionCalls(filePaths: string[]): CallSiteInfo[] { errorMessage = arg.getText(sourceFile); } else if (ts.isNumericLiteral(arg)) { - errorCode = parseInt(arg.getText(sourceFile), 10); + assertionId = parseInt(arg.getText(sourceFile), 10); } }); } @@ -158,7 +151,7 @@ function findFunctionCalls(filePaths: string[]): CallSiteInfo[] { character: character + 1, argumentsText: argsText, // Store the extracted arguments, errorMessage, - errorCode: errorCode ?? -1 + assertionId: assertionId ?? -1 }); } } @@ -183,26 +176,26 @@ function findFunctionCalls(filePaths: string[]): CallSiteInfo[] { function handleList(occurrences: CallSiteInfo[]): void { if (occurrences.length === 0) { - log("No error codes found."); + log("No assertion ids found."); return; } - occurrences.sort((a, b) => a.errorCode - b.errorCode).forEach((call) => { + occurrences.sort((a, b) => a.assertionId - b.assertionId).forEach((call) => { console.log( - `CODE: ${call.errorCode}; MESSAGE: ${call.errorMessage}; SOURCE: '${call.functionName}' call at ${path.relative(process.cwd(), call.fileName)}:${call.line}:${call.character}` + `ID: ${call.assertionId}; MESSAGE: ${call.errorMessage}; SOURCE: '${call.functionName}' call at ${path.relative(process.cwd(), call.fileName)}:${call.line}:${call.character}` ); }); } -function handleFind(occurrences: CallSiteInfo[], targetCode: string | number): void { +function handleFind(occurrences: CallSiteInfo[], targetId: string | number): void { // Normalize target code for comparison if necessary (e.g., string vs number) - const target = typeof targetCode === 'number' ? targetCode : targetCode.toString(); + const target = typeof targetId === 'number' ? targetId : targetId.toString(); - const foundLocations = occurrences.filter(o => String(o.errorCode) === String(target)); // Compare as strings + const foundLocations = occurrences.filter(o => String(o.assertionId) === String(target)); // Compare as strings if (foundLocations.length === 0) { - log(`Error code "${targetCode}" not found.`); + log(`Assertion id "${targetId}" not found.`); process.exit(1); } @@ -211,25 +204,25 @@ function handleFind(occurrences: CallSiteInfo[], targetCode: string | number): v function handleCheck(occurrences: CallSiteInfo[]): void { if (occurrences.length === 0) { - log("No error codes found to check for duplicates."); + log("No assertion ids found to check for duplicates."); return; } - const codeCounts: { [code: string]: CallSiteInfo[] } = {}; + const idCounts: { [id: string]: CallSiteInfo[] } = {}; occurrences.forEach(occ => { - const codeStr = String(occ.errorCode); // Use string representation as key - if (!codeCounts[codeStr]) { - codeCounts[codeStr] = []; + const codeStr = String(occ.assertionId); // Use string representation as key + if (!idCounts[codeStr]) { + idCounts[codeStr] = []; } - codeCounts[codeStr].push(occ); + idCounts[codeStr].push(occ); }); let duplicatesFound = false; - log("Checking for duplicate error code usage:"); - Object.entries(codeCounts).forEach(([code, locations]) => { + log("Checking for duplicate assertion id usage:"); + Object.entries(idCounts).forEach(([code, locations]) => { if (locations.length > 1) { duplicatesFound = true; - console.error(`\nDuplicate error code "${code}" found at ${locations.length} locations:`); + console.error(`\nDuplicate assertion id "${code}" found at ${locations.length} locations:`); locations.forEach(loc => { const relativePath = path.relative(process.cwd(), loc.fileName); console.error(`- ${relativePath}:${loc.line}:${loc.character}`); @@ -238,7 +231,7 @@ function handleCheck(occurrences: CallSiteInfo[]): void { }); if (!duplicatesFound) { - log("No duplicate error codes found."); + log("No duplicate assertion ids found."); } else { process.exit(1); @@ -250,8 +243,8 @@ function handleNew(occurrences: CallSiteInfo[]): void { let maxCode = 0; occurrences.forEach(occ => { - if (occ.errorCode > maxCode) { - maxCode = occ.errorCode; + if (occ.assertionId > maxCode) { + maxCode = occ.assertionId; } }); @@ -281,23 +274,23 @@ async function main(): Promise { }) .option("find", { alias: "F", - describe: "Find locations of a specific {errorCode}", + describe: "Find locations of a specific {assertionId}", type: "string", nargs: 1, }) .option("list", { alias: "L", - describe: "List all unique error codes found (default action)", + describe: "List all unique assertion ids found (default action)", type: "boolean", }) .option("new", { alias: "N", - describe: "Suggest a new error code based on existing ones", + describe: "Suggest a new assertion id based on existing ones", type: "boolean", }) .option("check", { alias: "C", - describe: "Check for duplicate usage of error codes", + describe: "Check for duplicate usage of assertion ids", type: "boolean", }) .check((argv) => { @@ -338,10 +331,10 @@ async function main(): Promise { log("No relevant .ts or .tsx files found."); process.exit(0); } - log(`Found ${filesToScan.length} files. Analyzing for error codes...`); + log(`Found ${filesToScan.length} files. Analyzing for assertion ids...`); const allOccurrences = findFunctionCalls(filesToScan); - log(`Scan complete. Found ${allOccurrences.length} potential error code occurrences.`); + log(`Scan complete. Found ${allOccurrences.length} potential assertion id occurrences.`); // Determine action based on flags if (argv['find']) { diff --git a/packages/firestore/scripts/error-code-tool.js b/packages/firestore/scripts/error-code-tool.js deleted file mode 100644 index 609f45fe00b..00000000000 --- a/packages/firestore/scripts/error-code-tool.js +++ /dev/null @@ -1,17 +0,0 @@ -"use strict"; -/** - * @license - * Copyright 2025 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * 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]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)){errorCode_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,errorCode:errorCode_1!==null&&errorCode_1!==void 0?errorCode_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;_i1){duplicatesFound=true;console.error('\nDuplicate error code "'.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 error codes found.")}else{process.exit(1)}}function handleNew(occurrences){var maxCode=0;occurrences.forEach((function(occ){if(occ.errorCode>maxCode){maxCode=occ.errorCode}}));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 {errorCode}",type:"string",nargs:1}).option("list",{alias:"L",describe:"List all unique error codes found (default action)",type:"boolean"}).option("new",{alias:"N",describe:"Suggest a new error code based on existing ones",type:"boolean"}).option("check",{alias:"C",describe:"Check for duplicate usage of error codes",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 error codes..."));allOccurrences=findFunctionCalls(filesToScan);log("Scan complete. Found ".concat(allOccurrences.length," potential error code 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 diff --git a/packages/firestore/src/util/assert.ts b/packages/firestore/src/util/assert.ts index 45b8d237daa..61cd5b3285d 100644 --- a/packages/firestore/src/util/assert.ts +++ b/packages/firestore/src/util/assert.ts @@ -27,8 +27,8 @@ import { logError } from './log'; * @example * let futureVar = fail('not implemented yet'); * - * @param code generate a new unique value with `yarn error-code:generate` - * Search for an existing value using `yarn error-code:find X` + * @param code generate a new unique value with `yarn assertion-id:generate` + * Search for an existing value using `yarn assertion-id:find X` */ export function fail( code: number, @@ -44,13 +44,13 @@ export function fail( * @example * let futureVar = fail('not implemented yet'); * - * @param code generate a new unique value with `yarn error-code:generate` - * Search for an existing value using `yarn error-code:find X` + * @param id generate a new unique value with `yarn assertion-id:generate` + * Search for an existing value using `yarn assertion-id:find X` */ -export function fail(code: number, context?: Record): never; +export function fail(id: number, context?: Record): never; export function fail( - code: number, + id: number, messageOrContext?: string | Record, context?: Record ): never { @@ -60,17 +60,17 @@ export function fail( } else { context = messageOrContext; } - _fail(code, message, context); + _fail(id, message, context); } function _fail( - code: number, + id: number, failure: string, context?: Record ): never { // Log the failure in addition to throw an exception, just in case the // exception is swallowed. - let message = `FIRESTORE (${SDK_VERSION}) INTERNAL ASSERTION FAILED: ${failure} (CODE: ${code.toString( + let message = `FIRESTORE (${SDK_VERSION}) INTERNAL ASSERTION FAILED: ${failure} (ID: ${id.toString( 16 )})`; if (context !== undefined) { @@ -95,12 +95,12 @@ function _fail( * * Messages are stripped in production builds. * - * @param code generate a new unique value with `yarn error-code:generate`. - * Search for an existing value using `yarn error-code:find X` + * @param id generate a new unique value with `yarn assertion-idgenerate`. + * Search for an existing value using `yarn assertion-id:find X` */ export function hardAssert( assertion: boolean, - code: number, + id: number, message: string, context?: Record ): asserts assertion; @@ -111,18 +111,18 @@ export function hardAssert( * * Messages are stripped in production builds. * - * @param code generate a new unique value with `yarn error-code:generate`. - * Search for an existing value using `yarn error-code:find X` + * @param id generate a new unique value with `yarn assertion-id:generate`. + * Search for an existing value using `yarn assertion-id:find X` */ export function hardAssert( assertion: boolean, - code: number, + id: number, context?: Record ): asserts assertion; export function hardAssert( assertion: boolean, - code: number, + id: number, messageOrContext?: string | Record, context?: Record ): asserts assertion { @@ -134,7 +134,7 @@ export function hardAssert( } if (!assertion) { - _fail(code, message, context); + _fail(id, message, context); } } From 3803a8637803ac563f760d02039edebf716de474 Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Mon, 7 Apr 2025 12:54:29 -0600 Subject: [PATCH 12/18] Assertion ID back to 4 digit hex --- packages/firestore/src/api/credentials.ts | 14 ++--- packages/firestore/src/api/snapshot.ts | 2 +- .../firestore/src/core/component_provider.ts | 2 +- packages/firestore/src/core/filter.ts | 4 +- packages/firestore/src/core/query.ts | 2 +- .../firestore/src/core/sync_engine_impl.ts | 12 ++-- packages/firestore/src/core/transaction.ts | 2 +- packages/firestore/src/core/view.ts | 2 +- packages/firestore/src/core/view_snapshot.ts | 2 +- .../src/index/firestore_index_value_writer.ts | 2 +- .../firestore/src/lite-api/reference_impl.ts | 6 +- .../firestore/src/lite-api/transaction.ts | 12 ++-- .../src/lite-api/user_data_reader.ts | 2 +- .../src/lite-api/user_data_writer.ts | 4 +- .../src/local/encoded_resource_path.ts | 8 +-- .../src/local/indexeddb_index_manager.ts | 4 +- .../local/indexeddb_mutation_batch_impl.ts | 4 +- .../src/local/indexeddb_mutation_queue.ts | 18 +++--- .../local/indexeddb_remote_document_cache.ts | 2 +- .../src/local/indexeddb_schema_converter.ts | 4 +- .../src/local/indexeddb_sentinels.ts | 2 +- .../src/local/indexeddb_target_cache.ts | 4 +- .../firestore/src/local/local_serializer.ts | 4 +- .../firestore/src/local/local_store_impl.ts | 4 +- .../src/local/memory_mutation_queue.ts | 2 +- .../firestore/src/local/memory_persistence.ts | 5 +- .../src/local/memory_remote_document_cache.ts | 2 +- .../src/local/persistence_promise.ts | 2 +- .../src/local/shared_client_state.ts | 2 +- packages/firestore/src/model/document.ts | 5 +- packages/firestore/src/model/mutation.ts | 2 +- .../firestore/src/model/mutation_batch.ts | 2 +- packages/firestore/src/model/normalize.ts | 4 +- packages/firestore/src/model/path.ts | 4 +- .../src/model/target_index_matcher.ts | 2 +- packages/firestore/src/model/values.ts | 14 ++--- .../platform/browser/webchannel_connection.ts | 8 ++- .../src/platform/node/grpc_connection.ts | 2 +- packages/firestore/src/remote/datastore.ts | 4 +- .../firestore/src/remote/persistent_stream.ts | 6 +- packages/firestore/src/remote/rpc_error.ts | 8 +-- packages/firestore/src/remote/serializer.ts | 56 +++++++++---------- packages/firestore/src/remote/watch_change.ts | 8 +-- packages/firestore/src/util/assert.ts | 2 +- .../firestore/src/util/async_queue_impl.ts | 2 +- .../firestore/src/util/input_validation.ts | 2 +- packages/firestore/src/util/logic_utils.ts | 20 +++---- packages/firestore/src/util/sorted_map.ts | 16 +++--- 48 files changed, 160 insertions(+), 142 deletions(-) diff --git a/packages/firestore/src/api/credentials.ts b/packages/firestore/src/api/credentials.ts index dd8a2d0d4d6..c1b10d61057 100644 --- a/packages/firestore/src/api/credentials.ts +++ b/packages/firestore/src/api/credentials.ts @@ -207,7 +207,7 @@ export class LiteAuthCredentialsProvider implements CredentialsProvider { if (tokenData) { hardAssert( typeof tokenData.accessToken === 'string', - 92, + 0xa539, 'Invalid tokenData returned from getToken()', { tokenData } ); @@ -261,7 +261,7 @@ export class FirebaseAuthCredentialsProvider ): void { hardAssert( this.tokenListener === undefined, - 93, + 0xa540, 'Token listener already added' ); let lastTokenId = this.tokenCounter; @@ -360,7 +360,7 @@ export class FirebaseAuthCredentialsProvider if (tokenData) { hardAssert( typeof tokenData.accessToken === 'string', - 94, + 0x7c5d, 'Invalid tokenData returned from getToken()', { tokenData } ); @@ -391,7 +391,7 @@ export class FirebaseAuthCredentialsProvider const currentUid = this.auth && this.auth.getUid(); hardAssert( currentUid === null || typeof currentUid === 'string', - 95, + 0x0807, 'Received invalid UID', { currentUid } ); @@ -520,7 +520,7 @@ export class FirebaseAppCheckTokenProvider ): void { hardAssert( this.tokenListener === undefined, - 96, + 0x0db8, 'Token listener already added' ); @@ -596,7 +596,7 @@ export class FirebaseAppCheckTokenProvider if (tokenResult) { hardAssert( typeof tokenResult.token === 'string', - 97, + 0xae0e, 'Invalid tokenResult returned from getToken()', { tokenResult } ); @@ -669,7 +669,7 @@ export class LiteAppCheckTokenProvider implements CredentialsProvider { if (tokenResult) { hardAssert( typeof tokenResult.token === 'string', - 98, + 0x0d8e, 'Invalid tokenResult returned from getToken()', { tokenResult } ); diff --git a/packages/firestore/src/api/snapshot.ts b/packages/firestore/src/api/snapshot.ts index 0e7f43c5642..669ac26cafe 100644 --- a/packages/firestore/src/api/snapshot.ts +++ b/packages/firestore/src/api/snapshot.ts @@ -749,7 +749,7 @@ export function resultChangeType(type: ChangeType): DocumentChangeType { case ChangeType.Removed: return 'removed'; default: - return fail(91, 'Unknown change type', { type }); + return fail(0xf03d, 'Unknown change type', { type }); } } diff --git a/packages/firestore/src/core/component_provider.ts b/packages/firestore/src/core/component_provider.ts index 3d1a3ffdc60..183a1046cc9 100644 --- a/packages/firestore/src/core/component_provider.ts +++ b/packages/firestore/src/core/component_provider.ts @@ -197,7 +197,7 @@ export class LruGcMemoryOfflineComponentProvider extends MemoryOfflineComponentP ): Scheduler | null { hardAssert( this.persistence.referenceDelegate instanceof MemoryLruDelegate, - 17, + 0xb743, 'referenceDelegate is expected to be an instance of MemoryLruDelegate.' ); diff --git a/packages/firestore/src/core/filter.ts b/packages/firestore/src/core/filter.ts index f7ecec783c2..d8907387057 100644 --- a/packages/firestore/src/core/filter.ts +++ b/packages/firestore/src/core/filter.ts @@ -168,7 +168,7 @@ export class FieldFilter extends Filter { case Operator.GREATER_THAN_OR_EQUAL: return comparison >= 0; default: - return fail(8, 'Unknown FieldFilter operator', { + return fail(0xb8a2, 'Unknown FieldFilter operator', { operator: this.op }); } @@ -317,7 +317,7 @@ export function filterEquals(f1: Filter, f2: Filter): boolean { } else if (f1 instanceof CompositeFilter) { return compositeFilterEquals(f1, f2); } else { - fail(9, 'Only FieldFilters and CompositeFilters can be compared'); + fail(0x4bef, 'Only FieldFilters and CompositeFilters can be compared'); } } diff --git a/packages/firestore/src/core/query.ts b/packages/firestore/src/core/query.ts index 97c378d4096..ca875bbb14d 100644 --- a/packages/firestore/src/core/query.ts +++ b/packages/firestore/src/core/query.ts @@ -578,6 +578,6 @@ export function compareDocs( case Direction.DESCENDING: return -1 * comparison; default: - return fail(19, 'Unknown direction', { direction: orderBy.dir }); + return fail(0x4d4e, 'Unknown direction', { direction: orderBy.dir }); } } diff --git a/packages/firestore/src/core/sync_engine_impl.ts b/packages/firestore/src/core/sync_engine_impl.ts index 7e430c5ddcb..404d4663a47 100644 --- a/packages/firestore/src/core/sync_engine_impl.ts +++ b/packages/firestore/src/core/sync_engine_impl.ts @@ -579,7 +579,7 @@ export async function syncEngineApplyRemoteEvent( targetChange.modifiedDocuments.size + targetChange.removedDocuments.size <= 1, - 10, + 0x5858, 'Limbo resolution for single document contains multiple changes.' ); if (targetChange.addedDocuments.size > 0) { @@ -587,13 +587,13 @@ export async function syncEngineApplyRemoteEvent( } else if (targetChange.modifiedDocuments.size > 0) { hardAssert( limboResolution.receivedDocument, - 11, + 0x390f, 'Received change for limbo target document without add.' ); } else if (targetChange.removedDocuments.size > 0) { hardAssert( limboResolution.receivedDocument, - 12, + 0xa4f3, 'Received remove for limbo target document without add.' ); limboResolution.receivedDocument = false; @@ -997,7 +997,7 @@ function updateTrackedLimbos( removeLimboTarget(syncEngineImpl, limboChange.key); } } else { - fail(13, 'Unknown limbo change', { limboChange }); + fail(0x4d4f, 'Unknown limbo change', { limboChange }); } } } @@ -1320,7 +1320,7 @@ export async function syncEngineApplyBatchState( batchId ); } else { - fail(14, `Unknown batchState`, { batchState }); + fail(0x1a40, `Unknown batchState`, { batchState }); } await syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngineImpl, documents); @@ -1563,7 +1563,7 @@ export async function syncEngineApplyTargetState( break; } default: - fail(15, 'Unexpected target state', state); + fail(0xfa9b, 'Unexpected target state', state); } } } diff --git a/packages/firestore/src/core/transaction.ts b/packages/firestore/src/core/transaction.ts index b7aea509601..d3dc6ee8a89 100644 --- a/packages/firestore/src/core/transaction.ts +++ b/packages/firestore/src/core/transaction.ts @@ -124,7 +124,7 @@ export class Transaction { // Represent a deleted doc using SnapshotVersion.min(). docVersion = SnapshotVersion.min(); } else { - throw fail(18, 'Document in a transaction was a ', { + throw fail(0xc542, 'Document in a transaction was a ', { documentName: doc.constructor.name }); } diff --git a/packages/firestore/src/core/view.ts b/packages/firestore/src/core/view.ts index eb5ef4a5a92..e0909de938f 100644 --- a/packages/firestore/src/core/view.ts +++ b/packages/firestore/src/core/view.ts @@ -505,7 +505,7 @@ function compareChangeType(c1: ChangeType, c2: ChangeType): number { case ChangeType.Removed: return 0; default: - return fail(16, 'Unknown ChangeType', { change }); + return fail(0x4f35, 'Unknown ChangeType', { change }); } }; diff --git a/packages/firestore/src/core/view_snapshot.ts b/packages/firestore/src/core/view_snapshot.ts index 9f3d04cd458..3bb6e8ae134 100644 --- a/packages/firestore/src/core/view_snapshot.ts +++ b/packages/firestore/src/core/view_snapshot.ts @@ -118,7 +118,7 @@ export class DocumentChangeSet { // Metadata->Added // Removed->Metadata fail( - 7, + 0xf76d, 'unsupported combination of changes: `change` after `oldChange`', { change, diff --git a/packages/firestore/src/index/firestore_index_value_writer.ts b/packages/firestore/src/index/firestore_index_value_writer.ts index b58c2a01b8c..b76ca7a930a 100644 --- a/packages/firestore/src/index/firestore_index_value_writer.ts +++ b/packages/firestore/src/index/firestore_index_value_writer.ts @@ -136,7 +136,7 @@ export class FirestoreIndexValueWriter { this.writeIndexArray(indexValue.arrayValue!, encoder); this.writeTruncationMarker(encoder); } else { - fail(99, 'unknown index value type', { indexValue }); + fail(0x4a4e, 'unknown index value type', { indexValue }); } } diff --git a/packages/firestore/src/lite-api/reference_impl.ts b/packages/firestore/src/lite-api/reference_impl.ts index 10b1920322d..6d92ccab479 100644 --- a/packages/firestore/src/lite-api/reference_impl.ts +++ b/packages/firestore/src/lite-api/reference_impl.ts @@ -133,7 +133,11 @@ export function getDoc( return invokeBatchGetDocumentsRpc(datastore, [reference._key]).then( result => { - hardAssert(result.length === 1, 1, 'Expected a single document result'); + hardAssert( + result.length === 1, + 0x3d02, + 'Expected a single document result' + ); const document = result[0]; return new DocumentSnapshot( reference.firestore, diff --git a/packages/firestore/src/lite-api/transaction.ts b/packages/firestore/src/lite-api/transaction.ts index 8b662bc6f15..9a405feebea 100644 --- a/packages/firestore/src/lite-api/transaction.ts +++ b/packages/firestore/src/lite-api/transaction.ts @@ -94,7 +94,7 @@ export class Transaction { const userDataWriter = new LiteUserDataWriter(this._firestore); return this._transaction.lookup([ref._key]).then(docs => { if (!docs || docs.length !== 1) { - return fail(2, 'Mismatch in docs returned from document lookup.'); + return fail(0x5de9, 'Mismatch in docs returned from document lookup.'); } const doc = docs[0]; if (doc.isFoundDocument()) { @@ -114,9 +114,13 @@ export class Transaction { ref.converter ); } else { - throw fail(3, 'BatchGetDocumentsRequest returned unexpected document', { - doc - }); + throw fail( + 0x4801, + 'BatchGetDocumentsRequest returned unexpected document', + { + doc + } + ); } }); } diff --git a/packages/firestore/src/lite-api/user_data_reader.ts b/packages/firestore/src/lite-api/user_data_reader.ts index 9ce6d59fbc9..aa5f9eeb5bf 100644 --- a/packages/firestore/src/lite-api/user_data_reader.ts +++ b/packages/firestore/src/lite-api/user_data_reader.ts @@ -175,7 +175,7 @@ function isWrite(dataSource: UserDataSource): boolean { case UserDataSource.ArrayArgument: return false; default: - throw fail(6, 'Unexpected case for UserDataSource', { + throw fail(0x9c4b, 'Unexpected case for UserDataSource', { dataSource }); } diff --git a/packages/firestore/src/lite-api/user_data_writer.ts b/packages/firestore/src/lite-api/user_data_writer.ts index 1a83e523efd..070c71c7832 100644 --- a/packages/firestore/src/lite-api/user_data_writer.ts +++ b/packages/firestore/src/lite-api/user_data_writer.ts @@ -89,7 +89,7 @@ export abstract class AbstractUserDataWriter { case TypeOrder.VectorValue: return this.convertVectorValue(value.mapValue!); default: - throw fail(4, 'Invalid value type', { + throw fail(0xf2a2, 'Invalid value type', { value }); } @@ -175,7 +175,7 @@ export abstract class AbstractUserDataWriter { const resourcePath = ResourcePath.fromString(name); hardAssert( isValidResourceName(resourcePath), - 5, + 0x25d8, 'ReferenceValue is not valid', { name } ); diff --git a/packages/firestore/src/local/encoded_resource_path.ts b/packages/firestore/src/local/encoded_resource_path.ts index 00b135a1341..497a65fdf8c 100644 --- a/packages/firestore/src/local/encoded_resource_path.ts +++ b/packages/firestore/src/local/encoded_resource_path.ts @@ -118,11 +118,11 @@ export function decodeResourcePath(path: EncodedResourcePath): ResourcePath { // Event the empty path must encode as a path of at least length 2. A path // with exactly 2 must be the empty path. const length = path.length; - hardAssert(length >= 2, 63, 'Invalid path', { path }); + hardAssert(length >= 2, 0xfb98, 'Invalid path', { path }); if (length === 2) { hardAssert( path.charAt(0) === escapeChar && path.charAt(1) === encodedSeparatorChar, - 64, + 0xdb51, 'Non-empty path had length 2', { path } ); @@ -141,7 +141,7 @@ export function decodeResourcePath(path: EncodedResourcePath): ResourcePath { // there must be an end to this segment. const end = path.indexOf(escapeChar, start); if (end < 0 || end > lastReasonableEscapeIndex) { - fail(65, 'Invalid encoded resource path', { path }); + fail(0xc553, 'Invalid encoded resource path', { path }); } const next = path.charAt(end + 1); @@ -169,7 +169,7 @@ export function decodeResourcePath(path: EncodedResourcePath): ResourcePath { segmentBuilder += path.substring(start, end + 1); break; default: - fail(66, 'Invalid encoded resource path', { path }); + fail(0xeeef, 'Invalid encoded resource path', { path }); } start = end + 2; diff --git a/packages/firestore/src/local/indexeddb_index_manager.ts b/packages/firestore/src/local/indexeddb_index_manager.ts index c0702586120..d2b8bc47163 100644 --- a/packages/firestore/src/local/indexeddb_index_manager.ts +++ b/packages/firestore/src/local/indexeddb_index_manager.ts @@ -1066,7 +1066,7 @@ export class IndexedDbIndexManager implements IndexManager { this.getSubTargets(target), (subTarget: Target) => this.getFieldIndex(transaction, subTarget).next(index => - index ? index : fail(53, 'Target cannot be served from index') + index ? index : fail(0xad8a, 'Target cannot be served from index') ) ).next(getMinOffsetFromFieldIndexes); } @@ -1118,7 +1118,7 @@ function indexStateStore( function getMinOffsetFromFieldIndexes(fieldIndexes: FieldIndex[]): IndexOffset { hardAssert( fieldIndexes.length !== 0, - 54, + 0x7099, 'Found empty index group when looking for least recent index offset.' ); diff --git a/packages/firestore/src/local/indexeddb_mutation_batch_impl.ts b/packages/firestore/src/local/indexeddb_mutation_batch_impl.ts index 6b6e01877fb..64b6df20b7e 100644 --- a/packages/firestore/src/local/indexeddb_mutation_batch_impl.ts +++ b/packages/firestore/src/local/indexeddb_mutation_batch_impl.ts @@ -64,7 +64,7 @@ export function removeMutationBatch( removePromise.next(() => { hardAssert( numDeleted === 1, - 55, + 0xb7de, 'Dangling document-mutation reference found: Missing batch', { batchId: batch.batchId } ); @@ -101,7 +101,7 @@ export function dbDocumentSize( } else if (doc.noDocument) { value = doc.noDocument; } else { - throw fail(56, 'Unknown remote document type'); + throw fail(0x398b, 'Unknown remote document type'); } return JSON.stringify(value).length; } diff --git a/packages/firestore/src/local/indexeddb_mutation_queue.ts b/packages/firestore/src/local/indexeddb_mutation_queue.ts index 69ad7b1642e..bcdafa6aa36 100644 --- a/packages/firestore/src/local/indexeddb_mutation_queue.ts +++ b/packages/firestore/src/local/indexeddb_mutation_queue.ts @@ -105,7 +105,7 @@ export class IndexedDbMutationQueue implements MutationQueue { // In particular, are there any reserved characters? are empty ids allowed? // For the moment store these together in the same mutations table assuming // that empty userIDs aren't allowed. - hardAssert(user.uid !== '', 67, 'UserID must not be an empty string.'); + hardAssert(user.uid !== '', 0xfb83, 'UserID must not be an empty string.'); const userId = user.isAuthenticated() ? user.uid! : ''; return new IndexedDbMutationQueue( userId, @@ -154,7 +154,7 @@ export class IndexedDbMutationQueue implements MutationQueue { return mutationStore.add({} as any).next(batchId => { hardAssert( typeof batchId === 'number', - 68, + 0xbf7b, 'Auto-generated key is not a number' ); @@ -207,7 +207,7 @@ export class IndexedDbMutationQueue implements MutationQueue { if (dbBatch) { hardAssert( dbBatch.userId === this.userId, - 69, + 0x0030, `Unexpected user for mutation batch`, { userId: dbBatch.userId, @@ -263,7 +263,7 @@ export class IndexedDbMutationQueue implements MutationQueue { if (dbBatch.userId === this.userId) { hardAssert( dbBatch.batchId >= nextBatchId, - 70, + 0xb9a4, 'Should have found mutation after `nextBatchId`', { nextBatchId } ); @@ -344,7 +344,7 @@ export class IndexedDbMutationQueue implements MutationQueue { .next(mutation => { if (!mutation) { throw fail( - 71, + 0xf028, 'Dangling document-mutation reference found: `indexKey` which points to `batchId`', { indexKey, @@ -354,7 +354,7 @@ export class IndexedDbMutationQueue implements MutationQueue { } hardAssert( mutation.userId === this.userId, - 72, + 0x2907, `Unexpected user for mutation batch`, { userId: mutation.userId, @@ -483,7 +483,7 @@ export class IndexedDbMutationQueue implements MutationQueue { .next(mutation => { if (mutation === null) { throw fail( - 73, + 0x89ca, 'Dangling document-mutation reference found, which points to `batchId`', { batchId @@ -492,7 +492,7 @@ export class IndexedDbMutationQueue implements MutationQueue { } hardAssert( mutation.userId === this.userId, - 74, + 0x2614, `Unexpected user for mutation batch`, { userId: mutation.userId, batchId } ); @@ -568,7 +568,7 @@ export class IndexedDbMutationQueue implements MutationQueue { .next(() => { hardAssert( danglingMutationReferences.length === 0, - 75, + 0xdd90, 'Document leak -- detected dangling mutation references when queue is empty.', { danglingKeys: danglingMutationReferences.map(p => diff --git a/packages/firestore/src/local/indexeddb_remote_document_cache.ts b/packages/firestore/src/local/indexeddb_remote_document_cache.ts index 313c0dba67c..fffe935c4f9 100644 --- a/packages/firestore/src/local/indexeddb_remote_document_cache.ts +++ b/packages/firestore/src/local/indexeddb_remote_document_cache.ts @@ -381,7 +381,7 @@ class IndexedDbRemoteDocumentCacheImpl implements IndexedDbRemoteDocumentCache { return documentGlobalStore(txn) .get(DbRemoteDocumentGlobalKey) .next(metadata => { - hardAssert(!!metadata, 62, 'Missing document cache metadata'); + hardAssert(!!metadata, 0x4e35, 'Missing document cache metadata'); return metadata!; }); } diff --git a/packages/firestore/src/local/indexeddb_schema_converter.ts b/packages/firestore/src/local/indexeddb_schema_converter.ts index 8e6ab40a952..7446ae7ae20 100644 --- a/packages/firestore/src/local/indexeddb_schema_converter.ts +++ b/packages/firestore/src/local/indexeddb_schema_converter.ts @@ -326,7 +326,7 @@ export class SchemaConverter implements SimpleDbSchemaConverter { (dbBatch: DbMutationBatch) => { hardAssert( dbBatch.userId === queue.userId, - 48, + 0x48da, `Cannot process batch from unexpected user`, { batchId: dbBatch.batchId } ); @@ -774,6 +774,6 @@ function extractKey(remoteDoc: DbRemoteDocumentLegacy): DocumentKey { } else if (remoteDoc.unknownDocument) { return DocumentKey.fromSegments(remoteDoc.unknownDocument.path); } else { - return fail(49, 'Unexpected DbRemoteDocument'); + return fail(0x8faf, 'Unexpected DbRemoteDocument'); } } diff --git a/packages/firestore/src/local/indexeddb_sentinels.ts b/packages/firestore/src/local/indexeddb_sentinels.ts index 7bc8fa07cf8..cb6ebcb664a 100644 --- a/packages/firestore/src/local/indexeddb_sentinels.ts +++ b/packages/firestore/src/local/indexeddb_sentinels.ts @@ -450,6 +450,6 @@ export function getObjectStores(schemaVersion: number): string[] { } else if (schemaVersion === 11) { return V11_STORES; } else { - fail(61, 'Only schema version 11 and 12 and 13 are supported'); + fail(0xeb55, 'Only schema version 11 and 12 and 13 are supported'); } } diff --git a/packages/firestore/src/local/indexeddb_target_cache.ts b/packages/firestore/src/local/indexeddb_target_cache.ts index 2cd80695980..1d5ed8f0c8b 100644 --- a/packages/firestore/src/local/indexeddb_target_cache.ts +++ b/packages/firestore/src/local/indexeddb_target_cache.ts @@ -144,7 +144,7 @@ export class IndexedDbTargetCache implements TargetCache { .next(metadata => { hardAssert( metadata.targetCount > 0, - 59, + 0x1f81, 'Removing from an empty target cache' ); metadata.targetCount -= 1; @@ -198,7 +198,7 @@ export class IndexedDbTargetCache implements TargetCache { return globalTargetStore(transaction) .get(DbTargetGlobalKey) .next(metadata => { - hardAssert(metadata !== null, 60, 'Missing metadata row.'); + hardAssert(metadata !== null, 0x0b48, 'Missing metadata row.'); return metadata; }); } diff --git a/packages/firestore/src/local/local_serializer.ts b/packages/firestore/src/local/local_serializer.ts index a2ed0f36bc9..bb1658caa52 100644 --- a/packages/firestore/src/local/local_serializer.ts +++ b/packages/firestore/src/local/local_serializer.ts @@ -101,7 +101,7 @@ export function fromDbRemoteDocument( const version = fromDbTimestamp(remoteDoc.unknownDocument.version); doc = MutableDocument.newUnknownDocument(key, version); } else { - return fail(45, 'Unexpected DbRemoteDocument'); + return fail(0xdd85, 'Unexpected DbRemoteDocument'); } if (remoteDoc.readTime) { @@ -138,7 +138,7 @@ export function toDbRemoteDocument( version: toDbTimestamp(document.version) }; } else { - return fail(46, 'Unexpected Document', { document }); + return fail(0xe230, 'Unexpected Document', { document }); } return remoteDoc; } diff --git a/packages/firestore/src/local/local_store_impl.ts b/packages/firestore/src/local/local_store_impl.ts index ca212ec66c0..31d2a46c326 100644 --- a/packages/firestore/src/local/local_store_impl.ts +++ b/packages/firestore/src/local/local_store_impl.ts @@ -496,7 +496,7 @@ export function localStoreRejectBatch( .next((batch: MutationBatch | null) => { hardAssert( batch !== null, - 50, + 0x90f9, 'Attempt to reject nonexistent batch!' ); affectedKeys = batch.keys(); @@ -1141,7 +1141,7 @@ function applyWriteToRemoteDocuments( const ackVersion = batchResult.docVersions.get(docKey); hardAssert( ackVersion !== null, - 51, + 0xbd9d, 'ackVersions should contain every doc in the write.' ); if (doc.version.compareTo(ackVersion!) < 0) { diff --git a/packages/firestore/src/local/memory_mutation_queue.ts b/packages/firestore/src/local/memory_mutation_queue.ts index d5e265497c1..f136fb7ad15 100644 --- a/packages/firestore/src/local/memory_mutation_queue.ts +++ b/packages/firestore/src/local/memory_mutation_queue.ts @@ -246,7 +246,7 @@ export class MemoryMutationQueue implements MutationQueue { const batchIndex = this.indexOfExistingBatchId(batch.batchId, 'removed'); hardAssert( batchIndex === 0, - 44, + 0xd6db, 'Can only remove the first entry of the mutation queue' ); this.mutationQueue.shift(); diff --git a/packages/firestore/src/local/memory_persistence.ts b/packages/firestore/src/local/memory_persistence.ts index 96bb2d4a723..fcb6db42059 100644 --- a/packages/firestore/src/local/memory_persistence.ts +++ b/packages/firestore/src/local/memory_persistence.ts @@ -235,7 +235,10 @@ export class MemoryEagerDelegate implements MemoryReferenceDelegate { private get orphanedDocuments(): Set { if (!this._orphanedDocuments) { - throw fail(58, 'orphanedDocuments is only valid during a transaction.'); + throw fail( + 0xee44, + 'orphanedDocuments is only valid during a transaction.' + ); } else { return this._orphanedDocuments; } diff --git a/packages/firestore/src/local/memory_remote_document_cache.ts b/packages/firestore/src/local/memory_remote_document_cache.ts index edc695ed5d9..0daf80b6a19 100644 --- a/packages/firestore/src/local/memory_remote_document_cache.ts +++ b/packages/firestore/src/local/memory_remote_document_cache.ts @@ -219,7 +219,7 @@ class MemoryRemoteDocumentCacheImpl implements MemoryRemoteDocumentCache { ): PersistencePromise { // This method should only be called from the IndexBackfiller if persistence // is enabled. - fail(57, 'getAllFromCollectionGroup() is not supported.'); + fail(0x251c, 'getAllFromCollectionGroup() is not supported.'); } forEachDocumentKey( diff --git a/packages/firestore/src/local/persistence_promise.ts b/packages/firestore/src/local/persistence_promise.ts index 1a9d4ffe6c1..812cc0fca85 100644 --- a/packages/firestore/src/local/persistence_promise.ts +++ b/packages/firestore/src/local/persistence_promise.ts @@ -86,7 +86,7 @@ export class PersistencePromise { catchFn?: RejectedHandler ): PersistencePromise { if (this.callbackAttached) { - fail(52, 'Called next() or catch() twice for PersistencePromise'); + fail(0xe830, 'Called next() or catch() twice for PersistencePromise'); } this.callbackAttached = true; if (this.isDone) { diff --git a/packages/firestore/src/local/shared_client_state.ts b/packages/firestore/src/local/shared_client_state.ts index 19c22a633b8..1000e63a0f6 100644 --- a/packages/firestore/src/local/shared_client_state.ts +++ b/packages/firestore/src/local/shared_client_state.ts @@ -1085,7 +1085,7 @@ function fromWebStorageSequenceNumber( const parsed = JSON.parse(seqString); hardAssert( typeof parsed === 'number', - 47, + 0x77ac, 'Found non-numeric sequence number', { seqString } ); diff --git a/packages/firestore/src/model/document.ts b/packages/firestore/src/model/document.ts index af946f2ea21..ac454704776 100644 --- a/packages/firestore/src/model/document.ts +++ b/packages/firestore/src/model/document.ts @@ -397,6 +397,9 @@ export function compareDocumentsByField( if (v1 !== null && v2 !== null) { return valueCompare(v1, v2); } else { - return fail(79, "Trying to compare documents on fields that don't exist"); + return fail( + 0xa786, + "Trying to compare documents on fields that don't exist" + ); } } diff --git a/packages/firestore/src/model/mutation.ts b/packages/firestore/src/model/mutation.ts index e7d0170c86f..0bcd1345b01 100644 --- a/packages/firestore/src/model/mutation.ts +++ b/packages/firestore/src/model/mutation.ts @@ -623,7 +623,7 @@ function serverTransformResults( const transformResults = new Map(); hardAssert( fieldTransforms.length === serverTransformResults.length, - 83, + 0x7f90, 'server transform result count should match field transform count', { serverTransformResultCount: serverTransformResults.length, diff --git a/packages/firestore/src/model/mutation_batch.ts b/packages/firestore/src/model/mutation_batch.ts index 509ac454b7e..703623da01a 100644 --- a/packages/firestore/src/model/mutation_batch.ts +++ b/packages/firestore/src/model/mutation_batch.ts @@ -219,7 +219,7 @@ export class MutationBatchResult { ): MutationBatchResult { hardAssert( batch.mutations.length === results.length, - 80, + 0xe5da, 'Mutations sent must equal results received', { mutationsSent: batch.mutations.length, diff --git a/packages/firestore/src/model/normalize.ts b/packages/firestore/src/model/normalize.ts index 31731e58c4e..986eeed1e48 100644 --- a/packages/firestore/src/model/normalize.ts +++ b/packages/firestore/src/model/normalize.ts @@ -32,7 +32,7 @@ export function normalizeTimestamp(date: Timestamp): { seconds: number; nanos: number; } { - hardAssert(!!date, 81, 'Cannot normalize null or undefined timestamp.'); + hardAssert(!!date, 0x986a, 'Cannot normalize null or undefined timestamp.'); // The json interface (for the browser) will return an iso timestamp string, // while the proto js library (for node) will return a @@ -44,7 +44,7 @@ export function normalizeTimestamp(date: Timestamp): { // Parse the nanos right out of the string. let nanos = 0; const fraction = ISO_TIMESTAMP_REG_EXP.exec(date); - hardAssert(!!fraction, 82, 'invalid timestamp', { + hardAssert(!!fraction, 0xb5de, 'invalid timestamp', { timestamp: date }); if (fraction[1]) { diff --git a/packages/firestore/src/model/path.ts b/packages/firestore/src/model/path.ts index f30d65d0a9c..0f4581da8d8 100644 --- a/packages/firestore/src/model/path.ts +++ b/packages/firestore/src/model/path.ts @@ -35,7 +35,7 @@ abstract class BasePath> { if (offset === undefined) { offset = 0; } else if (offset > segments.length) { - fail(76, 'offset out of range', { + fail(0x027d, 'offset out of range', { offset, range: segments.length }); @@ -44,7 +44,7 @@ abstract class BasePath> { if (length === undefined) { length = segments.length - offset; } else if (length > segments.length - offset) { - fail(77, 'length out of range', { + fail(0x06d2, 'length out of range', { length, range: segments.length - offset }); diff --git a/packages/firestore/src/model/target_index_matcher.ts b/packages/firestore/src/model/target_index_matcher.ts index 73aae4ba0a0..407eae337c7 100644 --- a/packages/firestore/src/model/target_index_matcher.ts +++ b/packages/firestore/src/model/target_index_matcher.ts @@ -111,7 +111,7 @@ export class TargetIndexMatcher { servedByIndex(index: FieldIndex): boolean { hardAssert( index.collectionGroup === this.collectionId, - 78, + 0xc07f, 'Collection IDs do not match' ); diff --git a/packages/firestore/src/model/values.ts b/packages/firestore/src/model/values.ts index 7f87e9c90bc..1ef54a98ad6 100644 --- a/packages/firestore/src/model/values.ts +++ b/packages/firestore/src/model/values.ts @@ -93,7 +93,7 @@ export function typeOrder(value: Value): TypeOrder { } return TypeOrder.ObjectValue; } else { - return fail(84, 'Invalid value type', { value }); + return fail(0x6e87, 'Invalid value type', { value }); } } @@ -140,7 +140,7 @@ export function valueEquals(left: Value, right: Value): boolean { case TypeOrder.MaxValue: return true; default: - return fail(85, 'Unexpected value type', { left }); + return fail(0xcbf8, 'Unexpected value type', { left }); } } @@ -269,7 +269,7 @@ export function valueCompare(left: Value, right: Value): number { case TypeOrder.ObjectValue: return compareMaps(left.mapValue!, right.mapValue!); default: - throw fail(86, 'Invalid value type', { leftType }); + throw fail(0x5ae0, 'Invalid value type', { leftType }); } } @@ -449,7 +449,7 @@ function canonifyValue(value: Value): string { } else if ('mapValue' in value) { return canonifyMap(value.mapValue!); } else { - return fail(87, 'Invalid value type', { value }); + return fail(0xee4d, 'Invalid value type', { value }); } } @@ -541,7 +541,7 @@ export function estimateByteSize(value: Value): number { case TypeOrder.ObjectValue: return estimateMapByteSize(value.mapValue!); default: - throw fail(88, 'Invalid value type', { value }); + throw fail(0x34ae, 'Invalid value type', { value }); } } @@ -701,7 +701,7 @@ export function valuesGetLowerBound(value: Value): Value { } return { mapValue: {} }; } else { - return fail(89, 'Invalid value type', { value }); + return fail(0x8c66, 'Invalid value type', { value }); } } @@ -731,7 +731,7 @@ export function valuesGetUpperBound(value: Value): Value { } return MAX_VALUE; } else { - return fail(90, 'Invalid value type', { value }); + return fail(0xf207, 'Invalid value type', { value }); } } diff --git a/packages/firestore/src/platform/browser/webchannel_connection.ts b/packages/firestore/src/platform/browser/webchannel_connection.ts index 91038042c60..ce20eef2049 100644 --- a/packages/firestore/src/platform/browser/webchannel_connection.ts +++ b/packages/firestore/src/platform/browser/webchannel_connection.ts @@ -142,7 +142,7 @@ export class WebChannelConnection extends RestConnection { break; default: fail( - 41, + 0x235f, 'RPC failed with unanticipated webchannel error. Giving up.', { rpcName, @@ -353,7 +353,11 @@ export class WebChannelConnection extends RestConnection { msg => { if (!closed) { const msgData = msg.data[0]; - hardAssert(!!msgData, 42, 'Got a webchannel message without data.'); + hardAssert( + !!msgData, + 0x3fdd, + 'Got a webchannel message without data.' + ); // TODO(b/35143891): There is a bug in One Platform that caused errors // (and only errors) to be wrapped in an extra array. To be forward // compatible with the bug we need to check either condition. The latter diff --git a/packages/firestore/src/platform/node/grpc_connection.ts b/packages/firestore/src/platform/node/grpc_connection.ts index d3be8385a8e..d50a3149416 100644 --- a/packages/firestore/src/platform/node/grpc_connection.ts +++ b/packages/firestore/src/platform/node/grpc_connection.ts @@ -48,7 +48,7 @@ function createMetadata( ): grpc.Metadata { hardAssert( authToken === null || authToken.type === 'OAuth', - 43, + 0x9048, 'If provided, token must be OAuth' ); const metadata = new grpc.Metadata(); diff --git a/packages/firestore/src/remote/datastore.ts b/packages/firestore/src/remote/datastore.ts index 0bfdb9ac1aa..f790ede0d5c 100644 --- a/packages/firestore/src/remote/datastore.ts +++ b/packages/firestore/src/remote/datastore.ts @@ -228,7 +228,7 @@ export async function invokeBatchGetDocumentsRpc( const result: Document[] = []; keys.forEach(key => { const doc = docs.get(key.toString()); - hardAssert(!!doc, 100, 'Missing entity in write response for `key`', { + hardAssert(!!doc, 0xd7c2, 'Missing entity in write response for `key`', { key }); result.push(doc); @@ -292,7 +292,7 @@ export async function invokeRunAggregationQueryRpc( hardAssert( filteredResult.length === 1, - 101, + 0xfcd7, 'Aggregation fields are missing from result.' ); debugAssert( diff --git a/packages/firestore/src/remote/persistent_stream.ts b/packages/firestore/src/remote/persistent_stream.ts index 427a9c5571d..4f3b91652ad 100644 --- a/packages/firestore/src/remote/persistent_stream.ts +++ b/packages/firestore/src/remote/persistent_stream.ts @@ -809,7 +809,7 @@ export class PersistentWriteStream extends PersistentStream< // Always capture the last stream token. hardAssert( !!responseProto.streamToken, - 134, + 0x7a5a, 'Got a write handshake response without a stream token' ); this.lastStreamToken = responseProto.streamToken; @@ -817,7 +817,7 @@ export class PersistentWriteStream extends PersistentStream< // The first response is always the handshake response hardAssert( !responseProto.writeResults || responseProto.writeResults.length === 0, - 135, + 0xda08, 'Got mutation results for handshake' ); return this.listener!.onHandshakeComplete(); @@ -827,7 +827,7 @@ export class PersistentWriteStream extends PersistentStream< // Always capture the last stream token. hardAssert( !!responseProto.streamToken, - 136, + 0x3186, 'Got a write response without a stream token' ); this.lastStreamToken = responseProto.streamToken; diff --git a/packages/firestore/src/remote/rpc_error.ts b/packages/firestore/src/remote/rpc_error.ts index bb0f49ed62e..2efee40f223 100644 --- a/packages/firestore/src/remote/rpc_error.ts +++ b/packages/firestore/src/remote/rpc_error.ts @@ -58,7 +58,7 @@ enum RpcCode { export function isPermanentError(code: Code): boolean { switch (code) { case Code.OK: - return fail(137, 'Treated status OK as error'); + return fail(0xfdaa, 'Treated status OK as error'); case Code.CANCELLED: case Code.UNKNOWN: case Code.DEADLINE_EXCEEDED: @@ -83,7 +83,7 @@ export function isPermanentError(code: Code): boolean { case Code.DATA_LOSS: return true; default: - return fail(138, 'Unknown status code', { code }); + return fail(0x3c6b, 'Unknown status code', { code }); } } @@ -171,7 +171,7 @@ export function mapCodeFromRpcCode(code: number | undefined): Code { case RpcCode.DATA_LOSS: return Code.DATA_LOSS; default: - return fail(139, 'Unknown status code', { code }); + return fail(0x999b, 'Unknown status code', { code }); } } @@ -220,7 +220,7 @@ export function mapRpcCodeFromCode(code: Code | undefined): number { case Code.DATA_LOSS: return RpcCode.DATA_LOSS; default: - return fail(140, 'Unknown status code', { code }); + return fail(0x3019, 'Unknown status code', { code }); } } diff --git a/packages/firestore/src/remote/serializer.ts b/packages/firestore/src/remote/serializer.ts index 6e2a83b1aee..aabdb263c1a 100644 --- a/packages/firestore/src/remote/serializer.ts +++ b/packages/firestore/src/remote/serializer.ts @@ -257,7 +257,7 @@ export function fromBytes( if (serializer.useProto3Json) { hardAssert( value === undefined || typeof value === 'string', - 106, + 0xe30b, 'value must be undefined or a string when using proto3 Json' ); return ByteString.fromBase64String(value ? value : ''); @@ -270,7 +270,7 @@ export function fromBytes( // does not indicate that it extends Uint8Array. value instanceof Buffer || value instanceof Uint8Array, - 107, + 0x3f41, 'value must be undefined, Buffer, or Uint8Array' ); return ByteString.fromUint8Array(value ? value : new Uint8Array()); @@ -285,7 +285,7 @@ export function toVersion( } export function fromVersion(version: ProtoTimestamp): SnapshotVersion { - hardAssert(!!version, 108, "Trying to deserialize version that isn't set"); + hardAssert(!!version, 0xc050, "Trying to deserialize version that isn't set"); return SnapshotVersion.fromTimestamp(fromTimestamp(version)); } @@ -308,7 +308,7 @@ function fromResourceName(name: string): ResourcePath { const resource = ResourcePath.fromString(name); hardAssert( isValidResourceName(resource), - 109, + 0x27ce, 'Tried to deserialize invalid key', { key: resource.toString() } ); @@ -393,7 +393,7 @@ function extractLocalPathFromResourceName( ): ResourcePath { hardAssert( resourceName.length > 4 && resourceName.get(4) === 'documents', - 110, + 0x71a3, 'tried to deserialize invalid key', { key: resourceName.toString() } ); @@ -460,7 +460,7 @@ function fromFound( ): MutableDocument { hardAssert( !!doc.found, - 111, + 0xaa33, 'Tried to deserialize a found document from a missing document.' ); assertPresent(doc.found.name, 'doc.found.name'); @@ -480,12 +480,12 @@ function fromMissing( ): MutableDocument { hardAssert( !!result.missing, - 112, + 0x0f36, 'Tried to deserialize a missing document from a found document.' ); hardAssert( !!result.readTime, - 113, + 0x5995, 'Tried to deserialize a missing document without a read time.' ); const key = fromName(serializer, result.missing); @@ -502,7 +502,7 @@ export function fromBatchGetDocumentsResponse( } else if ('missing' in result) { return fromMissing(serializer, result); } - return fail(114, 'invalid batch get response', { result }); + return fail(0x1c42, 'invalid batch get response', { result }); } export function fromWatchChange( @@ -587,7 +587,7 @@ export function fromWatchChange( const targetId = filter.targetId; watchChange = new ExistenceFilterChange(targetId, existenceFilter); } else { - return fail(115, 'Unknown change type', { change }); + return fail(0x2d51, 'Unknown change type', { change }); } return watchChange; } @@ -606,7 +606,7 @@ function fromWatchTargetChangeState( } else if (state === 'RESET') { return WatchTargetChangeState.Reset; } else { - return fail(116, 'Got unexpected TargetChange.state', { state }); + return fail(0x9991, 'Got unexpected TargetChange.state', { state }); } } @@ -650,7 +650,7 @@ export function toMutation( verify: toName(serializer, mutation.key) }; } else { - return fail(117, 'Unknown mutation type', { + return fail(0x40d7, 'Unknown mutation type', { mutationType: mutation.type }); } @@ -708,7 +708,7 @@ export function fromMutation( const key = fromName(serializer, proto.verify); return new VerifyMutation(key, precondition); } else { - return fail(118, 'unknown mutation proto', { proto }); + return fail(0x05b7, 'unknown mutation proto', { proto }); } } @@ -724,7 +724,7 @@ function toPrecondition( } else if (precondition.exists !== undefined) { return { exists: precondition.exists }; } else { - return fail(119, 'Unknown precondition'); + return fail(0x6b69, 'Unknown precondition'); } } @@ -766,7 +766,7 @@ export function fromWriteResults( if (protos && protos.length > 0) { hardAssert( commitTime !== undefined, - 120, + 0x3811, 'Received a write result without a commit time' ); return protos.map(proto => fromWriteResult(proto, commitTime)); @@ -805,7 +805,7 @@ function toFieldTransform( increment: transform.operand }; } else { - throw fail(121, 'Unknown transform', { + throw fail(0x51c2, 'Unknown transform', { transform: fieldTransform.transform }); } @@ -819,7 +819,7 @@ function fromFieldTransform( if ('setToServerValue' in proto) { hardAssert( proto.setToServerValue === 'REQUEST_TIME', - 122, + 0x40f6, 'Unknown server value transform proto', { proto } ); @@ -836,7 +836,7 @@ function fromFieldTransform( proto.increment! ); } else { - fail(123, 'Unknown transform proto', { proto }); + fail(0x40c8, 'Unknown transform proto', { proto }); } const fieldPath = FieldPath.fromServerFormat(proto.fieldPath!); return new FieldTransform(fieldPath, transform!); @@ -855,7 +855,7 @@ export function fromDocumentsTarget( const count = documentsTarget.documents!.length; hardAssert( count === 1, - 124, + 0x07ae, 'DocumentsTarget contained other than 1 document', { count @@ -989,7 +989,7 @@ export function convertQueryTargetToQuery(target: ProtoQueryTarget): Query { if (fromCount > 0) { hardAssert( fromCount === 1, - 125, + 0xfe26, 'StructuredQuery.from with more than one collection is not supported.' ); const from = query.from![0]; @@ -1066,7 +1066,7 @@ export function toLabel(purpose: TargetPurpose): string | null { case TargetPurpose.LimboResolution: return 'limbo-document'; default: - return fail(126, 'Unrecognized query purpose', { purpose }); + return fail(0x713b, 'Unrecognized query purpose', { purpose }); } } @@ -1137,7 +1137,7 @@ function fromFilter(filter: ProtoFilter): Filter { } else if (filter.compositeFilter !== undefined) { return fromCompositeFilter(filter); } else { - return fail(127, 'Unknown filter', { filter }); + return fail(0x7591, 'Unknown filter', { filter }); } } @@ -1231,9 +1231,9 @@ export function fromOperatorName(op: ProtoFieldFilterOp): Operator { case 'ARRAY_CONTAINS_ANY': return Operator.ARRAY_CONTAINS_ANY; case 'OPERATOR_UNSPECIFIED': - return fail(128, 'Unspecified operator'); + return fail(0xe2fe, 'Unspecified operator'); default: - return fail(129, 'Unknown operator'); + return fail(0xc54a, 'Unknown operator'); } } @@ -1246,7 +1246,7 @@ export function fromCompositeOperatorName( case 'OR': return CompositeOperator.OR; default: - return fail(130, 'Unknown operator'); + return fail(0x0402, 'Unknown operator'); } } @@ -1282,7 +1282,7 @@ export function toFilter(filter: Filter): ProtoFilter { } else if (filter instanceof CompositeFilter) { return toCompositeFilter(filter); } else { - return fail(131, 'Unrecognized filter type', { filter }); + return fail(0xd65d, 'Unrecognized filter type', { filter }); } } @@ -1367,9 +1367,9 @@ export function fromUnaryFilter(filter: ProtoFilter): Filter { nullValue: 'NULL_VALUE' }); case 'OPERATOR_UNSPECIFIED': - return fail(132, 'Unspecified filter'); + return fail(0xef81, 'Unspecified filter'); default: - return fail(133, 'Unknown filter'); + return fail(0xed36, 'Unknown filter'); } } diff --git a/packages/firestore/src/remote/watch_change.ts b/packages/firestore/src/remote/watch_change.ts index 5cc6b2971a8..a656d8fdf6e 100644 --- a/packages/firestore/src/remote/watch_change.ts +++ b/packages/firestore/src/remote/watch_change.ts @@ -203,7 +203,7 @@ class TargetState { removedDocuments = removedDocuments.add(key); break; default: - fail(102, 'Encountered invalid change type', { changeType }); + fail(0x9481, 'Encountered invalid change type', { changeType }); } }); @@ -242,7 +242,7 @@ class TargetState { this.pendingResponses -= 1; hardAssert( this.pendingResponses >= 0, - 103, + 0x0ca9, '`pendingResponses` is less than 0. This indicates that the SDK received more target acks from the server than expected. The SDK should not continue to operate.', { pendingResponses: this.pendingResponses } ); @@ -377,7 +377,7 @@ export class WatchChangeAggregator { } break; default: - fail(104, 'Unknown target watch change state', { + fail(0xddd6, 'Unknown target watch change state', { state: targetChange.state }); } @@ -433,7 +433,7 @@ export class WatchChangeAggregator { } else { hardAssert( expectedCount === 1, - 105, + 0x4e2d, 'Single document existence filter with count', { expectedCount } ); diff --git a/packages/firestore/src/util/assert.ts b/packages/firestore/src/util/assert.ts index 61cd5b3285d..07ebe775e9c 100644 --- a/packages/firestore/src/util/assert.ts +++ b/packages/firestore/src/util/assert.ts @@ -153,7 +153,7 @@ export function debugAssert( message: string ): asserts assertion { if (!assertion) { - fail(21, message); + fail(0xdeb6, message); } } diff --git a/packages/firestore/src/util/async_queue_impl.ts b/packages/firestore/src/util/async_queue_impl.ts index 9379a0e41e6..f8c7a995761 100644 --- a/packages/firestore/src/util/async_queue_impl.ts +++ b/packages/firestore/src/util/async_queue_impl.ts @@ -236,7 +236,7 @@ export class AsyncQueueImpl implements AsyncQueue { private verifyNotFailed(): void { if (this.failure) { - fail(20, 'AsyncQueue is already failed', { + fail(0xb815, 'AsyncQueue is already failed', { messageOrStack: getMessageOrStack(this.failure) }); } diff --git a/packages/firestore/src/util/input_validation.ts b/packages/firestore/src/util/input_validation.ts index ea77fbc1956..7fd9967b5a0 100644 --- a/packages/firestore/src/util/input_validation.ts +++ b/packages/firestore/src/util/input_validation.ts @@ -128,7 +128,7 @@ export function valueDescription(input: unknown): string { } else if (typeof input === 'function') { return 'a function'; } else { - return fail(30, 'Unknown wrong type', { type: typeof input }); + return fail(0x3029, 'Unknown wrong type', { type: typeof input }); } } diff --git a/packages/firestore/src/util/logic_utils.ts b/packages/firestore/src/util/logic_utils.ts index b40194f5ae5..b2167c385e9 100644 --- a/packages/firestore/src/util/logic_utils.ts +++ b/packages/firestore/src/util/logic_utils.ts @@ -44,7 +44,7 @@ import { hardAssert } from './assert'; export function computeInExpansion(filter: Filter): Filter { hardAssert( filter instanceof FieldFilter || filter instanceof CompositeFilter, - 31, + 0x4e2c, 'Only field filters and composite filters are accepted.' ); @@ -91,7 +91,7 @@ export function getDnfTerms(filter: CompositeFilter): Filter[] { hardAssert( isDisjunctiveNormalForm(result), - 32, + 0x1cdf, 'computeDistributedNormalForm did not result in disjunctive normal form' ); @@ -159,7 +159,7 @@ function isDisjunctionOfFieldFiltersAndFlatConjunctions( export function computeDistributedNormalForm(filter: Filter): Filter { hardAssert( filter instanceof FieldFilter || filter instanceof CompositeFilter, - 33, + 0x84e2, 'Only field filters and composite filters are accepted.' ); @@ -185,17 +185,17 @@ export function computeDistributedNormalForm(filter: Filter): Filter { hardAssert( newFilter instanceof CompositeFilter, - 34, + 0xfbf2, 'field filters are already in DNF form' ); hardAssert( compositeFilterIsConjunction(newFilter), - 35, + 0x9d3b, 'Disjunction of filters all of which are already in DNF form is itself in DNF form.' ); hardAssert( newFilter.filters.length > 1, - 36, + 0xe247, 'Single-filter composite filters are already in DNF form.' ); @@ -207,12 +207,12 @@ export function computeDistributedNormalForm(filter: Filter): Filter { export function applyDistribution(lhs: Filter, rhs: Filter): Filter { hardAssert( lhs instanceof FieldFilter || lhs instanceof CompositeFilter, - 37, + 0x95f4, 'Only field filters and composite filters are accepted.' ); hardAssert( rhs instanceof FieldFilter || rhs instanceof CompositeFilter, - 38, + 0x6381, 'Only field filters and composite filters are accepted.' ); @@ -253,7 +253,7 @@ function applyDistributionCompositeFilters( ): Filter { hardAssert( lhs.filters.length > 0 && rhs.filters.length > 0, - 39, + 0xbb85, 'Found an empty composite filter' ); @@ -315,7 +315,7 @@ function applyDistributionFieldAndCompositeFilters( export function applyAssociation(filter: Filter): Filter { hardAssert( filter instanceof FieldFilter || filter instanceof CompositeFilter, - 40, + 0x2e4a, 'Only field filters and composite filters are accepted.' ); diff --git a/packages/firestore/src/util/sorted_map.ts b/packages/firestore/src/util/sorted_map.ts index 9f05fba543e..023354173d3 100644 --- a/packages/firestore/src/util/sorted_map.ts +++ b/packages/firestore/src/util/sorted_map.ts @@ -511,20 +511,20 @@ export class LLRBNode { // leaves is equal on both sides. This function verifies that or asserts. protected check(): number { if (this.isRed() && this.left.isRed()) { - throw fail(22, 'Red node has red child', { + throw fail(0xaad2, 'Red node has red child', { key: this.key, value: this.value }); } if (this.right.isRed()) { - throw fail(23, 'Right child of (`key`, `value`) is red', { + throw fail(0x3721, 'Right child of (`key`, `value`) is red', { key: this.key, value: this.value }); } const blackDepth = (this.left as LLRBNode).check(); if (blackDepth !== (this.right as LLRBNode).check()) { - throw fail(24, 'Black depths differ'); + throw fail(0x6d2d, 'Black depths differ'); } else { return blackDepth + (this.isRed() ? 0 : 1); } @@ -534,19 +534,19 @@ export class LLRBNode { // Represents an empty node (a leaf node in the Red-Black Tree). export class LLRBEmptyNode { get key(): never { - throw fail(25, 'LLRBEmptyNode has no key.'); + throw fail(0xe1a6, 'LLRBEmptyNode has no key.'); } get value(): never { - throw fail(26, 'LLRBEmptyNode has no value.'); + throw fail(0x3f0d, 'LLRBEmptyNode has no value.'); } get color(): never { - throw fail(27, 'LLRBEmptyNode has no color.'); + throw fail(0x4157, 'LLRBEmptyNode has no color.'); } get left(): never { - throw fail(28, 'LLRBEmptyNode has no left child.'); + throw fail(0x741e, 'LLRBEmptyNode has no left child.'); } get right(): never { - throw fail(29, 'LLRBEmptyNode has no right child.'); + throw fail(0x901e, 'LLRBEmptyNode has no right child.'); } size = 0; From fef77fb60e3e3ad19465834e4098636770074dd8 Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Mon, 7 Apr 2025 15:12:26 -0600 Subject: [PATCH 13/18] Updated assertion id tool to use hex string --- packages/firestore/package.json | 2 +- .../firestore/scripts/assertion-id-tool.ts | 54 +++++++++++-------- 2 files changed, 34 insertions(+), 22 deletions(-) diff --git a/packages/firestore/package.json b/packages/firestore/package.json index 21684e461fe..315832b130b 100644 --- a/packages/firestore/package.json +++ b/packages/firestore/package.json @@ -54,7 +54,7 @@ "doc": "api-documenter markdown --input temp --output docs", "typings:public": "node ../../scripts/build/use_typings.js ./dist/index.d.ts", "assertion-id:check": "ts-node scripts/assertion-id-tool.ts --dir=src --check", - "assertion-id:generate": "ts-node scripts/assertion-id-tool.ts --dir=src --new", + "assertion-id:new": "ts-node scripts/assertion-id-tool.ts --dir=src --new", "assertion-id:list": "ts-node scripts/assertion-id-tool.ts --dir=src --list", "assertion-id:find": "ts-node scripts/assertion-id-tool.ts --dir=src --find" }, diff --git a/packages/firestore/scripts/assertion-id-tool.ts b/packages/firestore/scripts/assertion-id-tool.ts index 1c8889d0d8c..d37997f1522 100644 --- a/packages/firestore/scripts/assertion-id-tool.ts +++ b/packages/firestore/scripts/assertion-id-tool.ts @@ -19,6 +19,7 @@ /* eslint-disable no-console */ import * as fs from "fs"; +import {getRandomValues} from 'node:crypto'; import * as path from "path"; import * as ts from "typescript"; @@ -44,7 +45,7 @@ interface CallSiteInfo { character: number; argumentsText: string[]; // Added to store argument text errorMessage: string | undefined; - assertionId: number; + assertionId: string; } /** @@ -127,7 +128,7 @@ function findFunctionCalls(filePaths: string[]): CallSiteInfo[] { // --- Extract Arguments --- const argsText: string[] = []; let errorMessage: string | undefined; - let assertionId: number | undefined; + let assertionId: string | undefined; if (node.arguments && node.arguments.length > 0) { node.arguments.forEach((arg: ts.Expression) => { // Get the source text of the argument node @@ -137,7 +138,7 @@ function findFunctionCalls(filePaths: string[]): CallSiteInfo[] { errorMessage = arg.getText(sourceFile); } else if (ts.isNumericLiteral(arg)) { - assertionId = parseInt(arg.getText(sourceFile), 10); + assertionId = arg.getText(sourceFile); } }); } @@ -151,7 +152,7 @@ function findFunctionCalls(filePaths: string[]): CallSiteInfo[] { character: character + 1, argumentsText: argsText, // Store the extracted arguments, errorMessage, - assertionId: assertionId ?? -1 + assertionId: assertionId ?? "INVALID", }); } } @@ -180,19 +181,20 @@ function handleList(occurrences: CallSiteInfo[]): void { return; } - occurrences.sort((a, b) => a.assertionId - b.assertionId).forEach((call) => { + occurrences.sort((a, b) => a.assertionId.localeCompare(b.assertionId)).forEach((call) => { console.log( `ID: ${call.assertionId}; MESSAGE: ${call.errorMessage}; SOURCE: '${call.functionName}' call at ${path.relative(process.cwd(), call.fileName)}:${call.line}:${call.character}` ); }); +} +function find(occurrences: CallSiteInfo[], targetId: string | number): CallSiteInfo[] { + const target = typeof targetId === 'number' ? targetId.toString(16) : targetId; + return occurrences.filter(o => String(o.assertionId) === String(target)); } function handleFind(occurrences: CallSiteInfo[], targetId: string | number): void { - // Normalize target code for comparison if necessary (e.g., string vs number) - const target = typeof targetId === 'number' ? targetId : targetId.toString(); - - const foundLocations = occurrences.filter(o => String(o.assertionId) === String(target)); // Compare as strings + const foundLocations = find(occurrences, targetId); if (foundLocations.length === 0) { log(`Assertion id "${targetId}" not found.`); @@ -210,11 +212,20 @@ function handleCheck(occurrences: CallSiteInfo[]): void { const idCounts: { [id: string]: CallSiteInfo[] } = {}; occurrences.forEach(occ => { + // Count ID occurrences const codeStr = String(occ.assertionId); // Use string representation as key if (!idCounts[codeStr]) { idCounts[codeStr] = []; } idCounts[codeStr].push(occ); + + // validate formats + if (!/^0x[0-9a-f]{4}$/.test(occ.assertionId)) { + console.error(`Invalid assertion ID '${occ.assertionId}'. Must match /^0x[0-9a-f]{4}$/`); + + const relativePath = path.relative(process.cwd(), occ.fileName); + console.error(`- at '${relativePath}:${occ.line}:${occ.character}`); + } }); let duplicatesFound = false; @@ -238,22 +249,23 @@ function handleCheck(occurrences: CallSiteInfo[]): void { } } -function handleNew(occurrences: CallSiteInfo[]): void { - // --- Simple Numeric Scheme: Find max numeric code and add 1 --- - let maxCode = 0; +function randomId(): string { + const randomBytes = new Uint8Array(2); + getRandomValues(randomBytes); - occurrences.forEach(occ => { - if (occ.assertionId > maxCode) { - maxCode = occ.assertionId; - } - }); + return '0x' + Array.from(randomBytes) + .map(byte => byte.toString(16).padStart(2, '0')) + .join(''); +} - if (occurrences.length === 0) { - log("0"); - return; +function handleNew(occurrences: CallSiteInfo[]): void { + let newCode: string = randomId(); + + // If we find this code already is used, regenerate it. + while (find(occurrences, newCode).length > 0) { + newCode = randomId(); } - const newCode = maxCode + 1; console.log(newCode); } From 70ebdc3b00962c94f735b87cd5e9ebb3cb245f36 Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Tue, 8 Apr 2025 09:03:42 -0600 Subject: [PATCH 14/18] Update odd-wolves-sit.md --- .changeset/odd-wolves-sit.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/odd-wolves-sit.md b/.changeset/odd-wolves-sit.md index 6cfc1590103..83c131371b2 100644 --- a/.changeset/odd-wolves-sit.md +++ b/.changeset/odd-wolves-sit.md @@ -2,4 +2,4 @@ "@firebase/firestore": patch --- -Adding error codes and error context to internal assertion messages. +Add assertion IDs and optional context objects that will be included in production log statements for fail and hardAsserts From d2ef447cd9fd58e1fb1512cdad474a0bed1a4894 Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Tue, 8 Apr 2025 17:06:35 -0600 Subject: [PATCH 15/18] Update odd-wolves-sit.md --- .changeset/odd-wolves-sit.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/odd-wolves-sit.md b/.changeset/odd-wolves-sit.md index 83c131371b2..fc63acc005e 100644 --- a/.changeset/odd-wolves-sit.md +++ b/.changeset/odd-wolves-sit.md @@ -2,4 +2,4 @@ "@firebase/firestore": patch --- -Add assertion IDs and optional context objects that will be included in production log statements for fail and hardAsserts +Add unique IDs and state information into fatal error messages instead of the generic "unexpected state" message. From 4374a98e38dae1e2fbea0e2418ae968110bd40b9 Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Tue, 8 Apr 2025 17:41:27 -0600 Subject: [PATCH 16/18] Address PR feedback --- .../firestore/scripts/assertion-id-tool.ts | 144 ++++++++---------- 1 file changed, 64 insertions(+), 80 deletions(-) diff --git a/packages/firestore/scripts/assertion-id-tool.ts b/packages/firestore/scripts/assertion-id-tool.ts index d37997f1522..1e847b3b89a 100644 --- a/packages/firestore/scripts/assertion-id-tool.ts +++ b/packages/firestore/scripts/assertion-id-tool.ts @@ -64,7 +64,6 @@ function getTsFilesRecursive(dirPath: string): string[] { if (entry.isDirectory()) { // Ignore node_modules for performance and relevance if (entry.name === 'node_modules') { - // log(`Skipping node_modules directory: ${fullPath}`); continue; } // Recursively scan subdirectories @@ -78,11 +77,11 @@ function getTsFilesRecursive(dirPath: string): string[] { } } catch (error: any) { console.error(`Error reading directory ${dirPath}: ${error.message}`); + throw error; } return tsFiles; } - /** * Analyzes TypeScript source files to find calls to specific functions. * @param filePaths An array of absolute paths to the TypeScript files to scan. @@ -92,87 +91,79 @@ function findFunctionCalls(filePaths: string[]): CallSiteInfo[] { const foundCalls: CallSiteInfo[] = []; for (const filePath of filePaths) { - try { - // Read the file content - const sourceText = fs.readFileSync(filePath, "utf8"); - - // Create the SourceFile AST node - const sourceFile = ts.createSourceFile( - path.basename(filePath), // Use basename for AST node name - sourceText, - ts.ScriptTarget.ESNext, // Or your project's target - true, // Set parent pointers - ts.ScriptKind.Unknown // Detect TS vs TSX automatically - ); - - // Define the visitor function - const visit = (node: ts.Node) :void => { - // Check if the node is a CallExpression (e.g., myFunction(...)) - if (ts.isCallExpression(node)) { - let functionName: string | null = null; - const expression = node.expression; - - // Check if the call is directly to an identifier (e.g., fail()) - if (ts.isIdentifier(expression)) { - functionName = expression.text; - } + // Read the file content + const sourceText = fs.readFileSync(filePath, 'utf8'); + + // Create the SourceFile AST node + const sourceFile = ts.createSourceFile( + path.basename(filePath), // Use basename for AST node name + sourceText, + ts.ScriptTarget.ESNext, + true, // Set parent pointers + ts.ScriptKind.Unknown // Detect TS vs TSX automatically + ); + + // Define the visitor function + const visit = (node: ts.Node): void => { + // Check if the node is a CallExpression (e.g., myFunction(...)) + if (ts.isCallExpression(node)) { + let functionName: string | null = null; + const expression = node.expression; - // If we found a function name, and it's one we're looking for - if (functionName && targetFunctionNames.has(functionName)) { - // Get line and character number - const { line, character } = ts.getLineAndCharacterOfPosition( - sourceFile, - node.getStart() // Get start position of the call expression - ); - - // --- Extract Arguments --- - const argsText: string[] = []; - let errorMessage: string | undefined; - let assertionId: string | undefined; - if (node.arguments && node.arguments.length > 0) { - node.arguments.forEach((arg: ts.Expression) => { - // Get the source text of the argument node - argsText.push(arg.getText(sourceFile)); - - if (ts.isStringLiteral(arg)) { - errorMessage = arg.getText(sourceFile); - } - else if (ts.isNumericLiteral(arg)) { - assertionId = arg.getText(sourceFile); - } - }); - } - // --- End Extract Arguments --- - - // Store the information (add 1 to line/char for 1-based indexing) - foundCalls.push({ - fileName: filePath, // Store the full path - functionName, - line: line + 1, - character: character + 1, - argumentsText: argsText, // Store the extracted arguments, - errorMessage, - assertionId: assertionId ?? "INVALID", + // Check if the call is directly to an identifier (e.g., fail()) + if (ts.isIdentifier(expression)) { + functionName = expression.text; + } + + // If we found a function name, and it's one we're looking for + if (functionName && targetFunctionNames.has(functionName)) { + // Get line and character number + const { line, character } = ts.getLineAndCharacterOfPosition( + sourceFile, + node.getStart() // Get start position of the call expression + ); + + // --- Extract Arguments --- + const argsText: string[] = []; + let errorMessage: string | undefined; + let assertionId: string | undefined; + if (node.arguments && node.arguments.length > 0) { + node.arguments.forEach((arg: ts.Expression) => { + // Get the source text of the argument node + argsText.push(arg.getText(sourceFile)); + + if (ts.isStringLiteral(arg)) { + errorMessage = arg.getText(sourceFile); + } else if (ts.isNumericLiteral(arg)) { + assertionId = arg.getText(sourceFile); + } }); } - } - // Continue traversing down the AST - ts.forEachChild(node, visit); - }; + // Store the information (add 1 to line/char for 1-based indexing) + foundCalls.push({ + fileName: filePath, // Store the full path + functionName, + line: line + 1, + character: character + 1, + argumentsText: argsText, // Store the extracted arguments, + errorMessage, + assertionId: assertionId ?? 'INVALID' + }); + } + } - // Start traversal from the root SourceFile node - visit(sourceFile); + // Continue traversing down the AST + ts.forEachChild(node, visit); + }; - } catch (error: any) { - console.error(`Error processing file ${filePath}: ${error.message}`); - } + // Start traversal from the root SourceFile node + visit(sourceFile); } // End loop through filePaths return foundCalls; } - // --- Action Handlers --- function handleList(occurrences: CallSiteInfo[]): void { @@ -362,11 +353,4 @@ async function main(): Promise { } // Run the main function -main().catch(error => { - console.error("\nAn unexpected error occurred:"); - console.error(error); - process.exit(1); -}); - - - +main(); From a46a1472983945e0c7af0007a8fc1c1e8fb76ae1 Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Tue, 8 Apr 2025 17:42:13 -0600 Subject: [PATCH 17/18] Update prettier to run on scripts/ and commit the results of prettier --- packages/firestore/package.json | 2 +- .../firestore/scripts/assertion-id-tool.ts | 145 ++++++++++-------- packages/firestore/scripts/build-bundle.ts | 26 ++-- packages/firestore/scripts/run-tests.ts | 42 ++--- 4 files changed, 122 insertions(+), 93 deletions(-) diff --git a/packages/firestore/package.json b/packages/firestore/package.json index 315832b130b..0c9ddeee843 100644 --- a/packages/firestore/package.json +++ b/packages/firestore/package.json @@ -20,7 +20,7 @@ "dev": "rollup -c -w", "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", - "prettier": "prettier --write '*.js' '@(lite|src|test)/**/*.ts' 'test/unit/remote/bloom_filter_golden_test_data/*.json'", + "prettier": "prettier --write '*.js' '@(lite|src|test|scripts)/**/*.ts' 'test/unit/remote/bloom_filter_golden_test_data/*.json'", "test:lite": "ts-node ./scripts/run-tests.ts --emulator --platform node_lite --main=lite/index.ts 'test/lite/**/*.test.ts'", "test:lite:prod": "ts-node ./scripts/run-tests.ts --platform node_lite --main=lite/index.ts 'test/lite/**/*.test.ts'", "test:lite:prod:nameddb": "ts-node ./scripts/run-tests.ts --platform node_lite --databaseId=test-db --main=lite/index.ts 'test/lite/**/*.test.ts'", diff --git a/packages/firestore/scripts/assertion-id-tool.ts b/packages/firestore/scripts/assertion-id-tool.ts index 1e847b3b89a..b026cd88ff0 100644 --- a/packages/firestore/scripts/assertion-id-tool.ts +++ b/packages/firestore/scripts/assertion-id-tool.ts @@ -18,13 +18,13 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable no-console */ -import * as fs from "fs"; -import {getRandomValues} from 'node:crypto'; -import * as path from "path"; +import * as fs from 'fs'; +import { getRandomValues } from 'node:crypto'; +import * as path from 'path'; -import * as ts from "typescript"; -import yargs from "yargs"; -import { hideBin } from "yargs/helpers"; +import * as ts from 'typescript'; +import yargs from 'yargs'; +import { hideBin } from 'yargs/helpers'; let isVerbose: boolean = false; @@ -35,7 +35,7 @@ function log(message: any): void { } // Define the names of the functions we are looking for -const targetFunctionNames: Set = new Set(["fail", "hardAssert"]); +const targetFunctionNames: Set = new Set(['fail', 'hardAssert']); // Interface to store information about found call sites interface CallSiteInfo { @@ -68,9 +68,9 @@ function getTsFilesRecursive(dirPath: string): string[] { } // Recursively scan subdirectories tsFiles = tsFiles.concat(getTsFilesRecursive(fullPath)); - } else if (entry.isFile() && (entry.name.endsWith(".ts"))) { + } else if (entry.isFile() && entry.name.endsWith('.ts')) { // Exclude declaration files (.d.ts) as they usually don't contain implementation - if (!entry.name.endsWith(".d.ts")) { + if (!entry.name.endsWith('.d.ts')) { tsFiles.push(fullPath); } } @@ -168,23 +168,36 @@ function findFunctionCalls(filePaths: string[]): CallSiteInfo[] { function handleList(occurrences: CallSiteInfo[]): void { if (occurrences.length === 0) { - log("No assertion ids found."); + log('No assertion ids found.'); return; } - occurrences.sort((a, b) => a.assertionId.localeCompare(b.assertionId)).forEach((call) => { - console.log( - `ID: ${call.assertionId}; MESSAGE: ${call.errorMessage}; SOURCE: '${call.functionName}' call at ${path.relative(process.cwd(), call.fileName)}:${call.line}:${call.character}` - ); - }); + occurrences + .sort((a, b) => a.assertionId.localeCompare(b.assertionId)) + .forEach(call => { + console.log( + `ID: ${call.assertionId}; MESSAGE: ${call.errorMessage}; SOURCE: '${ + call.functionName + }' call at ${path.relative(process.cwd(), call.fileName)}:${ + call.line + }:${call.character}` + ); + }); } -function find(occurrences: CallSiteInfo[], targetId: string | number): CallSiteInfo[] { - const target = typeof targetId === 'number' ? targetId.toString(16) : targetId; +function find( + occurrences: CallSiteInfo[], + targetId: string | number +): CallSiteInfo[] { + const target = + typeof targetId === 'number' ? targetId.toString(16) : targetId; return occurrences.filter(o => String(o.assertionId) === String(target)); } -function handleFind(occurrences: CallSiteInfo[], targetId: string | number): void { +function handleFind( + occurrences: CallSiteInfo[], + targetId: string | number +): void { const foundLocations = find(occurrences, targetId); if (foundLocations.length === 0) { @@ -197,7 +210,7 @@ function handleFind(occurrences: CallSiteInfo[], targetId: string | number): voi function handleCheck(occurrences: CallSiteInfo[]): void { if (occurrences.length === 0) { - log("No assertion ids found to check for duplicates."); + log('No assertion ids found to check for duplicates.'); return; } const idCounts: { [id: string]: CallSiteInfo[] } = {}; @@ -212,7 +225,9 @@ function handleCheck(occurrences: CallSiteInfo[]): void { // validate formats if (!/^0x[0-9a-f]{4}$/.test(occ.assertionId)) { - console.error(`Invalid assertion ID '${occ.assertionId}'. Must match /^0x[0-9a-f]{4}$/`); + console.error( + `Invalid assertion ID '${occ.assertionId}'. Must match /^0x[0-9a-f]{4}$/` + ); const relativePath = path.relative(process.cwd(), occ.fileName); console.error(`- at '${relativePath}:${occ.line}:${occ.character}`); @@ -220,11 +235,13 @@ function handleCheck(occurrences: CallSiteInfo[]): void { }); let duplicatesFound = false; - log("Checking for duplicate assertion id usage:"); + log('Checking for duplicate assertion id usage:'); Object.entries(idCounts).forEach(([code, locations]) => { if (locations.length > 1) { duplicatesFound = true; - console.error(`\nDuplicate assertion id "${code}" found at ${locations.length} locations:`); + console.error( + `\nDuplicate assertion id "${code}" found at ${locations.length} locations:` + ); locations.forEach(loc => { const relativePath = path.relative(process.cwd(), loc.fileName); console.error(`- ${relativePath}:${loc.line}:${loc.character}`); @@ -233,9 +250,8 @@ function handleCheck(occurrences: CallSiteInfo[]): void { }); if (!duplicatesFound) { - log("No duplicate assertion ids found."); - } - else { + log('No duplicate assertion ids found.'); + } else { process.exit(1); } } @@ -244,9 +260,12 @@ function randomId(): string { const randomBytes = new Uint8Array(2); getRandomValues(randomBytes); - return '0x' + Array.from(randomBytes) - .map(byte => byte.toString(16).padStart(2, '0')) - .join(''); + return ( + '0x' + + Array.from(randomBytes) + .map(byte => byte.toString(16).padStart(2, '0')) + .join('') + ); } function handleNew(occurrences: CallSiteInfo[]): void { @@ -263,44 +282,44 @@ function handleNew(occurrences: CallSiteInfo[]): void { // --- Main Execution --- async function main(): Promise { const argv = yargs(hideBin(process.argv)) - .usage("Usage: $0 [options]") - .option("dir", { + .usage('Usage: $0 [options]') + .option('dir', { alias: 'D', - describe: "Directory to scan recursively for TS files", - type: "string", - demandOption: true, + describe: 'Directory to scan recursively for TS files', + type: 'string', + demandOption: true }) - .option("verbose", { - alias: "V", - describe: "verbose", - type: "boolean", + .option('verbose', { + alias: 'V', + describe: 'verbose', + type: 'boolean' }) - .option("find", { - alias: "F", - describe: "Find locations of a specific {assertionId}", - type: "string", - nargs: 1, + .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('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('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", + .option('check', { + alias: 'C', + describe: 'Check for duplicate usage of assertion ids', + type: 'boolean' }) - .check((argv) => { + .check(argv => { // Enforce mutual exclusivity among options *within* the scan command const 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."); + throw new Error('Options -F, -L, -N, -C are mutually exclusive.'); } return true; }) @@ -319,11 +338,15 @@ async function main(): Promise { try { const stats = fs.statSync(targetDirectory); if (!stats.isDirectory()) { - console.error(`Error: Provided path is not a directory: ${targetDirectory}`); + console.error( + `Error: Provided path is not a directory: ${targetDirectory}` + ); process.exit(1); } } catch (error: any) { - console.error(`Error accessing directory ${targetDirectory}: ${error.message}`); + console.error( + `Error accessing directory ${targetDirectory}: ${error.message}` + ); process.exit(1); } @@ -331,13 +354,15 @@ async function main(): Promise { const filesToScan = getTsFilesRecursive(targetDirectory); if (filesToScan.length === 0) { - log("No relevant .ts or .tsx files found."); + log('No relevant .ts or .tsx files found.'); process.exit(0); } log(`Found ${filesToScan.length} files. Analyzing for assertion ids...`); const allOccurrences = findFunctionCalls(filesToScan); - log(`Scan complete. Found ${allOccurrences.length} potential assertion id occurrences.`); + log( + `Scan complete. Found ${allOccurrences.length} potential assertion id occurrences.` + ); // Determine action based on flags if (argv['find']) { diff --git a/packages/firestore/scripts/build-bundle.ts b/packages/firestore/scripts/build-bundle.ts index e20106f5e25..ed9fc7c41ae 100644 --- a/packages/firestore/scripts/build-bundle.ts +++ b/packages/firestore/scripts/build-bundle.ts @@ -24,18 +24,20 @@ const typescriptPlugin = require('rollup-plugin-typescript2'); const util = require('../rollup.shared'); -const argv = yargs.options({ - input: { - type: 'string', - demandOption: true, - desc: 'The location of the index.ts file' - }, - output: { - type: 'string', - demandOption: true, - desc: 'The location for the transpiled JavaScript bundle' - } -}).parseSync(); +const argv = yargs + .options({ + input: { + type: 'string', + demandOption: true, + desc: 'The location of the index.ts file' + }, + output: { + type: 'string', + demandOption: true, + desc: 'The location for the transpiled JavaScript bundle' + } + }) + .parseSync(); /** * Builds an ESM bundle for the TypeScript file at `index` and writes it the diff --git a/packages/firestore/scripts/run-tests.ts b/packages/firestore/scripts/run-tests.ts index 7e5cdf8fc80..bd721944647 100644 --- a/packages/firestore/scripts/run-tests.ts +++ b/packages/firestore/scripts/run-tests.ts @@ -20,32 +20,34 @@ import { resolve } from 'path'; import { spawn } from 'child-process-promise'; import * as yargs from 'yargs'; -const argv = yargs.options({ - main: { - type: 'string', - demandOption: true - }, - platform: { - type: 'string', - default: 'node' - }, - emulator: { - type: 'boolean' - }, - persistence: { - type: 'boolean' - }, - databaseId: { - type: 'string' - } -}).parseSync(); +const argv = yargs + .options({ + main: { + type: 'string', + demandOption: true + }, + platform: { + type: 'string', + default: 'node' + }, + emulator: { + type: 'boolean' + }, + persistence: { + type: 'boolean' + }, + databaseId: { + type: 'string' + } + }) + .parseSync(); const nyc = resolve(__dirname, '../../../node_modules/.bin/nyc'); const mocha = resolve(__dirname, '../../../node_modules/.bin/mocha'); const babel = resolve(__dirname, '../babel-register.js'); // used in '../../config/mocharc.node.js' to disable ts-node -process.env.NO_TS_NODE = "true"; +process.env.NO_TS_NODE = 'true'; process.env.TEST_PLATFORM = argv.platform; let args = [ From 833f972f6680f88aadbb39da62bdca517d480187 Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Fri, 11 Apr 2025 13:13:29 -0600 Subject: [PATCH 18/18] Fix lint error --- packages/firestore/scripts/assertion-id-tool.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/firestore/scripts/assertion-id-tool.ts b/packages/firestore/scripts/assertion-id-tool.ts index b026cd88ff0..8bde791cc6d 100644 --- a/packages/firestore/scripts/assertion-id-tool.ts +++ b/packages/firestore/scripts/assertion-id-tool.ts @@ -378,4 +378,4 @@ async function main(): Promise { } // Run the main function -main(); +void main();