datastore driver code

This is the the driver for DB.DATASTORE described in Database abstraction with google apps script
The library reference is MPZF_EC6nOZFAjMRqCxEaUyz3TLx7pV4j
 This is a little more complicated that the other drivers, and in fact is made up of 3 different classes

Here is the code consisting of 3 modules

/**TODO
 * immediate queries after write are too quick .. update not yet done - maybe transactional is needed
 * indexes required for filter/sort combination
 */
/** wrapper
 */
function createDriver (handler,siloId,driverSpecific,driverOb, accessToken) {
    return new DriverDataStore(handler,siloId,driverSpecific,driverOb, accessToken);
}

/** TODO
 * having problems with 412 and predefined indexes
 */
function getLibraryInfo () {
  return {
    info: {
      name:'cDriverDataStore',
      version:'2.0.1',
      key:'MPZF_EC6nOZFAjMRqCxEaUyz3TLx7pV4j',
      share:'https://script.google.com/d/1gKZkk4zuouPmIf1JYAFGTfCW0AmMtbL5eTohuLmcOE2WqIDxLudAMrxB/edit?usp=sharing',
      description:'cloud datastore driver for dbabstraction'
    },
    dependencies:[
      cFlatten.getLibraryInfo()
    ]
  }; 
}
/**
 * DriverDataStore
 * @param {cDataHandler} handler the datahandler thats calling me
 * @param {string} tableName this is the DataStore collection
 * @param {string} id some id you create for identifying this collection
 * @param {object} dataStoreOb a DataStoreOb ob if required  ( { restAPIKey:"your DataStore developer key"} )
 * @param {string} [accessToken] an oauth2 access token
 * @return {DriverDataStore} self
 */
 
