UNPKG

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