├── .babelrc
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .travis.yml
├── .vscode
└── settings.json
├── LICENSE
├── README.md
├── __mocks__
├── defaultConfig.js
└── index.js
├── __tests__
├── AppBaseClientTest.js
├── ReactiveSearchTest.js
├── ReactiveSearchV3Test.js
├── bulkTest.js
├── getMappingsTest.js
├── getTest.js
├── msearchTest.js
├── searchTest.js
└── updateTest.js
├── jest.config.js
├── package-scripts.js
├── package.json
├── rollup.config.js
├── src
├── core
│ ├── api
│ │ ├── bulk.js
│ │ ├── delete.js
│ │ ├── get.js
│ │ ├── getMappings.js
│ │ ├── getSuggestionsv3Api.js
│ │ ├── index.js
│ │ ├── msearch.js
│ │ ├── reactiveSearchApi.js
│ │ ├── reactiveSearchv3Api.js
│ │ ├── search.js
│ │ └── update.js
│ ├── index.js
│ └── request
│ │ └── fetch.js
├── index.d.ts
├── index.js
└── utils
│ ├── index.js
│ └── schema
│ ├── elasticsearch.js
│ ├── index.js
│ └── mongodb.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "env",
5 | {
6 | "modules": "commonjs"
7 | }
8 | ]
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | test
2 | __tests__
3 | node_modules
4 | coverage
5 | dist
6 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: 'airbnb/base',
3 | env: {
4 | browser: true,
5 | es6: true,
6 | node: true,
7 | },
8 | parser: 'babel-eslint',
9 | rules: {
10 | indent: 0,
11 | 'no-tabs': 0,
12 | 'eol-last': ['error', 'always'],
13 | 'no-underscore-dangle': 0,
14 | },
15 | };
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | coverage
4 | dist
5 | lib
6 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - '9'
4 | - '8'
5 | - '6'
6 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.trimTrailingWhitespace": true,
3 | "files.insertFinalNewline": true,
4 | "files.trimFinalNewlines": true,
5 | // to resolve flow issues - Refer: https://github.com/flowtype/flow-for-vscode#setup
6 | "javascript.validate.enable": false,
7 | "editor.codeActionsOnSave": {
8 | "source.fixAll.tslint": true,
9 | "source.fixAll.eslint": true
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Kuldeep Saxena
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## 
2 |
3 |
4 |
5 |
6 | appbase-js
7 |
8 |
9 |
10 | [appbase-js](https://github.com/appbaseio/appbase-js) is a universal JavaScript client library for working with the appbase.io database, for Node.JS and Javascript (browser UMD build is in the [dist/](https://github.com/appbaseio/appbase-js/tree/master/dist) directory); compatible with [elasticsearch.js](https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/index.html).
11 |
12 | An up-to-date documentation for Node.JS API is available at http://docs.appbase.io/javascript/quickstart.
13 |
14 | ## TOC
15 |
16 | 1. **[appbase-js: Intro](#1-appbase-js-intro)**
17 | 2. **[Features](#2-features)**
18 | 3. **[Live Examples](#3-live-examples)**
19 | 4. **[Installation](#4-installation)**
20 | 5. **[Docs Manual](#5-docs-manual)**
21 | 6. **[Other Projects You Might Like](#6-other-projects-you-might-like)**
22 |
23 |
24 | ## 1. appbase-js: Intro
25 |
26 | [appbase-js](https://github.com/appbaseio/appbase-js) is a universal JavaScript client library for working with the appbase.io database.
27 |
28 | ## 2. Features
29 |
30 | It can:
31 |
32 | - Index new documents or update / delete existing ones.
33 | - Work universally with Node.JS, Browser, and React Native.
34 |
35 | It can't:
36 |
37 | - Configure mappings, change analyzers, or capture snapshots. All these are provided by [elasticsearch.js](https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/index.html) - the official Elasticsearch JS client library.
38 |
39 | [Appbase.io - the database service](https://appbase.io) is opinionated about cluster setup and hence doesn't support the Elasticsearch devops APIs. See [rest.appbase.io](https://rest.appbase.io) for a full reference on the supported APIs.
40 |
41 | ## 3. Live Examples
42 |
43 |
44 | Check out the Live interactive Examples at reactiveapps.io .
46 |
47 |
48 | [](https://docs.appbase.io/api/examples/rest/)
49 |
50 | ## 4. Installation
51 |
52 | We will fetch and install the **appbase-js** lib using npm. `4.0.0-beta` is the most current version.
53 |
54 | ```js
55 | npm install appbase-js
56 | ```
57 |
58 | Adding it in the browser should be a one line script addition.
59 |
60 | ```html
61 |
65 | ```
66 |
67 | Alternatively, a UMD build of the library can be used directly from [jsDelivr](https://cdn.jsdelivr.net/npm/appbase-js/dist/).
68 |
69 | To write data to [appbase.io](https://appbase.io), we need to first create a reference object. We do this by passing the appbase.io API URL, app name, and credentials into the `Appbase` constructor:
70 |
71 | ```js
72 | var appbaseRef = Appbase({
73 | url: "https://appbase-demo-ansible-abxiydt-arc.searchbase.io",
74 | app: "good-books-demo",
75 | credentials: "c84fb24cbe08:db2a25b5-1267-404f-b8e6-cf0754953c68",
76 | });
77 | ```
78 |
79 | **OR**
80 |
81 | ```js
82 | var appbaseRef = Appbase({
83 | url: "https://c84fb24cbe08:db2a25b5-1267-404f-b8e6-cf0754953c68@appbase-demo-ansible-abxiydt-arc.searchbase.io",
84 | app: "good-books-demo",
85 | });
86 | ```
87 |
88 | Credentials can also be directly passed as a part of the API URL.
89 |
90 | ## 5. Docs Manual
91 |
92 | For a complete API reference, check out [JS API Ref doc](http://docs.appbase.io/javascript/apireference).
93 |
94 | ## 6. Other Projects You Might Like
95 |
96 | - [**arc**](https://github.com/appbaseio/arc) API Gateway for ElasticSearch (Out of the box Security, Rate Limit Features, Record Analytics and Request Logs).
97 |
98 | - [**searchbox**](https://github.com/appbaseio/searchox) A lightweight and performance focused searchbox UI libraries to query and display results from your ElasticSearch app (aka index).
99 |
100 | - **Vanilla JS** - (~16kB Minified + Gzipped)
101 | - **React** - (~30kB Minified + Gzipped)
102 | - **Vue** - (~22kB Minified + Gzipped)
103 |
104 | - [**dejavu**](https://github.com/appbaseio/dejavu) allows viewing raw data within an appbase.io (or Elasticsearch) app. **Soon to be released feature:** An ability to import custom data from CSV and JSON files, along with a guided walkthrough on applying data mappings.
105 |
106 | - [**mirage**](https://github.com/appbaseio/mirage) ReactiveSearch components can be extended using custom Elasticsearch queries. For those new to Elasticsearch, Mirage provides an intuitive GUI for composing queries.
107 |
108 | - [**ReactiveMaps**](https://github.com/appbaseio/reactivesearch/tree/next/packages/maps) is a similar project to Reactive Search that allows building realtime maps easily.
109 |
110 | - [**reactivesearch**](https://github.com/appbaseio/reactivesearch) UI components library for Elasticsearch: Available for React and Vue.
111 |
112 | [⬆ Back to Top](#top)
113 |
114 |
115 |
--------------------------------------------------------------------------------
/__mocks__/defaultConfig.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | URL: 'https://appbase-demo-ansible-abxiydt-arc.searchbase.io',
3 | APPNAME: 'appbase-js-test-cases',
4 | CREDENTIALS: 'f1a7b4562098:35fed6ff-4a19-4387-a188-7cdfe759c40f',
5 | };
6 |
--------------------------------------------------------------------------------
/__mocks__/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | uuidv4() {
3 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
4 | const r = (Math.random() * 16) | 0; // eslint-disable-line no-bitwise
5 |
6 | const v = c === 'x' ? r : (r & 0x3) | 0x8; // eslint-disable-line no-bitwise
7 | return v.toString(16);
8 | });
9 | },
10 | };
11 |
--------------------------------------------------------------------------------
/__tests__/AppBaseClientTest.js:
--------------------------------------------------------------------------------
1 | const appbase = require("..");
2 |
3 | const { uuidv4 } = require("../__mocks__");
4 |
5 | describe("#AppBase_Client", () => {
6 | test("should create appbase client", () => {
7 | const client = appbase({
8 | url: "https://appbase-demo-ansible-abxiydt-arc.searchbase.io",
9 | app: "appbasejs-test-app",
10 | credentials: "f1a7b4562098:35fed6ff-4a19-4387-a188-7cdfe759c40f",
11 | });
12 | expect(client).toEqual({
13 | url: "appbase-demo-ansible-abxiydt-arc.searchbase.io",
14 | protocol: "https",
15 | app: "appbasejs-test-app",
16 | credentials: "f1a7b4562098:35fed6ff-4a19-4387-a188-7cdfe759c40f",
17 | });
18 | });
19 | test("should throw url missing error", () => {
20 | try {
21 | appbase({
22 | app: "appbasejs-test-app",
23 | credentials: "f1a7b4562098:35fed6ff-4a19-4387-a188-7cdfe759c40f",
24 | });
25 | expect(true).toBe(false);
26 | } catch (e) {
27 | expect(e.message).toEqual(
28 | "url is required when using the elasticsearch Search backend."
29 | );
30 | }
31 | });
32 | test("should throw app missing error", () => {
33 | try {
34 | appbase({
35 | url: "https://appbase-demo-ansible-abxiydt-arc.searchbase.io",
36 | credentials: "f1a7b4562098:35fed6ff-4a19-4387-a188-7cdfe759c40f",
37 | });
38 | expect(true).toBe(false);
39 | } catch (e) {
40 | expect(e.message).toEqual(
41 | "app is required when using the elasticsearch Search backend."
42 | );
43 | }
44 | });
45 | test("should throw protocol missing error", () => {
46 | try {
47 | appbase({
48 | url: "appbase-demo-ansible-abxiydt-arc.searchbase.io",
49 | app: "appbasejs-test-app",
50 | credentials: "f1a7b4562098:35fed6ff-4a19-4387-a188-7cdfe759c40f",
51 | });
52 | expect(true).toBe(false);
53 | } catch (e) {
54 | expect(e.message).toEqual(
55 | "Protocol is not present in url. URL should be of the form https://appbase-demo-ansible-abxiydt-arc.searchbase.io"
56 | );
57 | }
58 | });
59 | test("should throw credentials missing error", () => {
60 | try {
61 | appbase({
62 | url: "https://scalr.api.appbase.io",
63 | app: "appbasejs-test-app",
64 | });
65 | expect(true).toBe(false);
66 | } catch (e) {
67 | expect(e.message).toEqual(
68 | "Authentication information is not present. Did you add credentials?"
69 | );
70 | }
71 | });
72 | test("should set headers", () => {
73 | const client = appbase({
74 | url: "https://appbase-demo-ansible-abxiydt-arc.searchbase.io",
75 | app: "appbasejs-test-app",
76 | credentials: "f1a7b4562098:35fed6ff-4a19-4387-a188-7cdfe759c40f",
77 | });
78 | client.setHeaders({
79 | authorization: "test-authorize",
80 | "x-search-key": "美女",
81 | });
82 | expect(client).toEqual({
83 | url: "appbase-demo-ansible-abxiydt-arc.searchbase.io",
84 | protocol: "https",
85 | app: "appbasejs-test-app",
86 | credentials: "f1a7b4562098:35fed6ff-4a19-4387-a188-7cdfe759c40f",
87 | headers: {
88 | authorization: "test-authorize",
89 | "x-search-key": "美女",
90 | },
91 | });
92 | });
93 | test("should set X-Enable-Telemetry header", () => {
94 | const client = appbase({
95 | url: "https://appbase-demo-ansible-abxiydt-arc.searchbase.io",
96 | app: "appbasejs-test-app",
97 | credentials: "f1a7b4562098:35fed6ff-4a19-4387-a188-7cdfe759c40f",
98 | enableTelemetry: false,
99 | });
100 | expect(client).toEqual({
101 | url: "appbase-demo-ansible-abxiydt-arc.searchbase.io",
102 | protocol: "https",
103 | app: "appbasejs-test-app",
104 | credentials: "f1a7b4562098:35fed6ff-4a19-4387-a188-7cdfe759c40f",
105 | enableTelemetry: false,
106 | });
107 | });
108 | test("should call transformRequest before fetch", async () => {
109 | const client = appbase({
110 | url: "https://appbase-demo-ansible-abxiydt-arc.searchbase.io",
111 | app: "appbasejs-test-app",
112 | credentials: "f1a7b4562098:35fed6ff-4a19-4387-a188-7cdfe759c40f",
113 | });
114 | client.transformRequest = (request) => {
115 | expect(true).toBe(true);
116 | return request;
117 | };
118 | const id = uuidv4();
119 | const tweet = {
120 | user: "olivere",
121 | message: "Welcome to Golang and Elasticsearch.",
122 | };
123 | client.index({
124 | type: "tweet_transform_error",
125 | id,
126 | body: tweet,
127 | });
128 | });
129 | test("should create appbase client with mongodb", () => {
130 | const client = appbase({
131 | url: "https://us-east-1.aws.webhooks.mongodb-realm.com/api/client/v2.0/app/public-demo-skxjb/service/http_endpoint/incoming_webhook/reactivesearch",
132 | app: "appbasejs-test-app",
133 | mongodb: {
134 | db: "test_db",
135 | collection: "test_collection",
136 | },
137 | });
138 | expect(client).toEqual({
139 | url: "us-east-1.aws.webhooks.mongodb-realm.com/api/client/v2.0/app/public-demo-skxjb/service/http_endpoint/incoming_webhook/reactivesearch",
140 | protocol: "https",
141 | credentials: null,
142 | app: "appbasejs-test-app",
143 | mongodb: {
144 | db: "test_db",
145 | collection: "test_collection",
146 | },
147 | });
148 | });
149 | });
150 |
--------------------------------------------------------------------------------
/__tests__/ReactiveSearchTest.js:
--------------------------------------------------------------------------------
1 | const appbase = require("..");
2 | const { uuidv4 } = require("../__mocks__");
3 | const config = require("../__mocks__/defaultConfig");
4 |
5 | describe("#Search", function () {
6 | let client;
7 | beforeAll(() => {
8 | client = appbase({
9 | url: config.URL,
10 | app: config.APPNAME,
11 | credentials: config.CREDENTIALS,
12 | });
13 | });
14 |
15 | test("should return results", async () => {
16 | var tweet = {
17 | user: "olivere",
18 | message: "Welcome to Golang and Elasticsearch.",
19 | };
20 | await client.index({
21 | id: uuidv4(),
22 | body: tweet,
23 | });
24 | const res = await client.reactiveSearch([
25 | {
26 | id: "tweet_search",
27 | dataField: ["user"],
28 | size: 10,
29 | value: "olivere",
30 | },
31 | ]);
32 | expect(res.tweet_search.hits.total.value).toBeGreaterThan(0);
33 | });
34 | test("Elastic error", async () => {
35 | try {
36 | const res = await client.reactiveSearch([
37 | {
38 | id: "tweet_search",
39 | dataField: ["user"],
40 | type: "term",
41 | size: 10,
42 | },
43 | ]);
44 | } catch (e) {
45 | expect(e.tweet_search.status).toEqual(400);
46 | }
47 | });
48 | });
49 |
--------------------------------------------------------------------------------
/__tests__/ReactiveSearchV3Test.js:
--------------------------------------------------------------------------------
1 | const appbase = require("..");
2 | const { uuidv4 } = require("../__mocks__");
3 | const config = require("../__mocks__/defaultConfig");
4 |
5 | describe("#Search", function () {
6 | let client;
7 | beforeAll(() => {
8 | client = appbase({
9 | url: config.URL,
10 | app: config.APPNAME,
11 | credentials: config.CREDENTIALS,
12 | });
13 | });
14 |
15 | test("should return results", async () => {
16 | var tweet = {
17 | user: "olivere",
18 | message: "Welcome to Golang and Elasticsearch.",
19 | };
20 | await client.index({
21 | id: uuidv4(),
22 | body: tweet,
23 | });
24 | const res = await client.reactiveSearchv3([
25 | {
26 | id: "tweet_search",
27 | dataField: ["user"],
28 | size: 10,
29 | value: "olivere",
30 | },
31 | ]);
32 | expect(res.tweet_search.hits.total.value).toBeGreaterThan(0);
33 | });
34 | test("Elastic error", async () => {
35 | try {
36 | const res = await client.reactiveSearchv3([
37 | {
38 | id: "tweet_search",
39 | dataField: ["user"],
40 | type: "term",
41 | size: 10,
42 | },
43 | ]);
44 | } catch (e) {
45 | expect(e.tweet_search.status).toEqual(400);
46 | }
47 | });
48 | });
49 |
--------------------------------------------------------------------------------
/__tests__/bulkTest.js:
--------------------------------------------------------------------------------
1 | const appbase = require("..");
2 | const { uuidv4 } = require("../__mocks__");
3 | const config = require("../__mocks__/defaultConfig");
4 |
5 | describe("#Bulk", function () {
6 | let client;
7 | beforeAll(() => {
8 | client = appbase({
9 | url: config.URL,
10 | app: config.APPNAME,
11 | credentials: config.CREDENTIALS,
12 | });
13 | });
14 |
15 | test("should bulk index one document", async (done) => {
16 | var tweet = {
17 | user: "olivere",
18 | message: "Welcome to Golang and Elasticsearch.",
19 | };
20 | const id = uuidv4();
21 | try {
22 | await client.bulk({
23 | body: [
24 | {
25 | index: {
26 | _id: id,
27 | },
28 | },
29 | tweet,
30 | ],
31 | });
32 | } catch (e) {
33 | console.error(e);
34 | }
35 | try {
36 | const res = await client.get({
37 | id: id,
38 | });
39 | try {
40 | expect(res).toMatchObject({
41 | _id: id,
42 | found: true,
43 | _source: tweet,
44 | });
45 | } catch (e) {
46 | return done(e);
47 | }
48 | } catch (e) {
49 | console.error(e);
50 | }
51 |
52 | const deleteRes = await client.delete({
53 | id: id,
54 | });
55 | if (deleteRes && deleteRes.result === 'deleted') {
56 | done();
57 | } else {
58 | done(new Error("Unable to delete data because it was not found"));
59 | }
60 | });
61 | test("should throw error if body is missing", function () {
62 | try {
63 | client.bulk({});
64 | } catch (e) {
65 | expect(true).toEqual(true);
66 | }
67 | });
68 |
69 | test("should throw error when no arguments is passed", function () {
70 | try {
71 | client.bulk();
72 | } catch (e) {
73 | expect(true).toEqual(true);
74 | }
75 | });
76 | });
77 |
--------------------------------------------------------------------------------
/__tests__/getMappingsTest.js:
--------------------------------------------------------------------------------
1 | const appbase = require('..');
2 | const config = require('../__mocks__/defaultConfig');
3 |
4 | describe('#Get_Mappings', function() {
5 | let client;
6 | beforeAll(() => {
7 | client = appbase({
8 | url: config.URL,
9 | app: config.APPNAME,
10 | credentials: config.CREDENTIALS,
11 | });
12 | });
13 |
14 | test('should get mappings object', async done => {
15 | const mappings = await client.getMappings();
16 | if (mappings[client.app].mappings) {
17 | done();
18 | } else {
19 | done(new Error('Mappings are not present'));
20 | }
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/__tests__/getTest.js:
--------------------------------------------------------------------------------
1 | const appbase = require('..');
2 | const { uuidv4 } = require('../__mocks__');
3 | const config = require('../__mocks__/defaultConfig');
4 |
5 | describe('#Get', function() {
6 | let client;
7 | beforeAll(() => {
8 | client = appbase({
9 | url: config.URL,
10 | app: config.APPNAME,
11 | credentials: config.CREDENTIALS,
12 | });
13 | });
14 |
15 | test('should get one document', async () => {
16 | const tweet = { user: 'olivere', message: 'Welcome to Golang and Elasticsearch.' };
17 | const id = uuidv4();
18 | await client.index({
19 | id,
20 | body: tweet,
21 | });
22 | const getRes = await client.get({
23 | id,
24 | });
25 | expect(getRes.found).toEqual(true);
26 | });
27 |
28 | test('should get one document: id as number', async () => {
29 | const tweet = { user: 'olivere', message: 'Welcome to Golang and Elasticsearch.' };
30 | const id = new Date().getTime();
31 | await client.index({
32 | id,
33 | body: tweet,
34 | });
35 | const getRes = await client.get({
36 | id,
37 | });
38 | expect(getRes.found).toEqual(true);
39 | });
40 |
41 | test('should throw error: id as object', async () => {
42 | const tweet = { user: 'olivere', message: 'Welcome to Golang and Elasticsearch.' };
43 | const id = { key: 'ded' };
44 | try {
45 | const res = await client.get({
46 | id,
47 | body: tweet,
48 | });
49 | expect(true).toBe(false)
50 | } catch(e) {
51 | expect(e.message).toEqual("fields missing: id, ")
52 | }
53 | });
54 | });
55 |
--------------------------------------------------------------------------------
/__tests__/msearchTest.js:
--------------------------------------------------------------------------------
1 | const appbase = require('..');
2 | const { uuidv4 } = require('../__mocks__');
3 | const config = require('../__mocks__/defaultConfig');
4 |
5 | describe('#Msearch', function() {
6 | let client;
7 | beforeAll(() => {
8 | client = appbase({
9 | url: config.URL,
10 | app: config.APPNAME,
11 | credentials: config.CREDENTIALS,
12 | });
13 | });
14 |
15 | test('should return results', async () => {
16 | var tweet = { user: 'olivere', message: 'Welcome to Golang and Elasticsearch.' };
17 | await client.index({
18 | id: uuidv4(),
19 | body: tweet,
20 | });
21 | // index second tweet
22 | var penguinTweet = { user: 'penguin', message: 'woot woot!' };
23 | await client.index({
24 | id: uuidv4(),
25 | body: penguinTweet,
26 | });
27 | const res = await client.msearch({
28 | body: [{}, { query: { match_all: {} } }, {}, { query: { match: { _id: 1 } } }],
29 | });
30 | expect(res.responses.length).toEqual(2);
31 | });
32 | });
33 |
--------------------------------------------------------------------------------
/__tests__/searchTest.js:
--------------------------------------------------------------------------------
1 | const appbase = require('..');
2 | const { uuidv4 } = require('../__mocks__');
3 | const config = require('../__mocks__/defaultConfig');
4 |
5 | describe('#Search', function() {
6 | let client;
7 | beforeAll(() => {
8 | client = appbase({
9 | url: config.URL,
10 | app: config.APPNAME,
11 | credentials: config.CREDENTIALS,
12 | });
13 | });
14 |
15 | test('should return results', async () => {
16 | var tweet = { user: 'olivere', message: 'Welcome to Golang and Elasticsearch.' };
17 | await client.index({
18 | id: uuidv4(),
19 | body: tweet,
20 | });
21 | const res = await client.search({
22 | body: {
23 | query: {
24 | match_all: {},
25 | },
26 | },
27 | });
28 | expect(res.hits.total.value).toBeGreaterThan(0);
29 | });
30 | });
31 |
--------------------------------------------------------------------------------
/__tests__/updateTest.js:
--------------------------------------------------------------------------------
1 | const appbase = require('..');
2 | const { uuidv4 } = require('../__mocks__');
3 | const config = require('../__mocks__/defaultConfig');
4 |
5 | describe('#Update', function() {
6 | let client;
7 | beforeAll(() => {
8 | client = appbase({
9 | url: config.URL,
10 | app: config.APPNAME,
11 | credentials: config.CREDENTIALS,
12 | });
13 | });
14 |
15 | test('should update one document', async () => {
16 | const id = uuidv4();
17 | var tweet = {
18 | user: 'olivere',
19 | message: 'Welcome to Golang and Elasticsearch.',
20 | };
21 | var tweet2 = {
22 | user: 'olivere',
23 | message: 'This is a new tweet.',
24 | };
25 | await client.index({
26 | id,
27 | body: tweet,
28 | });
29 | const updateRes = await client.update({
30 | id,
31 | body: {
32 | doc: tweet2
33 | },
34 | })
35 | expect(updateRes.result).toEqual('updated');
36 | });
37 | });
38 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | collectCoverageFrom: ['src/**/*.{js}'],
3 | testEnvironment: 'node',
4 | };
5 |
--------------------------------------------------------------------------------
/package-scripts.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line
2 | const npsUtils = require("nps-utils");
3 |
4 | const {
5 | series, concurrent, rimraf, crossEnv,
6 | } = npsUtils;
7 |
8 | module.exports = {
9 | scripts: {
10 | test: {
11 | default: crossEnv('NODE_ENV=test jest --coverage'),
12 | update: crossEnv('NODE_ENV=test jest --coverage'),
13 | watch: crossEnv('NODE_ENV=test jest --watch'),
14 | codeCov: crossEnv(
15 | 'cat ./coverage/lcov.info | ./node_modules/codecov.io/bin/codecov.io.js',
16 | ),
17 | },
18 | build: {
19 | description: 'delete the dist directory and run all builds',
20 | default: series(
21 | rimraf('dist'),
22 | concurrent.nps(
23 | 'compileToes5',
24 | 'build.es',
25 | 'build.cjs',
26 | 'build.umd.main',
27 | 'build.umd.min',
28 | ),
29 | ),
30 | es: {
31 | description: 'run the build with rollup (uses rollup.config.js)',
32 | script: 'rollup --config --environment FORMAT:es',
33 | },
34 | cjs: {
35 | description: 'run rollup build with CommonJS format',
36 | script: 'rollup --config --environment FORMAT:cjs',
37 | },
38 | umd: {
39 | min: {
40 | description: 'run the rollup build with sourcemaps',
41 | script: 'rollup --config --sourcemap --environment MINIFY,FORMAT:umd',
42 | },
43 | main: {
44 | description: 'builds the cjs and umd files',
45 | script: 'rollup --config --sourcemap --environment FORMAT:umd',
46 | },
47 | },
48 | andTest: series.nps('build'),
49 | },
50 | copyTypes: series(npsUtils.copy('dist')),
51 | compileToes5: series(rimraf('lib')),
52 | lint: {
53 | description: 'lint the entire project',
54 | script: 'eslint .',
55 | },
56 | validate: {
57 | description:
58 | 'This runs several scripts to make sure things look good before committing or on clean install',
59 | default: concurrent.nps('compileToes5', 'lint', 'build.andTest'),
60 | },
61 | },
62 | options: {
63 | silent: false,
64 | },
65 | };
66 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "appbase-js",
3 | "version": "5.3.4",
4 | "main": "dist/appbase-js.cjs.js",
5 | "jsnext:main": "dist/appbase-js.es.js",
6 | "module": "dist/appbase-js.es.js",
7 | "description": "Appbase.io streaming client lib for Javascript",
8 | "types": "dist/index.d.ts",
9 | "keywords": [
10 | "appbase",
11 | "elasticsearch",
12 | "search"
13 | ],
14 | "author": "Kuldeep Saxena ",
15 | "license": "MIT",
16 | "devDependencies": {
17 | "babel-cli": "^6.26.0",
18 | "babel-core": "^6.26.3",
19 | "babel-eslint": "^8.2.6",
20 | "babel-jest": "^23.4.2",
21 | "babel-plugin-external-helpers": "^6.22.0",
22 | "babel-preset-env": "^1.7.0",
23 | "eslint": "^5.3.0",
24 | "eslint-config-airbnb": "^17.1.0",
25 | "eslint-plugin-import": "^2.14.0",
26 | "husky": "^0.14.3",
27 | "jest": "^23.5.0",
28 | "lint-staged": "^7.2.2",
29 | "nps": "^5.9.3",
30 | "nps-utils": "^1.7.0",
31 | "rollup": "^0.64.0",
32 | "rollup-plugin-babel": "^3.0.7",
33 | "rollup-plugin-commonjs": "^9.1.0",
34 | "rollup-plugin-copy": "^3.4.0",
35 | "rollup-plugin-node-builtins": "^2.1.2",
36 | "rollup-plugin-node-resolve": "^3.3.0",
37 | "rollup-plugin-replace": "^2.0.0",
38 | "rollup-plugin-terser": "^1.0.1"
39 | },
40 | "scripts": {
41 | "start": "nps",
42 | "pretest": "nps build",
43 | "test": "nps test",
44 | "precommit": "lint-staged",
45 | "prepare": "npm start validate"
46 | },
47 | "lint-staged": {
48 | "*.js": [
49 | "eslint"
50 | ]
51 | },
52 | "files": [
53 | "dist",
54 | "lib"
55 | ],
56 | "dependencies": {
57 | "cross-fetch": "^3.1.5",
58 | "querystring": "^0.2.0",
59 | "url-parser-lite": "^0.1.0"
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import resolve from 'rollup-plugin-node-resolve';
2 | import commonjs from 'rollup-plugin-commonjs';
3 | import { terser } from 'rollup-plugin-terser';
4 | import replace from 'rollup-plugin-replace';
5 | import builtins from 'rollup-plugin-node-builtins';
6 | import babel from 'rollup-plugin-babel';
7 | import copy from 'rollup-plugin-copy';
8 | import pkg from './package.json';
9 |
10 | const minify = process.env.MINIFY;
11 | const format = process.env.FORMAT;
12 | const es = format === 'es';
13 | const umd = format === 'umd';
14 | const cjs = format === 'cjs';
15 |
16 | let output;
17 |
18 | if (es) {
19 | output = { file: 'dist/appbase-js.es.js', format: 'es' };
20 | } else if (umd) {
21 | if (minify) {
22 | output = {
23 | file: 'dist/appbase-js.umd.min.js',
24 | format: 'umd',
25 | };
26 | } else {
27 | output = { file: 'dist/appbase-js.umd.js', format: 'umd' };
28 | }
29 | } else if (cjs) {
30 | output = { file: 'dist/appbase-js.cjs.js', format: 'cjs' };
31 | } else if (format) {
32 | throw new Error(`invalid format specified: "${format}".`);
33 | } else {
34 | throw new Error('no format specified. --environment FORMAT:xxx');
35 | }
36 |
37 | export default {
38 | input: 'src/index.js',
39 | output: Object.assign(
40 | {
41 | name: umd ? 'Appbase' : 'appbase-js',
42 | },
43 | output,
44 | ),
45 | external: umd
46 | ? Object.keys(pkg.peerDependencies || {})
47 | : [
48 | ...Object.keys(pkg.dependencies || {}),
49 | ...Object.keys(pkg.peerDependencies || {}),
50 | ],
51 | plugins: [
52 | umd
53 | ? resolve({
54 | jsnext: true,
55 | main: true,
56 | preferBuiltins: false,
57 | browser: true,
58 | })
59 | : {},
60 | umd ? commonjs({ include: 'node_modules/**' }) : {},
61 | babel({
62 | exclude: 'node_modules/**',
63 | babelrc: false,
64 | presets: [['env', { loose: true, modules: false }]],
65 | plugins: ['external-helpers'],
66 | }),
67 | umd ? builtins() : {},
68 | umd
69 | ? replace({
70 | 'process.env.NODE_ENV': JSON.stringify(
71 | minify ? 'production' : 'development',
72 | ),
73 | })
74 | : null,
75 | minify ? terser() : null,
76 | copy({
77 | targets: [{ src: 'src/index.d.ts', dest: 'dist' }],
78 | }),
79 | ].filter(Boolean),
80 | };
81 |
--------------------------------------------------------------------------------
/src/core/api/bulk.js:
--------------------------------------------------------------------------------
1 | import { removeUndefined, validate } from '../../utils/index';
2 |
3 | /**
4 | * Bulk Service
5 | * @param {Object} args
6 | * @param {String} args.type
7 | * @param {Object} args.body
8 | */
9 | function bulkApi(args) {
10 | const parsedArgs = removeUndefined(args);
11 | // Validate arguments
12 | const valid = validate(parsedArgs, {
13 | body: 'object',
14 | });
15 | if (valid !== true) {
16 | throw valid;
17 | }
18 |
19 | const { type, body } = parsedArgs;
20 |
21 | delete parsedArgs.type;
22 | delete parsedArgs.body;
23 |
24 | let path;
25 | if (type) {
26 | path = `${type}/_bulk`;
27 | } else {
28 | path = '_bulk';
29 | }
30 |
31 | return this.performFetchRequest({
32 | method: 'POST',
33 | path,
34 | params: parsedArgs,
35 | body,
36 | });
37 | }
38 | export default bulkApi;
39 |
--------------------------------------------------------------------------------
/src/core/api/delete.js:
--------------------------------------------------------------------------------
1 | import { removeUndefined, validate } from '../../utils/index';
2 |
3 | /**
4 | * Delete Service
5 | * @param {Object} args
6 | * @param {String} args.type
7 | * @param {String} args.id
8 | */
9 | function deleteApi(args) {
10 | const parsedArgs = removeUndefined(args);
11 | // Validate arguments
12 | const valid = validate(parsedArgs, {
13 | id: 'string|number',
14 | });
15 | if (valid !== true) {
16 | throw valid;
17 | }
18 |
19 | const { type = '_doc', id } = parsedArgs;
20 | delete parsedArgs.type;
21 | delete parsedArgs.id;
22 |
23 | const path = `${type}/${encodeURIComponent(id)}`;
24 |
25 | return this.performFetchRequest({
26 | method: 'DELETE',
27 | path,
28 | params: parsedArgs,
29 | });
30 | }
31 | export default deleteApi;
32 |
--------------------------------------------------------------------------------
/src/core/api/get.js:
--------------------------------------------------------------------------------
1 | import { removeUndefined, validate } from '../../utils/index';
2 |
3 | /**
4 | * Get Service
5 | * @param {Object} args
6 | * @param {String} args.type
7 | * @param {String} args.id
8 | */
9 | function getApi(args) {
10 | const parsedArgs = removeUndefined(args);
11 | // Validate arguments
12 | const valid = validate(parsedArgs, {
13 | id: 'string|number',
14 | });
15 |
16 | if (valid !== true) {
17 | throw valid;
18 | }
19 |
20 | const { type = '_doc', id } = parsedArgs;
21 |
22 | delete parsedArgs.type;
23 | delete parsedArgs.id;
24 |
25 | const path = `${type}/${encodeURIComponent(id)}`;
26 |
27 | return this.performFetchRequest({
28 | method: 'GET',
29 | path,
30 | params: parsedArgs,
31 | });
32 | }
33 | export default getApi;
34 |
--------------------------------------------------------------------------------
/src/core/api/getMappings.js:
--------------------------------------------------------------------------------
1 | /**
2 | * To get mappings
3 | */
4 | function getMappings() {
5 | return this.performFetchRequest({
6 | method: 'GET',
7 | path: '_mapping',
8 | });
9 | }
10 | export default getMappings;
11 |
--------------------------------------------------------------------------------
/src/core/api/getSuggestionsv3Api.js:
--------------------------------------------------------------------------------
1 | import {
2 | getMongoRequest,
3 | getTelemetryHeaders,
4 | removeUndefined,
5 | validateRSQuery,
6 | } from '../../utils/index';
7 |
8 | /**
9 | * ReactiveSearch suggestions API for v3
10 | * @param {Array} query
11 | * @param {Object} settings
12 | * @param {boolean} settings.recordAnalytics
13 | * @param {boolean} settings.userId
14 | * @param {boolean} settings.enableQueryRules
15 | * @param {boolean} settings.customEvents
16 | */
17 | function getSuggestionsv3Api(query, settings) {
18 | const parsedSettings = removeUndefined(settings);
19 |
20 | // Validate query
21 | const valid = validateRSQuery(query);
22 |
23 | if (valid !== true) {
24 | throw valid;
25 | }
26 |
27 | const body = {
28 | settings: parsedSettings,
29 | query,
30 | };
31 |
32 | if (this.mongodb) {
33 | Object.assign(body, { mongodb: getMongoRequest(this.app, this.mongodb) });
34 | }
35 | return this.performFetchRequest({
36 | method: 'POST',
37 | path: '_reactivesearch.v3',
38 | body,
39 | headers: getTelemetryHeaders(this.enableTelemetry),
40 | isRSAPI: true,
41 | isSuggestionsAPI: true,
42 | isMongoRequest: !!this.mongodb,
43 | });
44 | }
45 |
46 | export default getSuggestionsv3Api;
47 |
--------------------------------------------------------------------------------
/src/core/api/index.js:
--------------------------------------------------------------------------------
1 | import { removeUndefined, validate } from '../../utils/index';
2 |
3 | /**
4 | * Index Service
5 | * @param {Object} args
6 | * @param {String} args.type
7 | * @param {Object} args.body
8 | * @param {String} args.id
9 | */
10 | function indexApi(args) {
11 | const parsedArgs = removeUndefined(args);
12 | // Validate arguments
13 | const valid = validate(parsedArgs, {
14 | body: 'object',
15 | });
16 | if (valid !== true) {
17 | throw valid;
18 | }
19 | const { type = '_doc', id, body } = parsedArgs;
20 |
21 | delete parsedArgs.type;
22 | delete parsedArgs.body;
23 | delete parsedArgs.id;
24 |
25 | let path;
26 | if (id) {
27 | path = type ? `${type}/${encodeURIComponent(id)}` : encodeURIComponent(id);
28 | } else {
29 | path = type;
30 | }
31 | return this.performFetchRequest({
32 | method: 'POST',
33 | path,
34 | params: parsedArgs,
35 | body,
36 | });
37 | }
38 | export default indexApi;
39 |
--------------------------------------------------------------------------------
/src/core/api/msearch.js:
--------------------------------------------------------------------------------
1 | import { removeUndefined, validate } from '../../utils/index';
2 |
3 | /**
4 | * Msearch Service
5 | * @param {Object} args
6 | * @param {String} args.type
7 | * @param {Object} args.body
8 | */
9 | function msearchApi(args) {
10 | const parsedArgs = removeUndefined(args);
11 | // Validate arguments
12 | const valid = validate(parsedArgs, {
13 | body: 'object',
14 | });
15 | if (valid !== true) {
16 | throw valid;
17 | }
18 |
19 | let type;
20 | if (Array.isArray(parsedArgs.type)) {
21 | type = parsedArgs.type.join();
22 | } else {
23 | ({ type } = parsedArgs);
24 | }
25 |
26 | const { body } = parsedArgs;
27 |
28 | delete parsedArgs.type;
29 | delete parsedArgs.body;
30 |
31 | let path;
32 | if (type) {
33 | path = `${type}/_msearch`;
34 | } else {
35 | path = '_msearch';
36 | }
37 |
38 | return this.performFetchRequest({
39 | method: 'POST',
40 | path,
41 | params: parsedArgs,
42 | body,
43 | });
44 | }
45 | export default msearchApi;
46 |
--------------------------------------------------------------------------------
/src/core/api/reactiveSearchApi.js:
--------------------------------------------------------------------------------
1 | import {
2 | getTelemetryHeaders,
3 | getMongoRequest,
4 | removeUndefined,
5 | validateRSQuery,
6 | } from '../../utils/index';
7 |
8 | /**
9 | * ReactiveSearch API Service for v3
10 | * @param {Array} query
11 | * @param {Object} settings
12 | * @param {boolean} settings.recordAnalytics
13 | * @param {boolean} settings.userId
14 | * @param {boolean} settings.enableQueryRules
15 | * @param {boolean} settings.customEvents
16 | */
17 | function reactiveSearchApi(query, settings, params) {
18 | const parsedSettings = removeUndefined(settings);
19 |
20 | // Validate query
21 | const valid = validateRSQuery(query);
22 |
23 | if (valid !== true) {
24 | throw valid;
25 | }
26 |
27 | const body = {
28 | settings: parsedSettings,
29 | query,
30 | };
31 |
32 | if (this.mongodb) {
33 | Object.assign(body, { mongodb: getMongoRequest(this.app, this.mongodb) });
34 | }
35 | return this.performFetchRequest({
36 | method: 'POST',
37 | path: '_reactivesearch',
38 | body,
39 | headers: getTelemetryHeaders(this.enableTelemetry, !this.mongodb),
40 | isRSAPI: true,
41 | isMongoRequest: !!this.mongodb,
42 | params,
43 | httpRequestTimeout: this.httpRequestTimeout || 0,
44 | });
45 | }
46 | export default reactiveSearchApi;
47 |
--------------------------------------------------------------------------------
/src/core/api/reactiveSearchv3Api.js:
--------------------------------------------------------------------------------
1 | import {
2 | removeUndefined,
3 | validateRSQuery,
4 | getMongoRequest,
5 | getTelemetryHeaders,
6 | } from '../../utils/index';
7 |
8 | /**
9 | * ReactiveSearch API Service for v3
10 | * @param {Array} query
11 | * @param {Object} settings
12 | * @param {boolean} settings.recordAnalytics
13 | * @param {boolean} settings.userId
14 | * @param {boolean} settings.enableQueryRules
15 | * @param {boolean} settings.customEvents
16 | */
17 | function reactiveSearchv3Api(query, settings, params) {
18 | const parsedSettings = removeUndefined(settings);
19 |
20 | // Validate query
21 | const valid = validateRSQuery(query);
22 |
23 | if (valid !== true) {
24 | throw valid;
25 | }
26 |
27 | const body = {
28 | settings: parsedSettings,
29 | query,
30 | };
31 | if (this.mongodb) {
32 | Object.assign(body, { mongodb: getMongoRequest(this.app, this.mongodb) });
33 | }
34 | return this.performFetchRequest({
35 | method: 'POST',
36 | path: '_reactivesearch.v3',
37 | body,
38 | headers: getTelemetryHeaders(this.enableTelemetry, !this.mongodb),
39 | isRSAPI: true,
40 | isMongoRequest: !!this.mongodb,
41 | params,
42 | httpRequestTimeout: this.httpRequestTimeout || 0,
43 | });
44 | }
45 | export default reactiveSearchv3Api;
46 |
--------------------------------------------------------------------------------
/src/core/api/search.js:
--------------------------------------------------------------------------------
1 | import { removeUndefined, validate } from '../../utils/index';
2 |
3 | /**
4 | * Search Service
5 | * @param {Object} args
6 | * @param {String} args.type
7 | * @param {Object} args.body
8 | */
9 | function searchApi(args) {
10 | const parsedArgs = removeUndefined(args);
11 | // Validate arguments
12 | const valid = validate(parsedArgs, {
13 | body: 'object',
14 | });
15 | if (valid !== true) {
16 | throw valid;
17 | }
18 |
19 | let type;
20 | if (Array.isArray(parsedArgs.type)) {
21 | type = parsedArgs.type.join();
22 | } else {
23 | // eslint-disable-next-line
24 | type = parsedArgs.type;
25 | }
26 |
27 | const { body } = parsedArgs;
28 |
29 | delete parsedArgs.type;
30 | delete parsedArgs.body;
31 |
32 | let path;
33 | if (type) {
34 | path = `${type}/_search`;
35 | } else {
36 | path = '_search';
37 | }
38 |
39 | return this.performFetchRequest({
40 | method: 'POST',
41 | path,
42 | params: parsedArgs,
43 | body,
44 | });
45 | }
46 | export default searchApi;
47 |
--------------------------------------------------------------------------------
/src/core/api/update.js:
--------------------------------------------------------------------------------
1 | import { removeUndefined, validate } from '../../utils/index';
2 |
3 | /**
4 | * Update Service
5 | * @param {Object} args
6 | * @param {String} args.type
7 | * @param {Object} args.body
8 | * @param {String} args.id
9 | */
10 | function updateApi(args) {
11 | const parsedArgs = removeUndefined(args);
12 | // Validate arguments
13 | const valid = validate(parsedArgs, {
14 | id: 'string|number',
15 | body: 'object',
16 | });
17 | if (valid !== true) {
18 | throw valid;
19 | }
20 |
21 | const { type = '_doc', id, body } = parsedArgs;
22 | delete parsedArgs.type;
23 | delete parsedArgs.id;
24 | delete parsedArgs.body;
25 | const path = `${type}/${encodeURIComponent(id)}/_update`;
26 |
27 | return this.performFetchRequest({
28 | method: 'POST',
29 | path,
30 | params: parsedArgs,
31 | body,
32 | });
33 | }
34 | export default updateApi;
35 |
--------------------------------------------------------------------------------
/src/core/index.js:
--------------------------------------------------------------------------------
1 | import URL from 'url-parser-lite';
2 | import {
3 | backendAlias,
4 | isAppbase,
5 | isValidHttpUrl,
6 | validateSchema,
7 | } from '../utils/index';
8 | import SCHEMA from '../utils/schema/index';
9 | /**
10 | * Returns an instance of Appbase client
11 | * @param {Object} config To configure properties
12 | * @param {String} config.url
13 | * @param {String} config.app
14 | * @param {String} config.credentials
15 | * @param {String} config.username
16 | * @param {String} config.password
17 | * @param {Boolean} config.enableTelemetry
18 | * @param {Object} config.mongodb
19 | * @param {Object} config.endpoint
20 | * @param {Object} config.httpRequestTimeout
21 | * A callback function which will be invoked before a fetch request made
22 | */
23 | function AppBase(config) {
24 | const {
25 | auth = null,
26 | host = '',
27 | path = '',
28 | protocol = '',
29 | } = URL((config.endpoint ? config.endpoint.url : config.url) || '');
30 | let { url } = config;
31 | url = host + path;
32 | // Parse url
33 | if (url.slice(-1) === '/') {
34 | url = url.slice(0, -1);
35 | }
36 | const backendName = backendAlias[config.mongodb ? 'MONGODB' : 'ELASTICSEARCH'];
37 | // eslint-disable-next-line
38 | const schema = SCHEMA[backendName];
39 |
40 | if (config.endpoint && isValidHttpUrl(config.endpoint.url)) {
41 | schema.url.required = false;
42 | schema.app.required = false;
43 | schema.credentials.required = false;
44 | }
45 |
46 | validateSchema(
47 | {
48 | url: config.url,
49 | app: config.app,
50 | credentials: config.credentials,
51 | username: config.username,
52 | password: config.password,
53 | enableTelemetry: config.enableTelemetry,
54 | mongodb: config.mongodb,
55 | },
56 | schema,
57 | backendName,
58 | );
59 |
60 | if (typeof protocol !== 'string' || protocol === '') {
61 | throw new Error(
62 | 'Protocol is not present in url. URL should be of the form https://appbase-demo-ansible-abxiydt-arc.searchbase.io',
63 | );
64 | }
65 |
66 | let credentials = auth || null;
67 | /**
68 | * Credentials can be provided as a part of the URL,
69 | * as username, password args or as a credentials argument directly */
70 | if (typeof config.credentials === 'string' && config.credentials !== '') {
71 | // eslint-disable-next-line
72 | credentials = config.credentials;
73 | } else if (
74 | typeof config.username === 'string'
75 | && config.username !== ''
76 | && typeof config.password === 'string'
77 | && config.password !== ''
78 | ) {
79 | credentials = `${config.username}:${config.password}`;
80 | }
81 | if (!config.mongodb) {
82 | if (isAppbase(url) && credentials === null) {
83 | throw new Error(
84 | 'Authentication information is not present. Did you add credentials?',
85 | );
86 | }
87 | }
88 |
89 | this.url = url;
90 | this.protocol = protocol;
91 | this.app = config.app;
92 | this.credentials = credentials;
93 | if (config.mongodb) {
94 | this.mongodb = config.mongodb;
95 | }
96 |
97 | if (config.httpRequestTimeout) {
98 | this.httpRequestTimeout = config.httpRequestTimeout;
99 | }
100 |
101 | if (typeof config.enableTelemetry === 'boolean') {
102 | this.enableTelemetry = config.enableTelemetry;
103 | }
104 | }
105 | export default AppBase;
106 |
--------------------------------------------------------------------------------
/src/core/request/fetch.js:
--------------------------------------------------------------------------------
1 | import querystring from 'querystring';
2 | import fetch from 'cross-fetch';
3 | import { btoa, removeUndefined } from '../../utils/index';
4 |
5 | /**
6 | * To perform fetch request
7 | * @param {Object} args
8 | * @param {String} args.method
9 | * @param {String} args.path
10 | * @param {Object} args.params
11 | * @param {Object} args.body
12 | * @param {Object} args.headers
13 | * @param {boolean} args.isSuggestionsAPI
14 | * @param {number} args.httpRequestTimeout - Timeout duration in milliseconds
15 | */
16 | function fetchRequest(args) {
17 | return new Promise((resolve, reject) => {
18 | const parsedArgs = removeUndefined(args);
19 | try {
20 | const {
21 | method,
22 | path,
23 | params,
24 | body,
25 | isRSAPI,
26 | isSuggestionsAPI,
27 | isMongoRequest = false,
28 | httpRequestTimeout = 0, // Default timeout is 0 (no timeout)
29 | } = parsedArgs;
30 | const app = isSuggestionsAPI ? '.suggestions' : this.app;
31 | let bodyCopy = body;
32 | const contentType = path.endsWith('msearch') || path.endsWith('bulk')
33 | ? 'application/x-ndjson'
34 | : 'application/json';
35 | const headers = Object.assign(
36 | {},
37 | {
38 | Accept: 'application/json',
39 | 'Content-Type': contentType,
40 | },
41 | args.headers,
42 | this.headers,
43 | );
44 | const timestamp = Date.now();
45 | if (this.credentials) {
46 | headers.Authorization = `Basic ${btoa(this.credentials)}`;
47 | }
48 | const requestOptions = {
49 | method,
50 | headers,
51 | };
52 | if (Array.isArray(bodyCopy)) {
53 | let arrayBody = '';
54 | bodyCopy.forEach((item) => {
55 | arrayBody += JSON.stringify(item);
56 | arrayBody += '\n';
57 | });
58 |
59 | bodyCopy = arrayBody;
60 | } else {
61 | bodyCopy = JSON.stringify(bodyCopy) || {};
62 | }
63 |
64 | if (Object.keys(bodyCopy).length !== 0) {
65 | requestOptions.body = bodyCopy;
66 | }
67 |
68 | const handleTransformRequest = (res) => {
69 | if (
70 | this.transformRequest
71 | && typeof this.transformRequest === 'function'
72 | ) {
73 | const transformRequestPromise = this.transformRequest(res);
74 | return transformRequestPromise instanceof Promise
75 | ? transformRequestPromise
76 | : Promise.resolve(transformRequestPromise);
77 | }
78 | return Promise.resolve(res);
79 | };
80 |
81 | let responseHeaders = {};
82 |
83 | let paramsString = '';
84 | if (params) {
85 | paramsString = `?${querystring.stringify(params)}`;
86 | }
87 | const finalURL = isMongoRequest
88 | ? `${this.protocol}://${this.url}`
89 | : `${this.protocol}://${this.url}/${app}/${path}${paramsString}`;
90 |
91 | return handleTransformRequest(
92 | Object.assign(
93 | {},
94 | {
95 | url: finalURL,
96 | },
97 | requestOptions,
98 | ),
99 | )
100 | .then((ts) => {
101 | const transformedRequest = Object.assign({}, ts);
102 | const { url } = transformedRequest;
103 | delete transformedRequest.url;
104 |
105 | const controller = new AbortController();
106 | const { signal } = controller;
107 |
108 | const fetchPromise = fetch(
109 | url || finalURL,
110 | Object.assign({}, transformedRequest, {
111 | // apply timestamp header for RS API
112 | headers:
113 | isRSAPI && !isMongoRequest
114 | ? Object.assign({}, transformedRequest.headers, {
115 | 'x-timestamp': new Date().getTime(),
116 | })
117 | : transformedRequest.headers,
118 | signal, // Attach the abort signal to the fetch request
119 | }),
120 | );
121 |
122 | const timeoutPromise = new Promise((_, rejectTP) => {
123 | if (httpRequestTimeout > 0) {
124 | setTimeout(() => {
125 | rejectTP(new Error('Request timeout'));
126 | controller.abort();
127 | }, httpRequestTimeout);
128 | }
129 | });
130 |
131 | return Promise.race([fetchPromise, timeoutPromise])
132 | .then((res) => {
133 | if (res.status >= 500) {
134 | return reject(res);
135 | }
136 | responseHeaders = res.headers;
137 | return res
138 | .json()
139 | .then((data) => {
140 | if (res.status >= 400) {
141 | return reject(res);
142 | }
143 | if (data && data.error) {
144 | return reject(data);
145 | }
146 | // Handle error from RS API RESPONSE
147 | if (
148 | isRSAPI
149 | && data
150 | && Object.prototype.toString.call(data) === '[object Object]'
151 | ) {
152 | if (body && body.query && body.query instanceof Array) {
153 | let errorResponses = 0;
154 | const allResponses = body.query.filter(
155 | q => q.execute || q.execute === undefined,
156 | ).length;
157 |
158 | if (data) {
159 | Object.keys(data).forEach((key) => {
160 | if (
161 | data[key]
162 | && Object.prototype.hasOwnProperty.call(
163 | data[key],
164 | 'error',
165 | )
166 | && !!data[key].error
167 | ) {
168 | errorResponses += 1;
169 | }
170 | });
171 | }
172 | // reject only when all responses have an error
173 | if (
174 | errorResponses > 0
175 | && allResponses === errorResponses
176 | ) {
177 | return reject(data);
178 | }
179 | }
180 | }
181 |
182 | // Handle error from _msearch response
183 | if (data && data.responses instanceof Array) {
184 | const allResponses = data.responses.length;
185 | const errorResponses = data.responses.filter(entry => Object.prototype.hasOwnProperty.call(entry, 'error')).length;
186 | // reject only when all responses have an error
187 | if (allResponses === errorResponses) {
188 | return reject(data);
189 | }
190 | }
191 | const response = Object.assign({}, data, {
192 | _timestamp: timestamp,
193 | _headers: responseHeaders,
194 | });
195 | return resolve(response);
196 | })
197 | .catch(e => reject(e));
198 | })
199 | .catch(e => reject(e));
200 | })
201 | .catch(err => reject(err));
202 | } catch (e) {
203 | return reject(e);
204 | }
205 | });
206 | }
207 |
208 | export default fetchRequest;
209 |
--------------------------------------------------------------------------------
/src/index.d.ts:
--------------------------------------------------------------------------------
1 | type BulkApiArgs = {
2 | type?: string;
3 | body: Object;
4 | };
5 | type DeleteApiArgs = {
6 | type?: string;
7 | id: string | number;
8 | };
9 | type GetApiArgs = DeleteApiArgs;
10 | type AppbaseSettings = {
11 | recordAnalytics?: boolean;
12 | enableQueryRules?: boolean;
13 | userId?: string;
14 | customEvents?: Object;
15 | };
16 | type IndexApiArgs = {
17 | type?: string;
18 | id?: string;
19 | body: Object;
20 | };
21 | type MsearchApiArgs = BulkApiArgs;
22 | type SearchApiArgs = BulkApiArgs;
23 | type UpdateApiArgs = {
24 | type?: string;
25 | id: string | number;
26 | body: Object;
27 | };
28 | type FetchApiArgs = {
29 | method: string;
30 | path: string;
31 | body: Object | Array;
32 | params?: Object;
33 | isSuggestionsAPI?: Boolean;
34 | };
35 |
36 | /* eslint-disable */
37 |
38 | interface MongoDbObject{
39 | db: string;
40 | collection: string;
41 | }
42 | interface AppbaseConfig {
43 | url?: string;
44 | app?: string;
45 | credentials?: string;
46 | username?: string;
47 | password?: string;
48 | enableTelemetry?: boolean;
49 | mongodb?: MongoDbObject;
50 | }
51 |
52 | interface AppbaseInstanceObject {
53 | performFetchRequest: (args: FetchApiArgs) => Object;
54 | index: (args: IndexApiArgs) => Object;
55 | get: (args: GetApiArgs) => Object;
56 | update: (args: UpdateApiArgs) => Object;
57 | delete: (args: DeleteApiArgs) => Object;
58 | bulk: (args: BulkApiArgs) => Object;
59 | search: (args: SearchApiArgs) => Object;
60 | msearch: (args: MsearchApiArgs) => Object;
61 | reactiveSearch: (query: Array, settings?: AppbaseSettings) => Object;
62 | reactiveSearchv3: (
63 | query: Array,
64 | settings?: AppbaseSettings
65 | ) => Object;
66 | getQuerySuggestions: (
67 | query: Array,
68 | settings?: AppbaseSettings
69 | ) => Object;
70 | getMappings: () => Object;
71 | setHeaders: (headers?: Object, shouldEncode?: Boolean) => void;
72 | }
73 |
74 | /* eslint-enable */
75 | declare function appbasejs(config: AppbaseConfig): AppbaseInstanceObject;
76 | export default appbasejs;
77 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import AppBaseClient from './core/index';
2 | import fetchRequest from './core/request/fetch';
3 | import indexApi from './core/api/index';
4 | import getApi from './core/api/get';
5 | import updateApi from './core/api/update';
6 | import deleteApi from './core/api/delete';
7 | import bulkApi from './core/api/bulk';
8 | import searchApi from './core/api/search';
9 | import msearchApi from './core/api/msearch';
10 | import reactiveSearchApi from './core/api/reactiveSearchApi';
11 | import reactiveSearchv3Api from './core/api/reactiveSearchv3Api';
12 | import getMappingsApi from './core/api/getMappings';
13 | import { encodeHeaders } from './utils/index';
14 | import getSuggestionsv3Api from './core/api/getSuggestionsv3Api';
15 |
16 | export default function appbasejs(config) {
17 | const client = new AppBaseClient(config);
18 |
19 | AppBaseClient.prototype.performFetchRequest = fetchRequest;
20 |
21 | AppBaseClient.prototype.index = indexApi;
22 |
23 | AppBaseClient.prototype.get = getApi;
24 |
25 | AppBaseClient.prototype.update = updateApi;
26 |
27 | AppBaseClient.prototype.delete = deleteApi;
28 |
29 | AppBaseClient.prototype.bulk = bulkApi;
30 |
31 | AppBaseClient.prototype.search = searchApi;
32 |
33 | AppBaseClient.prototype.msearch = msearchApi;
34 |
35 | AppBaseClient.prototype.reactiveSearch = reactiveSearchApi;
36 |
37 | AppBaseClient.prototype.reactiveSearchv3 = reactiveSearchv3Api;
38 |
39 | AppBaseClient.prototype.getQuerySuggestions = getSuggestionsv3Api;
40 |
41 | AppBaseClient.prototype.getMappings = getMappingsApi;
42 |
43 | AppBaseClient.prototype.setHeaders = function setHeaders(
44 | headers = {},
45 | shouldEncode = false,
46 | ) {
47 | // Encode headers
48 | if (shouldEncode) {
49 | this.headers = encodeHeaders(headers);
50 | } else {
51 | this.headers = headers;
52 | }
53 | };
54 |
55 | if (typeof window !== 'undefined') {
56 | window.Appbase = client;
57 | }
58 | return client;
59 | }
60 |
--------------------------------------------------------------------------------
/src/utils/index.js:
--------------------------------------------------------------------------------
1 | export function contains(string, substring) {
2 | return string.indexOf(substring) !== -1;
3 | }
4 | export function isAppbase(url) {
5 | return contains(url, 'scalr.api.appbase.io');
6 | }
7 | export function btoa(input = '') {
8 | const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
9 | const str = input;
10 | let output = '';
11 |
12 | // eslint-disable-next-line
13 | for (
14 | let block = 0, charCode, i = 0, map = chars;
15 | str.charAt(i | 0) || ((map = '='), i % 1); // eslint-disable-line no-bitwise
16 | output += map.charAt(63 & (block >> (8 - (i % 1) * 8))) // eslint-disable-line no-bitwise
17 | ) {
18 | charCode = str.charCodeAt((i += 3 / 4));
19 |
20 | if (charCode > 0xff) {
21 | throw new Error(
22 | '"btoa" failed: The string to be encoded contains characters outside of the Latin1 range.',
23 | );
24 | }
25 |
26 | block = (block << 8) | charCode; // eslint-disable-line no-bitwise
27 | }
28 |
29 | return output;
30 | }
31 | export function uuidv4() {
32 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
33 | const r = (Math.random() * 16) | 0; // eslint-disable-line no-bitwise
34 |
35 | const v = c === 'x' ? r : (r & 0x3) | 0x8; // eslint-disable-line no-bitwise
36 | return v.toString(16);
37 | });
38 | }
39 |
40 | export function validateRSQuery(query) {
41 | if (query && Object.prototype.toString.call(query) === '[object Array]') {
42 | for (let i = 0; i < query.length; i += 1) {
43 | const q = query[i];
44 | if (q) {
45 | if (!q.id) {
46 | return new Error("'id' field must be present in query object");
47 | }
48 | } else {
49 | return new Error('query object can not have an empty value');
50 | }
51 | }
52 | return true;
53 | }
54 | return new Error("invalid query value, 'query' value must be an array");
55 | }
56 |
57 | export function validate(object, fields) {
58 | const invalid = [];
59 | const emptyFor = {
60 | object: null,
61 | string: '',
62 | number: 0,
63 | };
64 | const keys = Object.keys(fields);
65 | keys.forEach((key) => {
66 | const types = fields[key].split('|');
67 | const matchedType = types.find(
68 | type =>
69 | // eslint-disable-next-line
70 | typeof object[key] === type
71 | );
72 | if (!matchedType || object[key] === emptyFor[matchedType]) {
73 | invalid.push(key);
74 | }
75 | });
76 | let missing = '';
77 | for (let i = 0; i < invalid.length; i += 1) {
78 | missing += `${invalid[i]}, `;
79 | }
80 | if (invalid.length > 0) {
81 | return new Error(`fields missing: ${missing}`);
82 | }
83 |
84 | return true;
85 | }
86 |
87 | export function removeUndefined(value = {}) {
88 | if (
89 | value
90 | || !(Object.keys(value).length === 0 && value.constructor === Object)
91 | ) {
92 | return JSON.parse(JSON.stringify(value));
93 | }
94 | return null;
95 | }
96 |
97 | /**
98 | * Send only when a connection is opened
99 | * @param {Object} socket
100 | * @param {Function} callback
101 | */
102 | export function waitForSocketConnection(socket, callback) {
103 | setTimeout(() => {
104 | if (socket.readyState === 1) {
105 | if (callback != null) {
106 | callback();
107 | }
108 | } else {
109 | waitForSocketConnection(socket, callback);
110 | }
111 | }, 5); // wait 5 ms for the connection...
112 | }
113 |
114 | export function encodeHeaders(headers = {}, shouldEncode = true) {
115 | // Encode headers
116 | let encodedHeaders = {};
117 | if (shouldEncode) {
118 | Object.keys(headers).forEach((header) => {
119 | encodedHeaders[header] = encodeURI(headers[header]);
120 | });
121 | } else {
122 | encodedHeaders = headers;
123 | }
124 | return encodedHeaders;
125 | }
126 | export function getMongoRequest(app, mongo) {
127 | const mongodb = {};
128 | if (app) {
129 | mongodb.index = app;
130 | }
131 | if (mongo) {
132 | if (mongo.db) {
133 | mongodb.db = mongo.db;
134 | }
135 | if (mongo.collection) {
136 | mongodb.collection = mongo.collection;
137 | }
138 | }
139 | return mongodb;
140 | }
141 |
142 | export function getTelemetryHeaders(enableTelemetry, shouldSetHeaders) {
143 | const headers = {};
144 | if (!shouldSetHeaders) {
145 | return headers;
146 | }
147 | Object.assign(headers, {
148 | 'X-Search-Client': 'Appbase JS',
149 | });
150 |
151 | if (enableTelemetry === false) {
152 | Object.assign(headers, {
153 | 'X-Enable-Telemetry': enableTelemetry,
154 | });
155 | }
156 |
157 | return headers;
158 | }
159 |
160 |
161 | export const backendAlias = {
162 | MONGODB: 'mongodb', // mongodb
163 | ELASTICSEARCH: 'elasticsearch', // elasticsearch
164 | };
165 | export const dataTypes = {
166 | ARRAY: 'array',
167 | FUNCTION: 'function',
168 | OBJECT: 'object',
169 | NUMBER: 'number',
170 | BOOLEAN: 'boolean',
171 | STRING: 'string',
172 | };
173 | const checkDataType = (temp) => {
174 | // eslint-disable-next-line
175 | if (typeof temp === dataTypes.OBJECT) {
176 | if (Array.isArray(temp)) {
177 | return dataTypes.ARRAY;
178 | }
179 |
180 | return dataTypes.OBJECT;
181 | }
182 | return typeof temp;
183 | };
184 |
185 | export function validateSchema(
186 | passedProperties = {},
187 | schema = {},
188 | backendName = '',
189 | ) {
190 | const passedPropertiesKeys = Object.keys(passedProperties).filter(
191 | propertyKey => !!passedProperties[propertyKey],
192 | );
193 | const acceptedProperties = Object.keys(schema);
194 | const requiredProperties = [];
195 | // fetch required properties
196 | acceptedProperties.forEach((propName) => {
197 | const currentProperty = schema[propName];
198 | if (currentProperty.required) {
199 | requiredProperties.push(propName);
200 | }
201 | });
202 | // check for required properties
203 | requiredProperties.forEach((requiredProperty) => {
204 | if (!passedPropertiesKeys.includes(requiredProperty)) {
205 | throw new Error(
206 | `${requiredProperty} is required when using the ${backendName} Search backend.`,
207 | );
208 | }
209 | });
210 |
211 | // check for accepted properties
212 | passedPropertiesKeys.forEach((passedPropertyKey) => {
213 | if (!acceptedProperties.includes(passedPropertyKey)) {
214 | throw new Error(
215 | `${passedPropertyKey} property isn't accepted property by ${backendName} backend.`,
216 | );
217 | }
218 |
219 | const acceptedTypes = Array.isArray(schema[passedPropertyKey].type)
220 | ? schema[passedPropertyKey].type
221 | : [...schema[passedPropertyKey].type];
222 | const receivedPropertyType = checkDataType(
223 | passedProperties[passedPropertyKey],
224 | );
225 | if (!acceptedTypes.includes(receivedPropertyType)) {
226 | throw new Error(
227 | `The property ${passedPropertyKey} is expected with type(s) [${acceptedTypes.join(
228 | ', ',
229 | )}], but type was set as ${receivedPropertyType}.`,
230 | );
231 | }
232 | });
233 | }
234 |
235 | export function isValidHttpUrl(string) {
236 | let url;
237 |
238 | try {
239 | url = new URL(string);
240 | } catch (_) {
241 | return false;
242 | }
243 |
244 | return url.protocol === 'http:' || url.protocol === 'https:';
245 | }
246 |
--------------------------------------------------------------------------------
/src/utils/schema/elasticsearch.js:
--------------------------------------------------------------------------------
1 | import { dataTypes } from '../index';
2 |
3 | export default {
4 | url: {
5 | type: dataTypes.STRING,
6 | required: true,
7 | },
8 | app: {
9 | type: dataTypes.STRING,
10 | required: true,
11 | },
12 | credentials: {
13 | type: dataTypes.STRING,
14 | required: false,
15 | },
16 | enableTelemetry: {
17 | type: dataTypes.BOOLEAN,
18 | required: false,
19 | },
20 | username: {
21 | type: dataTypes.STRING,
22 | required: false,
23 | },
24 | password: {
25 | type: dataTypes.STRING,
26 | required: false,
27 | },
28 | };
29 |
--------------------------------------------------------------------------------
/src/utils/schema/index.js:
--------------------------------------------------------------------------------
1 | import mongodb from './mongodb';
2 | import elasticsearch from './elasticsearch';
3 |
4 | export default { mongodb, elasticsearch };
5 |
--------------------------------------------------------------------------------
/src/utils/schema/mongodb.js:
--------------------------------------------------------------------------------
1 | import { dataTypes } from '../index';
2 |
3 | export default {
4 | url: {
5 | type: dataTypes.STRING,
6 | required: true,
7 | },
8 | app: {
9 | type: dataTypes.STRING,
10 | required: false,
11 | },
12 | credentials: {
13 | type: dataTypes.STRING,
14 | required: false,
15 | },
16 | enableTelemetry: {
17 | type: dataTypes.BOOLEAN,
18 | required: false,
19 | },
20 | mongodb: {
21 | type: dataTypes.OBJECT,
22 | required: true,
23 | },
24 | username: {
25 | type: dataTypes.STRING,
26 | required: false,
27 | },
28 | password: {
29 | type: dataTypes.STRING,
30 | required: false,
31 | },
32 | };
33 |
--------------------------------------------------------------------------------