var DriverDataStore = function (handler,tableName,id,dataStoreOb,optAccessToken) {
  var siloId = tableName;  // the kind
  var dbId = id;           // cloud datastore project id    
  var self = this;
  var parentHandler = handler;
  var enums = parentHandler.getEnums();  
  var keyOb = dataStoreOb; 
  var handleError, handleCode; 
  var accessToken = optAccessToken || null;
  var handle = new DatastoreWorker(parentHandler, accessToken, dbId,siloId);
  var complex_ = false;
  
  // im not able to do transactions
  self.transactionCapable = false;
  
  // i  need transaction locking
  self.lockingBypass = false;
  
  // i am aware of transactions and know about the locking i should do
  self.transactionAware = true;
  
  self.getType = function () {
    return enums.DB.DATASTORE;
  };
  
  self.getDbId = function () {
    return dbId;
  };
  
  /** no persistent handle for this rest query - just return self
   * @return {DriverDataStore} self
   */
  self.getDriveHandle =  function () {
    return handle;
  };  

  
  /**
   * DriverDataStore.getTableName()
   * @return {string} table name or silo
   */
  self.getTableName = function () {
    return siloId;
  };
 /** create the driver version
  * @return {string} the driver version
  */ 
  self.getVersion = function () {
    var v = getLibraryInfo().info;
    return v.name + ':' + v.version;
  };

   /**
   * DriverDataStore.query()
   * @param {object} queryOb some query object 
   * @param {object} queryParams additional query parameters (if available)
   * @param {boolean} keepIds whether or not to keep driver specifc ids in the results
   * @return {object} results from selected handler
   */
  self.query = function (queryOb,queryParams,keepIds) {
  
    return parentHandler.readGuts ( 'query' , function() {
      return queryGuts_(queryOb,queryParams,keepIds);
    });
  };

function queryGuts_(queryOb,queryParams,keepIds) {

  var result,driverIds, handleKeys;
  handleCode = enums.CODE.OK, handleError='',qp=null;
  complex_ = false;
  if (queryParams) {
    result= self.sortOutParams(queryOb,queryParams);
    if (result.handleCode !== enums.CODE.OK) return result;
    qp = result.data;
  }
  
  result = handle.runQuery(queryOb,qp);

  
  if (result.handleCode !== enums.CODE.OK) {
    return parentHandler.makeResults (result.handleCode,result.handleError);
  }
  
  // apply anyfilters
  var pr = parentHandler.processFilters (queryOb, result.data); 
  handleCode =pr.handleCode;
  handleError = pr.handleError;
  if (handleCode === enums.CODE.OK) {
    result.data = pr.data;
    // fix up the result
    if (keepIds) {
      var d = handle.getIds(result);
      driverIds = pr.handleKeys.map(function(k) {
        return d[k];
      });
      handleKeys= driverIds;
    }
  }
  
  // next fix up parameters datastore couldnt handle
  
  if (complex_ ) {
    var npr = parentHandler.processParams( queryParams,result.data);
    if (handleCode === enums.CODE.OK) {
      result.data = npr.data;
      // fix up the result
      if (keepIds) {
        driverIds = npr.handleKeys.map(function(k) {
          return driverIds[k];
        });
        handleKeys= driverIds;
      }
    }
  }
  return parentHandler.makeResults (handleCode,handleError,result.data,keepIds ? driverIds :null,keepIds ? handleKeys:null);
}

  /**
   * DriverDataStore.removeByIds()
   * @memberof DriverDataStore
   * @param {Array.string} ids list of handleKey ids to remove
   * @return {object} results from selected handler
   */
  self.removeByIds = function (ids) {

    var result = {};
    
    try {
    Logger.log('removing by ids');
    Logger.log(ids);
      result = handle.remove (ids);
    Logger.log(result);
    }
    catch(err) {
      result.handleError = err;
      result.handleCode =  enums.CODE.DRIVER;
    }
    
    return parentHandler.makeResults (result.handleCode,result.handleError,result);
  };
  

   /**
   * DriverDataStore.remove()
   * @param {object} queryOb some query object 
   * @param {object} queryParams additional query parameters (if available)
   * @return {object} results from selected handler
   */
  self.remove = function (queryOb,queryParams) {
    return parentHandler.writeGuts ( 'remove' , function() {

      try { 
        var result;
        try {
          // start with a query
          result = queryGuts_ (queryOb, queryParams , true);
          if (result.handleCode === enums.CODE.OK) {
            result = self.removeByIds (result.handleKeys);
          }

        }
        catch(err) {
          result = {
            handleError: err,
            handleCode:  enums.CODE.DRIVER
          };
        }
        return parentHandler.makeResults (result.handleCode,result.handleError,result);
      }
      catch (err) {
        return parentHandler.makeResults(enums.CODE.LOCK,err);
      }
    });
  };

  /**
   * DriverDataStore.save()
   * @param {Array.object} obs array of objects to write
   * @return {object} results from selected handler
   */
  self.save = function (obs) {
    return parentHandler.writeGuts ( 'save' , function() {
      var result = null;
      handleError='', handleCode=enums.CODE.OK;
      
      if(handleCode === enums.CODE.OK) {
        try {
          result = handle.insert(obs);
          if (result.handleCode !== enums.CODE.OK) {
            handleError = result.handleError;
            handleCode = result.handleCode;
          }
        }
        catch(err) {
          handleError = err ;
          handleCode =  enums.CODE.DRIVER;
        }
      
      }
      return parentHandler.makeResults (handleCode,handleError,result);
    });
  };
 

  /**
   * DriverDataStore.count()
   * @param {object} queryOb some query object 
   * @param {object} queryParams additional query parameters (if available)
   * @return {object} results from selected handler
   */
  self.count = function (queryOb,queryParams) {
    return parentHandler.readGuts ( 'count' , function() {
      var c=0, result= self.query(queryOb,queryParams);
      if(result.handleCode >= 0) { 
        c = result.data.length;
      }
      return parentHandler.makeResults (handleCode,handleError,[{count:c}]);
    });
  };
  
  /**
   * Driver.get()
   * @param {string} key the unique return in handleKeys for this object
   * @param {boolean} keepIds whether or not to keep driver specifc ids in the results
   * @return {object} results from selected handler
   */

  self.get = function (key,keepIds) {
    return parentHandler.readGuts ( 'get' , function() {
      var result =null;
      handleError='', handleCode=enums.CODE.OK;
      var driverIds, handleKeys;
  
      try {
        result = handle.lookup(key);
        if (result.handleCode !== enums.CODE.OK) {
          handleError = result.handleError;
          handleCode = result.handleCode;
        }
      }
      catch(err) {
        handleError = err ;
        handleCode =  enums.CODE.DRIVER;
      }
      
      // fix up the result
      if (keepIds) {
          driverIds= handle.getIds(result);
          handleKeys= driverIds;
      }
      return parentHandler.makeResults (handleCode,handleError,result.data,keepIds ? driverIds :null,keepIds ? handleKeys:null);
    }); 
  };
  
  /**
   * Driver.update()
   * @param {string} key the unique return in handleKeys for this object
   * @param {object} ob what to update it to
   * @return {object} results from selected handler
   */
  self.update = function (key,ob) {
    return parentHandler.writeGuts ( 'update' , function() {

      try {
        var result;
        try {
          // do the update
          result = handle.update(key,ob);
        }
        catch(err) {
          result = {
            handleError: err,
            handleCode:  enums.CODE.DRIVER
          };
        }
        return parentHandler.makeResults (result.handleCode,result.handleError,result);
      }
      catch (err) {
        return parentHandler.makeResults(enums.CODE.LOCK,err);
      }
    });
  };
  
  /**
   * sort out query params
   * @param {object} queryParams parameters
   * @return 
   */
   self.sortOutParams = function (queryOb,queryParams) {
     
     var pOb = null;
     if (queryParams) {
     
       // we cant rely on datastore to do limits and skips if there are complex queries with no index
      complex_ = queryOb && Object.keys(queryOb).some (function(k) {
        return (parentHandler.isObject (queryOb[k]) );
      });
      
       var result = parentHandler.getQueryParams(handle.flatten(queryParams));
       if(result.handleCode === enums.CODE.OK) {
          pOb = result.data.reduce (function (p,c) {
            if (c.param === 'sort') {
                p.order = [{property:{name:c.sortKey},direction: c.sortDescending ?  'DESCENDING' : 'ASCENDING' }];
            }
            else if (!complex_ && c.param === 'limit' ) {
              p.limit = c.limit;
            }
            else if (!complex_ && c.param === 'skip') {
              p.offset = c.skip;
            }
            return p; }, {});
            
          return parentHandler.makeResults (enums.CODE.OK,'',pOb);
       }
       else {
         return result;
       }
     }
     return (enums.CODE.OK);  
   }

  return self;
  
}
/** 
 * interface for data store - takes care of accessing datastore
 * @param {DataHandler} parentHandler the abstract handker
 * @param {string} accessToken - the oauth2 access token
 * @param {string} dataStoreName  - the name of this data store (project id incloud console)
 * @param {string} kind - somewhat like the table name
 * @return {DatastoreWorker} self
 * @class DatastoreWorker
 */
 
