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