1717import arrify = require( 'arrify' ) ;
1818import * as extend from 'extend' ;
1919import * as is from 'is' ;
20- import { Query , QueryProto } from './query' ;
20+ import { Query , QueryProto , IntegerTypeCastOptions } from './query' ;
2121import { PathType } from '.' ;
2222import * as Protobuf from 'protobufjs' ;
2323import * as path from 'path' ;
@@ -111,16 +111,36 @@ export namespace entity {
111111 *
112112 * @class
113113 * @param {number|string } value The integer value.
114+ * @param {object } [typeCastOptions] Configuration to convert
115+ * values of `integerValue` type to a custom value. Must provide an
116+ * `integerTypeCastFunction` to handle `integerValue` conversion.
117+ * @param {function } typeCastOptions.integerTypeCastFunction A custom user
118+ * provided function to convert `integerValue`.
119+ * @param {sting|string[] } [typeCastOptions.properties] `Entity` property
120+ * names to be converted using `integerTypeCastFunction`.
114121 *
115122 * @example
116123 * const {Datastore} = require('@google-cloud/datastore');
117124 * const datastore = new Datastore();
118125 * const anInt = datastore.int(7);
119126 */
120- export class Int {
127+ export class Int extends Number {
121128 type : string ;
122129 value : string ;
123- constructor ( value : number | string ) {
130+ typeCastFunction ?: Function ;
131+ typeCastProperties ?: string [ ] ;
132+ private _entityPropertyName : string | undefined ;
133+ constructor (
134+ value : number | string | ValueProto ,
135+ typeCastOptions ?: IntegerTypeCastOptions
136+ ) {
137+ super ( typeof value === 'object' ? value . integerValue : value ) ;
138+ this . _entityPropertyName =
139+ typeof value === 'object' ? value . propertyName : undefined ;
140+ this . value =
141+ typeof value === 'object'
142+ ? value . integerValue . toString ( )
143+ : value . toString ( ) ;
124144 /**
125145 * @name Int#type
126146 * @type {string }
@@ -130,7 +150,46 @@ export namespace entity {
130150 * @name Int#value
131151 * @type {string }
132152 */
133- this . value = value . toString ( ) ;
153+ if ( typeCastOptions ) {
154+ this . typeCastFunction = typeCastOptions . integerTypeCastFunction ;
155+ if ( typeof typeCastOptions . integerTypeCastFunction !== 'function' ) {
156+ throw new Error (
157+ `integerTypeCastFunction is not a function or was not provided.`
158+ ) ;
159+ }
160+
161+ this . typeCastProperties = typeCastOptions . properties
162+ ? arrify ( typeCastOptions . properties )
163+ : undefined ;
164+ }
165+ }
166+ // tslint:disable-next-line no-any
167+ valueOf ( ) : any {
168+ let shouldCustomCast = this . typeCastFunction ? true : false ;
169+ if (
170+ this . typeCastProperties &&
171+ ! this . typeCastProperties . includes ( this . _entityPropertyName ! )
172+ ) {
173+ shouldCustomCast = false ;
174+ }
175+
176+ if ( shouldCustomCast ) {
177+ try {
178+ return this . typeCastFunction ! ( this . value ) ;
179+ } catch ( error ) {
180+ error . message = `integerTypeCastFunction threw an error:\n\n - ${ error . message } ` ;
181+ throw error ;
182+ }
183+ } else {
184+ return decodeIntegerValue ( {
185+ integerValue : this . value ,
186+ propertyName : this . _entityPropertyName ,
187+ } ) ;
188+ }
189+ }
190+
191+ toJSON ( ) : Json {
192+ return { type : this . type , value : this . value } ;
134193 }
135194 }
136195
@@ -376,11 +435,52 @@ export namespace entity {
376435 return value instanceof entity . Key ;
377436 }
378437
438+ /**
439+ * Convert a protobuf `integerValue`.
440+ *
441+ * @private
442+ * @param {object } value The `integerValue` to convert.
443+ */
444+ function decodeIntegerValue ( value : ValueProto ) {
445+ const num = Number ( value . integerValue ) ;
446+ if ( ! Number . isSafeInteger ( num ) ) {
447+ throw new Error (
448+ 'We attempted to return all of the numeric values, but ' +
449+ ( value . propertyName ? value . propertyName + ' ' : '' ) +
450+ 'value ' +
451+ value . integerValue +
452+ " is out of bounds of 'Number.MAX_SAFE_INTEGER'.\n" +
453+ "To prevent this error, please consider passing 'options.wrapNumbers=true' or\n" +
454+ "'options.wrapNumbers' as\n" +
455+ '{\n' +
456+ ' integerTypeCastFunction: provide <your_custom_function>\n' +
457+ ' properties: optionally specify property name(s) to be cutom casted' +
458+ '}\n'
459+ ) ;
460+ }
461+ return num ;
462+ }
463+
464+ /**
465+ * @typedef {object } IntegerTypeCastOptions Configuration to convert
466+ * values of `integerValue` type to a custom value. Must provide an
467+ * `integerTypeCastFunction` to handle `integerValue` conversion.
468+ * @property {function } integerTypeCastFunction A custom user
469+ * provided function to convert `integerValue`.
470+ * @property {string | string[] } [properties] `Entity` property
471+ * names to be converted using `integerTypeCastFunction`.
472+ */
379473 /**
380474 * Convert a protobuf Value message to its native value.
381475 *
382476 * @private
383477 * @param {object } valueProto The protobuf Value message to convert.
478+ * @param {boolean | IntegerTypeCastOptions } [wrapNumbers=false] Wrap values of integerValue type in
479+ * {@link Datastore#Int} objects.
480+ * If a `boolean`, this will wrap values in {@link Datastore#Int} objects.
481+ * If an `object`, this will return a value returned by
482+ * `wrapNumbers.integerTypeCastFunction`.
483+ * Please see {@link IntegerTypeCastOptions} for options descriptions.
384484 * @returns {* }
385485 *
386486 * @example
@@ -399,13 +499,19 @@ export namespace entity {
399499 * });
400500 * // <Buffer 68 65 6c 6c 6f>
401501 */
402- export function decodeValueProto ( valueProto : ValueProto ) {
502+ export function decodeValueProto (
503+ valueProto : ValueProto ,
504+ wrapNumbers ?: boolean | IntegerTypeCastOptions
505+ ) {
403506 const valueType = valueProto . valueType ! ;
404507 const value = valueProto [ valueType ] ;
405508
406509 switch ( valueType ) {
407510 case 'arrayValue' : {
408- return value . values . map ( entity . decodeValueProto ) ;
511+ // tslint:disable-next-line no-any
512+ return value . values . map ( ( val : any ) =>
513+ entity . decodeValueProto ( val , wrapNumbers )
514+ ) ;
409515 }
410516
411517 case 'blobValue' : {
@@ -421,11 +527,15 @@ export namespace entity {
421527 }
422528
423529 case 'integerValue' : {
424- return Number ( value ) ;
530+ return wrapNumbers
531+ ? typeof wrapNumbers === 'object'
532+ ? new entity . Int ( valueProto , wrapNumbers ) . valueOf ( )
533+ : new entity . Int ( valueProto , undefined )
534+ : decodeIntegerValue ( valueProto ) ;
425535 }
426536
427537 case 'entityValue' : {
428- return entity . entityFromEntityProto ( value ) ;
538+ return entity . entityFromEntityProto ( value , wrapNumbers ) ;
429539 }
430540
431541 case 'keyValue' : {
@@ -554,6 +664,12 @@ export namespace entity {
554664 *
555665 * @private
556666 * @param {object } entityProto The protocol entity object to convert.
667+ * @param {boolean | IntegerTypeCastOptions } [wrapNumbers=false] Wrap values of integerValue type in
668+ * {@link Datastore#Int} objects.
669+ * If a `boolean`, this will wrap values in {@link Datastore#Int} objects.
670+ * If an `object`, this will return a value returned by
671+ * `wrapNumbers.integerTypeCastFunction`.
672+ * Please see {@link IntegerTypeCastOptions} for options descriptions.
557673 * @returns {object }
558674 *
559675 * @example
@@ -574,15 +690,18 @@ export namespace entity {
574690 * // }
575691 */
576692 // tslint:disable-next-line no-any
577- export function entityFromEntityProto ( entityProto : EntityProto ) : any {
693+ export function entityFromEntityProto (
694+ entityProto : EntityProto ,
695+ wrapNumbers ?: boolean | IntegerTypeCastOptions
696+ ) {
578697 // tslint:disable-next-line no-any
579698 const entityObject : any = { } ;
580699 const properties = entityProto . properties || { } ;
581700
582701 // tslint:disable-next-line forin
583702 for ( const property in properties ) {
584703 const value = properties [ property ] ;
585- entityObject [ property ] = entity . decodeValueProto ( value ) ;
704+ entityObject [ property ] = entity . decodeValueProto ( value , wrapNumbers ) ;
586705 }
587706
588707 return entityObject ;
@@ -768,7 +887,12 @@ export namespace entity {
768887 * @param {object[] } results The response array.
769888 * @param {object } results.entity An entity object.
770889 * @param {object } results.entity.key The entity's key.
771- * @returns {object[] }
890+ * @param {boolean | IntegerTypeCastOptions } [wrapNumbers=false] Wrap values of integerValue type in
891+ * {@link Datastore#Int} objects.
892+ * If a `boolean`, this will wrap values in {@link Datastore#Int} objects.
893+ * If an `object`, this will return a value returned by
894+ * `wrapNumbers.integerTypeCastFunction`.
895+ * Please see {@link IntegerTypeCastOptions} for options descriptions.
772896 *
773897 * @example
774898 * request_('runQuery', {}, (err, response) => {
@@ -782,9 +906,12 @@ export namespace entity {
782906 * //
783907 * });
784908 */
785- export function formatArray ( results : ResponseResult [ ] ) {
909+ export function formatArray (
910+ results : ResponseResult [ ] ,
911+ wrapNumbers ?: boolean | IntegerTypeCastOptions
912+ ) {
786913 return results . map ( result => {
787- const ent = entity . entityFromEntityProto ( result . entity ! ) ;
914+ const ent = entity . entityFromEntityProto ( result . entity ! , wrapNumbers ) ;
788915 ent [ entity . KEY_SYMBOL ] = entity . keyFromKeyProto ( result . entity ! . key ! ) ;
789916 return ent ;
790917 } ) ;
@@ -1274,6 +1401,7 @@ export interface ValueProto {
12741401 values ?: ValueProto [ ] ;
12751402 // tslint:disable-next-line no-any
12761403 value ?: any ;
1404+ propertyName ?: string ;
12771405}
12781406
12791407export interface EntityProto {
@@ -1305,3 +1433,7 @@ export interface EntityObject {
13051433 data : { [ k : string ] : Entity } ;
13061434 excludeFromIndexes : string [ ] ;
13071435}
1436+
1437+ export interface Json {
1438+ [ field : string ] : string ;
1439+ }
0 commit comments