UNPKG

ran-boilerplate

Version:

React . Apollo (GraphQL) . Next.js Toolkit

272 lines (254 loc) 6.3 kB
/*! * Copyright 2017 Google Inc. All Rights Reserved. * * 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. */ 'use strict'; const is = require('is'); /*! * @see ResourcePath */ const ResourcePath = require('./path').ResourcePath; /*! * The type order as defined by the backend. */ const types = { NULL: 0, BOOLEAN: 1, NUMBER: 2, TIMESTAMP: 3, STRING: 4, BLOB: 5, REF: 6, GEOPOINT: 7, ARRAY: 8, OBJECT: 9, }; /*! * @private */ function typeOrder(val) { switch (val.valueType) { case 'nullValue': { return types.NULL; } case 'integerValue': { return types.NUMBER; } case 'doubleValue': { return types.NUMBER; } case 'stringValue': { return types.STRING; } case 'booleanValue': { return types.BOOLEAN; } case 'arrayValue': { return types.ARRAY; } case 'timestampValue': { return types.TIMESTAMP; } case 'geoPointValue': { return types.GEOPOINT; } case 'bytesValue': { return types.BLOB; } case 'referenceValue': { return types.REF; } case 'mapValue': { return types.OBJECT; } default: { throw new Error( 'Cannot use type (' + val + ': ' + JSON.stringify(val) + ') as a Firestore value.' ); } } } /*! * @private */ function primitiveComparator(left, right) { if (left < right) { return -1; } if (left > right) { return 1; } return 0; } /*! * Utility function to compare doubles (using Firestore semantics for NaN). * @private */ function compareNumbers(left, right) { if (left < right) { return -1; } if (left > right) { return 1; } if (left === right) { return 0; } // one or both are NaN. if (isNaN(left)) { return isNaN(right) ? 0 : -1; } return 1; } /*! * @private */ function compareNumberProtos(left, right) { let leftValue, rightValue; if (left.valueType === 'integerValue') { leftValue = parseInt(left.integerValue, 10); } else { leftValue = parseFloat(left.doubleValue, 10); } if (right.valueType === 'integerValue') { rightValue = parseInt(right.integerValue, 10); } else { rightValue = parseFloat(right.doubleValue, 10); } return compareNumbers(leftValue, rightValue); } /*! * @private */ function compareTimestamps(left, right) { let seconds = primitiveComparator(left.seconds || 0, right.seconds || 0); if (seconds !== 0) { return seconds; } return primitiveComparator(left.nanos || 0, right.nanos || 0); } /*! * @private */ function compareBlobs(left, right) { if (!is.instanceof(left, Buffer) || !is.instanceof(right, Buffer)) { throw new Error('Blobs can only be compared if they are Buffers.'); } return Buffer.compare(left, right); } /*! * @private */ function compareReferenceProtos(left, right) { const leftPath = ResourcePath.fromSlashSeparatedString(left.referenceValue); const rightPath = ResourcePath.fromSlashSeparatedString(right.referenceValue); return leftPath.compareTo(rightPath); } /*! * @private */ function compareGeoPoints(left, right) { return ( primitiveComparator(left.latitude, right.latitude) || primitiveComparator(left.longitude, right.longitude) ); } /*! * @private */ function compareArrays(left, right) { for (let i = 0; i < left.length && i < right.length; i++) { const valueComparison = compare(left[i], right[i]); if (valueComparison !== 0) { return valueComparison; } } // If all the values matched so far, just check the length. return primitiveComparator(left.length, right.length); } /*! * @private */ function compareObjects(left, right) { // This requires iterating over the keys in the object in order and doing a // deep comparison. const leftKeys = Object.keys(left); const rightKeys = Object.keys(right); leftKeys.sort(); rightKeys.sort(); for (let i = 0; i < leftKeys.length && i < rightKeys.length; i++) { const keyComparison = primitiveComparator(leftKeys[i], rightKeys[i]); if (keyComparison !== 0) { return keyComparison; } const key = leftKeys[i]; const valueComparison = compare(left[key], right[key]); if (valueComparison !== 0) { return valueComparison; } } // If all the keys matched so far, just check the length. return primitiveComparator(leftKeys.length, rightKeys.length); } /*! * @private */ function compare(left, right) { // First compare the types. const leftType = typeOrder(left); const rightType = typeOrder(right); const typeComparison = primitiveComparator(leftType, rightType); if (typeComparison !== 0) { return typeComparison; } // So they are the same type. switch (leftType) { case types.NULL: { // Nulls are all equal. return 0; } case types.BOOLEAN: { return primitiveComparator(left.booleanValue, right.booleanValue); } case types.STRING: { return primitiveComparator(left.stringValue, right.stringValue); } case types.NUMBER: { return compareNumberProtos(left, right); } case types.TIMESTAMP: { return compareTimestamps(left.timestampValue, right.timestampValue); } case types.BLOB: { return compareBlobs(left.bytesValue, right.bytesValue); } case types.REF: { return compareReferenceProtos(left, right); } case types.GEOPOINT: { return compareGeoPoints(left.geoPointValue, right.geoPointValue); } case types.ARRAY: { return compareArrays(left.arrayValue.values, right.arrayValue.values); } case types.OBJECT: { return compareObjects(left.mapValue.fields, right.mapValue.fields); } } } module.exports = {compare, primitiveComparator};