Edit File: serializer.js
"use strict"; /*! * Copyright 2019 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. */ Object.defineProperty(exports, "__esModule", { value: true }); const convert_1 = require("./convert"); const field_value_1 = require("./field-value"); const geo_point_1 = require("./geo-point"); const index_1 = require("./index"); const path_1 = require("./path"); const timestamp_1 = require("./timestamp"); const util_1 = require("./util"); const validate_1 = require("./validate"); /** * The maximum depth of a Firestore object. * * @private * @internal */ const MAX_DEPTH = 20; /** * Serializer that is used to convert between JavaScript types and their * Firestore Protobuf representation. * * @private * @internal */ class Serializer { constructor(firestore) { // Instead of storing the `firestore` object, we store just a reference to // its `.doc()` method. This avoid a circular reference, which breaks // JSON.stringify(). this.createReference = path => firestore.doc(path); this.createInteger = n => firestore._settings.useBigInt ? BigInt(n) : Number(n); this.allowUndefined = !!firestore._settings.ignoreUndefinedProperties; } /** * Encodes a JavaScript object into the Firestore 'Fields' representation. * * @private * @internal * @param obj The object to encode. * @returns The Firestore 'Fields' representation */ encodeFields(obj) { const fields = {}; for (const prop of Object.keys(obj)) { const val = this.encodeValue(obj[prop]); if (val) { fields[prop] = val; } } return fields; } /** * Encodes a JavaScript value into the Firestore 'Value' representation. * * @private * @internal * @param val The object to encode * @returns The Firestore Proto or null if we are deleting a field. */ encodeValue(val) { if (val instanceof field_value_1.FieldTransform) { return null; } if (typeof val === 'string') { return { stringValue: val, }; } if (typeof val === 'boolean') { return { booleanValue: val, }; } if (typeof val === 'number') { if (Number.isSafeInteger(val)) { return { integerValue: val, }; } else { return { doubleValue: val, }; } } if (typeof val === 'bigint') { return { integerValue: val.toString(), }; } if (val instanceof Date) { const timestamp = timestamp_1.Timestamp.fromDate(val); return { timestampValue: { seconds: timestamp.seconds, nanos: timestamp.nanoseconds, }, }; } if (isMomentJsType(val)) { const timestamp = timestamp_1.Timestamp.fromDate(val.toDate()); return { timestampValue: { seconds: timestamp.seconds, nanos: timestamp.nanoseconds, }, }; } if (val === null) { return { nullValue: 'NULL_VALUE', }; } if (val instanceof Buffer || val instanceof Uint8Array) { return { bytesValue: val, }; } if (util_1.isObject(val)) { const toProto = val['toProto']; if (typeof toProto === 'function') { return toProto.bind(val)(); } } if (Array.isArray(val)) { const array = { arrayValue: {}, }; if (val.length > 0) { array.arrayValue.values = []; for (let i = 0; i < val.length; ++i) { const enc = this.encodeValue(val[i]); if (enc) { array.arrayValue.values.push(enc); } } } return array; } if (typeof val === 'object' && util_1.isPlainObject(val)) { const map = { mapValue: {}, }; // If we encounter an empty object, we always need to send it to make sure // the server creates a map entry. if (!util_1.isEmpty(val)) { map.mapValue.fields = this.encodeFields(val); if (util_1.isEmpty(map.mapValue.fields)) { return null; } } return map; } if (val === undefined && this.allowUndefined) { return null; } throw new Error(`Cannot encode value: ${val}`); } /** * Decodes a single Firestore 'Value' Protobuf. * * @private * @internal * @param proto A Firestore 'Value' Protobuf. * @returns The converted JS type. */ decodeValue(proto) { const valueType = convert_1.detectValueType(proto); switch (valueType) { case 'stringValue': { return proto.stringValue; } case 'booleanValue': { return proto.booleanValue; } case 'integerValue': { return this.createInteger(proto.integerValue); } case 'doubleValue': { return proto.doubleValue; } case 'timestampValue': { return timestamp_1.Timestamp.fromProto(proto.timestampValue); } case 'referenceValue': { const resourcePath = path_1.QualifiedResourcePath.fromSlashSeparatedString(proto.referenceValue); return this.createReference(resourcePath.relativeName); } case 'arrayValue': { const array = []; if (Array.isArray(proto.arrayValue.values)) { for (const value of proto.arrayValue.values) { array.push(this.decodeValue(value)); } } return array; } case 'nullValue': { return null; } case 'mapValue': { const obj = {}; const fields = proto.mapValue.fields; if (fields) { for (const prop of Object.keys(fields)) { obj[prop] = this.decodeValue(fields[prop]); } } return obj; } case 'geoPointValue': { return geo_point_1.GeoPoint.fromProto(proto.geoPointValue); } case 'bytesValue': { return proto.bytesValue; } default: { throw new Error('Cannot decode type from Firestore Value: ' + JSON.stringify(proto)); } } } } exports.Serializer = Serializer; /** * Validates a JavaScript value for usage as a Firestore value. * * @private * @internal * @param arg The argument name or argument index (for varargs methods). * @param value JavaScript value to validate. * @param desc A description of the expected type. * @param path The field path to validate. * @param options Validation options * @param level The current depth of the traversal. This is used to decide * whether undefined values or deletes are allowed. * @param inArray Whether we are inside an array. * @throws when the object is invalid. */ function validateUserInput(arg, value, desc, options, path, level, inArray) { if (path && path.size > MAX_DEPTH) { throw new Error(`${validate_1.invalidArgumentMessage(arg, desc)} Input object is deeper than ${MAX_DEPTH} levels or contains a cycle.`); } level = level || 0; inArray = inArray || false; const fieldPathMessage = path ? ` (found in field "${path}")` : ''; if (Array.isArray(value)) { for (let i = 0; i < value.length; ++i) { validateUserInput(arg, value[i], desc, options, path ? path.append(String(i)) : new path_1.FieldPath(String(i)), level + 1, /* inArray= */ true); } } else if (util_1.isPlainObject(value)) { for (const prop of Object.keys(value)) { validateUserInput(arg, value[prop], desc, options, path ? path.append(new path_1.FieldPath(prop)) : new path_1.FieldPath(prop), level + 1, inArray); } } else if (value === undefined) { if (options.allowUndefined && level === 0) { throw new Error(`${validate_1.invalidArgumentMessage(arg, desc)} "undefined" values are only ignored inside of objects.`); } else if (!options.allowUndefined) { throw new Error(`${validate_1.invalidArgumentMessage(arg, desc)} Cannot use "undefined" as a Firestore value${fieldPathMessage}. ` + 'If you want to ignore undefined values, enable `ignoreUndefinedProperties`.'); } } else if (value instanceof field_value_1.DeleteTransform) { if (inArray) { throw new Error(`${validate_1.invalidArgumentMessage(arg, desc)} ${value.methodName}() cannot be used inside of an array${fieldPathMessage}.`); } else if (options.allowDeletes === 'none') { throw new Error(`${validate_1.invalidArgumentMessage(arg, desc)} ${value.methodName}() must appear at the top-level and can only be used in update() ` + `or set() with {merge:true}${fieldPathMessage}.`); } else if (options.allowDeletes === 'root') { if (level === 0) { // Ok (update() with UpdateData). } else if (level === 1 && (path === null || path === void 0 ? void 0 : path.size) === 1) { // Ok (update with varargs). } else { throw new Error(`${validate_1.invalidArgumentMessage(arg, desc)} ${value.methodName}() must appear at the top-level and can only be used in update() ` + `or set() with {merge:true}${fieldPathMessage}.`); } } } else if (value instanceof field_value_1.FieldTransform) { if (inArray) { throw new Error(`${validate_1.invalidArgumentMessage(arg, desc)} ${value.methodName}() cannot be used inside of an array${fieldPathMessage}.`); } else if (!options.allowTransforms) { throw new Error(`${validate_1.invalidArgumentMessage(arg, desc)} ${value.methodName}() can only be used in set(), create() or update()${fieldPathMessage}.`); } } else if (value instanceof path_1.FieldPath) { throw new Error(`${validate_1.invalidArgumentMessage(arg, desc)} Cannot use object of type "FieldPath" as a Firestore value${fieldPathMessage}.`); } else if (value instanceof index_1.DocumentReference) { // Ok. } else if (value instanceof geo_point_1.GeoPoint) { // Ok. } else if (value instanceof timestamp_1.Timestamp || value instanceof Date) { // Ok. } else if (isMomentJsType(value)) { // Ok. } else if (value instanceof Buffer || value instanceof Uint8Array) { // Ok. } else if (value === null) { // Ok. } else if (typeof value === 'object') { throw new Error(validate_1.customObjectMessage(arg, value, path)); } } exports.validateUserInput = validateUserInput; /** * Returns true if value is a MomentJs date object. * @private * @internal */ function isMomentJsType(value) { return (typeof value === 'object' && value !== null && value.constructor && value.constructor.name === 'Moment' && typeof value.toDate === 'function'); } //# sourceMappingURL=serializer.js.map
Back to File Manager