├── .circleci
└── config.yml
├── .gitignore
├── .npmrc
├── .nvmrc
├── LICENSE.txt
├── NOTICE.txt
├── README.md
├── fixtures
├── host-c5s2mj.api.swiftype.com-443
│ ├── 401
│ ├── 404
│ ├── 15193573244029391
│ ├── 151935732448113219
│ ├── 151935732459996374
│ ├── 151935732470971687
│ ├── 15208899348639621
│ ├── 152643363747751014
│ ├── 152643460487616422
│ ├── 152643531437267705
│ ├── 152643588771342928
│ ├── 152666640463285327
│ ├── 154775139235186519
│ ├── 154775139267473451
│ ├── 154775207819050958
│ ├── 154775369860896529
│ ├── 154775388696063661
│ ├── 154775411564681581
│ ├── 154775710380210466
│ ├── 154844848285366145
│ ├── 154905605865969485
│ ├── 154905668608952844
│ ├── 158153945918596904
│ ├── 158646044979291659
│ ├── 158646109175536388
│ └── multiSearchErrors
└── localhost-3002
│ ├── 15815391173475749
│ ├── 15815978342879229
│ ├── 158159799829857287
│ ├── get-schema
│ └── update-schema
├── lib
├── appSearch.js
└── client.js
├── logo-app-search.png
├── package.json
└── test
└── test.js
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | # Javascript Node CircleCI 2.0 configuration file
2 | #
3 | # Check https://circleci.com/docs/2.0/language-javascript/ for more details
4 | #
5 | version: 2
6 | jobs:
7 | build:
8 | docker:
9 | # specify the version you desire here
10 | - image: circleci/node:8
11 |
12 | # Specify service dependencies here if necessary
13 | # CircleCI maintains a library of pre-built images
14 | # documented at https://circleci.com/docs/2.0/circleci-images/
15 | # - image: circleci/mongo:3.4.4
16 |
17 | working_directory: ~/repo
18 |
19 | steps:
20 | - checkout
21 |
22 | # Download and cache dependencies
23 | - restore_cache:
24 | keys:
25 | - v1-dependencies-{{ checksum "package.json" }}
26 | # fallback to using the latest cache if no exact match is found
27 | - v1-dependencies-
28 |
29 | - run: npm install
30 |
31 | - save_cache:
32 | paths:
33 | - node_modules
34 | key: v1-dependencies-{{ checksum "package.json" }}
35 |
36 | # run tests!
37 | - run: npm test
38 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (http://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # Typescript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 | package-lock.json
60 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | package-lock=false
2 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 8
2 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright 2019 Elasticsearch B.V.
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/NOTICE.txt:
--------------------------------------------------------------------------------
1 | Elastic App Search Node client.
2 | Copyright 2012-2019 Elasticsearch B.V.
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | > **⚠️ This client is deprecated ⚠️**
2 | >
3 | > As of Enterprise Search version 8.3.2, we are directing users to the new [Enterprise Search Node Client](https://github.com/elastic/enterprise-search-js) and
4 | > deprecating this client.
5 | >
6 | > Our development effort on this project will be limited to bug fixes.
7 | > All future enhancements will be focused on the Enterprise Search Node Client.
8 | >
9 | > Thank you! - Elastic
10 |
11 |
12 |
13 |
14 |
15 | > A first-party Node.JS client for building excellent, relevant search experiences with [Elastic App Search](https://www.elastic.co/products/app-search).
16 |
17 | ## Contents
18 |
19 | - [Getting started](#getting-started-)
20 | - [Versioning](#versioning)
21 | - [Usage](#usage)
22 | - [Running tests](#running-tests)
23 | - [FAQ](#faq-)
24 | - [Contribute](#contribute-)
25 | - [License](#license-)
26 |
27 | ---
28 |
29 | ## Getting started 🐣
30 |
31 | To install this package, run:
32 |
33 | ```bash
34 | npm install @elastic/app-search-node
35 | ```
36 |
37 | ## Versioning
38 |
39 | This client is versioned and released alongside App Search.
40 |
41 | To guarantee compatibility, use the most recent version of this library within the major version of the corresponding App Search implementation.
42 |
43 | For example, for App Search `7.3`, use `7.3` of this library or above, but not `8.0`.
44 |
45 | If you are using the [SaaS version available on swiftype.com](https://app.swiftype.com/as) of App Search, you should use the version 7.5.x of the client.
46 |
47 | ## Usage
48 |
49 | ### Setup: Configuring the client and authentication
50 |
51 | Using this client assumes that you have already an instance of [Elastic App Search](https://www.elastic.co/products/app-search) up and running.
52 |
53 | The client is configured using the `baseUrlFn` and `apiKey` parameters.
54 |
55 | ```javascript
56 | const apiKey = 'private-mu75psc5egt9ppzuycnc2mc3'
57 | const baseUrlFn = () => 'http://localhost:3002/api/as/v1/'
58 | const client = new AppSearchClient(undefined, apiKey, baseUrlFn)
59 | ```
60 |
61 | Note:
62 |
63 | The `[apiKey]` authenticates requests to the API.
64 | You can use any key type with the client, however each has a different scope.
65 | For more information on keys, check out the [documentation](https://swiftype.com/documentation/app-search/api/credentials).
66 |
67 | #### Swiftype.com App Search users:
68 |
69 | When using the [SaaS version available on swiftype.com](https://app.swiftype.com/as) of App Search, you can configure the client using your `hostIdentifier` instead of the `baseUrlFn` parameter.
70 | The `hostIdentifier` can be found within the [Credentials](https://app.swiftype.com/as#/credentials) menu.
71 |
72 | ```javascript
73 | const AppSearchClient = require('@elastic/app-search-node')
74 | const hostIdentifier = 'host-c5s2mj'
75 | const apiKey = 'private-mu75psc5egt9ppzuycnc2mc3'
76 | const client = new AppSearchClient(hostIdentifier, apiKey)
77 | ```
78 |
79 | ### API Methods
80 |
81 | ##### Indexing: Creating or Replacing Documents
82 |
83 | ```javascript
84 | const engineName = 'favorite-videos'
85 | const documents = [
86 | {
87 | id: 'INscMGmhmX4',
88 | url: 'https://www.youtube.com/watch?v=INscMGmhmX4',
89 | title: 'The Original Grumpy Cat',
90 | body: 'A wonderful video of a magnificent cat.'
91 | },
92 | {
93 | id: 'JNDFojsd02',
94 | url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
95 | title: 'Another Grumpy Cat',
96 | body: 'A great video of another cool cat.'
97 | }
98 | ]
99 |
100 | client
101 | .indexDocuments(engineName, documents)
102 | .then(response => console.log(response))
103 | .catch(error => console.log(error))
104 | ```
105 |
106 | Note that this API will not throw on an indexing error. Errors are inlined in the response body per document:
107 |
108 | ```json
109 | [
110 | { "id": "park_rocky-mountain", "errors": [] },
111 | {
112 | "id": "park_saguaro",
113 | "errors": ["Invalid field value: Value 'foo' cannot be parsed as a float"]
114 | }
115 | ]
116 |
117 | ```
118 |
119 | ##### Indexing: Updating Documents (Partial Updates)
120 |
121 | ```javascript
122 | const engineName = 'favorite-videos'
123 | const documents = [
124 | {
125 | id: 'INscMGmhmX4',
126 | title: 'Updated title'
127 | }
128 | ]
129 |
130 | client
131 | .updateDocuments(engineName, documents)
132 | .then(response => console.log(response))
133 | .catch(error => console.log(error))
134 | ```
135 |
136 |
137 | ##### Retrieving Documents
138 |
139 | ```javascript
140 | const engineName = 'favorite-videos'
141 | const documentIds = ['INscMGmhmX4', 'JNDFojsd02']
142 |
143 | client
144 | .getDocuments(engineName, documentIds)
145 | .then(response => console.log(response))
146 | .catch(error => console.log(error.errorMessages))
147 | ```
148 |
149 | ##### Listing Documents
150 |
151 | ```javascript
152 | const engineName = 'favorite-videos'
153 |
154 | // Without paging
155 | client
156 | .listDocuments(engineName)
157 | .then(response => console.log(response))
158 | .catch(error => console.log(error.errorMessages))
159 |
160 | // With paging
161 | client
162 | .listDocuments(engineName, { page: { size: 10, current: 1 } })
163 | .then(response => console.log(response))
164 | .catch(error => console.log(error.errorMessages))
165 | ```
166 |
167 | ##### Destroying Documents
168 |
169 | ```javascript
170 | const engineName = 'favorite-videos'
171 | const documentIds = ['INscMGmhmX4', 'JNDFojsd02']
172 |
173 | client
174 | .destroyDocuments(engineName, documentIds)
175 | .then(response => console.log(response))
176 | .catch(error => console.log(error.errorMessages))
177 | ```
178 |
179 | ##### Listing Engines
180 |
181 | ```javascript
182 | client
183 | .listEngines({ page: { size: 10, current: 1 } })
184 | .then(response => console.log(response))
185 | .catch(error => console.log(error.errorMessages))
186 | ```
187 |
188 | ##### Retrieving Engines
189 |
190 | ```javascript
191 | const engineName = 'favorite-videos'
192 |
193 | client
194 | .getEngine(engineName)
195 | .then(response => console.log(response))
196 | .catch(error => console.log(error.errorMessages))
197 | ```
198 |
199 | ##### Creating Engines
200 |
201 | ```javascript
202 | const engineName = 'favorite-videos'
203 |
204 | client
205 | .createEngine(engineName, { language: 'en' })
206 | .then(response => console.log(response))
207 | .catch(error => console.log(error.errorMessages))
208 | ```
209 |
210 | ##### Destroying Engines
211 |
212 | ```javascript
213 | const engineName = 'favorite-videos'
214 |
215 | client
216 | .destroyEngine(engineName)
217 | .then(response => console.log(response))
218 | .catch(error => console.log(error.errorMessages))
219 | ```
220 |
221 | ##### Searching
222 |
223 | ```javascript
224 | const engineName = 'favorite-videos'
225 | const query = 'cat'
226 | const searchFields = { title: {} }
227 | const resultFields = { title: { raw: {} } }
228 | const options = { search_fields: searchFields, result_fields: resultFields }
229 |
230 | client
231 | .search(engineName, query, options)
232 | .then(response => console.log(response))
233 | .catch(error => console.log(error.errorMessages))
234 | ```
235 |
236 | ##### Multi-Search
237 |
238 | ```javascript
239 | const engineName = 'favorite-videos'
240 | const searches = [
241 | { query: 'cat', options: {
242 | search_fields: { title: {} },
243 | result_fields: { title: { raw: {} } }
244 | } },
245 | { query: 'grumpy', options: {} }
246 | ]
247 |
248 | client
249 | .multiSearch(engineName, searches)
250 | .then(response => console.log(response))
251 | .catch(error => console.log(error.errorMessages))
252 | ```
253 |
254 | ##### Query Suggestion
255 |
256 | ```javascript
257 | const engineName = 'favorite-videos'
258 | const options = {
259 | size: 3,
260 | types: {
261 | documents: {
262 | fields: ['title']
263 | }
264 | }
265 | }
266 |
267 | client
268 | .querySuggestion(engineName, 'cat', options)
269 | .then(response => console.log(response))
270 | .catch(error => console.log(error.errorMessages))
271 | ```
272 |
273 | ##### Listing Curations
274 |
275 | ```javascript
276 | const engineName = 'favorite-videos'
277 |
278 | client
279 | .listCurations(engineName)
280 | .then(response => console.log(response))
281 | .catch(error => console.log(error.errorMessages))
282 |
283 | // Pagination details are optional
284 | const paginationDetails = {
285 | page: {
286 | current: 2,
287 | size: 10
288 | }
289 | }
290 |
291 | client
292 | .listCurations(engineName, paginationDetails)
293 | .then(response => console.log(response))
294 | .catch(error => console.log(error.errorMessages))
295 | ```
296 |
297 | ##### Retrieving Curations
298 |
299 | ```javascript
300 | const engineName = 'favorite-videos'
301 | const curationId = 'cur-7438290'
302 |
303 | client
304 | .getCuration(engineName, curationId)
305 | .then(response => console.log(response))
306 | .catch(error => console.log(error.errorMessages))
307 | ```
308 |
309 | ##### Creating Curations
310 |
311 | ```javascript
312 | const engineName = 'favorite-videos'
313 | const newCuration = {
314 | queries: ['cat blop'],
315 | promoted: ['Jdas78932'],
316 | hidden: ['INscMGmhmX4', 'JNDFojsd02']
317 | }
318 |
319 | client
320 | .createCuration(engineName, newCuration)
321 | .then(response => console.log(response))
322 | .catch(error => console.log(error.errorMessages))
323 | ```
324 |
325 | ##### Updating Curations
326 |
327 | ```javascript
328 | const engineName = 'favorite-videos'
329 | const curationId = 'cur-7438290'
330 | // "queries" is required, either "promoted" or "hidden" is required.
331 | // Values sent for all fields will overwrite existing values.
332 | const newDetails = {
333 | queries: ['cat blop'],
334 | promoted: ['Jdas78932', 'JFayf782']
335 | }
336 |
337 | client
338 | .updateCuration(engineName, curationId, newDetails)
339 | .then(response => console.log(response))
340 | .catch(error => console.log(error.errorMessages))
341 | ```
342 |
343 | ##### Deleting Curations
344 |
345 | ```javascript
346 | const engineName = 'favorite-videos'
347 | const curationId = 'cur-7438290'
348 |
349 | client
350 | .destroyCuration(engineName, curationId)
351 | .then(response => console.log(response))
352 | .catch(error => console.log(error.errorMessages))
353 | ```
354 |
355 | ##### Retrieving Schemas
356 |
357 | ```javascript
358 | const engineName = 'favorite-videos'
359 |
360 | client
361 | .getSchema(engineName)
362 | .then(response => console.log(response))
363 | .catch(error => console.log(error.errorMessages))
364 | ```
365 |
366 | ##### Updating Schemas
367 |
368 | ```javascript
369 | const engineName = 'favorite-videos'
370 | const schema = {
371 | views: 'number',
372 | created_at: 'date'
373 | }
374 |
375 | client
376 | .updateSchema(engineName, schema)
377 | .then(response => console.log(response))
378 | .catch(error => console.log(error.errorMessages))
379 | ```
380 |
381 | ##### Create a Signed Search Key
382 |
383 | Creating a search key that will only return the title field.
384 |
385 | ```javascript
386 | const publicSearchKey = 'search-xxxxxxxxxxxxxxxxxxxxxxxx'
387 | // This name must match the name of the key above from your App Search dashboard
388 | const publicSearchKeyName = 'search-key'
389 | const enforcedOptions = {
390 | result_fields: { title: { raw: {} } },
391 | filters: { world_heritage_site: 'true' }
392 | }
393 |
394 | // Optional. See https://github.com/auth0/node-jsonwebtoken#usage for all options
395 | const signOptions = {
396 | expiresIn: '5 minutes'
397 | }
398 |
399 | const signedSearchKey = AppSearchClient.createSignedSearchKey(
400 | publicSearchKey,
401 | publicSearchKeyName,
402 | enforcedOptions,
403 | signOptions
404 | )
405 |
406 | const baseUrlFn = () => 'http://localhost:3002/api/as/v1/'
407 | const client = new AppSearchClient(undefined, signedSearchKey, baseUrlFn)
408 |
409 | client.search('sample-engine', 'everglade')
410 | ```
411 |
412 | ##### Create a Meta Engine
413 |
414 | ```javascript
415 | const engineName = 'my-meta-engine'
416 |
417 | client
418 | .createMetaEngine(engineName, ['source-engine-1', 'source-engine-2'])
419 | .then(response => console.log(response))
420 | .catch(error => console.log(error.errorMessages))
421 | ```
422 |
423 | ##### Add a Source Engine to a Meta Engine
424 |
425 | ```javascript
426 | const engineName = 'my-meta-engine'
427 |
428 | client
429 | .addMetaEngineSources(engineName, ['source-engine-3'])
430 | .then(response => console.log(response))
431 | .catch(error => console.log(error.errorMessages))
432 | ```
433 |
434 | ##### Remove a Source Engine from a Meta Engine
435 |
436 | ```javascript
437 | const engineName = 'my-meta-engine'
438 |
439 | client
440 | .deleteMetaEngineSources(engineName, ['source-engine-3'])
441 | .then(response => console.log(response))
442 | .catch(error => console.log(error.errorMessages))
443 | ```
444 |
445 | ##### Creating Engines
446 |
447 | ```javascript
448 | const engineName = 'my-meta-engine'
449 |
450 | client
451 | .createEngine(engineName, {
452 | type: 'meta',
453 | source_engines: ['source-engine-1', 'source-engine-2']
454 | })
455 | .then(response => console.log(response))
456 | .catch(error => console.log(error.errorMessages))
457 | ```
458 |
459 | ### For App Search APIs not available in this client
460 |
461 | We try to keep this client up to date with all of the available API endpoints available from App Search.
462 |
463 | There are a few APIs that may not be available yet. For those APIs, please use the low-level client to connect to hit any App Search endpoint.
464 |
465 | ```javascript
466 | const engineName = 'favorite-videos'
467 | const options = {
468 | query: 'cats'
469 | }
470 |
471 | const Client = require('@elastic/app-search-node/lib/client')
472 | const client = new Client('private-mu75psc5egt9ppzuycnc2mc3', 'http://localhost:3002/api/as/v1/')
473 | client.post(`engines/${encodeURIComponent(engineName)}/search`, options).then(console.log)
474 | ```
475 |
476 | ## Running tests
477 |
478 | ```bash
479 | npm test
480 | ```
481 |
482 | The specs in this project use [node-replay](https://github.com/assaf/node-replay) to capture fixtures.
483 |
484 | New fixtures should be captured from a running instance of App Search.
485 |
486 | To capture new fixtures, run a command like the following:
487 |
488 | ```
489 | nvm use
490 | HOST_IDENTIFIER=host-c5s2mj API_KEY=private-b94wtaoaym2ovdk5dohj3hrz REPLAY=record npm run test -- -g 'should create a meta engine'
491 | ```
492 |
493 | To break that down a little...
494 | - `HOST_IDENTIFIER` - Use this to override the fake value used in tests with an actual valid value for your App Search instance to record from
495 | - `API_KEY` - Use this to override the fake value used in tests with an actual valid value for your App Search instance to record from
496 | - `REPLAY=record` - Tells replay to record a new response if one doesn't already exist
497 | - `npm run test` - Run the tests
498 | - `-- -g 'should create a meta engine'` - Limit the tests to ONLY run the new test you've created, 'should create a meta engine' for example
499 |
500 | This will create a new fixture, make sure you manually edit that fixture to replace the host identifier and api key
501 | recorded in that fixture with the values the tests use.
502 |
503 | You'll also need to make sure that fixture is located in the correctly named directory under `fixtures` according to the host that was used.
504 |
505 | You'll know if something is not right because this will error when you run `npm run test` with an error like:
506 | ```
507 | Error: POST https://host-c5s2mj.api.swiftype.com:443/api/as/v1/engines refused: not recording and no network access
508 | ```
509 |
510 |
511 | ## FAQ 🔮
512 |
513 | ### Where do I report issues with the client?
514 |
515 | If something is not working as expected, please open an [issue](https://github.com/elastic/app-search-node/issues/new).
516 |
517 | ### Where can I learn more about App Search?
518 |
519 | Your best bet is to read the [documentation](https://swiftype.com/documentation/app-search).
520 |
521 | ### Where else can I go to get help?
522 |
523 | You can checkout the [Elastic App Search community discuss forums](https://discuss.elastic.co/c/app-search).
524 |
525 | ## Contribute 🚀
526 |
527 | We welcome contributors to the project. Before you begin, a couple notes...
528 |
529 | - Prior to opening a pull request, please create an issue to [discuss the scope of your proposal](https://github.com/elastic/app-search-node/issues).
530 | - Please write simple code and concise documentation, when appropriate.
531 |
532 | ## License 📗
533 |
534 | [Apache 2.0](https://github.com/elastic/app-search-node/blob/master/LICENSE.txt) © [Elastic](https://github.com/elastic)
535 |
536 | Thank you to all the [contributors](https://github.com/elastic/app-search-node/graphs/contributors)!
537 |
--------------------------------------------------------------------------------
/fixtures/host-c5s2mj.api.swiftype.com-443/15193573244029391:
--------------------------------------------------------------------------------
1 | POST /api/as/v1/engines/swiftype-api-example/documents
2 | content-type: application/json
3 | host: host-c5s2mj.api.swiftype.com
4 | x-swiftype-client: elastic-app-search-node
5 | x-swiftype-client-version: 8.3.2
6 | authorization: Bearer api-mu75psc5egt9ppzuycnc2mc3
7 | accept: application/json
8 | body: [{\"id\":\"INscMGmhmX4\",\"url\":\"http://www.youtube.com/watch?v=v1uyQZNg2vE\",\"title\":\"The Original Grumpy Cat\",\"body\":\"this is a test\"},{\"id\":\"JNDFojsd02\",\"url\":\"http://www.youtube.com/watch?v=tsdfhk2j\",\"title\":\"Another Grumpy Cat\",\"body\":\"this is also a test\"}]
9 |
10 | HTTP/1.1 200 OK
11 | x-frame-options: SAMEORIGIN
12 | x-xss-protection: 1; mode=block
13 | x-content-type-options: nosniff
14 | content-type: application/json; charset=utf-8
15 | vary: Origin
16 | etag: W/"1c505a964639396d60ef34105952fd98"
17 | cache-control: max-age=0, private, must-revalidate
18 | x-request-id: f8afdd65-b6a4-4fda-bc6d-304ef9d0fafe
19 | x-runtime: 0.124646
20 | connection: close
21 | transfer-encoding: chunked
22 |
23 | [{"id":"INscMGmhmX4","errors":[]},{"id":"JNDFojsd02","errors":[]}]
24 |
--------------------------------------------------------------------------------
/fixtures/host-c5s2mj.api.swiftype.com-443/151935732448113219:
--------------------------------------------------------------------------------
1 | GET /api/as/v1/engines/swiftype-api-example/documents
2 | content-type: application/json
3 | host: host-c5s2mj.api.swiftype.com
4 | x-swiftype-client: elastic-app-search-node
5 | x-swiftype-client-version: 8.3.2
6 | authorization: Bearer api-mu75psc5egt9ppzuycnc2mc3
7 | accept: application/json
8 | body: [\"INscMGmhmX4\",\"JNDFojsd02\"]
9 |
10 | HTTP/1.1 200 OK
11 | x-frame-options: SAMEORIGIN
12 | x-xss-protection: 1; mode=block
13 | x-content-type-options: nosniff
14 | content-type: application/json; charset=utf-8
15 | vary: Origin
16 | etag: W/"fe9687ede417e1dedbe25fdcc23d2e03"
17 | cache-control: max-age=0, private, must-revalidate
18 | x-request-id: 0bc07f29-0ad0-4f6a-b625-129788683fab
19 | x-runtime: 0.062718
20 | connection: close
21 | transfer-encoding: chunked
22 |
23 | [{"url":"http://www.youtube.com/watch?v=v1uyQZNg2vE","title":"The Original Grumpy Cat","body":"this is a test","id":"INscMGmhmX4"},{"url":"http://www.youtube.com/watch?v=tsdfhk2j","title":"Another Grumpy Cat","body":"this is also a test","id":"JNDFojsd02"}]
24 |
--------------------------------------------------------------------------------
/fixtures/host-c5s2mj.api.swiftype.com-443/151935732459996374:
--------------------------------------------------------------------------------
1 | DELETE /api/as/v1/engines/swiftype-api-example/documents
2 | content-type: application/json
3 | host: host-c5s2mj.api.swiftype.com
4 | x-swiftype-client: elastic-app-search-node
5 | x-swiftype-client-version: 8.3.2
6 | authorization: Bearer api-mu75psc5egt9ppzuycnc2mc3
7 | accept: application/json
8 | body: [\"INscMGmhmX4\",\"FakeId\"]
9 |
10 | HTTP/1.1 200 OK
11 | x-frame-options: SAMEORIGIN
12 | x-xss-protection: 1; mode=block
13 | x-content-type-options: nosniff
14 | content-type: application/json; charset=utf-8
15 | vary: Origin
16 | etag: W/"ceced1d361650a5d277df9c5c364a48c"
17 | cache-control: max-age=0, private, must-revalidate
18 | x-request-id: a3cb32e5-caa3-4e36-ac25-3820fcf8ef0e
19 | x-runtime: 0.111995
20 | connection: close
21 | transfer-encoding: chunked
22 |
23 | [{"id":"INscMGmhmX4","deleted":true},{"id":"FakeId","deleted":false}]
24 |
--------------------------------------------------------------------------------
/fixtures/host-c5s2mj.api.swiftype.com-443/151935732470971687:
--------------------------------------------------------------------------------
1 | GET /api/as/v1/engines/swiftype-api-example/search
2 | content-type: application/json
3 | host: host-c5s2mj.api.swiftype.com
4 | x-swiftype-client: elastic-app-search-node
5 | x-swiftype-client-version: 8.3.2
6 | authorization: Bearer api-mu75psc5egt9ppzuycnc2mc3
7 | accept: application/json
8 | body: {\"query\":\"cat\"}
9 |
10 | HTTP/1.1 200 OK
11 | x-frame-options: SAMEORIGIN
12 | x-xss-protection: 1; mode=block
13 | x-content-type-options: nosniff
14 | content-type: application/json; charset=utf-8
15 | vary: Origin
16 | etag: W/"fd34dfceda0904c198699f8354e48ee3"
17 | cache-control: max-age=0, private, must-revalidate
18 | x-request-id: 347bf555-bd81-4ec2-b156-a387ce69354a
19 | x-runtime: 0.101711
20 | connection: close
21 | transfer-encoding: chunked
22 |
23 | {"meta":{"page":{"current":1,"total_pages":1,"total_results":2,"size":10},"request_id":"347bf555-bd81-4ec2-b156-a387ce69354a"},"results":[{"body":{"raw":"this is a test"},"title":{"raw":"The Original Grumpy Cat"},"url":{"raw":"http://www.youtube.com/watch?v=v1uyQZNg2vE"},"id":{"raw":"INscMGmhmX4"},"_meta":{"score":0.42733237}},{"body":{"raw":"this is also a test"},"title":{"raw":"Another Grumpy Cat"},"url":{"raw":"http://www.youtube.com/watch?v=tsdfhk2j"},"id":{"raw":"JNDFojsd02"},"_meta":{"score":0.42733237}}]}
24 |
--------------------------------------------------------------------------------
/fixtures/host-c5s2mj.api.swiftype.com-443/15208899348639621:
--------------------------------------------------------------------------------
1 | POST /api/as/v1/engines/swiftype-api-example/documents
2 | content-type: application/json
3 | host: host-c5s2mj.api.swiftype.com
4 | x-swiftype-client: elastic-app-search-node
5 | x-swiftype-client-version: 8.3.2
6 | authorization: Bearer api-mu75psc5egt9ppzuycnc2mc3
7 | accept: application/json
8 | body: [{\"id\":\"INscMGmhmX4\",\"url\":\"http://www.youtube.com/watch?v=v1uyQZNg2vE\",\"title\":\"The Original Grumpy Cat\",\"body\":\"this is a test\"}]
9 |
10 | HTTP/1.1 200 OK
11 | date: Mon, 12 Mar 2018 21:25:29 GMT
12 | content-type: application/json; charset=utf-8
13 | transfer-encoding: chunked
14 | connection: close
15 | vary: Accept-Encoding, Accept-Encoding, Origin
16 | status: 200 OK
17 | x-frame-options: SAMEORIGIN
18 | x-xss-protection: 1; mode=block
19 | x-content-type-options: nosniff
20 | etag: W/"a724d96214a9badc432200ab6d253ac2"
21 | cache-control: max-age=0, private, must-revalidate
22 | x-request-id: e47a13e9ba8080ce151027e3195cccc5
23 | x-runtime: 0.146371
24 | x-swiftype-datacenter: dal05
25 | x-swiftype-frontend-node: web02.dal05
26 | x-swiftype-edge-node: web02.dal05
27 |
28 | [{"id":"INscMGmhmX4","errors":[]}]
29 |
--------------------------------------------------------------------------------
/fixtures/host-c5s2mj.api.swiftype.com-443/152643363747751014:
--------------------------------------------------------------------------------
1 | GET /api/as/v1/engines
2 | content-type: application/json
3 | host: host-c5s2mj.api.swiftype.com
4 | x-swiftype-client: elastic-app-search-node
5 | x-swiftype-client-version: 8.3.2
6 | authorization: Bearer api-mu75psc5egt9ppzuycnc2mc3
7 |
8 | HTTP/1.1 200 OK
9 | date: Wed, 16 May 2018 01:20:37 GMT
10 | content-type: application/json; charset=utf-8
11 | transfer-encoding: chunked
12 | connection: close
13 | vary: Accept-Encoding, Accept-Encoding, Origin
14 | status: 200 OK
15 | x-frame-options: SAMEORIGIN
16 | x-xss-protection: 1; mode=block
17 | x-content-type-options: nosniff
18 | etag: W/"250d4a4717082f5a2e3b40250c8923f4"
19 | cache-control: max-age=0, private, must-revalidate
20 | x-request-id: ed93b4296c3b369916cecd6ef2aef89c
21 | x-runtime: 0.070549
22 | x-swiftype-datacenter: dal05
23 | x-swiftype-frontend-node: web02.dal05
24 | x-swiftype-edge-node: web02.dal05
25 |
26 | {"meta":{"page":{"current":1,"total_pages":1,"total_results":3,"size":25}},"results":[{"name":"node-modules","type":"default","language":null},{"name":"ruby-gems","type":"default","language":null},{"name":"test-engine","type":"default","language":null}]}
27 |
--------------------------------------------------------------------------------
/fixtures/host-c5s2mj.api.swiftype.com-443/152643460487616422:
--------------------------------------------------------------------------------
1 | GET /api/as/v1/engines/swiftype-api-example
2 | content-type: application/json
3 | host: host-c5s2mj.api.swiftype.com
4 | x-swiftype-client: elastic-app-search-node
5 | x-swiftype-client-version: 8.3.2
6 | authorization: Bearer api-mu75psc5egt9ppzuycnc2mc3
7 | accept: application/json
8 | body: {}
9 |
10 | HTTP/1.1 200 OK
11 | date: Wed, 16 May 2018 01:36:44 GMT
12 | content-type: application/json; charset=utf-8
13 | transfer-encoding: chunked
14 | connection: close
15 | vary: Accept-Encoding, Accept-Encoding, Origin
16 | status: 200 OK
17 | x-frame-options: SAMEORIGIN
18 | x-xss-protection: 1; mode=block
19 | x-content-type-options: nosniff
20 | etag: W/"b33399d52d37875c034420987d0c9a3f"
21 | cache-control: max-age=0, private, must-revalidate
22 | x-request-id: 5e06f6a80b733ec4341b5e60b7681bd2
23 | x-runtime: 0.071062
24 | x-swiftype-datacenter: dal05
25 | x-swiftype-frontend-node: web02.dal05
26 | x-swiftype-edge-node: web02.dal05
27 |
28 | {"name":"swiftype-api-example","type":"default","language":null}
29 |
--------------------------------------------------------------------------------
/fixtures/host-c5s2mj.api.swiftype.com-443/152643531437267705:
--------------------------------------------------------------------------------
1 | POST /api/as/v1/engines
2 | content-type: application/json
3 | host: host-c5s2mj.api.swiftype.com
4 | x-swiftype-client: elastic-app-search-node
5 | x-swiftype-client-version: 8.3.2
6 | authorization: Bearer api-mu75psc5egt9ppzuycnc2mc3
7 | accept: application/json
8 | body: {\"name\":\"new-engine\"}
9 |
10 | HTTP/1.1 200 OK
11 | date: Wed, 16 May 2018 01:48:34 GMT
12 | content-type: application/json; charset=utf-8
13 | transfer-encoding: chunked
14 | connection: close
15 | vary: Accept-Encoding, Accept-Encoding, Origin
16 | status: 200 OK
17 | x-frame-options: SAMEORIGIN
18 | x-xss-protection: 1; mode=block
19 | x-content-type-options: nosniff
20 | etag: W/"36c4e09df35ed4a65dc5d132bd9a6ffb"
21 | cache-control: max-age=0, private, must-revalidate
22 | x-request-id: 308ddf80b7f98bff62da82fc1270cfb5
23 | x-runtime: 0.347975
24 | x-swiftype-datacenter: dal05
25 | x-swiftype-frontend-node: web01.dal05
26 | x-swiftype-edge-node: web01.dal05
27 |
28 | {"name":"new-engine","type":"default","language":null}
29 |
--------------------------------------------------------------------------------
/fixtures/host-c5s2mj.api.swiftype.com-443/152643588771342928:
--------------------------------------------------------------------------------
1 | DELETE /api/as/v1/engines/new-engine
2 | content-type: application/json
3 | host: host-c5s2mj.api.swiftype.com
4 | x-swiftype-client: elastic-app-search-node
5 | x-swiftype-client-version: 8.3.2
6 | authorization: Bearer api-mu75psc5egt9ppzuycnc2mc3
7 | accept: application/json
8 | body: {}
9 |
10 | HTTP/1.1 200 OK
11 | date: Wed, 16 May 2018 01:58:07 GMT
12 | content-type: application/json; charset=utf-8
13 | transfer-encoding: chunked
14 | connection: close
15 | vary: Accept-Encoding, Accept-Encoding, Origin
16 | status: 200 OK
17 | x-frame-options: SAMEORIGIN
18 | x-xss-protection: 1; mode=block
19 | x-content-type-options: nosniff
20 | etag: W/"ebaee1a83baef03e1492e5832fc006d0"
21 | cache-control: max-age=0, private, must-revalidate
22 | x-request-id: 3bb56da4af9688430c5e51955073afa3
23 | x-runtime: 0.131635
24 | x-swiftype-datacenter: dal05
25 | x-swiftype-frontend-node: web01.dal05
26 | x-swiftype-edge-node: web01.dal05
27 |
28 | {"deleted":true}
29 |
--------------------------------------------------------------------------------
/fixtures/host-c5s2mj.api.swiftype.com-443/152666640463285327:
--------------------------------------------------------------------------------
1 | GET /api/as/v1/engines?page[current]=2&page[size]=1
2 | content-type: application/json
3 | host: host-c5s2mj.api.swiftype.com
4 | x-swiftype-client: elastic-app-search-node
5 | x-swiftype-client-version: 8.3.2
6 | authorization: Bearer api-mu75psc5egt9ppzuycnc2mc3
7 | accept: application/json
8 | body: {}
9 |
10 | HTTP/1.1 200 OK
11 | date: Fri, 18 May 2018 18:00:04 GMT
12 | content-type: application/json; charset=utf-8
13 | transfer-encoding: chunked
14 | connection: close
15 | vary: Accept-Encoding, Accept-Encoding, Origin
16 | status: 200 OK
17 | x-frame-options: SAMEORIGIN
18 | x-xss-protection: 1; mode=block
19 | x-content-type-options: nosniff
20 | etag: W/"8cbec2b26f50c6ff8cf9c94af0593aec"
21 | cache-control: max-age=0, private, must-revalidate
22 | x-request-id: 66e9c1b313caaa0c6105eb538a46e1aa
23 | x-runtime: 0.026786
24 | x-swiftype-datacenter: dal05
25 | x-swiftype-frontend-node: web01.dal05
26 | x-swiftype-edge-node: web01.dal05
27 |
28 | {"meta":{"page":{"current":2,"total_pages":3,"total_results":3,"size":1}},"results":[{"name":"ruby-gems","type":"default","language":null}]}
29 |
--------------------------------------------------------------------------------
/fixtures/host-c5s2mj.api.swiftype.com-443/154775139235186519:
--------------------------------------------------------------------------------
1 | GET /api/as/v1/engines/swiftype-api-example/curations
2 | x-swiftype-client: elastic-app-search-node
3 | x-swiftype-client-version: 8.3.2
4 | content-type: application/json
5 | host: host-c5s2mj.api.swiftype.com
6 | authorization: Bearer api-mu75psc5egt9ppzuycnc2mc3
7 | accept: application/json
8 | body: {}
9 |
10 | HTTP/1.1 200 OK
11 | date: Thu, 17 Jan 2019 18:56:32 GMT
12 | content-type: application/json; charset=utf-8
13 | transfer-encoding: chunked
14 | connection: close
15 | status: 401 Unauthorized
16 | x-frame-options: SAMEORIGIN
17 | x-xss-protection: 1; mode=block
18 | x-content-type-options: nosniff
19 | www-authenticate: Basic realm="Swiftype"
20 | vary: Origin
21 | cache-control: no-cache
22 | x-request-id: c3f4f569d971ad431d1287b3caa52c31
23 | x-runtime: 0.035655
24 | x-swiftype-frontend-datacenter: dal05
25 | x-swiftype-frontend-node: web02.dal05
26 | x-swiftype-edge-datacenter: dal05
27 | x-swiftype-edge-node: web02.dal05
28 |
29 | {"meta":{"page":{"current":1,"total_pages":1,"total_results":2,"size":25}},"results":[{"id":"cur-9382","queries":["mew hiss"],"promoted":["INscMGmhmX4"],"hidden":["JNDFojsd02"]},{"id":"cur-378291","queries":["grrr scratch"],"promoted":["JNDFojsd02"],"hidden":["INscMGmhmX4"]}]}
30 |
--------------------------------------------------------------------------------
/fixtures/host-c5s2mj.api.swiftype.com-443/154775139267473451:
--------------------------------------------------------------------------------
1 | GET /api/as/v1/engines/swiftype-api-example/curations?page[current]=2&page[size]=1
2 | x-swiftype-client: elastic-app-search-node
3 | x-swiftype-client-version: 8.3.2
4 | content-type: application/json
5 | host: host-c5s2mj.api.swiftype.com
6 | authorization: Bearer api-mu75psc5egt9ppzuycnc2mc3
7 | accept: application/json
8 | body: {}
9 |
10 | HTTP/1.1 200 OK
11 | date: Thu, 17 Jan 2019 18:56:32 GMT
12 | content-type: application/json; charset=utf-8
13 | transfer-encoding: chunked
14 | connection: close
15 | vary: Accept-Encoding, Accept-Encoding, Origin
16 | status: 200 OK
17 | x-frame-options: SAMEORIGIN
18 | x-xss-protection: 1; mode=block
19 | x-content-type-options: nosniff
20 | www-authenticate: Basic realm="Swiftype"
21 | cache-control: no-cache
22 | x-request-id: 03632a579705aa38573c9fcbd29c0be2
23 | x-runtime: 0.021305
24 | x-swiftype-frontend-datacenter: dal05
25 | x-swiftype-frontend-node: web02.dal05
26 | x-swiftype-edge-datacenter: dal05
27 | x-swiftype-edge-node: web02.dal05
28 |
29 | {"meta":{"page":{"current":2,"total_pages":2,"total_results":2,"size":1}},"results":[{"id":"cur-378291","queries":["grrr scratch"],"promoted":["JNDFojsd02"],"hidden":["INscMGmhmX4"]}]}
30 |
--------------------------------------------------------------------------------
/fixtures/host-c5s2mj.api.swiftype.com-443/154775207819050958:
--------------------------------------------------------------------------------
1 | GET /api/as/v1/engines/swiftype-api-example/curations/cur-9382
2 | x-swiftype-client: elastic-app-search-node
3 | x-swiftype-client-version: 8.3.2
4 | content-type: application/json
5 | host: host-c5s2mj.api.swiftype.com
6 | authorization: Bearer api-mu75psc5egt9ppzuycnc2mc3
7 | accept: application/json
8 | body: {}
9 |
10 | HTTP/1.1 200 OK
11 | date: Thu, 17 Jan 2019 19:07:58 GMT
12 | content-type: application/json; charset=utf-8
13 | transfer-encoding: chunked
14 | connection: close
15 | status: 200 OK
16 | x-frame-options: SAMEORIGIN
17 | x-xss-protection: 1; mode=block
18 | x-content-type-options: nosniff
19 | www-authenticate: Basic realm="Swiftype"
20 | vary: Origin
21 | cache-control: no-cache
22 | x-request-id: 401c83bae4b9e9be82acf7366a8a15bd
23 | x-runtime: 0.017829
24 | x-swiftype-frontend-datacenter: dal05
25 | x-swiftype-frontend-node: web02.dal05
26 | x-swiftype-edge-datacenter: dal05
27 | x-swiftype-edge-node: web02.dal05
28 |
29 | {"meta":{"page":{"current":1,"total_pages":1,"total_results":1,"size":25}},"results":[{"id":"cur-9382","queries":["mew hiss"],"promoted":["INscMGmhmX4"],"hidden":["JNDFojsd02"]}]}
30 |
--------------------------------------------------------------------------------
/fixtures/host-c5s2mj.api.swiftype.com-443/154775369860896529:
--------------------------------------------------------------------------------
1 | POST /api/as/v1/engines/swiftype-api-example/curations
2 | x-swiftype-client: elastic-app-search-node
3 | x-swiftype-client-version: 8.3.2
4 | content-type: application/json
5 | host: host-c5s2mj.api.swiftype.com
6 | authorization: Bearer api-mu75psc5egt9ppzuycnc2mc3
7 | accept: application/json
8 | body: {\"queries\":[\"arf arf\"],\"promoted\":[],\"hidden\":[\"INscMGmhmX4\",\"JNDFojsd02\"]}
9 |
10 | HTTP/1.1 200 OK
11 | date: Thu, 17 Jan 2019 19:34:58 GMT
12 | content-type: application/json; charset=utf-8
13 | transfer-encoding: chunked
14 | connection: close
15 | status: 200 OK
16 | x-frame-options: SAMEORIGIN
17 | x-xss-protection: 1; mode=block
18 | x-content-type-options: nosniff
19 | www-authenticate: Basic realm="Swiftype"
20 | vary: Origin
21 | cache-control: no-cache
22 | x-request-id: ffc905295c0530ef21f71cf5c322ba91
23 | x-runtime: 0.021898
24 | x-swiftype-frontend-datacenter: dal05
25 | x-swiftype-frontend-node: web02.dal05
26 | x-swiftype-edge-datacenter: dal05
27 | x-swiftype-edge-node: web02.dal05
28 |
29 | {"id": "cur-9043829"}
30 |
--------------------------------------------------------------------------------
/fixtures/host-c5s2mj.api.swiftype.com-443/154775388696063661:
--------------------------------------------------------------------------------
1 | DELETE /api/as/v1/engines/swiftype-api-example/curations/cur-378291
2 | x-swiftype-client: elastic-app-search-node
3 | x-swiftype-client-version: 8.3.2
4 | content-type: application/json
5 | host: host-c5s2mj.api.swiftype.com
6 | authorization: Bearer api-mu75psc5egt9ppzuycnc2mc3
7 | accept: application/json
8 | body: {}
9 |
10 | HTTP/1.1 200 OK
11 | date: Thu, 17 Jan 2019 19:38:06 GMT
12 | content-type: application/json; charset=utf-8
13 | transfer-encoding: chunked
14 | connection: close
15 | status: 200 OK
16 | x-frame-options: SAMEORIGIN
17 | x-xss-protection: 1; mode=block
18 | x-content-type-options: nosniff
19 | www-authenticate: Basic realm="Swiftype"
20 | vary: Origin
21 | cache-control: no-cache
22 | x-request-id: 83fb305ee12bfe40282325524789753f
23 | x-runtime: 0.024097
24 | x-swiftype-frontend-datacenter: dal05
25 | x-swiftype-frontend-node: web01.dal05
26 | x-swiftype-edge-datacenter: dal05
27 | x-swiftype-edge-node: web01.dal05
28 |
29 | {"deleted": true}
30 |
--------------------------------------------------------------------------------
/fixtures/host-c5s2mj.api.swiftype.com-443/154775411564681581:
--------------------------------------------------------------------------------
1 | PUT /api/as/v1/engines/swiftype-api-example/curations/cur-9382
2 | x-swiftype-client: elastic-app-search-node
3 | x-swiftype-client-version: 8.3.2
4 | content-type: application/json
5 | host: host-c5s2mj.api.swiftype.com
6 | authorization: Bearer api-mu75psc5egt9ppzuycnc2mc3
7 | accept: application/json
8 | body: {\"queries\":[\"mew hiss\",\"sideways hop\"],\"promoted\":[\"INscMGmhmX4\"],\"hidden\":[\"JNDFojsd02\"]}
9 |
10 | HTTP/1.1 200 OK
11 | date: Thu, 17 Jan 2019 19:41:55 GMT
12 | content-type: application/json; charset=utf-8
13 | transfer-encoding: chunked
14 | connection: close
15 | status: 200 OK
16 | x-frame-options: SAMEORIGIN
17 | x-xss-protection: 1; mode=block
18 | x-content-type-options: nosniff
19 | www-authenticate: Basic realm="Swiftype"
20 | vary: Origin
21 | cache-control: no-cache
22 | x-request-id: c7f73cec2a3c5ac9177e93cf6ac32032
23 | x-runtime: 0.020304
24 | x-swiftype-frontend-datacenter: dal05
25 | x-swiftype-frontend-node: web02.dal05
26 | x-swiftype-edge-datacenter: dal05
27 | x-swiftype-edge-node: web02.dal05
28 |
29 | {"id": "cur-9382"}
30 |
--------------------------------------------------------------------------------
/fixtures/host-c5s2mj.api.swiftype.com-443/154775710380210466:
--------------------------------------------------------------------------------
1 | GET /api/as/v1/engines/swiftype-api-example/curations/cur-0000
2 | x-swiftype-client: elastic-app-search-node
3 | x-swiftype-client-version: 8.3.2
4 | content-type: application/json
5 | host: host-c5s2mj.api.swiftype.com
6 | authorization: Bearer api-mu75psc5egt9ppzuycnc2mc3
7 | accept: application/json
8 | body: {}
9 |
10 | HTTP/1.1 404 Not Found
11 | date: Thu, 17 Jan 2019 20:31:43 GMT
12 | content-type: application/json; charset=utf-8
13 | transfer-encoding: chunked
14 | connection: close
15 | status: 404 Not Found
16 | x-frame-options: SAMEORIGIN
17 | x-xss-protection: 1; mode=block
18 | x-content-type-options: nosniff
19 | www-authenticate: Basic realm="Swiftype"
20 | vary: Origin
21 | cache-control: no-cache
22 | x-request-id: fc44266bdab7628ed1cdef8e6af334b0
23 | x-runtime: 0.030466
24 | x-swiftype-frontend-datacenter: dal05
25 | x-swiftype-frontend-node: web02.dal05
26 | x-swiftype-edge-datacenter: dal05
27 | x-swiftype-edge-node: web02.dal05
28 |
29 | {"errors":["Curation not found with id: cur-0000"]}
30 |
--------------------------------------------------------------------------------
/fixtures/host-c5s2mj.api.swiftype.com-443/154844848285366145:
--------------------------------------------------------------------------------
1 | POST /api/as/v1/engines/swiftype-api-example/multi_search
2 | x-swiftype-client: elastic-app-search-node
3 | x-swiftype-client-version: 8.3.2
4 | content-type: application/json
5 | host: host-c5s2mj.api.swiftype.com
6 | authorization: Bearer api-mu75psc5egt9ppzuycnc2mc3
7 | accept: application/json
8 | body: {\"queries\":[{\"query\":\"cat\"},{\"query\":\"grumpy\"}]}
9 |
10 | HTTP/1.1 200 OK
11 | date: Fri, 25 Jan 2019 20:34:42 GMT
12 | content-type: application/json; charset=utf-8
13 | transfer-encoding: chunked
14 | connection: close
15 | vary: Accept-Encoding, Accept-Encoding, Origin
16 | status: 200 OK
17 | x-frame-options: SAMEORIGIN
18 | x-xss-protection: 1; mode=block
19 | x-content-type-options: nosniff
20 | x-ratelimit-limit: 2400
21 | x-ratelimit-remaining: 2397
22 | etag: W/"97487a783361f0beef00632c932f7d96"
23 | cache-control: max-age=0, private, must-revalidate
24 | x-request-id: 40b424eace5b5edd28157be497d52c7b
25 | x-runtime: 0.082590
26 | x-swiftype-frontend-datacenter: dal05
27 | x-swiftype-frontend-node: web01.dal05
28 | x-swiftype-edge-datacenter: dal05
29 | x-swiftype-edge-node: web01.dal05
30 |
31 | [{"meta":{"warnings":[],"page":{"current":1,"total_pages":1,"total_results":2,"size":10}},"results":[{"body":{"raw":"A wonderful video of a magnificent cat."},"title":{"raw":"The Original Grumpy Cat"},"url":{"raw":"https://www.youtube.com/watch?v=INscMGmhmX4"},"id":{"raw":"INscMGmhmX4"},"_meta":{"score":0.459039}},{"body":{"raw":"A great video of another cool cat."},"title":{"raw":"Another Grumpy Cat"},"url":{"raw":"https://www.youtube.com/watch?v=dQw4w9WgXcQ"},"id":{"raw":"JNDFojsd02"},"_meta":{"score":0.4121608}}]},{"meta":{"warnings":[],"page":{"current":1,"total_pages":1,"total_results":2,"size":10}},"results":[{"body":{"raw":"A great video of another cool cat."},"title":{"raw":"Another Grumpy Cat"},"url":{"raw":"https://www.youtube.com/watch?v=dQw4w9WgXcQ"},"id":{"raw":"JNDFojsd02"},"_meta":{"score":0.39919013}},{"body":{"raw":"A wonderful video of a magnificent cat."},"title":{"raw":"The Original Grumpy Cat"},"url":{"raw":"https://www.youtube.com/watch?v=INscMGmhmX4"},"id":{"raw":"INscMGmhmX4"},"_meta":{"score":0.39919013}}]}]
32 |
--------------------------------------------------------------------------------
/fixtures/host-c5s2mj.api.swiftype.com-443/154905605865969485:
--------------------------------------------------------------------------------
1 | POST /api/as/v1/engines/swiftype-api-example/query_suggestion
2 | x-swiftype-client: elastic-app-search-node
3 | x-swiftype-client-version: 8.3.2
4 | content-type: application/json
5 | host: host-c5s2mj.api.swiftype.com
6 | authorization: Bearer api-mu75psc5egt9ppzuycnc2mc3
7 | accept: application/json
8 | body: {\"query\":\"cat\"}
9 |
10 | HTTP/1.1 200 OK
11 | date: Fri, 01 Feb 2019 21:20:58 GMT
12 | content-type: application/json; charset=utf-8
13 | transfer-encoding: chunked
14 | connection: close
15 | vary: Accept-Encoding, Accept-Encoding, Origin
16 | status: 200 OK
17 | x-frame-options: SAMEORIGIN
18 | x-xss-protection: 1; mode=block
19 | x-content-type-options: nosniff
20 | x-ratelimit-limit: 6000
21 | x-ratelimit-remaining: 5994
22 | etag: W/"527a446e8aab1ba89e4c08f53dbbd470"
23 | cache-control: max-age=0, private, must-revalidate
24 | x-request-id: 7414beb06c644b1aa88accb6019c6d6f
25 | x-runtime: 0.168318
26 | x-swiftype-frontend-datacenter: dal05
27 | x-swiftype-frontend-node: web02.dal05
28 | x-swiftype-edge-datacenter: dal05
29 | x-swiftype-edge-node: web02.dal05
30 |
31 | {"results":{"documents":[{"suggestion":"cat"}]},"meta":{"request_id":"7414beb06c644b1aa88accb6019c6d6f"}}
32 |
--------------------------------------------------------------------------------
/fixtures/host-c5s2mj.api.swiftype.com-443/154905668608952844:
--------------------------------------------------------------------------------
1 | POST /api/as/v1/engines/swiftype-api-example/query_suggestion
2 | x-swiftype-client: elastic-app-search-node
3 | x-swiftype-client-version: 8.3.2
4 | content-type: application/json
5 | host: host-c5s2mj.api.swiftype.com
6 | authorization: Bearer api-mu75psc5egt9ppzuycnc2mc3
7 | accept: application/json
8 | body: {\"size\":3,\"types\":{\"documents\":{\"fields\":[\"title\"]}},\"query\":\"cat\"}
9 |
10 | HTTP/1.1 200 OK
11 | date: Fri, 01 Feb 2019 21:31:26 GMT
12 | content-type: application/json; charset=utf-8
13 | transfer-encoding: chunked
14 | connection: close
15 | vary: Accept-Encoding, Accept-Encoding, Origin
16 | status: 200 OK
17 | x-frame-options: SAMEORIGIN
18 | x-xss-protection: 1; mode=block
19 | x-content-type-options: nosniff
20 | x-ratelimit-limit: 6000
21 | x-ratelimit-remaining: 5999
22 | etag: W/"c6568d07f99643b506ad7a1b1e5c70dd"
23 | cache-control: max-age=0, private, must-revalidate
24 | x-request-id: a0e24c46d4379e58bf871a6a515b8d94
25 | x-runtime: 0.124237
26 | x-swiftype-frontend-datacenter: dal05
27 | x-swiftype-frontend-node: web01.dal05
28 | x-swiftype-edge-datacenter: dal05
29 | x-swiftype-edge-node: web01.dal05
30 |
31 | {"results":{"documents":[{"suggestion":"cat"}]},"meta":{"request_id":"a0e24c46d4379e58bf871a6a515b8d94"}}
32 |
--------------------------------------------------------------------------------
/fixtures/host-c5s2mj.api.swiftype.com-443/158153945918596904:
--------------------------------------------------------------------------------
1 | POST /api/as/v1/engines
2 | x-swiftype-client: elastic-app-search-node
3 | x-swiftype-client-version: 8.3.2
4 | content-type: application/json
5 | host: host-c5s2mj.api.swiftype.com
6 | authorization: Bearer api-mu75psc5egt9ppzuycnc2mc3
7 | accept: application/json
8 | body: {\"name\":\"new-engine\",\"language\":\"en\"}
9 |
10 | HTTP/1.1 200 OK
11 | date: Wed, 12 Feb 2020 20:30:58 GMT
12 | content-type: application/json; charset=utf-8
13 | transfer-encoding: chunked
14 | connection: close
15 | vary: Accept-Encoding, Accept-Encoding, Origin
16 | status: 200 OK
17 | x-frame-options: SAMEORIGIN
18 | x-xss-protection: 1; mode=block
19 | x-content-type-options: nosniff
20 | x-swiftype-backend-region: dal
21 | x-swiftype-backend-datacenter: dal10
22 | x-swiftype-backend-node: app-api02b.dal10
23 | x-ratelimit-limit: 15000
24 | x-ratelimit-remaining: 14999
25 | etag: W/"86b23aaaf3bab9826cdf8c71cbc909f2"
26 | cache-control: max-age=0, private, must-revalidate
27 | x-request-id: 047d3cd04240c5599dd99b3692df7a8a
28 | x-runtime: 0.583643
29 | x-swiftype-frontend-datacenter: dal10
30 | x-swiftype-frontend-node: web02b.dal10
31 | x-swiftype-edge-datacenter: dal10
32 | x-swiftype-edge-node: web02b.dal10
33 |
34 | {"name":"new-engine","type":"default","language":"en"}
35 |
--------------------------------------------------------------------------------
/fixtures/host-c5s2mj.api.swiftype.com-443/158646044979291659:
--------------------------------------------------------------------------------
1 | GET /api/as/v1/engines/swiftype-api-example/documents/list
2 | x-swiftype-client: elastic-app-search-node
3 | x-swiftype-client-version: 8.3.2
4 | content-type: application/json
5 | host: host-c5s2mj.api.swiftype.com
6 | authorization: Bearer api-mu75psc5egt9ppzuycnc2mc3
7 | accept: application/json
8 | body: {}
9 |
10 | HTTP/1.1 200 OK
11 | date: Thu, 09 Apr 2020 19:27:29 GMT
12 | content-type: application/json; charset=utf-8
13 | transfer-encoding: chunked
14 | connection: close
15 | vary: Accept-Encoding, Accept-Encoding, Origin
16 | status: 200 OK
17 | x-frame-options: SAMEORIGIN
18 | x-xss-protection: 1; mode=block
19 | x-content-type-options: nosniff
20 | x-swiftype-backend-region: dal
21 | x-swiftype-backend-datacenter: dal10
22 | x-swiftype-backend-node: app-api01a.dal10
23 | x-ratelimit-limit: 15000
24 | x-ratelimit-remaining: 14998
25 | etag: W/"da0db5a8dc938c11da9bd83edab9628b"
26 | cache-control: max-age=0, private, must-revalidate
27 | x-request-id: e495da5964b2e17ede42ee2f58430f54
28 | x-runtime: 0.042386
29 | x-swiftype-frontend-datacenter: dal12
30 | x-swiftype-frontend-node: web01a.dal12
31 | x-swiftype-edge-datacenter: dal12
32 | x-swiftype-edge-node: web01a.dal12
33 |
34 | {"meta":{"page":{"current":1,"total_pages":1,"total_results":3,"size":100}},"results":[{"body":"this is a test","id":"INscMGmhmX4","title":"The Original Grumpy Cat","url":"http://www.youtube.com/watch?v=v1uyQZNg2vE"},{"body":"this is also a test","id":"JNDFojsd02","title":"Another Grumpy Cat","url":"http://www.youtube.com/watch?v=tsdfhk2j"}]}
35 |
--------------------------------------------------------------------------------
/fixtures/host-c5s2mj.api.swiftype.com-443/158646109175536388:
--------------------------------------------------------------------------------
1 | PATCH /api/as/v1/engines/swiftype-api-example/documents
2 | x-swiftype-client: elastic-app-search-node
3 | x-swiftype-client-version: 8.3.2
4 | content-type: application/json
5 | host: host-c5s2mj.api.swiftype.com
6 | authorization: Bearer api-mu75psc5egt9ppzuycnc2mc3
7 | accept: application/json
8 | body: [{\"id\":\"INscMGmhmX4\",\"url\":\"http://www.youtube.com/watch?v=v1uyQZNg2vE\",\"title\":\"The Original Grumpy Cat\",\"body\":\"this is a test\"},{\"id\":\"JNDFojsd02\",\"url\":\"http://www.youtube.com/watch?v=tsdfhk2j\",\"title\":\"Another Grumpy Cat\",\"body\":\"this is also a test\"}]
9 |
10 | HTTP/1.1 200 OK
11 | date: Thu, 09 Apr 2020 19:38:11 GMT
12 | content-type: application/json; charset=utf-8
13 | transfer-encoding: chunked
14 | connection: close
15 | vary: Accept-Encoding, Accept-Encoding, Origin
16 | status: 200 OK
17 | x-frame-options: SAMEORIGIN
18 | x-xss-protection: 1; mode=block
19 | x-content-type-options: nosniff
20 | x-swiftype-backend-region: dal
21 | x-swiftype-backend-datacenter: dal10
22 | x-swiftype-backend-node: app-api02b.dal10
23 | x-ratelimit-limit: 360000
24 | x-ratelimit-remaining: 359997
25 | etag: W/"1c505a964639396d60ef34105952fd98"
26 | cache-control: max-age=0, private, must-revalidate
27 | x-request-id: ca2fd5ce01ab2f2b29dfbb8bcc3430e8
28 | x-runtime: 0.061411
29 | x-swiftype-frontend-datacenter: dal12
30 | x-swiftype-frontend-node: web01a.dal12
31 | x-swiftype-edge-datacenter: dal12
32 | x-swiftype-edge-node: web01a.dal12
33 |
34 | [{"id":"INscMGmhmX4","errors":[]},{"id":"JNDFojsd02","errors":[]}]
35 |
--------------------------------------------------------------------------------
/fixtures/host-c5s2mj.api.swiftype.com-443/401:
--------------------------------------------------------------------------------
1 | GET /api/as/v1/engines/swiftype-api-example/search
2 | x-swiftype-client: elastic-app-search-node
3 | x-swiftype-client-version: 8.3.2
4 | content-type: application/json
5 | host: host-c5s2mj.api.swiftype.com
6 | authorization: Bearer invalid
7 | accept: application/json
8 | body: {\"query\":\"cat\"}
9 |
10 | HTTP/1.1 401 Unauthorized
11 | date: Mon, 28 Jan 2019 15:29:54 GMT
12 | content-type: application/json; charset=utf-8
13 | transfer-encoding: chunked
14 | connection: close
15 | status: 401 Unauthorized
16 | x-frame-options: SAMEORIGIN
17 | x-xss-protection: 1; mode=block
18 | x-content-type-options: nosniff
19 | www-authenticate: Basic realm="Swiftype"
20 | vary: Origin
21 | cache-control: no-cache
22 | x-request-id: 35f86e7b2507d8437778666411599f3e
23 | x-runtime: 0.022311
24 | x-swiftype-frontend-datacenter: dal05
25 | x-swiftype-frontend-node: web02.dal05
26 | x-swiftype-edge-datacenter: dal05
27 | x-swiftype-edge-node: web02.dal05
28 |
29 | {"error":"Invalid authentication token."}
30 |
--------------------------------------------------------------------------------
/fixtures/host-c5s2mj.api.swiftype.com-443/404:
--------------------------------------------------------------------------------
1 | GET /api/as/v1/engines/invalid-engine-name/search
2 | x-swiftype-client: elastic-app-search-node
3 | x-swiftype-client-version: 8.3.2
4 | content-type: application/json
5 | host: host-c5s2mj.api.swiftype.com
6 | authorization: Bearer api-mu75psc5egt9ppzuycnc2mc3
7 | accept: application/json
8 | body: {\"query\":\"cat\"}
9 |
10 | HTTP/1.1 404 Not Found
11 | date: Mon, 28 Jan 2019 15:31:36 GMT
12 | content-type: application/json; charset=utf-8
13 | transfer-encoding: chunked
14 | connection: close
15 | vary: Accept-Encoding, Accept-Encoding, Origin
16 | status: 404 Not Found
17 | x-frame-options: SAMEORIGIN
18 | x-xss-protection: 1; mode=block
19 | x-content-type-options: nosniff
20 | x-ratelimit-limit: 6000
21 | x-ratelimit-remaining: 5999
22 | cache-control: no-cache
23 | x-request-id: 7a6ce42f728bac23a887f64468a41a8d
24 | x-runtime: 0.044010
25 | x-swiftype-frontend-datacenter: dal05
26 | x-swiftype-frontend-node: web02.dal05
27 | x-swiftype-edge-datacenter: dal05
28 | x-swiftype-edge-node: web02.dal05
29 |
30 | {"errors":["Could not find engine."]}
31 |
--------------------------------------------------------------------------------
/fixtures/host-c5s2mj.api.swiftype.com-443/multiSearchErrors:
--------------------------------------------------------------------------------
1 | POST /api/as/v1/engines/swiftype-api-example/multi_search
2 | x-swiftype-client: elastic-app-search-node
3 | x-swiftype-client-version: 8.3.2
4 | content-type: application/json
5 | host: host-c5s2mj.api.swiftype.com
6 | authorization: Bearer api-mu75psc5egt9ppzuycnc2mc3
7 | accept: application/json
8 | body: {\"queries\":[{\"badField\":\"whatever\",\"query\":\"cat\"},{\"query\":\"grumpy\"}]}
9 |
10 | HTTP/1.1 400 Bad Request
11 | date: Mon, 28 Jan 2019 15:55:46 GMT
12 | content-type: application/json; charset=utf-8
13 | transfer-encoding: chunked
14 | connection: close
15 | status: 400 Bad Request
16 | x-frame-options: SAMEORIGIN
17 | x-xss-protection: 1; mode=block
18 | x-content-type-options: nosniff
19 | x-ratelimit-limit: 6000
20 | x-ratelimit-remaining: 5999
21 | vary: Origin
22 | cache-control: no-cache
23 | x-request-id: 9660acf321b86cb57033aa1447a84249
24 | x-runtime: 0.093594
25 | x-swiftype-frontend-datacenter: dal05
26 | x-swiftype-frontend-node: web02.dal05
27 | x-swiftype-edge-datacenter: dal05
28 | x-swiftype-edge-node: web02.dal05
29 |
30 | [{"errors":["Options contains invalid key: badField"]},{"errors":[]}]
31 |
--------------------------------------------------------------------------------
/fixtures/localhost-3002/15815391173475749:
--------------------------------------------------------------------------------
1 | POST /api/as/v1/engines
2 | x-swiftype-client: elastic-app-search-node
3 | x-swiftype-client-version: 8.3.2
4 | content-type: application/json
5 | host: localhost:3002
6 | authorization: Bearer api-mu75psc5egt9ppzuycnc2mc3
7 | accept: application/json
8 | body: {\"name\":\"new-meta-engine\",\"type\":\"meta\",\"source_engines\":[\"source-engine-1\",\"source-engine-2\"]}
9 |
10 | HTTP/1.1 200 OK
11 | x-frame-options: SAMEORIGIN
12 | x-xss-protection: 1; mode=block
13 | x-content-type-options: nosniff
14 | x-app-search-version: 8.3.2
15 | content-type: application/json; charset=utf-8
16 | vary: Origin
17 | etag: W/"cdb1493c20f29d2310c3222758a6c07b"
18 | cache-control: max-age=0, private, must-revalidate
19 | x-request-id: a644b984-1214-4b9c-992e-0382e4443bf6
20 | x-runtime: 0.534623
21 | connection: close
22 | transfer-encoding: chunked
23 |
24 | {"name":"new-meta-engine","type":"meta","source_engines":["source-engine-1","source-engine-2"]}
25 |
--------------------------------------------------------------------------------
/fixtures/localhost-3002/15815978342879229:
--------------------------------------------------------------------------------
1 | POST /api/as/v1/engines/new-meta-engine/source_engines
2 | x-swiftype-client: elastic-app-search-node
3 | x-swiftype-client-version: 8.3.2
4 | content-type: application/json
5 | host: localhost:3002
6 | authorization: Bearer api-mu75psc5egt9ppzuycnc2mc3
7 | accept: application/json
8 | body: [\"source-engine-3\"]
9 |
10 | HTTP/1.1 200 OK
11 | x-frame-options: SAMEORIGIN
12 | x-xss-protection: 1; mode=block
13 | x-content-type-options: nosniff
14 | x-app-search-version: 8.3.2
15 | content-type: application/json; charset=utf-8
16 | vary: Origin
17 | etag: W/"3d866b390e18c1bf51503b81707dc011"
18 | cache-control: max-age=0, private, must-revalidate
19 | x-request-id: 95a0ef7d-ea6a-48f7-ba28-826f62c3e4fc
20 | x-runtime: 0.355023
21 | connection: close
22 | transfer-encoding: chunked
23 |
24 | {"name":"new-meta-engine","type":"meta","source_engines":["source-engine-1","source-engine-2","source-engine-3"]}
25 |
--------------------------------------------------------------------------------
/fixtures/localhost-3002/158159799829857287:
--------------------------------------------------------------------------------
1 | DELETE /api/as/v1/engines/new-meta-engine/source_engines
2 | x-swiftype-client: elastic-app-search-node
3 | x-swiftype-client-version: 8.3.2
4 | content-type: application/json
5 | host: localhost:3002
6 | authorization: Bearer api-mu75psc5egt9ppzuycnc2mc3
7 | accept: application/json
8 | body: [\"source-engine-3\"]
9 |
10 | HTTP/1.1 200 OK
11 | x-frame-options: SAMEORIGIN
12 | x-xss-protection: 1; mode=block
13 | x-content-type-options: nosniff
14 | x-app-search-version: 8.3.2
15 | content-type: application/json; charset=utf-8
16 | vary: Origin
17 | etag: W/"cdb1493c20f29d2310c3222758a6c07b"
18 | cache-control: max-age=0, private, must-revalidate
19 | x-request-id: a0cc4718-3a7d-4b60-9cd2-32684a582e96
20 | x-runtime: 0.305840
21 | connection: close
22 | transfer-encoding: chunked
23 |
24 | {"name":"new-meta-engine","type":"meta","source_engines":["source-engine-1","source-engine-2"]}
25 |
--------------------------------------------------------------------------------
/fixtures/localhost-3002/get-schema:
--------------------------------------------------------------------------------
1 | GET /api/as/v1/engines/swiftype-api-example/schema
2 | x-swiftype-client: elastic-app-search-node
3 | x-swiftype-client-version: 8.3.2
4 | content-type: application/json
5 | host: localhost:3002
6 | authorization: Bearer api-mu75psc5egt9ppzuycnc2mc3
7 | accept: application/json
8 | body: {}
9 |
10 | HTTP/1.1 200 OK
11 | x-frame-options: SAMEORIGIN
12 | x-xss-protection: 1; mode=block
13 | x-content-type-options: nosniff
14 | x-app-search-version: 8.3.2
15 | content-type: application/json; charset=utf-8
16 | vary: Origin
17 | etag: W/"ac828da6380c08d10df2df85cf73c250"
18 | cache-control: max-age=0, private, must-revalidate
19 | x-request-id: 952ae812-c1fc-4292-bff5-e17276f6ea10
20 | x-runtime: 0.042176
21 | connection: close
22 | server: Jetty(9.2.29.v20191105)
23 |
24 | {"location":"geolocation","created_date":"date","title":"text","url":"text","views":"number"}
25 |
--------------------------------------------------------------------------------
/fixtures/localhost-3002/update-schema:
--------------------------------------------------------------------------------
1 | POST /api/as/v1/engines/swiftype-api-example/schema
2 | x-swiftype-client: elastic-app-search-node
3 | x-swiftype-client-version: 8.3.2
4 | content-type: application/json
5 | host: localhost:3002
6 | authorization: Bearer api-mu75psc5egt9ppzuycnc2mc3
7 | accept: application/json
8 | body: {\"views\":\"text\"}
9 |
10 | HTTP/1.1 200 OK
11 | x-frame-options: SAMEORIGIN
12 | x-xss-protection: 1; mode=block
13 | x-content-type-options: nosniff
14 | x-app-search-version: 8.3.2
15 | content-type: application/json; charset=utf-8
16 | vary: Origin
17 | etag: W/"5669e9ad4cde52b649ba90479fff9e1f"
18 | cache-control: max-age=0, private, must-revalidate
19 | x-request-id: f80545b5-9269-43e7-844d-5dae39c4c172
20 | x-runtime: 0.184414
21 | connection: close
22 | server: Jetty(9.2.29.v20191105)
23 |
24 | {"location":"geolocation","created_date":"date","title":"text","url":"text","views":"text"}
25 |
--------------------------------------------------------------------------------
/lib/appSearch.js:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright 2019 Elastic and contributors
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 |
17 | */
18 |
19 | const Client = require('./client')
20 | const jwt = require('jsonwebtoken')
21 |
22 | const SIGNED_SEARCH_TOKEN_JWT_ALGORITHM = 'HS256'
23 |
24 | const DEFAULT_BASE_URL_FN = (accountHostKey) => {
25 | return `https://${accountHostKey}.api.swiftype.com/api/as/v1/`
26 | }
27 |
28 | /**
29 | * Takes a paging object in the format {current, page} and converts it to an array of query string parameters, like
30 | * ['page[current]=1', 'page[size]=10']
31 | */
32 | function buildPagingParams(options) {
33 | return Object.keys(options).map(key => {
34 | const value = options[key]
35 | if (!value) return null
36 | return `page[${key}]=${value}`
37 | }).filter(v => v)
38 | }
39 |
40 | class AppSearchClient {
41 | constructor(accountHostKey, apiKey, baseUrlFn = DEFAULT_BASE_URL_FN) {
42 | const baseUrl = baseUrlFn(accountHostKey)
43 | this.client = new Client(apiKey, baseUrl)
44 | }
45 |
46 | /**
47 | * Send a search request to the App Search Api
48 | * https://swiftype.com/documentation/app-search/api/overview
49 | *
50 | * @param {String} engineName unique Engine name
51 | * @param {String} query String that is used to perform a search request.
52 | * @param {Object} options Object used for configuring the search like search_fields and result_fields
53 | * @returns {Promise} a Promise that returns a result {Object} when resolved, otherwise throws an Error.
54 | */
55 | search(engineName, query, options = {}) {
56 | options = Object.assign({}, options, { query })
57 | return this.client.get(`engines/${encodeURIComponent(engineName)}/search`, options)
58 | }
59 |
60 | /**
61 | * Run multiple searches for documents on a single request
62 | *
63 | * @param {String} engineName unique Engine name
64 | * @param {Array} searches Searches to execute, [{query: String, options: Object}]
65 | * @returns {Promise} a Promise that returns an array of results {Object} when resolved, otherwise throws an Error.
66 | */
67 | multiSearch(engineName, searches) {
68 | searches = searches.map(search => {
69 | return Object.assign({}, search.options, { query: search.query })
70 | })
71 |
72 | return this.client.post(`engines/${encodeURIComponent(engineName)}/multi_search`, {
73 | queries: searches
74 | })
75 | }
76 |
77 | /**
78 | * Sends a query suggestion request to the App Search Api
79 | *
80 | * @param {String} engineName unique Engine name
81 | * @param {String} query String that is used to perform a query suggestion request.
82 | * @param {Object} options Object used for configuring the request
83 | */
84 | querySuggestion(engineName, query, options = {}) {
85 | options = Object.assign({}, options, { query })
86 | return this.client.post(`engines/${encodeURIComponent(engineName)}/query_suggestion`, options)
87 | }
88 |
89 | /**
90 | * Index a document.
91 | *
92 | * @param {String} engineName unique Engine name
93 | * @param {Object} document document object to be indexed.
94 | * @returns {Promise} a Promise that returns a result {Object} when resolved, otherwise throws an Error.
95 | */
96 | indexDocument(engineName, document) {
97 | return this.indexDocuments(engineName, [document])
98 | .then((processedDocumentResults) => {
99 | return new Promise((resolve, reject) => {
100 |
101 | const processedDocumentResult = processedDocumentResults[0]
102 | const errors = processedDocumentResult['errors']
103 | if (errors.length) {
104 | reject(new Error(errors.join('; ')))
105 | }
106 | delete processedDocumentResult['errors']
107 | resolve(processedDocumentResult)
108 | })
109 | })
110 | }
111 |
112 | /**
113 | * Index a batch of documents.
114 | *
115 | * @param {String} engineName unique Engine name
116 | * @param {Array} documents Array of document objects to be indexed.
117 | * @returns {Promise>} a Promise that returns a result {Object} when resolved, otherwise throws an Error.
118 | */
119 | indexDocuments(engineName, documents) {
120 | return this.client.post(`engines/${encodeURIComponent(engineName)}/documents`, documents)
121 | }
122 |
123 | /**
124 | * Partial update a batch of documents.
125 | *
126 | * @param {String} engineName unique Engine name
127 | * @param {Array} documents Array of document objects to be updated.
128 | * @returns {Promise>} a Promise that returns an array of status objects, otherwise throws an Error.
129 | */
130 | updateDocuments(engineName, documents) {
131 | return this.client.patch(`engines/${encodeURIComponent(engineName)}/documents`, documents)
132 | }
133 |
134 | /**
135 | * List all documents
136 | *
137 | * @param {String} engineName unique Engine name
138 | * @param {Object} options see the App Search API for supported search options
139 | * @returns {Promise>} a Promise that returns an array of documents, otherwise throws an Error.
140 | */
141 | listDocuments(engineName, options = {}) {
142 | const pagingParams = buildPagingParams(options.page || {})
143 | const prefix = pagingParams.length ? '?' : ''
144 |
145 | return this.client.get(`engines/${encodeURIComponent(engineName)}/documents/list${prefix}${pagingParams.join('&')}`, {})
146 | }
147 |
148 | /**
149 | * Retrieve a batch of documents.
150 | *
151 | * @param {String} engineName unique Engine name
152 | * @param {Array} ids Array of document ids to be retrieved
153 | * @returns {Promise>} a Promise that returns an array of documents, otherwise throws an Error.
154 | */
155 | getDocuments(engineName, ids) {
156 | return this.client.get(`engines/${encodeURIComponent(engineName)}/documents`, ids)
157 | }
158 |
159 | /**
160 | * Destroy a batch of documents.
161 | *
162 | * @param {String} engineName unique Engine name
163 | * @param {Array} ids Array of document ids to be destroyed
164 | * @returns {Promise} a Promise that returns a result {Object} when resolved, otherwise throws an Error. Includes "result" keys to maintain backward compatibility.
165 | */
166 | destroyDocuments(engineName, ids) {
167 | return this.client.delete(`engines/${encodeURIComponent(engineName)}/documents`, ids)
168 | .then((response) => {
169 | response.forEach((docResult)=>{
170 | docResult.result = docResult.deleted
171 | })
172 | return response
173 | })
174 | }
175 |
176 |
177 | /**
178 | * List all engines
179 | *
180 | * @returns {Promise} a Promise that returns a result {Object} when resolved, otherwise throws an Error.
181 | */
182 | listEngines(options = {}) {
183 | const pagingParams = buildPagingParams(options.page || {})
184 | const prefix = pagingParams.length ? '?' : ''
185 |
186 | return this.client.get(`engines${prefix}${pagingParams.join('&')}`, {})
187 | }
188 |
189 | /**
190 | * Retrieve an engine by name
191 | *
192 | * @param {String} engineName unique Engine name
193 | * @returns {Promise} a Promise that returns a result {Object} when resolved, otherwise throws an Error.
194 | */
195 | getEngine(engineName) {
196 | return this.client.get(`engines/${encodeURIComponent(engineName)}`, {})
197 | }
198 |
199 | /**
200 | * Create a new engine
201 | *
202 | * @param {String} engineName unique Engine name
203 | * @param {Object} options
204 | * @returns {Promise} a Promise that returns a result {Object} when resolved, otherwise throws an Error.
205 | */
206 | createEngine(engineName, options) {
207 | return this.client.post(`engines`, {
208 | name: engineName,
209 | ...options
210 | })
211 | }
212 |
213 | /**
214 | * Add a Source Engine to a Meta Engine
215 | *
216 | * @param {String} engineName Name of Meta Engine
217 | * @param {Array[String]} sourceEngines Names of Engines to use as Source Engines
218 | * @returns {Promise} a Promise that returns a result {Object} when resolved, otherwise throws an Error.
219 | */
220 | addMetaEngineSources(engineName, sourceEngines) {
221 | return this.client.post(`engines/${engineName}/source_engines`, sourceEngines)
222 | }
223 |
224 | /**
225 | * Remove a Source Engine from a Meta Engine
226 | *
227 | * @param {String} engineName Name of Meta Engine
228 | * @param {Array[String]} sourceEngines Names of existing Source Engines to remove
229 | * @returns {Promise} a Promise that returns a result {Object} when resolved, otherwise throws an Error.
230 | */
231 | deleteMetaEngineSources(engineName, sourceEngines) {
232 | return this.client.delete(`engines/${engineName}/source_engines`, sourceEngines)
233 | }
234 |
235 | /**
236 | * Delete an engine
237 | *
238 | * @param {String} engineName unique Engine name
239 | * @returns {Promise} a Promise that returns a result {Object} when resolved, otherwise throws an Error.
240 | */
241 | destroyEngine(engineName) {
242 | return this.client.delete(`engines/${encodeURIComponent(engineName)}`, {})
243 | }
244 |
245 |
246 | /**
247 | * List all Curations
248 | *
249 | * @param {String} engineName unique Engine name
250 | * @returns {Promise} a Promise that returns a result {Object} when resolved, otherwise throws an Error.
251 | */
252 | listCurations(engineName, options = {}) {
253 | const pagingParams = buildPagingParams(options.page || {});
254 | const prefix = pagingParams.length ? '?' : '';
255 |
256 | return this.client.get(`engines/${encodeURIComponent(engineName)}/curations${prefix}${pagingParams.join('&')}`, {})
257 | }
258 |
259 | /**
260 | * Retrieve a Curation by id
261 | *
262 | * @param {String} engineName unique Engine name
263 | * @param {String} curationId unique Curation id
264 | * @returns {Promise} a Promise that returns a result {Object} when resolved, otherwise throws an Error.
265 | */
266 | getCuration(engineName, curationId) {
267 |
268 | return this.client.get(`engines/${encodeURIComponent(engineName)}/curations/${encodeURIComponent(curationId)}`, {})
269 | }
270 |
271 | /**
272 | * Create a new Curation
273 | *
274 | * @param {String} engineName unique Engine name
275 | * @param {Object} newCuration body of the Curation object
276 | * @returns {Promise} a Promise that returns a result {Object} when resolved, otherwise throws an Error.
277 | */
278 | createCuration(engineName, newCuration) {
279 | return this.client.post(`engines/${encodeURIComponent(engineName)}/curations`, newCuration)
280 | }
281 |
282 | /**
283 | * Update an existing curation
284 | *
285 | * @param {String} engineName unique Engine name
286 | * @param {String} curationId unique Curation id
287 | * @param {Object} newCuration body of the Curation object
288 | * @returns {Promise} a Promise that returns a result {Object} when resolved, otherwise throws an Error.
289 | */
290 | updateCuration(engineName, curationId, newCuration) {
291 | return this.client.put(`engines/${encodeURIComponent(engineName)}/curations/${encodeURIComponent(curationId)}`, newCuration)
292 | }
293 |
294 | /**
295 | * Create a new meta engine
296 | *
297 | * @param {String} engineName unique Engine name
298 | * @param {Array[String]} sourceEngines list of engine names to use as source engines
299 | * @returns {Promise} a Promise that returns a result {Object} when resolved, otherwise throws an Error.
300 | */
301 | createMetaEngine(engineName, sourceEngines) {
302 | return this.client.post(`engines`, {
303 | name: engineName,
304 | type: 'meta',
305 | source_engines: sourceEngines
306 | })
307 | }
308 |
309 | /**
310 | * Delete a curation
311 | *
312 | * @param {String} engineName unique Engine name
313 | * @param {String} curationId unique Curation name
314 | * @returns {Promise} a Promise that returns a result {Object} when resolved, otherwise throws an Error.
315 | */
316 | destroyCuration(engineName, curationId) {
317 | return this.client.delete(`engines/${encodeURIComponent(engineName)}/curations/${encodeURIComponent(curationId)}`, {})
318 | }
319 |
320 | /**
321 | * Retrieve a schema by engine name
322 | *
323 | * @param {String} engineName unique Engine name
324 | * @returns {Promise} a Promise that returns the current schema {Object} when resolved, otherwise throws an Error.
325 | */
326 | getSchema(engineName) {
327 | return this.client.get(`engines/${encodeURIComponent(engineName)}/schema`, {})
328 | }
329 |
330 | /**
331 | * Update an existing schema
332 | *
333 | * @param {String} engineName unique Engine name
334 | * @param {Object} schema body of schema object
335 | * @returns {Promise} a Promise that returns the current schema {Object} when resolved, otherwise throws an Error.
336 | */
337 | updateSchema(engineName, schema) {
338 | return this.client.post(`engines/${encodeURIComponent(engineName)}/schema`, schema)
339 | }
340 |
341 | /**
342 | * Creates a jwt search key that can be used for authentication to enforce a set of required search options.
343 | *
344 | * @param {String} apiKey the API Key used to sign the search key
345 | * @param {String} apiKeyName the unique name for the API Key
346 | * @param {Object} options Object see the App Search API for supported search options
347 | * @param {Object} signOptions Object see the node-jsonwebtoken for supported sign options
348 | * @returns {String} jwt search key
349 | */
350 | static createSignedSearchKey(apiKey, apiKeyName, options = {}, signOptions = {}) {
351 | const payload = Object.assign({}, options, { api_key_name: apiKeyName })
352 | return jwt.sign(payload, apiKey, Object.assign({}, signOptions, { algorithm: SIGNED_SEARCH_TOKEN_JWT_ALGORITHM }))
353 | }
354 | }
355 |
356 | module.exports = AppSearchClient
357 |
--------------------------------------------------------------------------------
/lib/client.js:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright 2019 Elastic and contributors
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 |
17 | */
18 |
19 | const packageJson = require('../package.json')
20 | const request = require('request')
21 |
22 | function getErrorMessages(responseBody) {
23 | // Some responses, like a 404 return an error array.
24 | if (responseBody.errors) {
25 | return responseBody.errors
26 | }
27 |
28 | // Other responses, like an Auth error, return a single
29 | // error.
30 | if (responseBody.error) {
31 | return [responseBody.error]
32 | }
33 |
34 | // Multi-search returns an array of responses, with error arrays.
35 | if (Array.isArray(responseBody)) {
36 | return responseBody.reduce((acc, response) => {
37 | const errors = (response && Array.isArray(response.errors))
38 | ? response.errors
39 | : []
40 | return [...acc, ...errors]
41 | }, [])
42 | }
43 | }
44 | class Client {
45 | constructor(apiKey, baseUrl) {
46 | this.apiKey = apiKey
47 | this.clientName = 'elastic-app-search-node'
48 | this.clientVersion = packageJson.version
49 | this.baseUrl = baseUrl
50 | }
51 |
52 | get(path, params) {
53 | return this._jsonRequest('GET', path, params)
54 | }
55 |
56 | post(path, params) {
57 | return this._jsonRequest('POST', path, params)
58 | }
59 |
60 | put(path, params) {
61 | return this._jsonRequest('PUT', path, params)
62 | }
63 |
64 | patch(path, params) {
65 | return this._jsonRequest('PATCH', path, params)
66 | }
67 |
68 | delete(path, params) {
69 | return this._jsonRequest('DELETE', path, params)
70 | }
71 |
72 | _jsonRequest(method, path, params) {
73 | return this._wrap({
74 | method: method,
75 | url: `${this.baseUrl}${path}`,
76 | json: params,
77 | auth: {
78 | bearer: this.apiKey
79 | },
80 | headers: {
81 | 'X-Swiftype-Client': this.clientName,
82 | 'X-Swiftype-Client-Version': this.clientVersion,
83 | 'Content-Type': 'application/json'
84 | }
85 | })
86 | }
87 |
88 | _wrap(options) {
89 | return new Promise((resolve, reject) => {
90 | request(options, (error, response, body) => {
91 | if (error) {
92 | error.errorMessages = [error.message]
93 | reject(error)
94 | } else if (!(response.statusCode.toString().match(/2[\d]{2}/))) {
95 | var apiError = new Error(response.statusMessage)
96 | apiError.errorMessages = getErrorMessages(response.body)
97 | reject(apiError)
98 | } else {
99 | resolve(body)
100 | }
101 | })
102 | })
103 | }
104 | }
105 |
106 | module.exports = Client
107 |
--------------------------------------------------------------------------------
/logo-app-search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elastic/app-search-node/7c8f794a7ed212688dd3dc870c512d0ec8cc06c8/logo-app-search.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@elastic/app-search-node",
3 | "version": "8.3.2",
4 | "description": "Elastic App Search Node.js client",
5 | "files": [
6 | "lib/"
7 | ],
8 | "main": "lib/appSearch.js",
9 | "scripts": {
10 | "test": "mocha"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "git+https://github.com/elastic/app-search-node.git"
15 | },
16 | "engines": {
17 | "node": ">= v6.5.0"
18 | },
19 | "author": "Elastic",
20 | "license": "Apache-2.0",
21 | "bugs": {
22 | "url": "https://github.com/elastic/app-search-node/issues"
23 | },
24 | "homepage": "https://github.com/elastic/app-search-node#readme",
25 | "devDependencies": {
26 | "mocha": "^7.0.0",
27 | "replay": "~2.4.0"
28 | },
29 | "dependencies": {
30 | "request": "^2.81.0",
31 | "jsonwebtoken": "^8.3.2"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/test/test.js:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright 2019 Elastic and contributors
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 |
17 | */
18 |
19 | const assert = require('assert')
20 | const AppSearchClient = require('../lib/appSearch')
21 | const replay = require('replay')
22 |
23 | describe('AppSearchClient', () => {
24 | const hostIdentifier = process.env.HOST_IDENTIFIER || 'host-c5s2mj'
25 | const apiKey = process.env.API_KEY || 'api-mu75psc5egt9ppzuycnc2mc3'
26 | const client = new AppSearchClient(hostIdentifier, apiKey)
27 |
28 | replay.reset('localhost') // So that replay can capture localhost requests
29 | const baseUrlFn = () => "http://localhost:3002/api/as/v1/";
30 | const selfManagedClient = new AppSearchClient(undefined, apiKey, baseUrlFn)
31 |
32 | const engineName = 'swiftype-api-example'
33 | const documents = [
34 | {
35 | id: 'INscMGmhmX4',
36 | url: 'http://www.youtube.com/watch?v=v1uyQZNg2vE',
37 | title: 'The Original Grumpy Cat',
38 | body: 'this is a test'
39 | },
40 | {
41 | id: 'JNDFojsd02',
42 | url: 'http://www.youtube.com/watch?v=tsdfhk2j',
43 | title: 'Another Grumpy Cat',
44 | body: 'this is also a test'
45 | }
46 | ]
47 | const curations = [
48 | {
49 | "id": "cur-9382",
50 | "queries": [
51 | "mew hiss"
52 | ],
53 | "promoted": [
54 | "INscMGmhmX4"
55 | ],
56 | "hidden": [
57 | "JNDFojsd02"
58 | ]
59 | },
60 | {
61 | "id": "cur-378291",
62 | "queries": [
63 | "grrr scratch"
64 | ],
65 | "promoted": [
66 | "JNDFojsd02"
67 | ],
68 | "hidden": [
69 | "INscMGmhmX4"
70 | ]
71 | }
72 | ]
73 |
74 | describe('#indexDocument', () => {
75 | it('should index a document successfully', (done) => {
76 | client.indexDocument(engineName, documents[0])
77 | .then((result) => {
78 | assert.deepEqual({ 'id': 'INscMGmhmX4' }, result)
79 | done()
80 | })
81 | .catch((error) => {
82 | done(error)
83 | })
84 | })
85 | })
86 |
87 | describe('#indexDocuments', () => {
88 | it('should index documents successfully', (done) => {
89 | client.indexDocuments(engineName, documents)
90 | .then((results) => {
91 | assert.deepEqual([
92 | { 'errors': [], 'id': 'INscMGmhmX4' },
93 | { 'errors': [], 'id': 'JNDFojsd02' }
94 | ], results)
95 | done()
96 | })
97 | .catch((error) => {
98 | done(error)
99 | })
100 | })
101 | })
102 |
103 | describe('#updateDocuments', () => {
104 | it('should update documents successfully', (done) => {
105 | client.updateDocuments(engineName, documents)
106 | .then((results) => {
107 | assert.deepEqual([
108 | { 'errors': [], 'id': 'INscMGmhmX4' },
109 | { 'errors': [], 'id': 'JNDFojsd02' }
110 | ], results)
111 | done()
112 | })
113 | .catch((error) => {
114 | done(error)
115 | })
116 | })
117 | })
118 |
119 | describe('#getDocuments', () => {
120 | const documentIds = documents.map((d) => d.id)
121 |
122 | it('should get documents successfully', (done) => {
123 | client.getDocuments(engineName, documentIds)
124 | .then((results) => {
125 | assert.deepEqual([
126 | {
127 | 'body': 'this is a test',
128 | 'id': 'INscMGmhmX4',
129 | 'title': 'The Original Grumpy Cat',
130 | 'url': 'http://www.youtube.com/watch?v=v1uyQZNg2vE',
131 | },
132 | {
133 | 'body': 'this is also a test',
134 | 'id': 'JNDFojsd02',
135 | 'title': 'Another Grumpy Cat',
136 | 'url': 'http://www.youtube.com/watch?v=tsdfhk2j',
137 | }
138 | ], results)
139 | done()
140 | })
141 | .catch((error) => {
142 | done(error)
143 | })
144 | })
145 | })
146 |
147 | describe('#listDocuments', () => {
148 | it('should list documents successfully', (done) => {
149 | client.listDocuments(engineName)
150 | .then((results) => {
151 | assert.deepEqual([
152 | {
153 | 'body': 'this is a test',
154 | 'id': 'INscMGmhmX4',
155 | 'title': 'The Original Grumpy Cat',
156 | 'url': 'http://www.youtube.com/watch?v=v1uyQZNg2vE',
157 | },
158 | {
159 | 'body': 'this is also a test',
160 | 'id': 'JNDFojsd02',
161 | 'title': 'Another Grumpy Cat',
162 | 'url': 'http://www.youtube.com/watch?v=tsdfhk2j',
163 | }
164 | ], results.results)
165 | done()
166 | })
167 | .catch((error) => {
168 | done(error)
169 | })
170 | })
171 | })
172 |
173 | describe('#destroyDocuments', () => {
174 | it('should destroy documents', (done) => {
175 | client.destroyDocuments(engineName, ['INscMGmhmX4', 'FakeId'])
176 | .then((results) => {
177 | assert.deepEqual([
178 | { 'id': 'INscMGmhmX4', 'deleted': true, 'result': true },
179 | { 'id': 'FakeId', 'deleted': false, 'result': false },
180 | ], results)
181 | done()
182 | })
183 | .catch((error) => {
184 | done(error)
185 | })
186 | })
187 | })
188 |
189 | describe('#listEngines', () => {
190 | it('should list engines successfully', (done) => {
191 | client.listEngines()
192 | .then((results) => {
193 | assert.deepEqual({
194 | 'meta': {
195 | 'page': {
196 | 'current': 1,
197 | 'total_pages': 1,
198 | 'total_results': 3,
199 | 'size': 25
200 | }
201 | },
202 | 'results': [{
203 | 'name': 'node-modules',
204 | 'type': 'default',
205 | 'language': null
206 | }, {
207 | 'name': 'ruby-gems',
208 | 'type': 'default',
209 | 'language': null
210 | }, {
211 | 'name': 'test-engine',
212 | 'type': 'default',
213 | 'language': null
214 | }]
215 | }, results)
216 | done()
217 | })
218 | .catch((error) => {
219 | done(error)
220 | })
221 | })
222 |
223 | it('should support paging', (done) => {
224 | client.listEngines({
225 | page: {
226 | current: 2,
227 | size: 1
228 | }
229 | })
230 | .then((results) => {
231 | assert.deepEqual({
232 | 'meta': {
233 | 'page': {
234 | 'current': 2,
235 | 'total_pages': 3,
236 | 'total_results': 3,
237 | 'size': 1
238 | }
239 | },
240 | 'results': [{
241 | 'name': 'ruby-gems',
242 | 'type': 'default',
243 | 'language': null
244 | }]
245 | }, results)
246 | done()
247 | })
248 | .catch((error) => {
249 | done(error)
250 | })
251 | })
252 | })
253 |
254 | describe('#getEngine', () => {
255 | it('should get an engine successfully', (done) => {
256 | client.getEngine(engineName)
257 | .then((results) => {
258 | assert.deepEqual({
259 | 'name': 'swiftype-api-example',
260 | 'type': 'default',
261 | 'language': null
262 | }, results)
263 | done()
264 | })
265 | .catch((error) => {
266 | done(error)
267 | })
268 | })
269 | })
270 |
271 | describe('#createEngine', () => {
272 | it('should create an engine successfully', (done) => {
273 | client.createEngine('new-engine')
274 | .then((results) => {
275 | assert.deepEqual({
276 | 'name': 'new-engine',
277 | 'type': 'default',
278 | 'language': null
279 | }, results)
280 | done()
281 | })
282 | .catch((error) => {
283 | done(error)
284 | })
285 | })
286 |
287 | it('should create an engine with a language', (done) => {
288 | client.createEngine('new-engine', {language: 'en'})
289 | .then((results) => {
290 | assert.deepEqual({
291 | 'name': 'new-engine',
292 | 'type': 'default',
293 | 'language': 'en'
294 | }, results)
295 | done()
296 | })
297 | .catch((error) => {
298 | done(error)
299 | })
300 | })
301 | })
302 |
303 | describe('#destroyEngine', () => {
304 | it('should delete an engine successfully', (done) => {
305 | client.destroyEngine('new-engine')
306 | .then((results) => {
307 | assert.deepEqual({
308 | 'deleted': true
309 | }, results)
310 | done()
311 | })
312 | .catch((error) => {
313 | done(error)
314 | })
315 | })
316 | })
317 |
318 | describe('#createMetaEngine', () => {
319 | it('should create a meta engine', (done) => {
320 | selfManagedClient.createMetaEngine('new-meta-engine', ['source-engine-1', 'source-engine-2'])
321 | .then((results) => {
322 | assert.deepEqual({
323 | 'name': 'new-meta-engine',
324 | 'type': 'meta',
325 | 'source_engines': [
326 | 'source-engine-1',
327 | 'source-engine-2'
328 | ]
329 | }, results)
330 | done()
331 | })
332 | .catch((error) => {
333 | done(error)
334 | })
335 | })
336 | })
337 |
338 | describe('#addMetaEngineSources', () => {
339 | it('should add a Source Engine to a Meta Engine', (done) => {
340 | selfManagedClient.addMetaEngineSources('new-meta-engine', ['source-engine-3'])
341 | .then((results) => {
342 | assert.deepEqual({
343 | 'name': 'new-meta-engine',
344 | 'type': 'meta',
345 | 'source_engines': [
346 | 'source-engine-1',
347 | 'source-engine-2',
348 | 'source-engine-3'
349 | ]
350 | }, results)
351 | done()
352 | })
353 | .catch((error) => {
354 | done(error)
355 | })
356 | })
357 | })
358 |
359 | describe('#deleteMetaEngineSources', () => {
360 | it('Remove a Source Engine from a Meta Engine', (done) => {
361 | selfManagedClient.deleteMetaEngineSources('new-meta-engine', ['source-engine-3'])
362 | .then((results) => {
363 | assert.deepEqual({
364 | 'name': 'new-meta-engine',
365 | 'type': 'meta',
366 | 'source_engines': [
367 | 'source-engine-1',
368 | 'source-engine-2'
369 | ]
370 | }, results)
371 | done()
372 | })
373 | .catch((error) => {
374 | done(error)
375 | })
376 | })
377 | })
378 |
379 | describe('#search', () => {
380 | it('should query', (done) => {
381 | client.search(engineName, 'cat')
382 | .then((resp) => {
383 | assert.deepEqual([
384 | { raw: 'The Original Grumpy Cat' },
385 | { raw: 'Another Grumpy Cat' }
386 | ], resp.results.map((r) => r.title ))
387 | done()
388 | })
389 | .catch((error) => {
390 | done(error)
391 | })
392 | })
393 | })
394 |
395 | describe('#multiSearch', () => {
396 | it('should query', (done) => {
397 | client.multiSearch(engineName, [{
398 | query: 'cat',
399 | options: {}
400 | }, {
401 | query: 'grumpy',
402 | options: {}
403 | }])
404 | .then((resp) => {
405 | assert.deepEqual(resp.map(res => res.results.map(res => res.title.raw)), [[
406 | 'The Original Grumpy Cat',
407 | 'Another Grumpy Cat'
408 | ], [
409 | 'Another Grumpy Cat',
410 | 'The Original Grumpy Cat'
411 | ]
412 | ])
413 | done()
414 | })
415 | .catch((error) => {
416 | done(error)
417 | })
418 | })
419 | })
420 |
421 | describe('#querySuggestion', () => {
422 | it('should request query suggestions', (done) => {
423 | client.querySuggestion(engineName, 'cat')
424 | .then((resp) => {
425 | assert.deepEqual({
426 | results: { documents: [{ suggestion: "cat" }] },
427 | meta: { request_id: "7414beb06c644b1aa88accb6019c6d6f" }
428 | }, resp)
429 | done()
430 | })
431 | .catch((error) => {
432 | done(error)
433 | })
434 | })
435 |
436 | it('should request query suggestions with options', (done) => {
437 | client.querySuggestion(engineName, 'cat', {
438 | size: 3,
439 | types: {
440 | documents: {
441 | fields: ['title']
442 | }
443 | }
444 | })
445 | .then((resp) => {
446 | assert.deepEqual({
447 | results: { documents: [{ suggestion: "cat" }] },
448 | meta: { request_id: "a0e24c46d4379e58bf871a6a515b8d94" }
449 | }, resp)
450 | done()
451 | })
452 | .catch((error) => {
453 | done(error)
454 | })
455 | })
456 | })
457 |
458 | describe('#createSignedSearchKey', () => {
459 | it('should build a valid jwt', (done) => {
460 | token = AppSearchClient.createSignedSearchKey('private-mu75psc5egt9ppzuycnc2mc3', 'my-token-name', { query: 'cat' })
461 | jwt = require('jsonwebtoken')
462 | decoded = jwt.verify(token, 'private-mu75psc5egt9ppzuycnc2mc3')
463 | assert.equal(decoded.api_key_name, 'my-token-name')
464 | assert.equal(decoded.query, 'cat')
465 | done()
466 | })
467 | it('should pass sign options to node-jsonwebtoken', (done) => {
468 | token = AppSearchClient.createSignedSearchKey('private-mu75psc5egt9ppzuycnc2mc3', 'my-token-name', { query: 'cat' }, { expiresIn: '5 minutes' })
469 | jwt = require('jsonwebtoken')
470 | decoded = jwt.verify(token, 'private-mu75psc5egt9ppzuycnc2mc3')
471 | assert.ok(decoded.exp)
472 | done()
473 | })
474 | })
475 |
476 | describe('#listCurations', () => {
477 | it('should list curations successfully', (done) => {
478 | client.listCurations(engineName)
479 | .then((results) => {
480 | assert.deepEqual({
481 | "meta": {
482 | "page": {
483 | "current": 1,
484 | "total_pages": 1,
485 | "total_results": 2,
486 | "size": 25
487 | }
488 | },
489 | "results": curations
490 | }, results)
491 | done()
492 | })
493 | .catch((error) => {
494 | done(error)
495 | })
496 | })
497 |
498 | it('should support paging', (done) => {
499 | client.listCurations(engineName, {
500 | page: {
501 | current: 2,
502 | size: 1
503 | }
504 | })
505 | .then((results) => {
506 | assert.deepEqual({
507 | "meta": {
508 | "page": {
509 | "current": 2,
510 | "total_pages": 2,
511 | "total_results": 2,
512 | "size": 1
513 | }
514 | },
515 | "results": [
516 | curations[1]
517 | ]
518 | }, results)
519 | done()
520 | })
521 | .catch((error) => {
522 | done(error)
523 | })
524 | })
525 | })
526 |
527 | describe('#getCuration', () => {
528 | it('should get a curation successfully', (done) => {
529 | const expected = {
530 | meta: {
531 | page: { current: 1, total_pages: 1, total_results: 1, size: 25 }
532 | },
533 | results:
534 | [ curations[0] ]
535 | }
536 | client.getCuration(engineName, curations[0].id)
537 | .then((results) => {
538 | assert.deepEqual(expected, results)
539 | done()
540 | })
541 | .catch((error) => {
542 | done(error)
543 | })
544 | })
545 |
546 | it('passes along errors', (done) => {
547 | client.getCuration(engineName, "cur-0000")
548 | .then((results) => {
549 | done(new Error('This should have resulted in an error'))
550 | })
551 | .catch((error) => {
552 | assert(error.errorMessages.join().toString() === "Curation not found with id: cur-0000")
553 | done()
554 | })
555 | })
556 | })
557 |
558 | describe('#createCuration', () => {
559 | it('should create a curation successfully', (done) => {
560 | client.createCuration(engineName, {queries: ["arf arf"], promoted: [], hidden: ["INscMGmhmX4", "JNDFojsd02"]})
561 | .then((results) => {
562 | assert.deepEqual({
563 | "id": "cur-9043829"
564 | }, results)
565 | done()
566 | })
567 | .catch((error) => {
568 | done(error)
569 | })
570 | })
571 | })
572 |
573 | describe('#destroyCuration', () => {
574 | it('should delete a curation successfully', (done) => {
575 | client.destroyCuration(engineName, curations[1].id)
576 | .then((results) => {
577 | assert.deepEqual({
578 | "deleted": true
579 | }, results)
580 | done()
581 | })
582 | .catch((error) => {
583 | done(error)
584 | })
585 | })
586 | })
587 |
588 | describe('#updateCuration', () => {
589 | it('should update a curation successfully', (done) => {
590 | client.updateCuration(engineName, "cur-9382", {queries: ["mew hiss", "sideways hop"], promoted: ["INscMGmhmX4"], hidden: ["JNDFojsd02"]})
591 | .then((results) => {
592 | assert.deepEqual({
593 | "id": "cur-9382"
594 | }, results)
595 | done()
596 | })
597 | .catch((error) => {
598 | done(error)
599 | })
600 | })
601 | })
602 |
603 | describe('#getSchema', () => {
604 | it('should get a schema successfully', (done) => {
605 | selfManagedClient.getSchema(engineName)
606 | .then((result) => {
607 | assert.deepEqual({
608 | 'url': 'text',
609 | 'title': 'text',
610 | 'views': 'number',
611 | 'location': 'geolocation',
612 | 'created_date': 'date'
613 | }, result)
614 | done()
615 | })
616 | .catch((error) => {
617 | done(error)
618 | })
619 | })
620 | })
621 |
622 | describe('#updateSchema', () => {
623 | it('should update a schema successfully', (done) => {
624 | selfManagedClient.updateSchema(engineName, { 'views': 'text' })
625 | .then((result) => {
626 | assert.deepEqual({
627 | 'url': 'text',
628 | 'title': 'text',
629 | 'views': 'text',
630 | 'location': 'geolocation',
631 | 'created_date': 'date'
632 | }, result)
633 | done()
634 | })
635 | .catch((error) => {
636 | done(error)
637 | })
638 | })
639 | })
640 |
641 | describe('error handling', () => {
642 | it('should handle 404', (done) => {
643 | client.search('invalid-engine-name', 'cat')
644 | .then(() => {
645 | done(new Error('was expected to throw with an error message'))
646 | })
647 | .catch(error => {
648 | const errorMessages = (error && error.errorMessages) ? error.errorMessages : null
649 |
650 | if (errorMessages) {
651 | try {
652 | assert.deepEqual(errorMessages, ['Could not find engine.'])
653 | done()
654 | } catch (e) {
655 | done(e)
656 | }
657 | } else {
658 | done(error)
659 | }
660 | })
661 | })
662 |
663 | it('should handle auth error', (done) => {
664 | const badAuthClient = new AppSearchClient(hostIdentifier, 'invalid')
665 | badAuthClient.search(engineName, 'cat')
666 | .then(() => {
667 | done(new Error('was expected to throw with an error message'))
668 | })
669 | .catch(error => {
670 | const errorMessages = (error && error.errorMessages) ? error.errorMessages : null
671 |
672 | if (errorMessages) {
673 | try {
674 | assert.deepEqual(errorMessages, ['Invalid authentication token.'])
675 | done()
676 | } catch (e) {
677 | done(e)
678 | }
679 | } else {
680 | done(error)
681 | }
682 | })
683 | })
684 |
685 | it('should handle multi_search errors', (done) => {
686 | client.multiSearch(engineName, [
687 | {
688 | query: 'cat',
689 | options: {
690 | badField: 'whatever'
691 | }
692 | }, {
693 | query: 'grumpy',
694 | options: {}
695 | }
696 | ])
697 | .then(() => {
698 | done(new Error('was expected to throw with an error message'))
699 | })
700 | .catch(error => {
701 | const errorMessages = (error && error.errorMessages) ? error.errorMessages : null
702 |
703 | if (errorMessages) {
704 | try {
705 | assert.deepEqual(errorMessages, ['Options contains invalid key: badField'])
706 | done()
707 | } catch (e) {
708 | done(e)
709 | }
710 | } else {
711 | done(error)
712 | }
713 | })
714 | })
715 | })
716 | })
717 |
--------------------------------------------------------------------------------