UNPKG

16.2 kBMarkdownView Raw
1# Horizon Client Library
2
3The Horizon client library. Built to interact with the [Horizon Server](/server) websocket API. Provides all the tooling to build a fully-functional and reactive front-end web application.
4
5## Building
6
7Running `npm install` for the first time will build the browser bundle and lib files.
8
91. `npm install`
102. `npm run dev` (or `npm run build` or `npm run compile`, see below)
11
12### Build Options
13
14Command | Description
15--------------------|----------------------------
16npm run dev | Watch directory for changes, build dist/horizon.js unminified browser bundle
17npm run build | Build dist/horizon.js minified production browser bundle
18npm run compile | Compile src to lib for CommonJS module loaders (such as webpack, browserify)
19npm test | Run tests in node
20npm run lint -s | Lint src
21npm run devtest | Run tests and linting continually
22
23## Running tests
24
25* `npm test` or open `dist/test.html` in your browser after getting setup and while you also have Horizon server with the `--dev` flag running on `localhost`.
26* You can spin up a dev server by cloning the horizon repo and running `node serve.js` in `test` directory in repo root. Then tests can be accessed from <http://localhost:8181/test.html>. Source maps work properly when served via http, not from file system. You can test the production version via `NODE_ENV=production node serve.js`. You may want to use `test/setupDev.sh` to set the needed local npm links for development.
27
28## Docs
29
30
31### Getting Started
32[Check out our Getting Started guide.](/GETTING-STARTED.md)
33
34### API
35
36* [Horizon](#horizon)
37* [Collection](#collection)
38* [above](#above-limit-integer--key-value-closed-string-)
39* [below](#below-limit-integer--key-value-closed-string-)
40* [fetch](#fetch)
41* [find](#find---id-any-)
42* [findAll](#findall--id-any----id-any--)
43* [limit](#limit-num-integer-)
44* [order](#order---directionascending-)
45* [remove](#remove-id-any--id-any-)
46* [removeAll](#removeall--id-any--id-any-----id-any---id-any---)
47* [replace](#replace--)
48* [store](#store-------)
49* [upsert](#upsert------)
50* [watch](#watch--rawchanges-false--)
51
52#### Horizon
53
54Object which initializes the connection to a Horizon Server.
55
56If Horizon server has been started with `--insecure` then you will need to connect unsecurely by passing `{secure: false}` as a second parameter.
57
58###### Example
59
60```js
61const Horizon = require("@horizon/client")
62const horizon = Horizon()
63
64const unsecure_horizon = Horizon({ secure: false })
65```
66
67#### Collection
68
69Object which represents a collection of documents on which queries can be performed.
70
71###### Example
72```js
73// Setup connection the Horizon server
74const Horizon = require("@horizon/client")
75const horizon = Horizon()
76
77// Create horizon collection
78const messages = horizon('messages')
79```
80
81##### above( *limit* *&lt;integer&gt;* || *{key: value}*, *closed* *&lt;string&gt;* )
82
83The `.above` method can be chained onto all methods with the exception of `.find` and `.limit` and restricts the range of results returned.
84
85The first parameter if an integer will limit based on `id` and if an object is provided the limit will be on the key provided and its value.
86
87The second parameter allows only either "closed" or "open" as arguments for inclusive or exclusive behavior for the limit value.
88
89###### Example
90
91```js
92
93// {
94// id: 1,
95// text: "Top o' the morning to ya! 🇮🇪",
96// author: "kittybot"
97// }, {
98// id: 2,
99// text: "Howdy! 🇺🇸",
100// author: "grey"
101// }, {
102// id: 3,
103// text: "Bonjour 🇫🇷",
104// author: "coffeemug"
105// }, {
106// id: 4,
107// text: "Gutentag 🇩🇪",
108// author: "deontologician"
109// }, {
110// id: 5,
111// text: "G'day 🇦🇺",
112// author: "dalanmiller"
113// }
114
115// Returns docs with id 4 and 5
116chat.messages.order("id").above(3).fetch().forEach(doc => console.log(doc));
117
118// Returns docs with id 3, 4, and 5
119chat.messages.order("id").above(3, "closed").fetch().forEach(doc => console.log(doc));
120
121// Returns the documents with ids 1, 2, 4, and 5 (alphabetical)
122chat.messages.order("id").above({author: "d"}).fetch().forEach(doc => console.log(doc));
123```
124
125##### below( *limit* *&lt;integer&gt;* || *{key: value}*, *closed* *&lt;string&gt;* )
126
127The `.below` method can only be chained onto an `.order(...)` method and limits the range of results returned.
128
129The first parameter if an integer will limit based on `id` and if an object is provided the limit will be on the key provided and its value.
130
131The second parameter allows only either "closed" or "open" as arguments for inclusive or exclusive behavior for the limit value.
132
133###### Example
134
135```javascript
136
137// {
138// id: 1,
139// text: "Top o' the morning to ya! 🇮🇪",
140// author: "kittybot"
141// }, {
142// id: 2,
143// text: "Howdy! 🇺🇸",
144// author: "grey"
145// }, {
146// id: 3,
147// text: "Bonjour 🇫🇷",
148// author: "coffeemug"
149// }, {
150// id: 4,
151// text: "Gutentag 🇩🇪",
152// author: "deontologician"
153// }, {
154// id: 5,
155// text: "G'day 🇦🇺",
156// author: "dalanmiller"
157// }
158
159// Returns docs with id 1 and 2
160chat.messages.order("id").below(3).fetch().forEach(doc => console.log(doc));
161
162// Returns docs with id 1, 2, and 3
163chat.messages.order("id").below(3, "closed").fetch().forEach(doc => console.log(doc));
164
165// Returns the document with id 3 (alphabetical)
166chat.messages.order("id").below({author: "d"}).fetch().forEach(doc => console.log(doc));
167```
168
169##### fetch()
170
171Queries for the results of a query currently, without updating results when they change. This is used to complete and send
172the query request.
173
174##### Example
175
176```js
177
178// Returns the entire contents of the collection
179horizon('chats').fetch().forEach(
180 result => console.log('Result:', result),
181 err => console.error(err),
182 () => console.log('Results fetched, query done!')
183)
184
185// Sample output
186// Result: { id: 1, chat: 'Hey there' }
187// Result: { id: 2, chat: 'Ho there' }
188// Results fetched, query done!
189```
190
191If you would rather get the results all at once as an array, you can
192chain `.toArray()` to the call:
193
194```js
195horizon('chats').fetch().toArray().forEach(
196 results => console.log('All results: ', results),
197 err => console.error(err),
198 () => console.log('Results fetched, query done!')
199)
200
201// Sample output
202// All results: [ { id: 1, chat: 'Hey there' }, { id: 2, chat: 'Ho there' } ]
203// Results fetched, query done!
204```
205
206##### find( *{}* || *id* *&lt;any&gt;* )
207
208Retrieve a single object from the Horizon collection.
209
210###### Example
211
212```js
213// Using id, both are equivalent
214chats.find(1).fetch().forEach(doc => console.log(doc));
215chats.find({ id: 1 }).fetch().forEach(doc => console.log(doc));
216
217// Using another field
218chats.find({ name: "dalan" }).fetch().forEach(doc => console.log(doc));
219```
220
221##### findAll( *{ id:* *&lt;any&gt; }* [, *{ id:* *&lt;any&gt; }*] )
222
223Retrieve multiple objects from the Horizon collection. Returns `[]` if queried documents do not exist.
224
225###### Example
226
227```js
228chats.findAll({ id: 1 }, { id: 2 }).fetch().forEach(doc => console.log(doc));
229
230chats.findAll({ name: "dalan" }, { id: 3 }).fetch().forEach(doc => console.log(doc));
231```
232
233##### forEach( *readResult[s]* *&lt;function&gt;*, *error* *&lt;function&gt;*, *completed* *&lt;function&gt;* || *writeResult[s] *&lt;function&gt;*, *error* *&lt;function&gt;* || *changefeedHandler* *&lt;function&gt;*, *error* *&lt;function&gt;*)
234
235Means of providing handlers to a query on a Horizon collection.
236
237###### Example
238
239When `.forEach` is chained off of a read operation it accepts three functions as parameters. A results handler, a error handler, and a result completion handler.
240
241```js
242// Documents are returned one at a time.
243chats.fetch().forEach(
244 (result) => { console.log("A single document =>" + result ) },
245 (error) => { console.log ("Danger Will Robinson 🤖! || " + error ) },
246 () => { console.log("Read is now complete" ) }
247);
248
249// To wait and retrieve all documents as a single array instead of immediately one at a time.
250chats.toArray().fetch().forEach(
251 (result) => { console.log("A single document =>" + result ) },
252 (error) => { console.log ("Danger Will Robinson 🤖! || " + error ) },
253 () => { console.log("Read is now complete" ) }
254);
255```
256
257When `.forEach` is chained off of a write operation it accepts two functions, one which handles successful writes and handles the returned `id` of the document from the server as well as an error handler.
258
259```js
260chats.store([
261 { text: "So long, and thanks for all the 🐟!" },
262 { id: 2, text: "Don't forget your towel!" }
263 ]).forEach(
264 (id) => { console.log("A saved document id =>" + id ) },
265 (error) => { console.log ("An error has occurred || " + error ) },
266 );
267
268// Output:
269// f8dd67dc-2301-487a-85ab-c4b573acad2d
270// 2 (because `id` was provided)
271```
272
273When `.forEach` is chained off of a changefeed it accepts two functions, one which handles the changefeed results as well as an error handler.
274
275```js
276chats.watch().forEach(
277 (chats) => { console.log("The entire chats collection triggered by changes =>" + chats ) },
278 (error) => { console.log ("An error has occurred || " + error ) },
279);
280```
281
282##### limit( *num* *&lt;integer&gt;* )
283
284Limit the output of a query to the provided number of documents. If the result of the query prior to `.limit(...)` is fewer than the value passed to `.limit` then the results returned will be limited to that amount.
285
286If using `.limit(...)` it must be the final method in your query.
287
288###### Example
289
290```js
291
292chats.limit(5).fetch().forEach(doc => console.log(doc));
293
294chats.findAll({ author: "dalan" }).limit(5).fetch().forEach(doc => console.log(doc));
295
296chats.order("datetime", "descending").limit(5).fetch().forEach(doc => console.log(doc));
297```
298
299##### order( *<string>* [, *direction*="ascending"] )
300
301Order the results of the query by the given field string. The second parameter is also a string that determines order direction. Default is ascending ⏫.
302
303###### Example
304
305```js
306chats.order("id").fetch().forEach(doc => console.log(doc));
307
308// Equal result
309chats.order("name").fetch().forEach(doc => console.log(doc));
310chats.order("name", "ascending").fetch().forEach(doc => console.log(doc));
311
312chats.order("age", "descending").fetch().forEach(doc => console.log(doc));
313```
314
315##### remove( *id* *&lt;any>* || *{id:* *\<any>}* )
316
317Remove a single document from the collection. Takes an `id` representing the `id` of the document to remove or an object that has an `id` key.
318
319###### Example
320
321```javascript
322
323// Equal results
324chat.remove(1);
325chat.remove({ id: 1 })
326
327```
328##### removeAll( [ *id* *&lt;any&gt;* [, *id* *&lt;any&gt;* ]] || [ *{* *id:* *&lt;any&gt;* [, *{* *id:* *&lt;any&gt;* *}* ]] )
329
330Remove multiple documents from the collection via an array of `id` integers or an array of objects that have an `id` key.
331
332###### Example
333
334```js
335
336// Equal results
337chat.removeAll([1, 2, 3]);
338chat.removeAll([{ id: 1 }, { id: 2 }, { id: 3 }]);
339```
340
341##### replace( *{}* )
342
343The `replace` command replaces documents already in the database. An error will occur if the document does not exist.
344
345###### Example
346
347```js
348
349// Will result in error
350chat.replace({
351 id: 1,
352 text: "Oh, hello"
353});
354
355// Store a document
356chat.store({
357 id: 1,
358 text: "Howdy!"
359});
360
361// Replace will be successful
362chat.replace({
363 id: 1,
364 text: "Oh, hello!"
365});
366```
367
368##### store( *{}* || [ *{}* [, *{}*] )
369
370The `store` method stores objects or arrays of objects. One can also chain `.forEach` off of `.store` which takes two
371functions to handle store succeses and errors.
372
373###### Example
374
375```js
376chat.store({
377 id:1,
378 text: "Hi 😁"
379});
380
381chat.find({ id: 1 }).fetch().forEach((doc) => {
382 console.log(doc); // matches stored document above
383});
384
385chat.store({ id: 2, text: "G'day!" }).forEach(
386 (id) => { console.log("saved doc id: " + id) },
387 (error) => { console.log(err) }
388);
389
390```
391
392##### upsert( *{}* || [ *{}* [, *{}* ]] )
393
394The `upsert` method allows storing a single or multiple documents in a single call. If any of them exist, the existing version of the document will be updated with the new version supplied to the method. Replacements are determined by already existing documents with an equal `id`.
395
396###### Example
397
398```javascript
399
400chat.store({
401 id: 1,
402 text: "Hi 😁"
403});
404
405chat.upsert([{
406 id: 1,
407 text: "Howdy 😅"
408}, {
409 id: 2,
410 text: "Hello there!"
411}, {
412 id: 3,
413 text: "How have you been?"
414}]);
415
416chat.find(1).fetch().forEach((doc) => {
417 // Returns "Howdy 😅"
418 console.log(message.text);
419});
420
421```
422
423##### watch( *{ rawChanges: false }* )
424Turns the query into a changefeed query, returning an observable that receives a live-updating view of the results every time they change.
425
426###### Example
427
428This query will get all chats in an array every time a chat is added,
429removed or deleted.
430
431```js
432horizon('chats').watch().forEach(allChats => {
433 console.log('Chats: ', allChats)
434})
435
436// Sample output
437// Chats: []
438// Chats: [{ id: 1, chat: 'Hey there' }]
439// Chats: [{ id: 1, chat: 'Hey there' }, {id: 2, chat: 'Ho there' }]
440// Chats: [{ id: 2, chat: 'Ho there' }]
441```
442
443Alternately, you can provide the `rawChanges: true` option to receive change documents from the server directly, instead of having the client maintain the array of results for you.
444
445```js
446horizon('chats').watch({ rawChanges: true }).forEach(change => {
447 console.log('Chats changed:', change)
448})
449
450// Sample output
451// Chat changed: { type: 'state', state: 'synced' }
452// Chat changed: { type: 'added', new_val: { id: 1, chat: 'Hey there' }, old_val: null }
453// Chat changed: { type: 'added', new_val: { id: 2, chat: 'Ho there' }, old_val: null }
454// Chat changed: { type: 'removed', new_val: null, old_val: { id: 1, chat: 'Hey there' } }
455```
456
457## Authenticating
458
459There are three types of authentication types that Horizon recognizes.
460
461### Unauthenticated
462
463The first auth type is unauthenticated. One [JWT](https://jwt.io/) is shared by all unauthenticated users. To create a connection using the 'unauthenticated' method do:
464
465``` js
466const horizon = Horizon({ authType: 'unauthenticated' });
467```
468
469This is the default authentication method and provides no means to separate user permissions or data in the Horizon application.
470
471### Anonymous
472
473The second auth type is anonymous. If anonymous authentication is enabled in the config, any user requesting anonymous authentication will be given a new JWT, with no other confirmation necessary. The server will create a user entry in the users table for this JWT, with no other way to authenticate as this user than by passing the JWT back. (This is done under the hood with the jwt being stored in localStorage and passed back on subsequent requests automatically).
474
475``` js
476const horizon = Horizon({ authType: 'anonymous' });
477```
478
479This type of authentication is useful when you need to differentiate users but don't want to use a popular 3rd party to authenticate them. This is essentially the means of "Creating an account" or "Signing up" for people who use your website.
480
481### Token
482
483This is the only method of authentication that verifies a user's identity with a third party. To authenticate, first pick an OAuth identity provider. For example, to use Twitter for authentication, you might do something like:
484
485``` js
486const horizon = Horizon({ authType: 'token' });
487if (!horizon.hasAuthToken()) {
488 horizon.authEndpoint('twitter').toPromise()
489 .then((endpoint) => {
490 window.location.pathname = endpoint;
491 })
492} else {
493 // We have a token already, do authenticated horizon stuff here...
494}
495```
496After logging in with Twitter, the user will be redirected back to the app, where the Horizon client will grab the JWT from the redirected url, which will be used on subsequent connections where `authType = 'token'`. If the token is lost (because of a browser wipe, or changing computers etc), the user can be recovered by re-authenticating with Twitter.
497
498This is type of authentication is useful for quickly getting your application running with information relevant to your application provided by a third party. Users don't need to create yet another user acount for your application and can reuse the ones they already have.
499
500### Clearing tokens
501
502Sometimes you may wish to delete all authentication tokens from localStorage. You can do that with:
503
504``` js
505// Note the 'H'
506Horizon.clearAuthTokens()
507```