diff --git a/datastore/concepts.js b/datastore/concepts.js
new file mode 100644
index 0000000000..b12588e40d
--- /dev/null
+++ b/datastore/concepts.js
@@ -0,0 +1,1316 @@
+// Copyright 2015, Google, Inc.
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+'use strict';
+
+var asyncUtil = require('async');
+var gcloud = require('gcloud');
+
+module.exports = {
+  Entity: Entity,
+  Index: Index,
+  Metadata: Metadata,
+  Query: Query,
+  Transaction: Transaction
+};
+
+// This mock is used in the documentation snippets.
+var datastore = {
+  delete: function() {},
+  get: function() {},
+  insert: function() {},
+  key: function() {},
+  update: function() {},
+  upsert: function() {},
+  runQuery: function() {},
+  save: function() {}
+};
+
+var keyFile = process.env.DATASTORE_KEYFILE ||
+              process.env.GOOGLE_APPLICATION_CREDENTIALS;
+
+function Entity(projectId) {
+  var options = {
+    projectId: projectId
+  };
+
+  if (keyFile) {
+    options.keyFilename = keyFile;
+  }
+  this.datastore = gcloud.datastore.dataset(options);
+
+  // To create the keys, we have to use this instance of Datastore.
+  datastore.key = this.datastore.key;
+
+  this.incompleteKey = this.getIncompleteKey();
+  this.namedKey = this.getNamedKey();
+  this.keyWithParent = this.getKeyWithParent();
+  this.keyWithMultiLevelParent = this.getKeyWithMultiLevelParent();
+}
+
+Entity.prototype.getIncompleteKey = function() {
+  // [START incomplete_key]
+  var taskKey = datastore.key('Task');
+  // [END incomplete_key]
+
+  return taskKey;
+};
+
+Entity.prototype.getNamedKey = function() {
+  // [START named_key]
+  var taskKey = datastore.key([
+    'Task',
+    'sampleTask'
+  ]);
+  // [END named_key]
+
+  return taskKey;
+};
+
+Entity.prototype.getKeyWithParent = function() {
+  // [START key_with_parent]
+  var taskKey = datastore.key([
+    'TaskList',
+    'default',
+    'Task',
+    'sampleTask'
+  ]);
+  // [END key_with_parent]
+
+  return taskKey;
+};
+
+Entity.prototype.getKeyWithMultiLevelParent = function() {
+  // [START key_with_multilevel_parent]
+  var taskKey = datastore.key([
+    'User',
+    'alice',
+    'TaskList',
+    'default',
+    'Task',
+    'sampleTask'
+  ]);
+  // [END key_with_multilevel_parent]
+
+  return taskKey;
+};
+
+Entity.prototype.getTask = function() {
+  // [START basic_entity]
+  var task = {
+    type: 'Personal',
+    done: false,
+    priority: 4,
+    description: 'Learn Cloud Datastore'
+  };
+  // [END basic_entity]
+
+  return task;
+};
+
+Entity.prototype.testIncompleteKey = function(callback) {
+  this.datastore.save({
+    key: this.incompleteKey,
+    data: {}
+  }, callback);
+};
+
+Entity.prototype.testNamedKey = function(callback) {
+  this.datastore.save({
+    key: this.namedKey,
+    data: {}
+  }, callback);
+};
+
+Entity.prototype.testKeyWithParent = function(callback) {
+  this.datastore.save({
+    key: this.keyWithParent,
+    data: {}
+  }, callback);
+};
+
+Entity.prototype.testKeyWithMultiLevelParent = function(callback) {
+  this.datastore.save({
+    key: this.keyWithMultiLevelParent,
+    data: {}
+  }, callback);
+};
+
+Entity.prototype.testEntityWithParent = function(callback) {
+  var taskKey = this.keyWithParent;
+
+  // [START entity_with_parent]
+  var task = {
+    key: taskKey,
+    data: {
+      type: 'Personal',
+      done: false,
+      priority: 4,
+      description: 'Learn Cloud Datastore'
+    }
+  };
+  // [END entity_with_parent]
+
+  this.datastore.save(task, callback);
+};
+
+Entity.prototype.testProperties = function(callback) {
+  // jshint camelcase:false
+  // [START properties]
+  var task = {
+    type: 'Personal',
+    created: new Date(),
+    done: false,
+    priority: 4,
+    percent_complete: 10.0,
+    description: 'Learn Cloud Datastore'
+  };
+  // [END properties]
+
+  this.datastore.save({
+    key: this.incompleteKey,
+    data: task
+  }, callback);
+};
+
+Entity.prototype.testArrayValue = function(callback) {
+  // [START array_value]
+  var task = {
+    tags: [
+      'fun',
+      'programming'
+    ],
+    collaborators: [
+      'alice',
+      'bob'
+    ]
+  };
+  // [END array_value]
+
+  this.datastore.save({
+    key: this.incompleteKey,
+    data: task
+  }, callback);
+};
+
+Entity.prototype.testBasicEntity = function(callback) {
+  this.datastore.save({
+    key: this.getIncompleteKey(),
+    data: this.getTask()
+  }, callback);
+};
+
+Entity.prototype.testInsert = function(callback) {
+  var taskKey = this.getIncompleteKey();
+  var task = this.getTask();
+
+  // [START insert]
+  datastore.insert({
+    key: taskKey,
+    data: task
+  }, function(err) {
+    if (!err) {
+      // Task inserted successfully.
+    }
+  });
+  // [END insert]
+
+  this.datastore.save({
+    method: 'insert_auto_id',
+    key: taskKey,
+    data: task
+  }, callback);
+};
+
+Entity.prototype.testLookup = function(callback) {
+  var self = this;
+  var taskKey = this.getIncompleteKey();
+
+  // jshint unused:false
+  // [START lookup]
+  datastore.get(taskKey, function(err, entity) {
+    if (!err) {
+      // Task found.
+
+      // entity.data = {
+      //   type: 'Personal',
+      //   done: false,
+      //   priority: 4,
+      //   description: 'Learn Cloud Datastore'
+      // };
+    }
+  });
+  // [END lookup]
+
+  this.datastore.save({
+    method: 'insert_auto_id',
+    key: taskKey,
+    data: {}
+  }, function(err) {
+    if (err) {
+      callback(err);
+      return;
+    }
+
+    self.datastore.get(taskKey, callback);
+  });
+};
+
+Entity.prototype.testUpdate = function(callback) {
+  var self = this;
+  var taskKey = this.getIncompleteKey();
+  var task = this.getTask();
+
+  // [START update]
+  datastore.update({
+    key: taskKey,
+    data: task
+  }, function(err) {
+    if (!err) {
+      // Task updated successfully.
+    }
+  });
+  // [END update]
+
+  this.datastore.save({
+    method: 'insert_auto_id',
+    key: taskKey,
+    data: {}
+  }, function(err) {
+    if (err) {
+      callback(err);
+      return;
+    }
+
+    self.datastore.update({
+      key: taskKey,
+      data: task
+    }, callback);
+  });
+};
+
+Entity.prototype.testDelete = function(callback) {
+  var self = this;
+  var taskKey = this.getIncompleteKey();
+
+  // [START delete]
+  datastore.delete(taskKey, function(err) {
+    if (!err) {
+      // Task deleted successfully.
+    }
+  });
+  // [END delete]
+
+  this.datastore.save({
+    method: 'insert_auto_id',
+    key: taskKey,
+    data: {}
+  }, function(err) {
+    if (err) {
+      callback(err);
+      return;
+    }
+
+    self.datastore.delete(taskKey, callback);
+  });
+};
+
+Entity.prototype.testBatchUpsert = function(callback) {
+  var taskKey1 = this.datastore.key(['Task', 1]);
+  var taskKey2 = this.datastore.key(['Task', 2]);
+
+  var task1 = {
+    type: 'Personal',
+    done: false,
+    priority: 4,
+    description: 'Learn Cloud Datastore'
+  };
+
+  var task2 = {
+    type: 'Work',
+    done: false,
+    priority: 8,
+    description: 'Integrate Cloud Datastore'
+  };
+
+  // [START batch_upsert]
+  datastore.upsert([
+    {
+      key: taskKey1,
+      data: task1
+    },
+    {
+      key: taskKey2,
+      data: task2
+    }
+  ], function(err) {
+    if (!err) {
+      // Tasks inserted successfully.
+    }
+  });
+  // [END batch_upsert]
+
+  this.datastore.upsert([
+    {
+      key: taskKey1,
+      data: task1
+    },
+    {
+      key: taskKey2,
+      data: task2
+    }
+  ], callback);
+};
+
+Entity.prototype.testBatchLookup = function(callback) {
+  var taskKey1 = this.datastore.key(['Task', 1]);
+  var taskKey2 = this.datastore.key(['Task', 2]);
+
+  // jshint unused:false
+  // [START batch_lookup]
+  datastore.get([
+    taskKey1,
+    taskKey2
+  ], function(err, tasks) {
+    if (!err) {
+      // Tasks retrieved successfully.
+    }
+  });
+  // [END batch_lookup]
+
+  this.datastore.get([
+    taskKey1,
+    taskKey2
+  ], callback);
+};
+
+Entity.prototype.testBatchDelete = function(callback) {
+  var taskKey1 = this.datastore.key(['Task', 1]);
+  var taskKey2 = this.datastore.key(['Task', 2]);
+
+  // [START batch_delete]
+  datastore.delete([
+    taskKey1,
+    taskKey2
+  ], function(err) {
+    if (!err) {
+      // Tasks deleted successfully.
+    }
+  });
+  // [END batch_delete]
+
+  this.datastore.delete([
+    taskKey1,
+    taskKey2
+  ], callback);
+};
+
+function Index(projectId) {
+  var options = {
+    projectId: projectId
+  };
+
+  if (keyFile) {
+    options.keyFilename = keyFile;
+  }
+  this.datastore = gcloud.datastore.dataset(options);
+}
+
+Index.prototype.testUnindexedPropertyQuery = function(callback) {
+  var datastore = this.datastore;
+
+  // [START unindexed_property_query]
+  var query = datastore.createQuery('Task')
+    .filter('description', '=', 'A task description.');
+  // [END unindexed_property_query]
+
+  this.datastore.runQuery(query, callback);
+};
+
+Index.prototype.testExplodingProperties = function(callback) {
+  datastore.key = this.datastore.key;
+
+  // [START exploding_properties]
+  var task = {
+    method: 'insert_auto_id',
+    key: datastore.key('Task'),
+    data: {
+      tags: [
+        'fun',
+        'programming',
+        'learn'
+      ],
+      collaborators: [
+        'alice',
+        'bob',
+        'charlie'
+      ],
+      created: new Date()
+    }
+  };
+  // [END exploding_properties]
+
+  delete datastore.key;
+
+  this.datastore.save(task, callback);
+};
+
+function Metadata(projectId) {
+  var options = {
+    projectId: projectId
+  };
+
+  if (keyFile) {
+    options.keyFilename = keyFile;
+  }
+  this.datastore = gcloud.datastore.dataset(options);
+}
+
+Metadata.prototype.testNamespaceRunQuery = function(callback) {
+  var self = this;
+
+  datastore.createQuery = this.datastore.createQuery;
+  datastore.key = this.datastore.key;
+
+  var startNamespace = 'Animals';
+  var endNamespace = 'Zoos';
+
+  this.datastore.save([
+    {
+      key: datastore.key({
+        namespace: 'Animals',
+        path: ['Ant', 1]
+      }),
+      data: {}
+    }
+  ], function(err) {
+    if (err) {
+      callback(err);
+      return;
+    }
+
+    // jshint unused:false
+    // [START namespace_run_query]
+    var query = datastore.createQuery('__namespace__')
+      .select('__key__')
+      .filter('__key__', '>=', datastore.key(['__namespace__', startNamespace]))
+      .filter('__key__', '<', datastore.key(['__namespace__', endNamespace]));
+
+    datastore.runQuery(query, function(err, entities) {
+      if (err) {
+        // An error occurred while running the query.
+        return;
+      }
+
+      var namespaces = entities.map(function(entity) {
+        return entity.key.path.pop();
+      });
+    });
+    // [END namespace_run_query]
+
+    self.datastore.runQuery(query, callback);
+  });
+};
+
+Metadata.prototype.testKindRunQuery = function(callback) {
+  datastore.createQuery = this.datastore.createQuery;
+
+  // jshint unused:false
+  // [START kind_run_query]
+  var query = datastore.createQuery('__kind__')
+    .select('__key__');
+
+  datastore.runQuery(query, function(err, entities) {
+    if (err) {
+      // An error occurred while running the query.
+      return;
+    }
+
+    var kinds = entities.map(function(entity) {
+      return entity.key.path.pop();
+    });
+  });
+  // [END kind_run_query]
+
+  this.datastore.runQuery(query, callback);
+};
+
+Metadata.prototype.testPropertyRunQuery = function(callback) {
+  datastore.createQuery = this.datastore.createQuery;
+
+  // [START property_run_query]
+  var query = datastore.createQuery('__property__')
+    .select('__key__');
+
+  datastore.runQuery(query, function(err, entities) {
+    if (err) {
+      // An error occurred while running the query.
+      return;
+    }
+
+    var propertiesByKind = {};
+
+    entities.forEach(function(entity) {
+      var kind = entity.key.path[1];
+      var propertyName = entity.key.path[3];
+
+      propertiesByKind[kind] = propertiesByKind[kind] || [];
+      propertiesByKind[kind].push(propertyName);
+    });
+  });
+  // [END property_run_query]
+
+  this.datastore.runQuery(query, callback);
+};
+
+Metadata.prototype.testPropertyByKindRunQuery = function(callback) {
+  var datastore = this.datastore;
+
+  // jshint camelcase:false
+  // [START property_by_kind_run_query]
+  var ancestorKey = datastore.key(['__kind__', 'Task']);
+
+  var query = datastore.createQuery('__property__')
+    .hasAncestor(ancestorKey);
+
+  datastore.runQuery(query, function(err, entities) {
+    if (err) {
+      // An error occurred while running the query.
+      return;
+    }
+
+    var representationsByProperty = {};
+
+    entities.forEach(function(entity) {
+      var propertyName = entity.key.path.pop();
+      var propertyType = entity.data.property_representation;
+
+      representationsByProperty[propertyName] = propertyType;
+    });
+  });
+  // [END property_by_kind_run_query]
+
+  this.datastore.runQuery(query, callback);
+};
+
+function Query(projectId) {
+  var options = {
+    projectId: projectId
+  };
+
+  if (keyFile) {
+    options.keyFilename = keyFile;
+  }
+  this.datastore = gcloud.datastore.dataset(options);
+
+  this.basicQuery = this.getBasicQuery();
+  this.projectionQuery = this.getProjectionQuery();
+  this.ancestorQuery = this.getAncestorQuery();
+}
+
+Query.prototype.getBasicQuery = function() {
+  var datastore = this.datastore;
+
+  // [START basic_query]
+  var query = datastore.createQuery('Task')
+    .filter('done', '=', false)
+    .filter('priority', '>=', 4)
+    .order('priority', {
+      descending: true
+    });
+  // [END basic_query]
+
+  return query;
+};
+
+Query.prototype.getProjectionQuery = function() {
+  var datastore = this.datastore;
+
+  // [START projection_query]
+  var query = datastore.createQuery('Task')
+    .select(['priority', 'percent_complete']);
+  // [END projection_query]
+
+  return query;
+};
+
+Query.prototype.getAncestorQuery = function() {
+  var datastore = this.datastore;
+
+  // [START ancestor_query]
+  var ancestorKey = datastore.key(['TaskList', 'default']);
+
+  var query = datastore.createQuery('Task')
+    .hasAncestor(ancestorKey);
+  // [END ancestor_query]
+
+  return query;
+};
+
+Query.prototype.testRunQuery = function(callback) {
+  var query = this.basicQuery;
+
+  // jshint unused:false
+  // [START run_query]
+  datastore.runQuery(query, function(err, tasks) {
+    if (!err) {
+      // Task entities found.
+    }
+  });
+  // [END run_query]
+
+  this.datastore.runQuery(query, callback);
+};
+
+Query.prototype.testPropertyFilter = function(callback) {
+  var datastore = this.datastore;
+
+  // [START property_filter]
+  var query = datastore.createQuery('Task')
+    .filter('done', '=', false);
+  // [END property_filter]
+
+  this.datastore.runQuery(query, callback);
+};
+
+Query.prototype.testCompositeFilter = function(callback) {
+  var datastore = this.datastore;
+
+  // [START composite_filter]
+  var query = datastore.createQuery('Task')
+    .filter('done', '=', false)
+    .filter('priority', '=', 4);
+  // [END composite_filter]
+
+  this.datastore.runQuery(query, callback);
+};
+
+Query.prototype.testKeyFilter = function(callback) {
+  var datastore = this.datastore;
+
+  // [START key_filter]
+  var query = datastore.createQuery('Task')
+    .filter('__key__', '>', datastore.key(['Task', 'someTask']));
+  // [END key_filter]
+
+  this.datastore.runQuery(query, callback);
+};
+
+Query.prototype.testAscendingSort = function(callback) {
+  var datastore = this.datastore;
+
+  // [START ascending_sort]
+  var query = datastore.createQuery('Task')
+    .order('created');
+  // [END ascending_sort]
+
+  this.datastore.runQuery(query, callback);
+};
+
+Query.prototype.testDescendingSort = function(callback) {
+  var datastore = this.datastore;
+
+  // [START descending_sort]
+  var query = datastore.createQuery('Task')
+    .order('created', {
+      descending: true
+    });
+  // [END descending_sort]
+
+  this.datastore.runQuery(query, callback);
+};
+
+Query.prototype.testMultiSort = function(callback) {
+  var datastore = this.datastore;
+
+  // [START multi_sort]
+  var query = datastore.createQuery('Task')
+    .order('priority', {
+      descending: true
+    })
+    .order('created');
+  // [END multi_sort]
+
+  this.datastore.runQuery(query, callback);
+};
+
+Query.prototype.testKindlessQuery = function(callback) {
+  var datastore = this.datastore;
+  var lastSeenKey = this.datastore.key(['Task', Date.now()]);
+
+  // [START kindless_query]
+  var query = datastore.createQuery()
+    .filter('__key__', '>', lastSeenKey);
+  // [END kindless_query]
+
+  this.datastore.runQuery(query, callback);
+};
+
+Query.prototype.testRunQueryProjection = function(callback) {
+  var self = this;
+  var query = this.projectionQuery;
+
+  // Overwrite the mock to actually run the query.
+  datastore.runQuery = function(query, queryCallback) {
+    // Restore the mock.
+    datastore.runQuery = function() {};
+
+    self.datastore.runQuery(query, function(err) {
+      if (err) {
+        callback(err);
+        return;
+      }
+
+      queryCallback.apply(null, arguments);
+
+      if (priorities.length === 0 || percentCompletes.length === 0) {
+        callback(new Error('Projection lists did not build up.'));
+      } else {
+        callback();
+      }
+    });
+  };
+
+  // jshint unused:false, camelcase:false
+  // [START run_query_projection]
+  var priorities = [];
+  var percentCompletes = [];
+
+  datastore.runQuery(query, function(err, tasks) {
+    if (err) {
+      // An error occurred while running the query.
+      return;
+    }
+
+    tasks.forEach(function(task) {
+      priorities.push(task.data.priority);
+      percentCompletes.push(task.data.percent_complete);
+    });
+  });
+  // [END run_query_projection]
+};
+
+Query.prototype.testKeysOnlyQuery = function(callback) {
+  var datastore = this.datastore;
+
+  // [START keys_only_query]
+  var query = datastore.createQuery()
+    .select('__key__');
+  // [END keys_only_query]
+
+  this.datastore.runQuery(query, callback);
+};
+
+Query.prototype.testDistinctQuery = function(callback) {
+  var datastore = this.datastore;
+
+  // [START distinct_query]
+  var query = datastore.createQuery('Task')
+    .groupBy(['type', 'priority'])
+    .order('type')
+    .order('priority');
+  // [END distinct_query]
+
+  this.datastore.runQuery(query, callback);
+};
+
+Query.prototype.testDistinctOnQuery = function(callback) {
+  var datastore = this.datastore;
+
+  // [START distinct_on_query]
+  var query = datastore.createQuery('Task')
+    .groupBy('type')
+    .order('type')
+    .order('priority');
+  // [END distinct_on_query]
+
+  this.datastore.runQuery(query, callback);
+};
+
+Query.prototype.testArrayValueInequalityRange = function(callback) {
+  var datastore = this.datastore;
+
+  // [START array_value_inequality_range]
+  var query = datastore.createQuery('Task')
+    .filter('tag', '>', 'learn')
+    .filter('tag', '<', 'math');
+  // [END array_value_inequality_range]
+
+  this.datastore.runQuery(query, callback);
+};
+
+Query.prototype.testArrayValueEquality = function(callback) {
+  var datastore = this.datastore;
+
+  // [START array_value_equality]
+  var query = datastore.createQuery('Task')
+    .filter('tag', '=', 'fun')
+    .filter('tag', '=', 'programming');
+  // [END array_value_equality]
+
+  this.datastore.runQuery(query, callback);
+};
+
+Query.prototype.testInequalityRange = function(callback) {
+  var datastore = this.datastore;
+
+  // [START inequality_range]
+  var query = datastore.createQuery('Task')
+    .filter('created', '>', new Date('1990-01-01T00:00:00z'))
+    .filter('created', '<', new Date('2000-12-31T23:59:59z'));
+  // [END inequality_range]
+
+  this.datastore.runQuery(query, callback);
+};
+
+Query.prototype.testInequalityInvalid = function(callback) {
+  var datastore = this.datastore;
+
+  // [START inequality_invalid]
+  var query = datastore.createQuery('Task')
+    .filter('priority', '>', 3)
+    .filter('created', '>', new Date('1990-01-01T00:00:00z'));
+  // [END inequality_invalid]
+
+  this.datastore.runQuery(query, callback);
+};
+
+Query.prototype.testEqualAndInequalityRange = function(callback) {
+  var datastore = this.datastore;
+
+  // [START equal_and_inequality_range]
+  var query = datastore.createQuery('Task')
+    .filter('priority', '=', 4)
+    .filter('done', '=', false)
+    .filter('created', '>', new Date('1990-01-01T00:00:00z'))
+    .filter('created', '<', new Date('2000-12-31T23:59:59z'));
+  // [END equal_and_inequality_range]
+
+  this.datastore.runQuery(query, callback);
+};
+
+Query.prototype.testInequalitySort = function(callback) {
+  var datastore = this.datastore;
+
+  // [START inequality_sort]
+  var query = datastore.createQuery('Task')
+    .filter('priority', '>', 3)
+    .order('priority')
+    .order('created');
+  // [END inequality_sort]
+
+  this.datastore.runQuery(query, callback);
+};
+
+Query.prototype.testInequalitySortInvalidNotSame = function(callback) {
+  var datastore = this.datastore;
+
+  // [START inequality_sort_invalid_not_same]
+  var query = datastore.createQuery('Task')
+    .filter('priority', '>', 3)
+    .order('created');
+  // [END inequality_sort_invalid_not_same]
+
+  this.datastore.runQuery(query, callback);
+};
+
+Query.prototype.testInequalitySortInvalidNotFirst = function(callback) {
+  var datastore = this.datastore;
+
+  // [START inequality_sort_invalid_not_first]
+  var query = datastore.createQuery('Task')
+    .filter('priority', '>', 3)
+    .order('created')
+    .order('priority');
+  // [END inequality_sort_invalid_not_first]
+
+  this.datastore.runQuery(query, callback);
+};
+
+Query.prototype.testLimit = function(callback) {
+  var datastore = this.datastore;
+
+  // [START limit]
+  var query = datastore.createQuery('Task')
+    .limit(5);
+  // [END limit]
+
+  this.datastore.runQuery(query, callback);
+};
+
+Query.prototype.testCursorPaging = function(callback) {
+  var pageSize = 1;
+  var pageCursor = '';
+
+  datastore.createQuery = this.datastore.createQuery;
+
+  // [START cursor_paging]
+  // By default, gcloud-node will paginate through all of the results that match
+  // a query, push them into an array, then return them to your callback after
+  // they have all been retrieved. You must execute `.autoPaginate(false)` on
+  // your query to disable this behavior.
+  var query = datastore.createQuery('Task')
+    .autoPaginate(false)
+    .limit(pageSize)
+    .start(pageCursor);
+
+  datastore.runQuery(query, function(err, results, nextQuery) {
+    if (err) {
+      // An error occurred while running the query.
+      return;
+    }
+
+    var nextPageCursor;
+
+    if (nextQuery) {
+      // If there are more results to retrieve, the start cursor is
+      // automatically set on `nextQuery`. To get this value directly, access
+      // the `startVal` property.
+      nextPageCursor = nextQuery.startVal;
+    } else {
+      // No more results exist.
+    }
+  });
+  // [END cursor_paging]
+
+  delete datastore.createQuery;
+  this.datastore.runQuery(query, function(err, results, nextQuery) {
+    if (err) {
+      callback(err);
+      return;
+    }
+
+    if (!nextQuery || !nextQuery.startVal) {
+      callback(new Error('A nextQuery with a startVal is not present.'));
+    } else {
+      callback();
+    }
+  });
+};
+
+Query.prototype.testEventualConsistentQuery = function() {
+  // [START eventual_consistent_query]
+  // Read consistency cannot be specified in gcloud-node.
+  // [END eventual_consistent_query]
+};
+
+// [START transactional_update]
+function transferFunds(fromKey, toKey, amount, callback) {
+  var error;
+
+  datastore.runInTransaction(function(transaction, done) {
+    transaction.get([
+      fromKey,
+      toKey
+    ], function(err, accounts) {
+      if (err) {
+        // An error occurred while getting the values.
+        error = err;
+        transaction.rollback(done);
+        return;
+      }
+
+      accounts[0].data.balance -= amount;
+      accounts[1].data.balance += amount;
+
+      transaction.save(accounts);
+
+      done();
+    });
+  }, function(transactionError) {
+    if (transactionError || error) {
+      callback(transactionError || error);
+    } else {
+      // The transaction completed successfully.
+      callback();
+    }
+  });
+}
+// [END transactional_update]
+
+function Transaction(projectId) {
+  var options = {
+    projectId: projectId
+  };
+
+  if (keyFile) {
+    options.keyFilename = keyFile;
+  }
+  this.datastore = gcloud.datastore.dataset(options);
+
+  this.fromKey = this.datastore.key(['Bank', 1, 'Account', 1]);
+  this.toKey = this.datastore.key(['Bank', 1, 'Account', 2]);
+
+  this.originalBalance = 100;
+  this.amountToTransfer = 10;
+}
+
+Transaction.prototype.restoreBankAccountBalances = function(config, callback) {
+  var saveArray = config.keys.map(function(key) {
+    return {
+      key: key,
+      data: {
+        balance: config.balance
+      }
+    };
+  });
+
+  this.datastore.save(saveArray, callback);
+};
+
+Transaction.prototype.testTransactionalUpdate = function(callback) {
+  var self = this;
+
+  var fromKey = this.fromKey;
+  var toKey = this.toKey;
+  var originalBalance = this.originalBalance;
+  var amountToTransfer = this.amountToTransfer;
+
+  this.restoreBankAccountBalances({
+    keys: [fromKey, toKey],
+    balance: originalBalance
+  }, function(err) {
+    if (err) {
+      callback(err);
+      return;
+    }
+
+    // Overwrite so the real Datastore instance is used in `transferFunds`.
+    var datastoreMock = datastore;
+    datastore = self.datastore;
+
+    transferFunds(fromKey, toKey, amountToTransfer, function(err) {
+      // Restore `datastore` to the mock API.
+      datastore = datastoreMock;
+
+      if (err) {
+        callback(err);
+        return;
+      }
+
+      self.datastore.get([
+        fromKey,
+        toKey
+      ], function(err, accounts) {
+        if (err) {
+          callback(err);
+          return;
+        }
+
+        var transactionWasSuccessful =
+          accounts[0].data.balance === originalBalance - amountToTransfer &&
+          accounts[1].data.balance === originalBalance + amountToTransfer;
+
+        if (!transactionWasSuccessful) {
+          callback(new Error('Accounts were not updated successfully.'));
+        } else {
+          callback();
+        }
+      });
+    });
+  });
+};
+
+Transaction.prototype.testTransactionalRetry = function(callback) {
+  // Overwrite so the real Datastore instance is used in `transferFunds`.
+  var datastoreMock = datastore;
+  datastore = this.datastore;
+
+  var originalCallback = callback;
+  callback = function() {
+    // Restore `datastore` to the mock API.
+    datastore = datastoreMock;
+    originalCallback.apply(null, arguments);
+  };
+
+  var fromKey = this.fromKey;
+  var toKey = this.toKey;
+
+  this.restoreBankAccountBalances({
+    keys: [fromKey, toKey],
+    balance: this.originalBalance
+  }, function(err) {
+    if (err) {
+      callback(err);
+      return;
+    }
+
+    // [START transactional_retry]
+    var async = require('async');
+
+    function attemptTransfer(callback) {
+      transferFunds(fromKey, toKey, 10, callback);
+    }
+
+    async.retry(5, attemptTransfer, callback);
+    // [END transactional_retry]
+  });
+};
+
+Transaction.prototype.testTransactionalGetOrCreate = function(callback) {
+  var taskKey = this.datastore.key(['Task', Date.now()]);
+
+  // Overwrite so the real Datastore instance is used in `transferFunds`.
+  var datastoreMock = datastore;
+  datastore = this.datastore;
+
+  var originalCallback = callback;
+  callback = function() {
+    // Restore `datastore` to the mock API.
+    datastore = datastoreMock;
+    originalCallback.apply(null, arguments);
+  };
+
+  // [START transactional_get_or_create]
+  function getOrCreate(taskKey, taskData, callback) {
+    var error;
+
+    var taskEntity = {
+      key: taskKey,
+      data: taskData
+    };
+
+    datastore.runInTransaction(function(transaction, done) {
+      transaction.get(taskKey, function(err, task) {
+        if (err) {
+          // An error occurred while getting the values.
+          error = err;
+          transaction.rollback(done);
+          return;
+        }
+
+        if (task) {
+          // The task entity already exists.
+          transaction.rollback(done);
+        } else {
+          // Create the task entity.
+          transaction.save(taskEntity);
+          done();
+        }
+      });
+    }, function(transactionError) {
+      if (transactionError || error) {
+        callback(transactionError || error);
+      } else {
+        // The transaction completed successfully.
+        callback(null, taskEntity);
+      }
+    });
+  }
+  // [END transactional_get_or_create]
+
+  asyncUtil.series([
+    // Create:
+    testWithCreateBehavior,
+    // Then try to get it:
+    testWithGetBehavior
+  ], callback);
+
+  function testWithCreateBehavior(callback) {
+    getOrCreate(taskKey, {}, function(err, task) {
+      if (err) {
+        callback(err);
+        return;
+      }
+
+      if (!task) {
+        callback(new Error('Entity was not created successfully.'));
+      } else {
+        callback();
+      }
+    });
+  }
+
+  function testWithGetBehavior(callback) {
+    getOrCreate(taskKey, {}, function(err, task) {
+      if (err) {
+        callback(err);
+        return;
+      }
+
+      if (!task) {
+        callback(new Error('Entity was not retrieved successfully.'));
+      } else {
+        callback();
+      }
+    });
+  }
+};
+
+Transaction.prototype.testSingleEntityGroupReadOnly = function(callback) {
+  // Overwrite so the real Datastore instance is used in `transferFunds`.
+  var datastoreMock = datastore;
+  datastore = this.datastore;
+
+  var originalCallback = callback;
+  callback = function() {
+    // Restore `datastore` to the mock API.
+    datastore = datastoreMock;
+    originalCallback.apply(null, arguments);
+  };
+
+  // [START transactional_single_entity_group_read_only]
+  function getTaskListEntities(callback) {
+    var error;
+    var taskListEntities;
+
+    datastore.runInTransaction(function(transaction, done) {
+      var taskListKey = datastore.key(['TaskList', 'default']);
+
+      datastore.get(taskListKey, function(err) {
+        if (err) {
+          error = err;
+          transaction.rollback(done);
+          return;
+        }
+
+        var query = datastore.createQuery('Task')
+          .hasAncestor(taskListKey);
+
+        datastore.runQuery(query, function(err, entities) {
+          if (err) {
+            // An error occurred while running the query.
+            error = err;
+            transaction.rollback(done);
+            return;
+          }
+
+          taskListEntities = entities;
+          done();
+        });
+      });
+
+    }, function(transactionError) {
+      if (transactionError || error) {
+        callback(transactionError || error);
+      } else {
+        // The transaction completed successfully.
+        callback(null, taskListEntities);
+      }
+    });
+  }
+  // [END transactional_single_entity_group_read_only]
+
+  getTaskListEntities(function(err, entities) {
+    if (err) {
+      callback(err);
+      return;
+    }
+
+    if (!entities) {
+      callback(new Error('Entities were not retrieved successfully.'));
+    } else {
+      callback();
+    }
+  });
+};
diff --git a/datastore/package.json b/datastore/package.json
index 8a0a7958a7..2124e5cd5a 100644
--- a/datastore/package.json
+++ b/datastore/package.json
@@ -14,6 +14,6 @@
   },
   "dependencies": {
     "async": "^1.5.2",
-    "gcloud": "^0.27.0"
+    "gcloud": "^0.28.0"
   }
 }