function DatastoreWorker(parentHandler, accessToken, dataStoreName, kind) {
  
    var name_ = dataStoreName;
    var self_ = this;
    var kind_ = kind;
    var accessToken_ = accessToken;
    var parentHandler_ = parentHandler;
    var enums = parentHandler.getEnums();  
    var SAVECHUNK = 500;
    
    self_.getParentHandler = function () {
      return parentHandler_;
    }
    /**
     * set a new access token
     * @param {string} accessToken - the oauth2 access token
     * @return {DatastoreWorker} self
     */
    self_.setAccessToken = function (token) {
      accessToken_ = token;
      return self_;
    };
    /**
     * flatten an array of objects/a single object
     * @param {Array.Object|Object} obs an array of/single unflattened objects
     * @param {boolean} optConstraints whether there might be constraints to preserve
     * @return {Array.Object|Object} an array of/single flattened objects
     */
    self_.flatten = function (obs,optConstraints) {
     
      return self_.getParentHandler().flatten(obs,optConstraints);

    };
    
    /**
     * unflatten an array of objects
     * @param {Array.Object} obs an array of flattened objects
     * @return {Array.Object} and array of unflattened objects
     */
    self_.unflatten = function (obs) {
      // unflatten the query
      if (!obs) return null;
      return obs.map(function(d) {
        return new cFlatten.Flattener().unFlatten(d);
      });

    };
    
    /**
     * generate a proper object from a datastore entity query response
     * @param {Array.object} entities - the datastore query response
     * @return {Array.object} an array of proper objects
     */
    self_.reconstructProperties = function (entities) {
      var root;
      if(entities.batch) {
        root = entities.batch.entityResults;
      }
      else if (entities.found) {
        root = entities.found;
      }
      else if (entities.entityResults) {
        root = entities.entityResults;
      }
      else {
        throw 'cant make anything of ' + JSON.stringify(entities);
      }

       return root.map( function(d) { 
         return new DataStoreEntity (d.entity.key.path[0].kind).injectProperties(d.entity.properties).reconstructProperties();
       });     
    };
    
    /**
     * insert an array of objects
     * @param {Array.object} obs - an array of objects to insert
     * @return {object} the datastore result object and success codes
     */
    self_.insert = function (obs) {
      var result = {handleCode:enums.CODE.OK,handleError:''};
      
      if (!Array.isArray (obs)) obs = [obs];
      var entities = obs.map(function(d){
        return new DataStoreEntity (kind_).setProperties (self_.flatten(d));
      });

      var options = getOptions_(), done =0;
      while (done < entities.length &amp;&amp; result.handleCode === enums.CODE.OK) {
        var chunk = entities.slice(done, Math.min(entities.length,done+SAVECHUNK));
        options.payload=  JSON.stringify({
          mutation: {insertAutoId: chunk.map ( function (d) { return d.objectify()})},
          mode: "NON_TRANSACTIONAL"
        });
        result = execute_ (self_.getEndpoint("commit"), options);
        done += chunk.length;
      }
      return result;
    };
    
    /**
     * delete an array of objects
     * @param {Array.string} obs - an array of ids to delete
     * @return {object} the datastore result object and success codes
     */
    self_.remove = function (ids) {
      var result = {handleCode:enums.CODE.OK,handleError:''};
      var options = getOptions_(),done =0;
      
      while (done < ids.length &amp;&amp; result.handleCode === enums.CODE.OK) {
        var t = new DataStoreEntity (kind_);
        var chunk = ids.slice(done, Math.min(ids.length,done+SAVECHUNK));
        options.payload =  JSON.stringify({
          mutation: {"delete": t.removeify(chunk)},
          mode: "NON_TRANSACTIONAL"
        });
        result= execute_ (self_.getEndpoint("commit"), options);
        done += chunk.length;
      }
      return result;

    };
    
    /**
     * update an array of objects
     * @param {Array.string} obs - an array of ids to update
     * @param {Array.object} obs - an array of objects to update it to
     * @return {object} the datastore result object and success codes
     */
    self_.update = function (ids,obs) {
      
      if (!Array.isArray (obs)) obs = [obs];
      if (!Array.isArray (ids)) ids = [ids];
      if(ids.length !== obs.length) {
       return {
         handleCode: enums.CODE.KEYS_AND_OBJECTS,
         handleError: 'objects- ' + obs.length + ' keys- ' + ids.length,
         result:null };
      }
      else {
        var entities = obs.map(function(d){
          return new DataStoreEntity (kind_).setProperties (self_.flatten(d));
        });

        var options = getOptions_();
  
        options.payload=  JSON.stringify({
          mutation: {update: entities.map ( function (d,i) { return d.updateify(ids[i])})},
          mode: "NON_TRANSACTIONAL"
        });
        
        var result= execute_ (self_.getEndpoint("commit"), options);
        if (result.handleCode >=0) {
          result.data = self_.unflatten (result.mutationResult);
        }
        return result;
      }
    };

    self_.getConstraintName = function (constraint) {
        return constraint ? 
          Object.keys(enums.CONSTRAINTS).reduce (function(p,c) { 
            return enums.CONSTRAINTS === constraint ? enums.DATASTORE_CONSTRAINTS : p;
          },'') 
          : null;
    }

    /**
     * run a query
     * @param {object} optOb - a nosql query
     * @param {optParams} optParams - sorted out parameters
     * @return {object} the datastore result object and success codes
     */
    self_.runQuery = function (optOb,optParams) {
      var options = getOptions_();
      var de = new DataStoreEntity (kind_,self_);

      // create query
      var qo = self_.flatten(optOb,true);
      var queryOb = de.querify(qo,optParams);
      options.payload = JSON.stringify(queryOb);
      
      var result= execute_ (self_.getEndpoint("runQuery"), options);

      if (result.handleCode >=0) {
        result.data = self_.unflatten (self_.reconstructProperties(result.result));
      }
      return result;
    };
    
    /**
     * lookup an object by id
     * @param {string} id - an id to lookup
     * @return {object} the datastore result object and success codes
     */
    self_.lookup = function (id) {
      var options = getOptions_();
      options.payload = JSON.stringify(new DataStoreEntity (kind_).lookupify(id) );
      
      var result= execute_ (self_.getEndpoint("lookup"), options);
      if (result.handleCode >=0) {
        result.data = self_.unflatten (self_.reconstructProperties(result.result));
      }
      return result;
    };
    
    /**
     * get the api url
     * @param {string} method - the method
     * @return {string} the url
     */
    self_.getEndpoint = function (method) {
      return 'https://www.googleapis.com/datastore/v1beta2/datasets/' + name_ + "/" + method; 
    };

    /**
     * get an array of ids
     * @param {object} result the datastore result object and success codes
     * @return {Array.string} the array of IDS
     */
    self_.getIds = function (result) {

        if (result.result.mutationResult ) { 
          return result.result.mutationResult.insertAutoIdKeys.map(function(d) { return d.path[0].id; }).filter(function(d) { return d });
        }
        else if (result.result.batch) {
          return result.result.batch.entityResults.map(function(d) { return d.entity.key.path[0].id; }).filter(function(d) { return d });
        }
        else if (result.result.found) {
          return result.result.found.map(function(d) { return d.entity.key.path[0].id; }).filter(function(d) { return d });
        }

    };
    
    /**
     * get basic http options
     * @return {object} the options
     */
    function getOptions_ () {
       return {
        method: "POST",
        contentType : "application/json" ,
        muteHttpExceptions : true,
        headers: {
          authorization: "Bearer " + accessToken_
        }
      };
    }
    
    /**
     * exponetial backoff get
     */
    function fetch_ (url,options) {
      return parentHandler_.rateLimitExpBackoff ( function () { 
              return UrlFetchApp.fetch(url,options); }) ;
    } 
    
    /** 
     * execute a fetch
     */
    function execute_ (url,options) {
      var result=null, error = '', code = enums.CODE.OK;
      
      return parentHandler.rateLimitExpBackoff ( function () {
        try {
          var response = fetch_(url, options);
          result = JSON.parse(response.getContentText());
          if (response.getResponseCode() !== 200) {
            code = enums.CODE.HTTP;
            error = 'status'+ response.getResponseCode();
          }
        }
        catch (err) {
          code= enums.CODE.DRIVER;
          error = err;
        }
          
        return {handleError:error, handleCode:code, result:result};
      });
      
     }
     
     return self_;
}
/** 
 * entity for data store - takes care of organizing properties and keys
 * @param {string} kind - somewhat like the table name
 * @param {DataStoreWorker} worker to get handler from
 * @return {DataStoreEntity} self
 * @class DataStoreEntity
 */
 
