Skip to content

Commit 61ffa82

Browse files
Merge pull request #648 from stephenplusplus/spp--search
Introduce Search API
2 parents 6e04e5e + abca6f1 commit 61ffa82

24 files changed

+3052
-13
lines changed

README.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ This client supports the following Google Cloud Platform services:
1414
* [Google Cloud Datastore](#google-cloud-datastore)
1515
* [Google Cloud Storage](#google-cloud-storage)
1616
* [Google Cloud Pub/Sub](#google-cloud-pubsub-beta) (Beta)
17+
* [Google Cloud Search](#google-cloud-search-alpha) (Alpha)
1718

1819
If you need support for other Google APIs, check out the [Google Node.js API Client library][googleapis].
1920

@@ -238,6 +239,48 @@ topic.subscribe('new-subscription', function(err, subscription) {
238239
});
239240
```
240241

242+
## Google Cloud Search (Alpha)
243+
> This is an *Alpha* release of Google Cloud Search. This feature is not covered by any SLA or deprecation policy and may be subject to backward-incompatible changes.
244+
245+
[Google Cloud Search][cloud-search] ([docs][cloud-search-docs]) allows you to quickly perform full-text and geospatial searches against your data without having to spin up your own instances and without the hassle of managing and maintaining a search service.
246+
247+
See the [gcloud-node Search API documentation][gcloud-search-docs] to learn how to store and query your indexes and documents using this library.
248+
249+
```js
250+
var gcloud = require('gcloud');
251+
252+
// Authorizing on a per-API-basis. You don't need to do this if you auth on a
253+
// global basis (see Authorization section above).
254+
255+
var search = gcloud.search({
256+
keyFilename: '/path/to/keyfile.json',
257+
projectId: 'my-project'
258+
});
259+
260+
// Create a document in a new index.
261+
var index = search.index('memberData');
262+
263+
var document = index.document('member-id-34211');
264+
document.addField('preferredContactForm').addTextValue('phone');
265+
266+
index.createDocument(document, function(err, document) {
267+
console.log(err || document);
268+
});
269+
270+
// Search an index and get the results as a readable object stream.
271+
var index = search.index('memberData');
272+
273+
index.search('preferredContactForm:phone')
274+
.on('error', console.error)
275+
.on('data', function(document) {
276+
// document.id = 'member-id-34211';
277+
})
278+
.on('end', function() {
279+
// All results consumed.
280+
});
281+
```
282+
283+
241284
## Contributing
242285

243286
Contributions to this library are always welcome and highly encouraged.
@@ -253,6 +296,7 @@ Apache 2.0 - See [COPYING](COPYING) for more information.
253296
[gcloud-bigquery-docs]: https://googlecloudplatform.github.io/gcloud-node/#/docs/bigquery
254297
[gcloud-datastore-docs]: https://googlecloudplatform.github.io/gcloud-node/#/docs/datastore
255298
[gcloud-pubsub-docs]: https://googlecloudplatform.github.io/gcloud-node/#/docs/pubsub
299+
[gcloud-search-docs]: https://googlecloudplatform.github.io/gcloud-node/#/docs/search
256300
[gcloud-storage-docs]: https://googlecloudplatform.github.io/gcloud-node/#/docs/storage
257301
[gcloud-todos]: https://github.com/GoogleCloudPlatform/gcloud-node-todos
258302
[gitnpm]: https://github.com/stephenplusplus/gitnpm
@@ -273,8 +317,12 @@ Apache 2.0 - See [COPYING](COPYING) for more information.
273317
[cloud-pubsub]: https://cloud.google.com/pubsub/
274318
[cloud-pubsub-docs]: https://cloud.google.com/pubsub/docs
275319

320+
[cloud-search]: https://cloud.google.com/search/
321+
[cloud-search-docs]: https://cloud.google.com/search/
322+
276323
[cloud-storage]: https://cloud.google.com/storage/
277324
[cloud-storage-docs]: https://cloud.google.com/storage/docs/overview
278325
[cloud-storage-create-bucket]: https://cloud.google.com/storage/docs/cloud-console#_creatingbuckets
326+
279327
[hya-wave]: https://wav.hya.io
280328
[hya-io]: https://hya.io

docs/json/master/search/.gitkeep

Whitespace-only changes.

docs/site/components/docs/docs-values.js

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,25 @@ angular.module('gcloud.docs')
8888
]
8989
},
9090

91+
search: {
92+
title: 'Search',
93+
_url: '{baseUrl}/search',
94+
pages: [
95+
{
96+
title: 'Index',
97+
url: '/index'
98+
},
99+
{
100+
title: 'Document',
101+
url: '/document'
102+
},
103+
{
104+
title: 'Field',
105+
url: '/field'
106+
}
107+
]
108+
},
109+
91110
storage: {
92111
title: 'Storage',
93112
_url: '{baseUrl}/storage'
@@ -135,6 +154,10 @@ angular.module('gcloud.docs')
135154
// introduce new storage api.
136155
'>=0.9.0': ['storageWithFiles'],
137156

138-
'>=0.10.0': ['bigquery']
157+
// introduce bigquery api.
158+
'>=0.10.0': ['bigquery'],
159+
160+
// introduce search api.
161+
'>=0.16.0': ['search']
139162
}
140163
});

docs/site/components/docs/docs.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ <h3 class="sub-heading">
4444
</article>
4545
<hr>
4646

47-
<article ng-repeat="service in ['bigquery', 'datastore', 'pubsub', 'storage']"
47+
<article ng-repeat="service in ['bigquery', 'datastore', 'pubsub', 'search', 'storage']"
4848
ng-if="isActiveDoc(service)"
4949
ng-include="'site/components/docs/' + service + '-overview.html'">
5050
</article>

docs/site/components/docs/docs.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,9 @@ angular
244244
path.push('index.json');
245245
} else if (module && cl) {
246246
path.push(module);
247+
if (cl === 'index') {
248+
cl = 'index-class';
249+
}
247250
path.push(cl + '.json');
248251
}
249252
return $http.get(path.join('/'))
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<h3>Search Overview</h3>
2+
<p>
3+
The object returned from <code>gcloud.search</code> gives you complete access to store your documents and search your indexes.
4+
</p>
5+
<p>
6+
To learn more about Search, see <a href="https://cloud.google.com/search">What is Google Cloud Search?</a>
7+
</p>

lib/common/stream-router.js

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
/*!
2+
* Copyright 2015 Google Inc. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/*!
18+
* @module common/streamrouter
19+
*/
20+
21+
'use strict';
22+
23+
var streamEvents = require('stream-events');
24+
var through = require('through2');
25+
26+
/**
27+
* @type {module:common/util}
28+
* @private
29+
*/
30+
var util = require('../common/util.js');
31+
32+
/*! Developer Documentation
33+
*
34+
* streamRouter is used to extend `nextQuery`+callback methods with stream
35+
* functionality.
36+
*
37+
* Before:
38+
*
39+
* search.query('done=true', function(err, results, nextQuery) {});
40+
*
41+
* After:
42+
*
43+
* search.query('done=true').on('data', function(result) {});
44+
*
45+
* Methods to extend should be written to accept callbacks and return a
46+
* `nextQuery`. All stream logic is handled in `streamRouter.router_`.
47+
*/
48+
var streamRouter = {};
49+
50+
/**
51+
* Cache the original method, then overwrite it on the Class's prototype.
52+
*
53+
* @param {function} Class - The parent class of the methods to extend.
54+
* @param {array|string} methodNames - Name(s) of the methods to extend.
55+
*/
56+
streamRouter.extend = function(Class, methodNames) {
57+
methodNames = util.arrayize(methodNames);
58+
59+
methodNames.forEach(function(methodName) {
60+
var originalMethod = Class.prototype[methodName];
61+
62+
Class.prototype[methodName] = function() {
63+
return streamRouter.router_(arguments, originalMethod.bind(this));
64+
};
65+
});
66+
};
67+
68+
/**
69+
* The router accepts all incoming arguments to the overwritten method. If the
70+
* last argument is a function, simply pass them through to the original method.
71+
* If the last argument is not a function, activate stream mode.
72+
*
73+
* Stream mode simply calls the nextQuery recursively. The stream ends when
74+
* `nextQuery` is null.
75+
*
76+
* @param {array} args - The original `arguments` pseudo-array as it was
77+
* received by the original method.
78+
* @param {function} originalMethod - The cached method that accepts a callback
79+
* and returns `nextQuery` to receive more results.
80+
* @return {undefined|stream}
81+
*/
82+
streamRouter.router_ = function(args, originalMethod) {
83+
args = util.toArray(args);
84+
var callback = args[args.length - 1];
85+
var isStreamMode = !util.is(callback, 'function');
86+
87+
if (!isStreamMode) {
88+
originalMethod.apply(null, args);
89+
return;
90+
}
91+
92+
var stream = streamEvents(through.obj());
93+
94+
// Results from the API are split apart for the user. If 50 results are
95+
// returned, we emit 50 data events. While the user is consuming these, they
96+
// might choose to end the stream early by calling ".end()". We keep track of
97+
// this state to prevent pushing more results to the stream, ending it again,
98+
// or making unnecessary API calls.
99+
var streamEnded = false;
100+
var _end = stream.end;
101+
stream.end = function() {
102+
streamEnded = true;
103+
_end.apply(this, arguments);
104+
};
105+
106+
function onResultSet(err, results, nextQuery) {
107+
if (err) {
108+
stream.emit('error', err);
109+
stream.end();
110+
return;
111+
}
112+
113+
results.forEach(function(result) {
114+
if (!streamEnded) {
115+
stream.push(result);
116+
}
117+
});
118+
119+
if (streamEnded) {
120+
return;
121+
}
122+
123+
if (nextQuery) {
124+
originalMethod(nextQuery, onResultSet);
125+
} else {
126+
stream.end();
127+
}
128+
}
129+
130+
stream.once('reading', function() {
131+
originalMethod.apply(null, args.concat(onResultSet));
132+
});
133+
134+
return stream;
135+
};
136+
137+
module.exports = streamRouter;

lib/common/util.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@
2323

2424
var extend = require('extend');
2525
var GoogleAuth = require('google-auth-library');
26+
var nodeutil = require('util');
2627
var request = require('request').defaults({
2728
pool: {
2829
maxSockets: Infinity
2930
}
3031
});
31-
var nodeutil = require('util');
3232
var uuid = require('node-uuid');
3333

3434
/** @const {object} gcloud-node's package.json file. */

lib/index.js

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ var Datastore = require('./datastore');
3838
*/
3939
var PubSub = require('./pubsub');
4040

41+
/**
42+
* @type {module:search}
43+
* @private
44+
*/
45+
var Search = require('./search');
46+
4147
/**
4248
* @type {module:storage}
4349
* @private
@@ -120,6 +126,10 @@ function gcloud(config) {
120126
options = options || {};
121127
return new PubSub(util.extendGlobalConfig(config, options));
122128
},
129+
search: function(options) {
130+
options = options || {};
131+
return new Search(util.extendGlobalConfig(config, options));
132+
},
123133
storage: function(options) {
124134
options = options || {};
125135
return new Storage(util.extendGlobalConfig(config, options));
@@ -173,11 +183,9 @@ gcloud.datastore = Datastore;
173183
* reliable, many-to-many, asynchronous messaging service from Google Cloud
174184
* Platform.
175185
*
176-
* Note: Google Cloud Pub/Sub API is available as a Limited Preview and the
177-
* client library we provide is currently experimental. The API and/or the
178-
* client might be changed in backward-incompatible ways. This API is not
179-
* subject to any SLA or deprecation policy. Request to be whitelisted to use it
180-
* by filling the [Limited Preview application form](http://goo.gl/sO0wTu).
186+
* Note: This is a *Beta* release of Google Cloud Pub/Sub. This feature is not
187+
* covered by any SLA or deprecation policy and may be subject to backward-
188+
* incompatible changes.
181189
*
182190
* @type {module:pubsub}
183191
*
@@ -194,6 +202,33 @@ gcloud.pubsub = function(config) {
194202
return new PubSub(config);
195203
};
196204

205+
/**
206+
* **Experimental**
207+
*
208+
* [Google Cloud Search](https://cloud.google.com/search/) allows you to quickly
209+
* perform full-text and geospatial searches against your data without having to
210+
* spin up your own instances and without the hassle of managing and maintaining
211+
* a search service.
212+
*
213+
* Note: This is an *Alpha* release of Google Cloud Search. This feature is not
214+
* covered by any SLA or deprecation policy and may be subject to backward-
215+
* incompatible changes.
216+
*
217+
* @type {module:search}
218+
*
219+
* @return {module:search}
220+
*
221+
* @example
222+
* var gcloud = require('gcloud');
223+
* var search = gcloud.search({
224+
* projectId: 'project-id',
225+
* keyFilename: '/path/to/keyfile.json'
226+
* });
227+
*/
228+
gcloud.search = function (config) {
229+
return new Search(config);
230+
};
231+
197232
/**
198233
* Google Cloud Storage allows you to store data on Google infrastructure.
199234
* Read [Google Cloud Storage API docs](https://developers.google.com/storage/)

0 commit comments

Comments
 (0)