UNPKG

43.5 kBJavaScriptView Raw
1"use strict";
2/* eslint-disable no-inner-declarations */
3// Copyright 2014 Google LLC
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16Object.defineProperty(exports, "__esModule", { value: true });
17exports.entity = void 0;
18const arrify = require("arrify");
19const extend = require("extend");
20const is = require("is");
21const google_gax_1 = require("google-gax");
22const path = require("path");
23const filter_1 = require("./filter");
24// eslint-disable-next-line @typescript-eslint/no-namespace
25var entity;
26(function (entity_1) {
27 class InvalidKeyError extends Error {
28 constructor(opts) {
29 const errorMessages = {
30 MISSING_KIND: 'A key should contain at least a kind.',
31 MISSING_ANCESTOR_ID: 'Ancestor keys require an id or name.',
32 };
33 super(errorMessages[opts.code]);
34 this.name = 'InvalidKey';
35 }
36 }
37 entity_1.InvalidKeyError = InvalidKeyError;
38 /**
39 * A symbol to access the Key object from an entity object.
40 *
41 * @type {symbol}
42 * @private
43 */
44 entity_1.KEY_SYMBOL = Symbol('KEY');
45 /**
46 * Build a Datastore Double object. For long doubles, a string can be
47 * provided.
48 *
49 * @class
50 * @param {number} value The double value.
51 *
52 * @example
53 * ```
54 * const {Datastore} = require('@google-cloud/datastore');
55 * const datastore = new Datastore();
56 * const aDouble = datastore.double(7.3);
57 * ```
58 */
59 class Double {
60 constructor(value) {
61 /**
62 * @name Double#type
63 * @type {string}
64 */
65 this.type = 'DatastoreDouble';
66 /**
67 * @name Double#value
68 * @type {number}
69 */
70 this.value = value;
71 }
72 }
73 entity_1.Double = Double;
74 /**
75 * Check if something is a Datastore Double object.
76 *
77 * @private
78 * @param {*} value
79 * @returns {boolean}
80 */
81 function isDsDouble(value) {
82 return value instanceof entity.Double;
83 }
84 entity_1.isDsDouble = isDsDouble;
85 /**
86 * Check if a value is a Datastore Double object converted from JSON.
87 *
88 * @private
89 * @param {*} value
90 * @returns {boolean}
91 */
92 function isDsDoubleLike(value) {
93 // eslint-disable-next-line @typescript-eslint/no-explicit-any
94 const maybeDsDouble = value;
95 return (isDsDouble(maybeDsDouble) ||
96 (is.object(maybeDsDouble) &&
97 is.number(maybeDsDouble.value) &&
98 maybeDsDouble.type === 'DatastoreDouble'));
99 }
100 entity_1.isDsDoubleLike = isDsDoubleLike;
101 /**
102 * Build a Datastore Int object. For long integers, a string can be provided.
103 *
104 * @class
105 * @param {number|string} value The integer value.
106 * @param {object} [typeCastOptions] Configuration to convert
107 * values of `integerValue` type to a custom value. Must provide an
108 * `integerTypeCastFunction` to handle `integerValue` conversion.
109 * @param {function} typeCastOptions.integerTypeCastFunction A custom user
110 * provided function to convert `integerValue`.
111 * @param {string|string[]} [typeCastOptions.properties] `Entity` property
112 * names to be converted using `integerTypeCastFunction`.
113 *
114 * @example
115 * ```
116 * const {Datastore} = require('@google-cloud/datastore');
117 * const datastore = new Datastore();
118 * const anInt = datastore.int(7);
119 * ```
120 */
121 class Int extends Number {
122 constructor(value, typeCastOptions) {
123 super(typeof value === 'object' ? value.integerValue : value);
124 this._entityPropertyName =
125 typeof value === 'object' ? value.propertyName : undefined;
126 this.value =
127 typeof value === 'object'
128 ? value.integerValue.toString()
129 : value.toString();
130 /**
131 * @name Int#type
132 * @type {string}
133 */
134 this.type = 'DatastoreInt';
135 /**
136 * @name Int#value
137 * @type {string}
138 */
139 if (typeCastOptions) {
140 this.typeCastFunction = typeCastOptions.integerTypeCastFunction;
141 if (typeof typeCastOptions.integerTypeCastFunction !== 'function') {
142 throw new Error('integerTypeCastFunction is not a function or was not provided.');
143 }
144 this.typeCastProperties = typeCastOptions.properties
145 ? arrify(typeCastOptions.properties)
146 : undefined;
147 }
148 }
149 // eslint-disable-next-line @typescript-eslint/no-explicit-any
150 valueOf() {
151 let shouldCustomCast = this.typeCastFunction ? true : false;
152 if (this.typeCastProperties &&
153 !this.typeCastProperties.includes(this._entityPropertyName)) {
154 shouldCustomCast = false;
155 }
156 if (shouldCustomCast) {
157 try {
158 return this.typeCastFunction(this.value);
159 }
160 catch (error) {
161 error.message = `integerTypeCastFunction threw an error:\n\n - ${error.message}`;
162 throw error;
163 }
164 }
165 else {
166 return decodeIntegerValue({
167 integerValue: this.value,
168 propertyName: this._entityPropertyName,
169 });
170 }
171 }
172 toJSON() {
173 return { type: this.type, value: this.value };
174 }
175 }
176 entity_1.Int = Int;
177 /**
178 * Check if something is a Datastore Int object.
179 *
180 * @private
181 * @param {*} value
182 * @returns {boolean}
183 */
184 function isDsInt(value) {
185 return value instanceof entity.Int;
186 }
187 entity_1.isDsInt = isDsInt;
188 /**
189 * Check if a value is a Datastore Int object converted from JSON.
190 *
191 * @private
192 * @param {*} value
193 * @returns {boolean}
194 */
195 function isDsIntLike(value) {
196 // eslint-disable-next-line @typescript-eslint/no-explicit-any
197 const maybeDsInt = value;
198 return (isDsInt(maybeDsInt) ||
199 (is.object(maybeDsInt) &&
200 is.string(maybeDsInt.value) &&
201 maybeDsInt.type === 'DatastoreInt'));
202 }
203 entity_1.isDsIntLike = isDsIntLike;
204 /**
205 * Build a Datastore Geo Point object.
206 *
207 * @class
208 * @param {object} coordinates Coordinate value.
209 * @param {number} coordinates.latitude Latitudinal value.
210 * @param {number} coordinates.longitude Longitudinal value.
211 *
212 * @example
213 * ```
214 * const {Datastore} = require('@google-cloud/datastore');
215 * const datastore = new Datastore();
216 * const coordinates = {
217 * latitude: 40.6894,
218 * longitude: -74.0447
219 * };
220 *
221 * const geoPoint = datastore.geoPoint(coordinates);
222 * ```
223 */
224 class GeoPoint {
225 constructor(coordinates) {
226 /**
227 * Coordinate value.
228 *
229 * @name GeoPoint#coordinates
230 * @type {object}
231 * @property {number} latitude Latitudinal value.
232 * @property {number} longitude Longitudinal value.
233 */
234 this.value = coordinates;
235 }
236 }
237 entity_1.GeoPoint = GeoPoint;
238 /**
239 * Check if something is a Datastore Geo Point object.
240 *
241 * @private
242 * @param {*} value
243 * @returns {boolean}
244 */
245 function isDsGeoPoint(value) {
246 return value instanceof entity.GeoPoint;
247 }
248 entity_1.isDsGeoPoint = isDsGeoPoint;
249 /**
250 * Build a Datastore Key object.
251 *
252 * @class
253 * @param {object} options Configuration object.
254 * @param {array} options.path Key path.
255 * @param {string} [options.namespace] Optional namespace.
256 *
257 * @example
258 * ```
259 * //-
260 * // Create an incomplete key with a kind value of `Company`.
261 * //-
262 * const {Datastore} = require('@google-cloud/datastore');
263 * const datastore = new Datastore();
264 * const key = datastore.key('Company');
265 *
266 * ```
267 * @example
268 * ```
269 * //-
270 * // Create a complete key with a kind value of `Company` and id`123`.
271 * //-
272 * const {Datastore} = require('@google-cloud/datastore');
273 * const datastore = new Datastore();
274 * const key = datastore.key(['Company', 123]);
275 *
276 * ```
277 * @example
278 * ```
279 * //-
280 * // If the ID integer is outside the bounds of a JavaScript Number
281 * // object, create an Int.
282 * //-
283 * const {Datastore} = require('@google-cloud/datastore');
284 * const datastore = new Datastore();
285 * const key = datastore.key([
286 * 'Company',
287 * datastore.int('100000000000001234')
288 * ]);
289 *
290 * ```
291 * @example
292 * ```
293 * const {Datastore} = require('@google-cloud/datastore');
294 * const datastore = new Datastore();
295 * // Create a complete key with a kind value of `Company` and name `Google`.
296 * // Note: `id` is used for numeric identifiers and `name` is used otherwise.
297 * const key = datastore.key(['Company', 'Google']);
298 *
299 * ```
300 * @example
301 * ```
302 * //-
303 * // Create a complete key from a provided namespace and path.
304 * //-
305 * const {Datastore} = require('@google-cloud/datastore');
306 * const datastore = new Datastore();
307 * const key = datastore.key({
308 * namespace: 'My-NS',
309 * path: ['Company', 123]
310 * });
311 *
312 * ```
313 * @example Serialize the key for later re-use.
314 * ```
315 * const {Datastore} = require('@google-cloud/datastore');
316 * const datastore = new Datastore();
317 * const key = datastore.key({
318 * namespace: 'My-NS',
319 * path: ['Company', 123]
320 * });
321 * // Later...
322 * const key = datastore.key(key.serialized);
323 * ```
324 */
325 class Key {
326 constructor(options) {
327 /**
328 * @name Key#namespace
329 * @type {string}
330 */
331 this.namespace = options.namespace;
332 options.path = [].slice.call(options.path);
333 if (options.path.length % 2 === 0) {
334 const identifier = options.path.pop();
335 if (is.number(identifier) ||
336 isDsInt(identifier) ||
337 isDsIntLike(identifier)) {
338 this.id = (identifier.value || identifier);
339 }
340 else if (is.string(identifier)) {
341 this.name = identifier;
342 }
343 }
344 this.kind = options.path.pop();
345 if (options.path.length > 0) {
346 this.parent = new Key(options);
347 }
348 // `path` is computed on demand to consider any changes that may have been
349 // made to the key.
350 /**
351 * @name Key#path
352 * @type {array}
353 */
354 Object.defineProperty(this, 'path', {
355 enumerable: true,
356 get() {
357 return arrify(this.parent && this.parent.path).concat([
358 this.kind,
359 this.name || this.id,
360 ]);
361 },
362 });
363 }
364 /**
365 * Access the `serialized` property for a library-compatible way to re-use a
366 * key.
367 *
368 * @returns {object}
369 *
370 * @example
371 * ```
372 * const key = datastore.key({
373 * namespace: 'My-NS',
374 * path: ['Company', 123]
375 * });
376 *
377 * // Later...
378 * const key = datastore.key(key.serialized);
379 * ```
380 */
381 get serialized() {
382 const serializedKey = {
383 namespace: this.namespace,
384 path: [this.kind, this.name || new Int(this.id)],
385 };
386 if (this.parent) {
387 serializedKey.path = this.parent.serialized.path.concat(serializedKey.path);
388 }
389 return serializedKey;
390 }
391 }
392 entity_1.Key = Key;
393 /**
394 * Check if something is a Datastore Key object.
395 *
396 * @private
397 * @param {*} value
398 * @returns {boolean}
399 */
400 function isDsKey(value) {
401 return value instanceof entity.Key;
402 }
403 entity_1.isDsKey = isDsKey;
404 /**
405 * Convert a protobuf `integerValue`.
406 *
407 * @private
408 * @param {object} value The `integerValue` to convert.
409 */
410 function decodeIntegerValue(value) {
411 const num = Number(value.integerValue);
412 if (!Number.isSafeInteger(num)) {
413 throw new Error('We attempted to return all of the numeric values, but ' +
414 (value.propertyName ? value.propertyName + ' ' : '') +
415 'value ' +
416 value.integerValue +
417 " is out of bounds of 'Number.MAX_SAFE_INTEGER'.\n" +
418 "To prevent this error, please consider passing 'options.wrapNumbers=true' or\n" +
419 "'options.wrapNumbers' as\n" +
420 '{\n' +
421 ' integerTypeCastFunction: provide <your_custom_function>\n' +
422 ' properties: optionally specify property name(s) to be custom casted\n' +
423 '}\n');
424 }
425 return num;
426 }
427 /**
428 * @typedef {object} IntegerTypeCastOptions Configuration to convert
429 * values of `integerValue` type to a custom value. Must provide an
430 * `integerTypeCastFunction` to handle `integerValue` conversion.
431 * @property {function} integerTypeCastFunction A custom user
432 * provided function to convert `integerValue`.
433 * @property {string | string[]} [properties] `Entity` property
434 * names to be converted using `integerTypeCastFunction`.
435 */
436 /**
437 * Convert a protobuf Value message to its native value.
438 *
439 * @private
440 * @param {object} valueProto The protobuf Value message to convert.
441 * @param {boolean | IntegerTypeCastOptions} [wrapNumbers=false] Wrap values of integerValue type in
442 * {@link Datastore#Int} objects.
443 * If a `boolean`, this will wrap values in {@link Datastore#Int} objects.
444 * If an `object`, this will return a value returned by
445 * `wrapNumbers.integerTypeCastFunction`.
446 * Please see {@link IntegerTypeCastOptions} for options descriptions.
447 * @returns {*}
448 *
449 * @example
450 * ```
451 * decodeValueProto({
452 * booleanValue: false
453 * });
454 * // false
455 *
456 * decodeValueProto({
457 * stringValue: 'Hi'
458 * });
459 * // 'Hi'
460 *
461 * decodeValueProto({
462 * blobValue: Buffer.from('68656c6c6f')
463 * });
464 * // <Buffer 68 65 6c 6c 6f>
465 * ```
466 */
467 function decodeValueProto(valueProto, wrapNumbers) {
468 const valueType = valueProto.valueType;
469 const value = valueProto[valueType];
470 switch (valueType) {
471 case 'arrayValue': {
472 // eslint-disable-next-line @typescript-eslint/no-explicit-any
473 return value.values.map((val) => entity.decodeValueProto(val, wrapNumbers));
474 }
475 case 'blobValue': {
476 return Buffer.from(value, 'base64');
477 }
478 case 'nullValue': {
479 return null;
480 }
481 case 'doubleValue': {
482 return Number(value);
483 }
484 case 'integerValue': {
485 return wrapNumbers
486 ? typeof wrapNumbers === 'object'
487 ? new entity.Int(valueProto, wrapNumbers).valueOf()
488 : new entity.Int(valueProto, undefined)
489 : decodeIntegerValue(valueProto);
490 }
491 case 'entityValue': {
492 return entity.entityFromEntityProto(value, wrapNumbers);
493 }
494 case 'keyValue': {
495 return entity.keyFromKeyProto(value);
496 }
497 case 'timestampValue': {
498 const milliseconds = Number(value.nanos) / 1e6;
499 return new Date(Number(value.seconds) * 1000 + milliseconds);
500 }
501 default: {
502 return value;
503 }
504 }
505 }
506 entity_1.decodeValueProto = decodeValueProto;
507 /**
508 * Convert any native value to a protobuf Value message object.
509 *
510 * @private
511 * @param {*} value Native value.
512 * @returns {object}
513 *
514 * @example
515 * ```
516 * encodeValue('Hi');
517 * // {
518 * // stringValue: 'Hi'
519 * // }
520 * ```
521 */
522 // eslint-disable-next-line @typescript-eslint/no-explicit-any
523 function encodeValue(value, property) {
524 const valueProto = {};
525 if (is.boolean(value)) {
526 valueProto.booleanValue = value;
527 return valueProto;
528 }
529 if (is.null(value)) {
530 valueProto.nullValue = 0;
531 return valueProto;
532 }
533 if (typeof value === 'number') {
534 if (Number.isInteger(value)) {
535 if (!Number.isSafeInteger(value)) {
536 process.emitWarning('IntegerOutOfBoundsWarning: ' +
537 "the value for '" +
538 property +
539 "' property is outside of bounds of a JavaScript Number.\n" +
540 "Use 'Datastore.int(<integer_value_as_string>)' to preserve accuracy during the upload.");
541 }
542 value = new entity.Int(value);
543 }
544 else {
545 value = new entity.Double(value);
546 }
547 }
548 if (isDsInt(value)) {
549 valueProto.integerValue = value.value;
550 return valueProto;
551 }
552 if (isDsDouble(value)) {
553 valueProto.doubleValue = value.value;
554 return valueProto;
555 }
556 if (isDsGeoPoint(value)) {
557 valueProto.geoPointValue = value.value;
558 return valueProto;
559 }
560 if (value instanceof Date) {
561 const seconds = value.getTime() / 1000;
562 valueProto.timestampValue = {
563 seconds: Math.floor(seconds),
564 nanos: value.getMilliseconds() * 1e6,
565 };
566 return valueProto;
567 }
568 if (is.string(value)) {
569 valueProto.stringValue = value;
570 return valueProto;
571 }
572 if (value instanceof Buffer) {
573 valueProto.blobValue = value;
574 return valueProto;
575 }
576 if (Array.isArray(value)) {
577 valueProto.arrayValue = {
578 values: value.map(val => entity.encodeValue(val, property)),
579 };
580 return valueProto;
581 }
582 if (isDsKey(value)) {
583 valueProto.keyValue = entity.keyToKeyProto(value);
584 return valueProto;
585 }
586 if (is.object(value)) {
587 if (!is.empty(value)) {
588 value = extend(true, {}, value);
589 for (const prop in value) {
590 value[prop] = entity.encodeValue(value[prop], prop);
591 }
592 }
593 valueProto.entityValue = {
594 properties: value,
595 };
596 return valueProto;
597 }
598 throw new Error('Unsupported field value, ' + value + ', was provided.');
599 }
600 entity_1.encodeValue = encodeValue;
601 /**
602 * Convert any entity protocol to a plain object.
603 *
604 * @todo Use registered metadata if provided.
605 *
606 * @private
607 * @param {object} entityProto The protocol entity object to convert.
608 * @param {boolean | IntegerTypeCastOptions} [wrapNumbers=false] Wrap values of integerValue type in
609 * {@link Datastore#Int} objects.
610 * If a `boolean`, this will wrap values in {@link Datastore#Int} objects.
611 * If an `object`, this will return a value returned by
612 * `wrapNumbers.integerTypeCastFunction`.
613 * Please see {@link IntegerTypeCastOptions} for options descriptions.
614 * @returns {object}
615 *
616 * @example
617 * ```
618 * entityFromEntityProto({
619 * properties: {
620 * map: {
621 * name: {
622 * value: {
623 * valueType: 'stringValue',
624 * stringValue: 'Stephen'
625 * }
626 * }
627 * }
628 * }
629 * });
630 * // {
631 * // name: 'Stephen'
632 * // }
633 * ```
634 */
635 // eslint-disable-next-line @typescript-eslint/no-explicit-any
636 function entityFromEntityProto(entityProto, wrapNumbers) {
637 // eslint-disable-next-line @typescript-eslint/no-explicit-any
638 const entityObject = {};
639 const properties = entityProto.properties || {};
640 for (const property in properties) {
641 const value = properties[property];
642 value.propertyName = property;
643 entityObject[property] = entity.decodeValueProto(value, wrapNumbers);
644 }
645 return entityObject;
646 }
647 entity_1.entityFromEntityProto = entityFromEntityProto;
648 /**
649 * Convert an entity object to an entity protocol object.
650 *
651 * @private
652 * @param {object} entityObject The entity object to convert.
653 * @returns {object}
654 *
655 * @example
656 * ```
657 * entityToEntityProto({
658 * excludeFromIndexes: [
659 * 'name'
660 * ],
661 * data: {
662 * name: 'Burcu',
663 * legit: true
664 * }
665 * });
666 * // {
667 * // key: null,
668 * // properties: {
669 * // name: {
670 * // stringValue: 'Burcu'
671 * // excludeFromIndexes: true
672 * // },
673 * // legit: {
674 * // booleanValue: true
675 * // }
676 * // }
677 * // }
678 * ```
679 */
680 function entityToEntityProto(entityObject) {
681 const properties = entityObject.data;
682 const excludeFromIndexes = entityObject.excludeFromIndexes;
683 const entityProto = {
684 key: null,
685 properties: Object.keys(properties).reduce((encoded, key) => {
686 encoded[key] = entity.encodeValue(properties[key], key);
687 return encoded;
688 },
689 // eslint-disable-next-line @typescript-eslint/no-explicit-any
690 {}),
691 };
692 if (excludeFromIndexes && excludeFromIndexes.length > 0) {
693 excludeFromIndexes.forEach((excludePath) => {
694 excludePathFromEntity(entityProto, excludePath);
695 });
696 }
697 return entityProto;
698 function excludePathFromEntity(entity, path) {
699 const arrayIndex = path.indexOf('[]');
700 const entityIndex = path.indexOf('.');
701 const wildcardIndex = path.indexOf('.*');
702 const hasArrayPath = arrayIndex > -1;
703 const hasEntityPath = entityIndex > -1;
704 const hasWildCard = wildcardIndex > -1;
705 if (!hasArrayPath && !hasEntityPath) {
706 // This is the path end node. Traversal ends here in either case.
707 if (entity.properties) {
708 if (entity.properties[path] &&
709 // array properties should be excluded with [] syntax:
710 !entity.properties[path].arrayValue) {
711 // This is the property to exclude!
712 entity.properties[path].excludeFromIndexes = true;
713 }
714 }
715 else if (!path) {
716 // This is a primitive or entity root that should be excluded.
717 entity.excludeFromIndexes = true;
718 }
719 return;
720 }
721 let delimiterIndex;
722 if (hasArrayPath && hasEntityPath) {
723 delimiterIndex = Math.min(arrayIndex, entityIndex);
724 }
725 else {
726 delimiterIndex = Math.max(arrayIndex, entityIndex);
727 }
728 const firstPathPartIsArray = delimiterIndex === arrayIndex;
729 const firstPathPartIsEntity = delimiterIndex === entityIndex;
730 const delimiter = firstPathPartIsArray ? '[]' : '.';
731 const splitPath = path.split(delimiter);
732 const firstPathPart = splitPath.shift();
733 const remainderPath = splitPath.join(delimiter).replace(/^(\.|\[\])/, '');
734 if (!(entity.properties && entity.properties[firstPathPart]) &&
735 !hasWildCard) {
736 // Either a primitive or an entity for which this path doesn't apply.
737 return;
738 }
739 const isFirstPathPartDefined = entity.properties[firstPathPart] !== undefined;
740 if (firstPathPartIsArray &&
741 isFirstPathPartDefined &&
742 // check also if the property in question is actually an array value.
743 entity.properties[firstPathPart].arrayValue &&
744 // check if wildcard is not applied
745 !hasWildCard) {
746 const array = entity.properties[firstPathPart].arrayValue;
747 // eslint-disable-next-line @typescript-eslint/no-explicit-any
748 array.values.forEach((value) => {
749 if (remainderPath === '') {
750 // We want to exclude *this* array property, which is
751 // equivalent with excluding all its values
752 // (including entity values at their roots):
753 excludePathFromEntity(value, remainderPath // === ''
754 );
755 }
756 else {
757 // Path traversal continues at value.entityValue,
758 // if it is an entity, or must end at value.
759 excludePathFromEntity(value.entityValue || value, remainderPath // !== ''
760 );
761 }
762 });
763 }
764 else if (firstPathPartIsArray &&
765 hasWildCard &&
766 remainderPath === '*' &&
767 isFirstPathPartDefined) {
768 const array = entity.properties[firstPathPart].arrayValue;
769 // eslint-disable-next-line @typescript-eslint/no-explicit-any
770 array.values.forEach((value) => {
771 if (value.entityValue) {
772 excludePathFromEntity(value.entityValue, '.*');
773 }
774 else {
775 excludePathFromEntity(value, '');
776 }
777 });
778 }
779 else if (firstPathPartIsEntity) {
780 if (firstPathPart === '') {
781 Object.keys(entity.properties).forEach(path => {
782 const newPath = entity.properties[path].arrayValue
783 ? path + '[].*'
784 : path + '.*';
785 excludePathFromEntity(entity, newPath);
786 });
787 }
788 else {
789 if (hasWildCard && remainderPath === '*' && isFirstPathPartDefined) {
790 const parentEntity = entity.properties[firstPathPart].entityValue;
791 if (parentEntity) {
792 Object.keys(parentEntity.properties).forEach(path => {
793 const newPath = parentEntity.properties[path].arrayValue
794 ? path + '[].*'
795 : path + '.*';
796 excludePathFromEntity(parentEntity, newPath);
797 });
798 }
799 else {
800 excludePathFromEntity(entity, firstPathPart);
801 }
802 }
803 else if (isFirstPathPartDefined) {
804 const parentEntity = entity.properties[firstPathPart].entityValue;
805 excludePathFromEntity(parentEntity, remainderPath);
806 }
807 }
808 }
809 }
810 }
811 entity_1.entityToEntityProto = entityToEntityProto;
812 /**
813 * Convert an API response array to a qualified Key and data object.
814 *
815 * @private
816 * @param {object[]} results The response array.
817 * @param {object} results.entity An entity object.
818 * @param {object} results.entity.key The entity's key.
819 * @param {boolean | IntegerTypeCastOptions} [wrapNumbers=false] Wrap values of integerValue type in
820 * {@link Datastore#Int} objects.
821 * If a `boolean`, this will wrap values in {@link Datastore#Int} objects.
822 * If an `object`, this will return a value returned by
823 * `wrapNumbers.integerTypeCastFunction`.
824 * Please see {@link IntegerTypeCastOptions} for options descriptions.
825 *
826 * @example
827 * ```
828 * request_('runQuery', {}, (err, response) => {
829 * const entityObjects = formatArray(response.batch.entityResults);
830 * // {
831 * // key: {},
832 * // data: {
833 * // fieldName: 'value'
834 * // }
835 * // }
836 * //
837 * });
838 * ```
839 */
840 function formatArray(results, wrapNumbers) {
841 return results.map(result => {
842 const ent = entity.entityFromEntityProto(result.entity, wrapNumbers);
843 ent[entity.KEY_SYMBOL] = entity.keyFromKeyProto(result.entity.key);
844 return ent;
845 });
846 }
847 entity_1.formatArray = formatArray;
848 /**
849 * Find the properties which value size is large than 1500 bytes,
850 * with excludeLargeProperties enabled, automatically exclude properties from indexing.
851 * This will allow storing string values larger than 1500 bytes
852 *
853 * @param entities Datastore key object(s).
854 * @param path namespace of provided entity properties
855 * @param properties properties which value size is large than 1500 bytes
856 */
857 function findLargeProperties_(entities, path, properties = []) {
858 const MAX_DATASTORE_VALUE_LENGTH = 1500;
859 if (Array.isArray(entities)) {
860 for (const entry of entities) {
861 if (entry && entry.name && entry.value) {
862 if (is.string(entry.value) &&
863 Buffer.from(entry.value).length > MAX_DATASTORE_VALUE_LENGTH) {
864 entry.excludeFromIndexes = true;
865 }
866 else {
867 continue;
868 }
869 }
870 findLargeProperties_(entry, path.concat('[]'), properties);
871 }
872 }
873 else if (is.object(entities)) {
874 const keys = Object.keys(entities);
875 for (const key of keys) {
876 findLargeProperties_(entities[key], path.concat(`${path ? '.' : ''}${key}`), properties);
877 }
878 }
879 else if (is.string(entities) &&
880 Buffer.from(entities).length > MAX_DATASTORE_VALUE_LENGTH) {
881 if (properties.indexOf(path) < 0) {
882 properties.push(path);
883 }
884 }
885 return properties;
886 }
887 entity_1.findLargeProperties_ = findLargeProperties_;
888 /**
889 * Check if a key is complete.
890 *
891 * @private
892 * @param {Key} key The Key object.
893 * @returns {boolean}
894 *
895 * @example
896 * ```
897 * isKeyComplete(new Key(['Company', 'Google'])); // true
898 * isKeyComplete(new Key('Company')); // false
899 * ```
900 */
901 function isKeyComplete(key) {
902 const lastPathElement = entity.keyToKeyProto(key).path.pop();
903 return !!(lastPathElement.id || lastPathElement.name);
904 }
905 entity_1.isKeyComplete = isKeyComplete;
906 /**
907 * Convert a key protocol object to a Key object.
908 *
909 * @private
910 * @param {object} keyProto The key protocol object to convert.
911 * @returns {Key}
912 *
913 * @example
914 * ```
915 * const key = keyFromKeyProto({
916 * partitionId: {
917 * projectId: 'project-id',
918 * namespaceId: ''
919 * },
920 * path: [
921 * {
922 * kind: 'Kind',
923 * id: '4790047639339008'
924 * }
925 * ]
926 * });
927 * ```
928 */
929 function keyFromKeyProto(keyProto) {
930 // eslint-disable-next-line @typescript-eslint/no-explicit-any
931 const keyOptions = {
932 path: [],
933 };
934 if (keyProto.partitionId && keyProto.partitionId.namespaceId) {
935 keyOptions.namespace = keyProto.partitionId.namespaceId;
936 }
937 keyProto.path.forEach((path, index) => {
938 keyOptions.path.push(path.kind);
939 let id = path[path.idType];
940 if (path.idType === 'id') {
941 id = new entity.Int(id);
942 }
943 if (is.defined(id)) {
944 keyOptions.path.push(id);
945 }
946 else if (index < keyProto.path.length - 1) {
947 throw new InvalidKeyError({
948 code: 'MISSING_ANCESTOR_ID',
949 });
950 }
951 });
952 return new entity.Key(keyOptions);
953 }
954 entity_1.keyFromKeyProto = keyFromKeyProto;
955 /**
956 * Convert a Key object to a key protocol object.
957 *
958 * @private
959 * @param {Key} key The Key object to convert.
960 * @returns {object}
961 *
962 * @example
963 * ```
964 * const keyProto = keyToKeyProto(new Key(['Company', 1]));
965 * // {
966 * // path: [
967 * // {
968 * // kind: 'Company',
969 * // id: 1
970 * // }
971 * // ]
972 * // }
973 * ```
974 */
975 function keyToKeyProto(key) {
976 if (is.undefined(key.kind)) {
977 throw new InvalidKeyError({
978 code: 'MISSING_KIND',
979 });
980 }
981 // eslint-disable-next-line @typescript-eslint/no-explicit-any
982 const keyProto = {
983 path: [],
984 };
985 if (key.namespace) {
986 keyProto.partitionId = {
987 namespaceId: key.namespace,
988 };
989 }
990 let numKeysWalked = 0;
991 // Reverse-iterate over the Key objects.
992 do {
993 if (numKeysWalked > 0 && is.undefined(key.id) && is.undefined(key.name)) {
994 // This isn't just an incomplete key. An ancestor key is incomplete.
995 throw new InvalidKeyError({
996 code: 'MISSING_ANCESTOR_ID',
997 });
998 }
999 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1000 const pathElement = {
1001 kind: key.kind,
1002 };
1003 if (is.defined(key.id)) {
1004 pathElement.id = key.id;
1005 }
1006 if (is.defined(key.name)) {
1007 pathElement.name = key.name;
1008 }
1009 keyProto.path.unshift(pathElement);
1010 } while ((key = key.parent) && ++numKeysWalked);
1011 return keyProto;
1012 }
1013 entity_1.keyToKeyProto = keyToKeyProto;
1014 /**
1015 * Convert a query object to a query protocol object.
1016 *
1017 * @private
1018 * @param {object} q The query object to convert.
1019 * @returns {object}
1020 *
1021 * @example
1022 * ```
1023 * queryToQueryProto({
1024 * namespace: '',
1025 * kinds: [
1026 * 'Kind'
1027 * ],
1028 * filters: [],
1029 * orders: [],
1030 * groupByVal: [],
1031 * selectVal: [],
1032 * startVal: null,
1033 * endVal: null,
1034 * limitVal: -1,
1035 * offsetVal: -1
1036 * });
1037 * // {
1038 * // projection: [],
1039 * // kinds: [
1040 * // {
1041 * // name: 'Kind'
1042 * // }
1043 * // ],
1044 * // order: [],
1045 * // groupBy: []
1046 * // }
1047 * ```
1048 */
1049 function queryToQueryProto(query) {
1050 const SIGN_TO_ORDER = {
1051 '-': 'DESCENDING',
1052 '+': 'ASCENDING',
1053 };
1054 const queryProto = {
1055 distinctOn: query.groupByVal.map(groupBy => {
1056 return {
1057 name: groupBy,
1058 };
1059 }),
1060 kind: query.kinds.map(kind => {
1061 return {
1062 name: kind,
1063 };
1064 }),
1065 order: query.orders.map(order => {
1066 return {
1067 property: {
1068 name: order.name,
1069 },
1070 direction: SIGN_TO_ORDER[order.sign],
1071 };
1072 }),
1073 projection: query.selectVal.map(select => {
1074 return {
1075 property: {
1076 name: select,
1077 },
1078 };
1079 }),
1080 };
1081 if (query.endVal) {
1082 queryProto.endCursor = query.endVal;
1083 }
1084 if (query.limitVal > 0) {
1085 queryProto.limit = {
1086 value: query.limitVal,
1087 };
1088 }
1089 if (query.offsetVal > 0) {
1090 queryProto.offset = query.offsetVal;
1091 }
1092 if (query.startVal) {
1093 queryProto.startCursor = query.startVal;
1094 }
1095 // Check to see if there is at least one type of legacy filter or new filter.
1096 if (query.filters.length > 0 || query.entityFilters.length > 0) {
1097 // Convert all legacy filters into new property filter objects
1098 const filters = query.filters.map(filter => new filter_1.PropertyFilter(filter.name, filter.op, filter.val));
1099 const entityFilters = query.entityFilters;
1100 const allFilters = entityFilters.concat(filters);
1101 /*
1102 To be consistent with prior implementation, apply an AND composite filter
1103 to the collection of Filter objects. Then, set the filter property as before
1104 to the output of the toProto method.
1105 */
1106 queryProto.filter = (0, filter_1.and)(allFilters).toProto();
1107 }
1108 return queryProto;
1109 }
1110 entity_1.queryToQueryProto = queryToQueryProto;
1111 /**
1112 * URL safe key encoding and decoding helper utility.
1113 *
1114 * This is intended to work with the "legacy" representation of a
1115 * datastore "Key" used within Google App Engine (a so-called "Reference").
1116 *
1117 * @private
1118 * @class
1119 */
1120 class URLSafeKey {
1121 constructor() {
1122 this.protos = this.loadProtos_();
1123 }
1124 /**
1125 * Load AppEngine protobuf file.
1126 *
1127 * @private
1128 */
1129 loadProtos_() {
1130 const root = new google_gax_1.protobuf.Root();
1131 const loadedRoot = root.loadSync(path.join(__dirname, '..', 'protos', 'app_engine_key.proto'));
1132 loadedRoot.resolveAll();
1133 return loadedRoot.nested;
1134 }
1135 /**
1136 * Convert key to url safe base64 encoded string.
1137 *
1138 * @private
1139 * @param {string} projectId Project Id.
1140 * @param {entity.Key} key Entity key object.
1141 * @param {string} locationPrefix Optional .
1142 * The location prefix of an App Engine project ID.
1143 * Often this value is 's~', but may also be 'e~', or other location prefixes
1144 * currently unknown.
1145 * @returns {string} base64 endocded urlsafe key.
1146 */
1147 legacyEncode(projectId, key, locationPrefix) {
1148 const elements = [];
1149 let currentKey = key;
1150 do {
1151 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1152 const element = {
1153 type: currentKey.kind,
1154 };
1155 if (is.defined(currentKey.id)) {
1156 element.id = currentKey.id;
1157 }
1158 if (is.defined(currentKey.name)) {
1159 element.name = currentKey.name;
1160 }
1161 elements.unshift(element);
1162 currentKey = currentKey.parent;
1163 } while (currentKey);
1164 if (locationPrefix) {
1165 projectId = `${locationPrefix}${projectId}`;
1166 }
1167 const reference = {
1168 app: projectId,
1169 namespace: key.namespace,
1170 path: { element: elements },
1171 };
1172 const buffer = this.protos.Reference.encode(reference).finish();
1173 return this.convertToBase64_(buffer);
1174 }
1175 /**
1176 * Helper to convert URL safe key string to entity key object
1177 *
1178 * This is intended to work with the "legacy" representation of a
1179 * datastore "Key" used within Google App Engine (a so-called "Reference").
1180 *
1181 * @private
1182 * @param {entity.Key} key Entity key object.
1183 * @param {string} locationPrefix Optional .
1184 * The location prefix of an App Engine project ID.
1185 * Often this value is 's~', but may also be 'e~', or other location prefixes
1186 * currently unknown.
1187 * @returns {string} Created urlsafe key.
1188 */
1189 legacyDecode(key) {
1190 const buffer = this.convertToBuffer_(key);
1191 const message = this.protos.Reference.decode(buffer);
1192 const reference = this.protos.Reference.toObject(message, {
1193 longs: String,
1194 });
1195 const pathElements = [];
1196 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1197 reference.path.element.forEach((element) => {
1198 pathElements.push(element.type);
1199 if (is.defined(element.name)) {
1200 pathElements.push(element.name);
1201 }
1202 if (is.defined(element.id)) {
1203 pathElements.push(new entity.Int(element.id));
1204 }
1205 });
1206 const keyOptions = {
1207 path: pathElements,
1208 };
1209 if (!is.empty(reference.namespace)) {
1210 keyOptions.namespace = reference.namespace;
1211 }
1212 return new entity.Key(keyOptions);
1213 }
1214 /**
1215 * Convert buffer to base64 encoding.
1216 *
1217 * @private
1218 * @param {Buffer} buffer
1219 * @returns {string} Base64 encoded string.
1220 */
1221 convertToBase64_(buffer) {
1222 return buffer
1223 .toString('base64')
1224 .replace(/\+/g, '-')
1225 .replace(/\//g, '_')
1226 .replace(/=+$/, '');
1227 }
1228 /**
1229 * Rebuild base64 from encoded url safe string and convert to buffer.
1230 *
1231 * @private
1232 * @param {string} val Encoded url safe string.
1233 * @returns {string} Base64 encoded string.
1234 */
1235 convertToBuffer_(val) {
1236 val = val.replace(/-/g, '+').replace(/_/g, '/');
1237 val += '='.repeat(val.length % 4);
1238 return Buffer.from(val, 'base64');
1239 }
1240 }
1241 entity_1.URLSafeKey = URLSafeKey;
1242})(entity || (exports.entity = entity = {}));
1243//# sourceMappingURL=entity.js.map
\No newline at end of file