function DataStoreEntity (kind,worker) {
  var self_ = this;
  var kind_ = kind;
  var worker_ = worker;
  var properties_ = {};

  
  /*
   * clear out current properties and optionall set some more
   * @param {object} optOb the object to set if required
   * @return {DataStoreEntity} self
   */
  self_.resetProperties = function  (optOb) {
    properties_ = {};
    if (optOb) {
      self_.setProperties(optOb);
    }
    return self_;
  };
  
  /*
   * add the object ob to the datastore properties
   * @param {object} optOb the object to set
   * @return {DataStoreEntity} self
   */
  self_.setProperties = function  (optOb) {
    ob = optOb || {};
    Object.keys(ob).forEach (function(k) {
      properties_[k] = {};
      properties_[k][getType_(ob[k])] = ob[k];
    });
    return self_;
  };
  

  /*
   * get a single property value by property name
   * @param {string} propertyName the property name
   * @return {*} the value of the property
   */
  self_.getProperty = function (propertyName) {
    var p = properties_[propertyName] ;
    // just takes the first data type
    return p ? convertType(p,Object.keys(p)[0]) : null; 
    
    // strangely, it returns integer type as a string
    function convertType (ob,type) {
      if (type === "integerValue") {
        return parseInt( p[type],10);
      }
      else {
        return  p[type];
      }
    }
    
    
  };
  
  /*
   * get a single property type by property name
   * @param {string} propertyName the property name
   * @return {string} the datastore type of the property
   */
  self_.getPropertyType = function (k) {
    var p = properties_[k] ;
    return p ? Object.keys(p)[0] : '';  
  };
  
  /*
   * get the datastore properties 
   * @return {Array.object} the properties
   */
  self_.getProperties = function () {
    return properties_;
  };
  
  /*
   * introduce new datastore properties
   * @return {DatastoreEntity} self
   */
  self_.injectProperties = function(p) {
    properties_ = p || {};
    return self_;
  };
  
  /*
   * recsontruct the datastore properties into a single object
   * @return {object} the properties
   */
  self_.reconstructProperties = function () {
    return Object.keys(properties_).reduce ( function (p,c) {
      p = self_.getProperty(c);
      return p;
    },{});
  };

  /*
   * construct the key part of the object for looking up a single id
   * @param {string} id the id
   * @return {object} the keys
   */
  self_.lookupify = function (id) {
    return {keys: [ {path: [ { kind: kind_, id: id }] } ]};
  };
  
  /*
   * make a stringifiable representation of an object ready for writing to the datastore
   * @return {object} a constructed entity
   */
  self_.objectify = function () {
    return {key: {path: [ { kind: kind_ }] },properties:properties_};
  };

  /*
   * make a stringifiable representation of an object ready for updating
   * @param {string} id the id
   * @return {object} a constructed entity
   */
  self_.updateify = function (id) {
    return {key: {path: [ { kind: kind_, id:id }] },properties:properties_};
  };
  
  /*
   * make a stringifiable representation of an object ready for deleting
   * @param {Array.string} ids array of ids to remove
   * @return {object} a constructed entity
   */
  self_.removeify = function (ids) {
    if (!Array.isArray(ids)) ids = [ids];
    return ids.map(function(d) { return {path: [{ "kind": kind_, "id": d }]}; });
  };
  
  /*
   * make a stringifiable representation of an object ready for querying
   * @param {Object} optQuery any sorting/skipping type params
   * @param {Object} optParams any sroting/skipping type params
   * @return {string} a datastore type name
   */     
  self_.querify = function (optQuery, optParams) {

    var enums = worker_.getParentHandler().getEnums();
    // the basic rule is
    // all queries that are = can be combined
    // you can only have multiple constraints (eg > &amp; <) if the property is the same one
    var q = {query:{ kinds: [{name: kind_}]}};
    
    if (optParams) {
      Object.keys(optParams).forEach (function(k){
        q.query[k] = optParams[k];
      });
    }
    
    if (optQuery) {

      var ks = '';
      
      // do the non-constraints first
      var fs = Object.keys(optQuery).reduce (function(p,c) {
        if (!optQuery.hasOwnProperty (enums.SETTINGS.CONSTRAINT)) {
          p.push(patchOb_ (c,optQuery , enums.DATASTORE_CONSTRAINTS.EQ));
          ks = c;
        }
        return p;
      },[]);

      var fs = Object.keys(optQuery).reduce (function(p,c) {
        if (isObject_ (optQuery) ){
          if (optQuery.hasOwnProperty (enums.SETTINGS.CONSTRAINT)) {
            if (!ks || ks === c) {
              optQuery[enums.SETTINGS.CONSTRAINT].forEach(function(d) {
                var operator = worker_.getConstraintName(d.constraint);
                if (operator) {
                  p.push(patchOb_ (c ,d.value, operator));          
                }
              });
              ks = c;
            }
          }
          else {
            throw 'unexpected unflattened property ' + JSON.stringify(optQuery);
          }
        }

        return p;
      },fs);
      
     if (fs &amp;&amp; fs.length > 0) {
       q.query.filter =  {
         compositeFilter:{
           operator: "AND",
           filters: fs,
         }
       };
     }
     
   }
   
   function patchOb_(name, v,oper) {
     var o =  { propertyFilter: 
       { property: { name: name },
         operator: oper,
         value:{}
       }
     };
     o.propertyFilter.value[getType_ (v)] = v;
     return o;
   }

    return q;
  };
  
  function isObject_ (obj) {
    return obj === Object(obj);
  }
  /*
   * return a datastore type deduced from the value
   * @param {*} a value
   * @return {string} a datastore type name
   */ 
  function getType_ (value) {
    
    var t = typeof value;
    if (t === "string") {
      return "stringValue"
    }
    
    else if (t==="number") {
    
      if (!isNaN(value) &amp;&amp; parseInt(Number(value),10) === value) {
        return "integerValue";
      }
      else {
        return "doubleValue";
      }
    }
    
    else if (t==="boolean") {
    
      return "booleanValue"
    }
    
    else if (t==="undefined") {
      return "stringValue"  
    }
    
    else if (value instanceof Date) {
      return "dateTimeValue"  
    }

    else {
      throw 'unable to determine type of ' + value;
    }

    
  }

  
  return self_;
}

See more like this in Database abstraction with google apps script and Datastore driver