diff --git a/test/datastore/entity.test.js b/test/datastore/entity.test.js
new file mode 100644
index 0000000000..0e0b784e13
--- /dev/null
+++ b/test/datastore/entity.test.js
@@ -0,0 +1,123 @@
+// Copyright 2015, Google, Inc.
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+'use strict';
+
+var testUtil = require('./util.js');
+
+var Entity = require('../../datastore/concepts').Entity;
+var entity;
+
+describe('datastore/concepts/entity', function () {
+  before(function() {
+    var projectId = process.env.GCLOUD_PROJECT || 'nodejs-docs-samples';
+    entity = new Entity(projectId);
+  });
+
+  after(function(done) {
+    var datastore = entity.datastore;
+    var query = datastore.createQuery('Task');
+
+    testUtil.deleteEntities(datastore, query, done);
+  });
+
+  describe('incomplete key', function() {
+    it('saves with an incomplete key', function(done) {
+      entity.testIncompleteKey(done);
+    });
+  });
+
+  describe('testNamedKey', function() {
+    it('saves with a named key', function(done) {
+      entity.testNamedKey(done);
+    });
+  });
+
+  describe('testKeyWithParent', function() {
+    it('saves a key with a parent', function(done) {
+      entity.testKeyWithParent(done);
+    });
+  });
+
+  describe('testKeyWithMultiLevelParent', function() {
+    it('saves a key with multiple parents', function(done) {
+      entity.testKeyWithMultiLevelParent(done);
+    });
+  });
+
+  describe('testEntityWithParent', function() {
+    it('saves an entity with a parent', function(done) {
+      entity.testEntityWithParent(done);
+    });
+  });
+
+  describe('testProperties', function() {
+    it('saves an entity with properties', function(done) {
+      entity.testProperties(done);
+    });
+  });
+
+  describe('testArrayValue', function() {
+    it('saves an entity with arrays', function(done) {
+      entity.testArrayValue(done);
+    });
+  });
+
+  describe('testBasicEntity', function() {
+    it('saves a basic entity', function(done) {
+      entity.testBasicEntity(done);
+    });
+  });
+
+  describe('testInsert', function() {
+    it('saves with an insert', function(done) {
+      entity.testInsert(done);
+    });
+  });
+
+  describe('testLookup', function() {
+    it('performs a lookup', function(done) {
+      entity.testLookup(done);
+    });
+  });
+
+  describe('testUpdate', function() {
+    it('saves with an update', function(done) {
+      entity.testUpdate(done);
+    });
+  });
+
+  describe('testDelete', function() {
+    it('deletes an entity', function(done) {
+      entity.testDelete(done);
+    });
+  });
+
+  describe('testBatchUpsert', function() {
+    it('performs a batch upsert', function(done) {
+      entity.testBatchUpsert(done);
+    });
+  });
+
+  describe('testBatchLookup', function() {
+    it('performs a batch lookup', function(done) {
+      entity.testBatchLookup(done);
+    });
+  });
+
+  describe('testBatchDelete', function() {
+    it('performs a batch delete', function(done) {
+      entity.testBatchDelete(done);
+    });
+  });
+});
diff --git a/test/datastore/index.yaml b/test/datastore/index.yaml
new file mode 100644
index 0000000000..e4c911fa5e
--- /dev/null
+++ b/test/datastore/index.yaml
@@ -0,0 +1,37 @@
+indexes:
+- kind: Task
+  properties:
+  - name: done
+  - name: priority
+    direction: desc
+- kind: Task
+  properties:
+  - name: priority
+  - name: percent_complete
+- kind: Task
+  properties:
+  - name: type
+  - name: priority
+- kind: Task
+  properties:
+  - name: priority
+  - name: created
+- kind: Task
+  properties:
+  - name: done
+  - name: created
+- kind: Task
+  properties:
+  - name: type
+  - name: priority
+    direction: desc
+- kind: Task
+  properties:
+  - name: type
+  - name: created
+- kind: Task
+  properties:
+  - name: priority
+    direction: desc
+  - name: created
+  
\ No newline at end of file
diff --git a/test/datastore/indexes.test.js b/test/datastore/indexes.test.js
new file mode 100644
index 0000000000..b1d8679551
--- /dev/null
+++ b/test/datastore/indexes.test.js
@@ -0,0 +1,47 @@
+// Copyright 2015, Google, Inc.
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+'use strict';
+
+var testUtil = require('./util.js');
+
+var Index = require('../../datastore/concepts').Index;
+var index;
+
+describe('datastore/concepts/indexes', function () {
+  before(function() {
+    var projectId = process.env.GCLOUD_PROJECT || 'nodejs-docs-samples';
+    index = new Index(projectId);
+  });
+
+  after(function(done) {
+    var datastore = index.datastore;
+    var query = datastore.createQuery('Task');
+
+    testUtil.deleteEntities(datastore, query, done);
+  });
+
+  describe('unindexed properties', function() {
+    it('performs a query with a filter on an unindexed property',
+      function(done) {
+        index.testUnindexedPropertyQuery(done);
+      }
+    );
+  });
+
+  describe('exploding properties', function() {
+    it('inserts arrays of data', function(done) {
+      index.testExplodingProperties(done);
+    });
+  });
+});
diff --git a/test/datastore/metadata.test.js b/test/datastore/metadata.test.js
new file mode 100644
index 0000000000..6d42faacf4
--- /dev/null
+++ b/test/datastore/metadata.test.js
@@ -0,0 +1,57 @@
+// Copyright 2015, Google, Inc.
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+'use strict';
+
+var testUtil = require('./util.js');
+
+var Metadata = require('../../datastore/concepts').Metadata;
+var metadata;
+
+describe('datastore/concepts/metadata', function () {
+  before(function() {
+    var projectId = process.env.GCLOUD_PROJECT || 'nodejs-docs-samples';
+    metadata = new Metadata(projectId);
+  });
+
+  after(function(done) {
+    var datastore = metadata.datastore;
+    var query = datastore.createQuery('Task');
+
+    testUtil.deleteEntities(datastore, query, done);
+  });
+
+  describe('namespace query', function() {
+    it('performs a namespace query', function(done) {
+      metadata.testNamespaceRunQuery(done);
+    });
+  });
+
+  describe('kinds query', function() {
+    it('performs a kind query', function(done) {
+      metadata.testKindRunQuery(done);
+    });
+  });
+
+  describe('property query', function() {
+    it('performs a property query', function(done) {
+      metadata.testPropertyRunQuery(done);
+    });
+  });
+
+  describe('property by kind query', function() {
+    it('performs a property by kind query', function(done) {
+      metadata.testPropertyByKindRunQuery(done);
+    });
+  });
+});
diff --git a/test/datastore/query.test.js b/test/datastore/query.test.js
new file mode 100644
index 0000000000..bf2b1aa6c0
--- /dev/null
+++ b/test/datastore/query.test.js
@@ -0,0 +1,190 @@
+// Copyright 2015, Google, Inc.
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+'use strict';
+
+var assert = require('assert');
+
+var concepts = require('../../datastore/concepts.js');
+var testUtil = require('./util.js');
+
+var query;
+var entity;
+
+describe('datastore/concepts/query', function () {
+  before(function() {
+    var projectId = process.env.GCLOUD_PROJECT || 'nodejs-docs-samples';
+    entity = new concepts.Entity(projectId);
+    query = new concepts.Query(projectId);
+  });
+
+  after(function(done) {
+    var datastore = query.datastore;
+    var q = datastore.createQuery('Task');
+
+    testUtil.deleteEntities(datastore, q, done);
+  });
+
+  describe('basic query', function() {
+    it('performs a basic query', function(done) {
+      query.testRunQuery(done);
+    });
+  });
+
+  describe('property filter', function() {
+    it('performs a query with a property filter', function(done) {
+      query.testPropertyFilter(done);
+    });
+  });
+
+  describe('composite filter', function() {
+    it('performs a query with a composite filter', function(done) {
+      query.testCompositeFilter(done);
+    });
+  });
+
+  describe('key filter', function() {
+    it('performs a query with a key filter', function(done) {
+      query.testKeyFilter(done);
+    });
+  });
+
+  describe('ascending sort', function() {
+    it('performs a query with ascending sort', function(done) {
+      query.testAscendingSort(done);
+    });
+  });
+
+  describe('descending sort', function() {
+    it('performs a query with descending sort', function(done) {
+      query.testDescendingSort(done);
+    });
+  });
+
+  describe('multi sort', function() {
+    it('performs a query with multi sort', function(done) {
+      query.testMultiSort(done);
+    });
+  });
+
+  describe('kindless query', function() {
+    it('performs a kindless query', function(done) {
+      query.testKindlessQuery(done);
+    });
+  });
+
+  describe('projection query', function() {
+    before(function(done) {
+      entity.testProperties(done);
+    });
+
+    it('performs a projection query', function(done) {
+      query.testRunQueryProjection(done);
+    });
+  });
+
+  describe('keys only query', function() {
+    it('performs a keys only query', function(done) {
+      query.testKeysOnlyQuery(done);
+    });
+  });
+
+  describe('distinct query', function() {
+    it('performs a distinct query', function(done) {
+      query.testDistinctQuery(done);
+    });
+  });
+
+  describe('distinct on query', function() {
+    it('performs a distinct on query', function(done) {
+      query.testDistinctOnQuery(done);
+    });
+  });
+
+  describe('array value inequality range', function() {
+    it('performs an array value inequality query', function(done) {
+      query.testArrayValueInequalityRange(done);
+    });
+  });
+
+  describe('array value equality', function() {
+    it('performs an array value equality query', function(done) {
+      query.testArrayValueEquality(done);
+    });
+  });
+
+  describe('inequality range', function() {
+    it('performs an inequality range query', function(done) {
+      query.testInequalityRange(done);
+    });
+  });
+
+  describe('inequality invalid', function() {
+    it('returns an error from an invalid query', function(done) {
+      query.testInequalityInvalid(function(err) {
+        assert.notStrictEqual(err, null);
+        done();
+      });
+    });
+  });
+
+  describe('equal and inequality range', function() {
+    it('performs an equal and inequality range query', function(done) {
+      query.testEqualAndInequalityRange(done);
+    });
+  });
+
+  describe('inequality sort', function() {
+    it('performs an equality sort query', function(done) {
+      query.testInequalitySort(done);
+    });
+  });
+
+  describe('inequality sort invalid', function() {
+    it('returns an error when not sorted on filtered property', function(done) {
+      query.testInequalitySortInvalidNotSame(function(err) {
+        assert.notStrictEqual(err, null);
+        done();
+      });
+    });
+
+    it('returns an error when not sorted on first filter prop', function(done) {
+      query.testInequalitySortInvalidNotFirst(function(err) {
+        assert.notStrictEqual(err, null);
+        done();
+      });
+    });
+  });
+
+  describe('limit query', function() {
+    it('performs a query with a limit', function(done) {
+      query.testLimit(done);
+    });
+  });
+
+  describe('cursor paging', function() {
+    before(function(done) {
+      entity.testBatchUpsert(done);
+    });
+
+    it('allows manual pagination through results', function(done) {
+      query.testCursorPaging(done);
+    });
+  });
+
+  describe.skip('eventually consistent query', function() {
+    it('performs an ancestor query', function(done) {
+      query.testEventualConsistentQuery(done);
+    });
+  });
+});
diff --git a/test/datastore/transaction.test.js b/test/datastore/transaction.test.js
new file mode 100644
index 0000000000..7266095d3b
--- /dev/null
+++ b/test/datastore/transaction.test.js
@@ -0,0 +1,57 @@
+// Copyright 2015, Google, Inc.
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+'use strict';
+
+var testUtil = require('./util.js');
+
+var Transaction = require('../../datastore/concepts').Transaction;
+var transaction;
+
+describe('datastore/concepts/transaction', function () {
+  before(function() {
+    var projectId = process.env.GCLOUD_PROJECT || 'nodejs-docs-samples';
+    transaction = new Transaction(projectId);
+  });
+
+  after(function(done) {
+    var datastore = transaction.datastore;
+    var query = datastore.createQuery('Task');
+
+    testUtil.deleteEntities(datastore, query, done);
+  });
+
+  describe('update', function() {
+    it('performs a transactional update', function(done) {
+      transaction.testTransactionalUpdate(done);
+    });
+  });
+
+  describe('retry', function() {
+    it('performs retries if necessary', function(done) {
+      transaction.testTransactionalRetry(done);
+    });
+  });
+
+  describe('getOrCreate', function() {
+    it('performs a get or create', function(done) {
+      transaction.testTransactionalGetOrCreate(done);
+    });
+  });
+
+  describe('single entity group read only', function() {
+    it('gets a snapshot of task list entities', function(done) {
+      transaction.testSingleEntityGroupReadOnly(done);
+    });
+  });
+});
diff --git a/test/datastore/util.js b/test/datastore/util.js
new file mode 100644
index 0000000000..0b01e787af
--- /dev/null
+++ b/test/datastore/util.js
@@ -0,0 +1,31 @@
+// Copyright 2015, Google, Inc.
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+'use strict';
+
+module.exports = {
+  deleteEntities: function(datastore, query, callback) {
+    datastore.runQuery(query, function(err, entities) {
+      if (err) {
+        callback(err);
+        return;
+      }
+
+      var keys = entities.map(function(entity) {
+        return entity.key;
+      });
+
+      datastore.delete(keys, callback);
+    });
+  }
+};
\ No newline at end of file