@sassoftware/restaf 5.5.1-0 → 5.5.1-10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +1139 -1139
  2. package/dist/restaf.js +64 -64
  3. package/lib/restaf.js +64 -64
  4. package/package.json +49 -49
package/README.md CHANGED
@@ -1,1139 +1,1139 @@
1
- # restAF- Building Applications with SAS REST API made simple
2
-
3
- SAS® Viya® is like a Swiss Army knife when it comes to allowing all types of
4
- clients to talk to it—Java, Python, Lua, R, and, of course, SAS®.
5
- In addition to using these language-based APIs, SAS also publishes APIs that
6
- conform to REST best practices. The well-designed APIs in SAS Viya make it relatively
7
- easy to build complex web and non-web applications with SAS Viya as the engine of your application.
8
- The applications can be solely based on SAS Viya capabilities or users can do a
9
- mashup of the advanced SAS Viya analytics with their other products and capabilities.
10
- restAF is a light-weight, UI-framework agnostic, JavaScript library designed to be
11
- easy-to-use for app developers. restAF takes advantage of the consistency of the REST APIs
12
- to reduce the responses from the server into a standard form, and it also manages the data on
13
- the client side. This reduction enables developers to interact with the server in a repeating pattern
14
- regardless of which service the application is using.
15
-
16
- Use restAF to exploit SAS® Cloud Analytic Services (CAS), compute server, SAS® Visual Analytics,
17
- and other key capabilities of SAS Viya in a very consistent manner with minimal coding.
18
-
19
-
20
-
21
-
22
- ## Introduction
23
-
24
- The concepts and usage of restAF are explained through examples. Addtional examples can be found in these repositories.
25
-
26
- * [A collection of nodejs based examples](https://github.com/sassoftware/restaf-demos)
27
- * [A collection of web apps using restAF](https://github.com/sassoftware/restaf-uidemos)
28
- * [a SAS API explorer built with restAF](https://github.com/sassoftware/restaf-apiexplorer)
29
- * [react components using restAF](https://github.com/sassoftware/restaf-uicomponents)
30
-
31
- restAF makes writing applications with SAS REST API simple
32
- * A small set of methods to make API calls
33
- * Reduces the information to readily useable parts
34
- * Manages the data for the application in a central store for one version of truth
35
- * Short learning curve
36
- * Results in productive application developers
37
-
38
- With restAF, you focus on your application and not on the nitty-gritty details of setting up HTTP calls, processing the
39
- returned results and HTTP codes, parsing the returned payload, managing the data, and so on.
40
-
41
- ## Key technologies
42
- restAF uses three key libraries(among others) that you are probably familiar with.
43
-
44
- 1. [redux-saga](https://redux-saga.js.org/)
45
- 2. [immutable-js](https://facebook.github.io/immutable-js/docs/#/)
46
- 3. [axios](https://github.com/axios/axios)
47
-
48
- I am extremely grateful to the developers of these packages for their wonderful contributions.
49
-
50
-
51
- ## Accessing restAF
52
-
53
- ### web Application
54
- In your web application you can access restaf with the following script tag.
55
-
56
- ```
57
- <script src="https://unpkg.com/restaf/dist/restaf.min.js"></script>
58
-
59
- ```
60
- A global variable called restaf is available for use in your javascript programs.
61
-
62
- ### nodejs application
63
-
64
- ```
65
- npm install restaf
66
- ```
67
-
68
- and then your program
69
-
70
- ```
71
- let restaf = require('restaf')
72
-
73
- ```
74
-
75
- ### Cloning and modifying restAF
76
- You can clone as follows
77
-
78
- ```
79
- clone https://github.com/sassoftware/restaf
80
- cd restaf
81
- npm install
82
- ```
83
- To build the code run this command
84
-
85
- ```
86
- npm run build
87
- ```
88
-
89
- ## Background
90
-
91
- restAF reduces the hypermedia returned from the REST API calls to a form that is suitable for rapid application development.
92
- THe next few sections will explain the reduction through examples and how you would use it to write your applications.
93
-
94
- REST API response is an hypermedia. In practical terms a hypermedia has the following:
95
-
96
- * Links to
97
- - What you can do to with this resource
98
-
99
- - Where you can go next
100
- - Examples: Get a list of items, create a new resource etc...
101
- - Optionally information(links) on paginating through the data
102
-
103
- * Data
104
- - A collection of items
105
- - String
106
- - arrays, objects etc...
107
- - Optionally links to operate on the returned data( delete, copy, etc...)
108
-
109
-
110
- restAF stores all the response and returns an object referred to as **rafObject***. The application will use this object
111
- to retrieve information from it use as it sees fit. The methods correspond to the various component parts of the
112
- response discussed above.
113
-
114
- ### Links
115
- A link has several key parts:
116
- 1. A URI for the next step (edit, delete, and so on).
117
- 2. An identification of the link to indicate what this next step is. This is referred to as **rel** – short for “link relationship.”
118
- You can discern the purpose of that link based on the rel. Some examples are:
119
-
120
- - If the rel is “content,” you will guess that that this end point returns the content.
121
- - If the rel is “execute,” you will guess that this end point is associated with some type of execution that is appropriate for that context.
122
- - If the rel is “create,” you will rightly assume that the call will result in something being created.
123
-
124
- 3. Media Types
125
- - The media type used for the payload if the end point needs a payload.
126
- - The media type of the returned data. This information can be used by applications to determine programmatically how to deal with the data either for
127
- processing or display.
128
-
129
- The links are reduced to an object called **rafLink**. You will use rel to locate a rafLink in the rafObject. The primary use of rafLink is as a parameter
130
- to the api calls to the server.
131
-
132
- ### Items
133
- Items are the data. Items can be a collection of items, a text string, an array, SVG, and so on.
134
- Some examples are as follows:
135
-
136
- - A collection of items: Each item has some data and links to indicate what you can do with this item (delete for example).
137
- - A string (examples: SVG, status of a job, and so on).
138
- - An array (example: SAS logs).
139
- - Some object.
140
- - And, in some cases, no data only http return codes.
141
-
142
- restAF manages the links and data for the application.
143
-
144
- ## Basic flow of an application
145
-
146
- The flow of an application using restAF is as follows:
147
-
148
- 1. Initialize restAF - you can have only one instance of restAF per application. The initialization creates a **store** to maintain the data and handle asynchronous calls
149
- to the server.
150
- 2. Logon to the server - setups connection to the Viya Server
151
- 3. Register the services - Inform restAF to setup structures(called folders)to handle data for each of the services your application will be using.
152
- 4. In the main loop of your application make calls to the server thru store. The calls will return an object that the application will interact with to
153
- retrieve the data(read-only) as and when they are needed in the application.
154
-
155
-
156
- ### Introductory Example for a nodejs application
157
-
158
- Here is a simple example running data step with compute service. In the example below ignore the following for now:
159
-
160
- * prtUtil - some utility functions to dump information to console
161
- * logonPayload - data for logging on to Viya Server - discussed later in this document
162
-
163
- ```javascript
164
- let restaf = require('restaf');
165
- ```
166
-
167
- Preamble: Application specific code to get configuration information
168
- You might do it differently
169
-
170
- ```javascript
171
- let config = require('./config');
172
- let logonPayload = config('raf.env');
173
- ```
174
-
175
-
176
- Step 1: Initialize the store
177
- ```javascript
178
- let store = restaf.initStore();
179
- ```
180
-
181
- Steps 2 and 3: Logon to the server and tell restAF which services you want to use
182
- ```javascript
183
- async function setup (store, logonPayload){
184
- let msg = await store.logon(logonPayload);
185
- let {compute} = await store.addServices('compute');
186
- return compute;
187
- }
188
- ```
189
-
190
- // Step 4: The main program
191
-
192
- ```javascript
193
- async function mainLoop (store, compute, code) {
194
-
195
- // get the list of contexts for compute server
196
- // This of contexts as your SAS configs
197
- let contexts = await store.apiCall(compute.links('contexts'));
198
-
199
- // lookup the name of the context and create a SAS session with that information
200
- // In the example we pick the first context that is returned.
201
- // the itemList function returns the list of contexts
202
- let context = contexts.itemsList(0);
203
- let createSession = contexts.itemsCmd(context, 'createSession');
204
- let session = await store.apiCall(createSession);
205
-
206
- // Define the payload
207
- // This is your SAS program
208
- let payload = {
209
- data: {code: code}
210
- };
211
-
212
- // Execute the data step and wait for completion
213
- // All calls to the server are managed by the store you created in step 1
214
- let job = await store.apiCall(session.links('execute'), payload);
215
- let status = await store.jobState(job, null, 5, 2);
216
-
217
- // Check the final status of job and view the logs
218
- if (status.data === 'running') {
219
- throw `ERROR: Job did not complete in allotted time`;
220
- } else {
221
- let logLines = await store.apiCall(status.job.links('log'));
222
- // print the log
223
- logViewer(logLines);
224
- }
225
- return 'restAF is cool or what';
226
- }
227
- ```
228
-
229
- Your program
230
-
231
- ```javascript
232
- let code = [`data _null_; do i = 1 to 100; x=1; end; run; `];
233
-
234
- setup (store, logonPayload)
235
- .then (compute => mainLoop(store, compute, code))
236
- .catch(err =>console.log(err));
237
-
238
-
239
- ```
240
-
241
- **A Cool Fact** \- _Notice that in the example above you see no reference to links, content-type, accept type, url's etc... Except in some rare cases you do not have to worry about these - restAF takes care of all those house keeping activities. You just focus on what your app needs to do._
242
-
243
- ## restAF Objects and restAF Links
244
-
245
- restAF reduces the information returned by an API call an object called rafObject. This object has several properties amd methods.
246
- One key reduction is the transformation of links into rafLinks objects. rafLink allows restAF to navigate its own structures and make API
247
- calls on behalf of the application.
248
-
249
- ## Initializing restAF
250
-
251
- To initialize restAF you will make the following 2 calls:
252
-
253
- ###initStore
254
-
255
- initStore must be called first to get the store object. **Only one store per application** . This call initializes the store to hold all the application data.
256
-
257
- ```javascript
258
- let restaf = require( 'restaf' );
259
- store = restaf.initStore();
260
- ```
261
-
262
- _If using the umd version of the library, the global 'restaf' will be set when the umd version is brought in using the script tag._
263
-
264
- At this point there is no data in the store and it is not attached to any Viya Server.
265
-
266
- ### logon
267
-
268
- The next step is to associate Viya server with this instance of restAF.
269
-
270
- ```javascript
271
- store.logon( payload )
272
- .then ( msg => console.log( msg ) )
273
- .catch( err => console.log( err ) )
274
- ```
275
-
276
- payload is either null or is an object with information to logon to the server. If null,
277
- restAF assumes that your browser session has been authenticated by other means. In an non-browser environment you have to
278
- pass in the valid payload.
279
-
280
- Below is a typical payload for a nodejs application
281
-
282
- ```javascript
283
-
284
- payload = {
285
- authType : 'password',
286
- host : 'http://yourserver:portno',
287
- user : username,
288
- password : user password,
289
- clientID : clientid, /* get this from your admin */
290
- clientSecret: clientsecret /* get this from your admin */
291
- } );
292
- ```
293
-
294
- A friendly note to protect the payload information on your client machine
295
- since it has all the key authentication information
296
- in plain text.
297
-
298
- In a browser use the initial logon payload will look as follows:
299
-
300
- ```javascript
301
- payload = {
302
- authType: 'implicit',
303
- host : 'http://yourserver:portno',
304
- clientId: clientId, /* get this from your admin */
305
- redirect: <The redirect that was specified when creating the clientId>
306
- }
307
- ```
308
- In the url that is the target of the redirect you will pass a null for payload
309
-
310
- ```javascript
311
- payload = null
312
- ```
313
-
314
- restAF will parse the incoming location information and set up access to the server.
315
-
316
-
317
- ##Adding Services
318
-
319
- After the initialization steps restAF still does not know what information to manage.
320
- Now populate restAF by registering the services you want to use in your application.
321
-
322
- ### addServices
323
-
324
- addServices method adds a folder for the each service.
325
- It additionally calls the root end point for that service
326
- and populates the folder with the links returned from that call.
327
- The addServices method returns a restAF object( rafObject) which we will discuss next.
328
-
329
- **All service names are case-sensitive**
330
-
331
- ```javascript
332
- let {serviceName1, serviceName2,....} = await store.addServices( 'serviceName1', 'serviceName2', .... );
333
- ```
334
-
335
- At this point serviceName1 is a rafObject with information for the root end points of that service.
336
- #### Example
337
-
338
- ```javascript
339
- let {compute, casManagement} = await store.addServices( 'compute', 'casManagement' );
340
- ```
341
-
342
-
343
- ##restAF Object(rafObject)
344
-
345
- In your application you will be dealing primarily with rafObjects and rafLinks. Every time a result is returned from an
346
- API call(including addServices), restAF will "reduce" the information into a folder and return a rafObject
347
-
348
- The folders are immutable objects based on [immutable.js.](https://facebook.github.io/immutable-js/) while rafObject is a
349
- standard javaScript object.
350
-
351
- The application will retrieve information using the methods of the rafObject.
352
-
353
- ### restAF Links ( rafLinks )
354
-
355
-
356
- restAF extends all the links returned by the server with information it needs to navigate the store and make the final calls.
357
- For an application the most common use of rafLinks is as an argument to apiCall method of the store to navigate to a new resource.
358
- The other common use is in associating links with UI components like buttons, navigators etc. This will be covered later in this document.
359
-
360
- ###apiCall - Making REST API calls
361
-
362
- The apiCall method on the store object is the most used method for making REST calls.
363
-
364
- ```javascript
365
- store.apiCall( rafLink <, payload>)
366
- ```
367
-
368
- rafLink is obtained using the links and itemsCmd methods on rafObject.
369
-
370
- The payload(optional parameter based on the REST end point) is an object with the following optional keys
371
-
372
- * data - If the REST call is a PUT or POST then specify the data payload
373
- * qs - Use this to specify the query parameters
374
- * headers - Use this to specify any special headers for this call. For example the upload action in cas requires the the JSON-Parameters header. Another example is file service's 'create' operation requires content-type to be specified
375
- * action - if you are running a cas action, you **must** specify the action name. Recommend that you use a fully qualified action name ( actionset.actionname )
376
- * All other information in the payload will be ignored silently. Since all the keys are optional restAF has no way to warn you about missing keys or
377
- extraneous keys.
378
-
379
-
380
- ### Examples of payload
381
-
382
- _To run sas code with compute service_
383
- ```json
384
- { data: { code: \[ 'data a; x=1;run;' , 'proc print;run' \] }}
385
- ```
386
-
387
- _To run datastep in cas_
388
-
389
- { action: 'datastep.runCode', data: { code: 'data a; x=1;run;' } }
390
-
391
- _To run upload action for CAS_
392
-
393
- ```javascript
394
- let JSON_Parameters = {
395
- casout: {
396
- caslib: 'casuser', /* a valid caslib */
397
- name : 'iris' /* name of output file on cas server */
398
- },
399
-
400
- importOptions: {
401
- fileType: 'csv' /* type of the file being uploaded */
402
- }
403
- };
404
- let payload = {
405
- action : 'table.upload',
406
- headers: { 'JSON-Parameters': JSON_Parameters },
407
- data : readFile( 'iris', 'csv' )
408
- }
409
- ```
410
-
411
- _To create a file with plain text_
412
-
413
- ```javascript
414
- let payload = {
415
- data : 'my data',
416
- headers: { 'content-type': 'text/plain' }
417
- }
418
- ```
419
-
420
- ### apiCallAll - Making api Calls in parallel
421
-
422
- apiCall method makes a single service call. The apiCallAll method on store allows the programmer to execute multiple service calls in parallel.
423
- A typical use case will be running forecast actions in parallel in different cas sessions.
424
- Another is running jobs in multiple compute server sessions. You can mix and match services in this single call( ex: create files, run cas jobs etc... but this would be an exception rather than a rule
425
-
426
- ```javascript
427
- store.apiCallAll( requestArray )
428
- ```
429
-
430
-
431
- * requestArray is an array with each row of the following format:
432
-
433
- ```javascript
434
- { rafLink: <the rafLink to be called>
435
- payload: <standard payload as described earlier in this document>
436
- }
437
- ```
438
-
439
-
440
- The method returns when all the calls in the requestArray return successfully or when one of them fails. In that sense apiCallAll behaves like PromiseAll.
441
- The order of the results array is the same as the requestArray. See the example for running parallel sas compute jobs
442
-
443
-
444
- ### jobState and jobStateAll
445
-
446
- Some services might take a long time to run. Typically these services support a "state" link to query the state of the job. Note that not all services support the 'state' link. Services like compute service and JobExecution support this
447
-
448
- In SAS API the state end point returns could be in one of these states.
449
-
450
- * **running or pending** \- This means the job did not complete and is still running or pending.
451
- * **Completed** The job completed but the actual state of the job will vary based on the service. Some possible completed states are:
452
-
453
- * Job completed with no additional clarification(completed)
454
- * Job completed with warnings(warning)
455
- * Job failed or completed with errors(errors)
456
- * Job failed(failed) - similar to the previous item but some services have chosen to return failed
457
- * probably other completed states now or in later releases
458
-
459
- Most of the services that have a "state" end point recommend that you call the 'self' end point after the job completes to get the latest results from the job run.
460
- jobState and jobStateAll will make this additional call on 'self' if none of the jobs are still running. This saves you an additional call
461
-
462
- At this point your code has to decide how best to proceed given these states.
463
-
464
- ### To check the status of a single job use jobState method.
465
-
466
- ```javascript
467
- store.jobState( rafObject-for-job <,payload> <,maxTries > )
468
- ```
469
-
470
-
471
- Some services take a query parameter to support long polling - pass that information in the standard payload structure
472
-
473
- maxTries is the number of times restAF will try if the job did not return a completed status
474
-
475
- The returned object has the following form:
476
-
477
- ```json
478
- {
479
- data : <completed|running|...|...>,
480
- statusCode: <http code >,
481
- job : <rafObject of the final job>
482
- }
483
- ```
484
-
485
- An example
486
- ```javascript
487
- /* pass a timeout of 2 seconds and try a max of 5 times */
488
- let status = await store.jobState( job, {qs: { timeout: 2} }, 5 );
489
- if (status.data === 'running') {
490
- throw \`ERROR: Job did not complete in allotted time\`;
491
- } else {
492
- viewer(status.job); /* some viewer of results results */
493
- switch(status.data) {
494
- case 'warning': console.log(\`Warning: check your log for warnings\`); break;
495
- case 'error':
496
- throw \`Please correct errors and rerun program\`;
497
- default:
498
- break;
499
- }
500
- }
501
-
502
- ```
503
- ### jobStateAll
504
-
505
- jobStateAll method to check the status of multiple jobs the jobs with a single call
506
-
507
- The returned value has the form
508
- ```json
509
- {
510
- running: <No of jobs still running >
511
- details: >details on the completion ( see note above and see below for the form of this object>
512
- jobState: [
513
- {
514
- job : < the job rafObject. see note below >
515
- statusCode: < The http code from the server >
516
- data : < text returned on completion( 'complete' for example ) >
517
- }
518
- ]
519
- }
520
- ```
521
-
522
- Some notes
523
-
524
- * 'running' will be 0 if all jobs completed and >0 if one or more jobs are still running
525
- * jobState will be an array for jobStateAll
526
-
527
- The details object gives you a summary of the returned state. It is easier to explain its structure with some examples
528
-
529
- For jobState the detail will have a single key/value. The key will be the state returned from the call. For example if the job returned "failed" then the detail object will be as follows
530
-
531
- detail: {
532
- failed: 1
533
- }
534
-
535
- For jobStateAll the detail could have multiple key/values. For example if you ran 4 jobs in parallel and 2 completed with state of 'warning' , 1 completed with state of 'completed' and one completed with state of 'error', then the detail will look as follows:
536
-
537
- ```json
538
- detail: {
539
- warning : 2,
540
- completed: 1,
541
- error : 1
542
- }
543
- ```
544
-
545
- ```javascript
546
- store.jobStateAll( array of jobs rafObject, payload, maxTries )
547
- .then ( status => {
548
- if ( status.running !== 0 ) {
549
- ...handle slow running jobs...
550
- } else {
551
- ... check the detail object and make decisions...
552
- }
553
-
554
- } )
555
- ```
556
-
557
- The payload parameter can be an array or single object. If single object then this payload will be applied to every rafObject in the first parameter.
558
- The primary purpose of the payload is to set timeout as a query parameter
559
-
560
- Maxtries works the same as in jobState
561
-
562
- ### Submit and SubmitStatus methods- run api and handle results in "background".
563
-
564
- REST APIs are great when the response to an api call is fast and instantaneous.
565
- In SAS it is not uncommon to have jobs that take a bit to run.
566
- For really long running jobs one should use our batch capabilities.
567
- However many solutions have applications where the user submits requests
568
- that can take from a few seconds to a minute or two to complete.
569
-
570
- This presents a problem for the application writers.They have to poll the server at some intervals and
571
- probably at many points in their application code.
572
-
573
- The submit method is designed to handle this scenario in a more elegant way.
574
-
575
- The parameters to submit are:
576
-
577
- ```javascript
578
- store.submit( rafLink, payload, delay, id , <OnCompletion exit>, <progress exit> )
579
- ```
580
-
581
- where:
582
-
583
- - raflink - same as for apiCall
584
-
585
- - payload - standard payload or null
586
-
587
- - delay - restAF will check the server after every "delay" seconds for completion and drive the callbacks if specified
588
-
589
- - id - specify an id(string). You will use this to retrieve the information the rafObject associated with this job
590
-
591
- - onCompletion - if non-null restAF will call this exit on completion ( success or failure) of the job.
592
-
593
- - progress - if non-null restAH will call this exist after every call to server to check the status.
594
-
595
-
596
- The method signature of progress exit is
597
-
598
- ```javascript
599
- function progress ( data, id )
600
- ```
601
- where
602
-
603
- - data - the current status of the job ( running, completed, failed etc...)
604
- - id - the id you passed to the submit method
605
-
606
- The function returns either true or false. If you return true, restAF will stop calling the server for the status
607
- of the job.
608
-
609
-
610
- The method signature of onCompletion is
611
-
612
- ```javascript
613
- function onCompletion( err, jobStatus, id );
614
- ```
615
- where
616
-
617
- - err - if non-null then this is the error message
618
- - jobStatus - this has the same structure as the return from jobStatus
619
- - id - the id that you passed to the submit method
620
-
621
- You will retrieve the final jobStatus using the **submitStatus** method of the store
622
-
623
- ```javascript
624
- let jobStatus = store.submitStatus(id);
625
- ```
626
- where jobStatus is the same as what jobState returns.
627
-
628
- One could write part of the introductory example as follows:
629
-
630
- ```javascript
631
- let payload = {
632
- data: {code: [`data _null_; do i = 1 to 10000000; end;run; `]}
633
- };
634
- store.submit( session.links('execute'), payload, 1,
635
- 'myJob',onCompletion, progress);
636
- ```
637
-
638
- ## Details on restAF Object
639
-
640
- ###Properties
641
-
642
-
643
- All property names are case sensitive
644
-
645
- 1. **type:** This indicates which of the results pattern this object represent
646
-
647
- 1. **links:** Use the links method below to view and obtain rafLinks for further calls. If type is "links" then this object only has links for other end points.
648
- 2. **itemsList:** The rafObject contains an array of items with id's.
649
- 1. Use itemsList method to retrieve the id's of the items returned.
650
- 2. Use the scrollCmds method to determine whether there are more data available and for making the pagination calls.
651
- 3. Use the items function to retrieve specific items( or all items ) from the collection.
652
- 4. use the itemsCmd method torRetrieve the cmds( links) associated with specific item
653
-
654
- 3. **itemArray:** The returned value is an array of text. Use items methods to retrieve this data
655
- 4. **items:** The exact object type is not known. Use items method to get the information and also use resultType property do decide how to handle that data
656
-
657
- 3. **resultType:** The media type of the result
658
- 4. **route:** This is an index into the store for this specific result (see section on route later in this document). This will be useful if you want to pass a reference to a rafObject.
659
- 6. **status:** This the http code returned from the api call
660
- 7. **statusInfo:** An object with details of the status
661
- 8. **host** - the fully qualified Viay host the web app is logged on to.
662
-
663
-
664
- Methods (function properties)
665
- -----------------------------
666
-
667
- Use these methods to interact with rafObject. These functions return either objects or string depending on the data. Remember these objects are immutable objects. In the document below the common usage patterns are described.
668
-
669
- If the returned value is an rafObject you can use query parameters to drill into a specific value in that object. Examples are provided below
670
-
671
- #### links method
672
-
673
- The links method will return an immutable object of rafLinks. You will use this method when the type of the rafObject is "links". But sometimes there will be links even when the type is not "links". In those cases the links are usually actions that operate on the whole collection
674
-
675
- ```javascript
676
- rafLink = rafObject.links( relName );
677
- ```
678
-
679
-
680
- relName is the rel of the resource you are looking for. The call will return a rafLink that you can use in an apiCall as shown below
681
-
682
- ```javascript
683
- contentLink = fileObject.links( 'content' );
684
- contentObject = store.apiCall ( contentLink );
685
- ```
686
-
687
- Sometimes you need the entire list of links. An example is displaying buttons with the titles of the links. Below is an example of printing the titles of all the links.
688
-
689
- ```javascript
690
- allLinks = fileObject.links();
691
- allLinks.forEach( ( l, rel ) => {
692
- console.log(\`Title: ${rel} rel: ${rel} \` );
693
- }
694
- ```
695
-
696
- #### itemsList method
697
-
698
- If the rafObject type is 'itemsList' use this method to get the immutable array of id's in the current result object. This is really a convenience method for UI ( ex: showing a list of the id's).
699
-
700
- Below is an example listing all the id's
701
-
702
- ```javascript
703
- rafObject.itemsList().map( id => console.log(id) )
704
- ```
705
-
706
- you can also do the following:
707
-
708
- ```javascript
709
- console.log( JSON.stringify(rafObject.itemsList(), null, 4) );
710
- ```
711
-
712
-
713
- #### scrollCmds method
714
-
715
- This method returns the rafLinks associated with pagination. This method can be used to get a rafLink associated with next, prev, last and first cmds(rel). At any given time the server may return some, all or none of these. To ensure safe programming, always check if the returned rafLink is non-null.
716
-
717
- ```javascript
718
- let nextCmd = rafObject.scrollCmds( 'next' );
719
- if ( nextCmd !== null ) {
720
- store.apiCall( nextCmd )
721
- .then ( restafobj => {...do something...} )
722
- } else {
723
- console.log( 'No more data' )
724
- }
725
- ```
726
-
727
- In an UI you would want to display buttons for scrolling. A typical code will look like this
728
-
729
- ```javascript
730
- let cmds = rafObject.scrollCmds();
731
-
732
- cmds.forEach( ( c, cmd ) => {
733
- ...make your scrolling menu...
734
- } );
735
- ```
736
-
737
- For example here is the code for creating a menubar in Reactjs
738
-
739
- ```javascript
740
- function ButtonMenu( props ) {
741
- let { cmds, onClick } = props;
742
- let menu = [];
743
- cmds.forEach( ( c, rel ) => {
744
- menu.push( <button key={rel} onClick={onClick(...)}
745
- className="button"> {rel} </button> );
746
- } );
747
-
748
- return (
749
- <div>
750
- {menu}
751
- </div>
752
- )
753
- }
754
- ```
755
-
756
- #### items method
757
-
758
- This method gives you access to the data that might have been returned. This could be log for compute server, cas results, tables, status, ods and so on. The combination of resultType and type should give you sufficient information on how to process this data. It is possible to write an intelligent UI that determines the "viewer" to use given these pieces of information. But in a typical scenario the application knows what it is expecting at any specific point in the flow of the application.
759
-
760
- The items method takes arguments that determines what is returned.
761
-
762
- ```javascript
763
- let result = rafObject.items( <query> )
764
- ```
765
-
766
- Let us examine a few typical cases
767
-
768
- **All Items**
769
-
770
- ```javascript
771
- let allItems = rafObject.items();
772
- ```
773
-
774
- If you get all the items then you need to write the code to manage the items based on the resultType
775
-
776
- **Get a specific item using the id ( which you will get from itemsList() )**
777
-
778
- ```javascript
779
- let item = rafObject.items( idOfItem );
780
- ```
781
-
782
- **Getting data for an item in a collection **
783
-
784
- ```javascript
785
- let data = rafObject.item( idOfItem, 'data' );
786
- ```
787
-
788
-
789
- #### itemsCmd
790
-
791
- Use this method to obtain the cmds associated with a particular item. Obtain the id of the item by using the itemList method.
792
- The most common usage is to get the rafLink for a specific command for the selected item
793
-
794
- **Get a specific command associated with a item with a specific id**
795
-
796
- ```javascript
797
- let item = rafObject.itemsCmd( idOfItem, 'name of command' );
798
- ex:
799
- let deleteCmd = rafObject.itemsCmd( '1234ID', 'delete' );
800
-
801
- store.apiCall( deleteCmd )
802
- .then ( f => { do whatever) }
803
- .catch( err => { do whatever) }
804
- ```
805
-
806
- **Get all commands associated with a item with a specific id**
807
-
808
- ```javascript
809
- let rafLinks = rafObject.itemsCmd( idOfItem );
810
- ```
811
-
812
- You can step thru this object as follows
813
-
814
- ```javascript
815
- rafObject.itemsCmd( idOfItem ).forEach( ( c, key) => {
816
- // c is the current cmd information
817
- // key is the name of the command( createSession, delete etc... )
818
- }
819
- ```
820
-
821
- #### responseHeaders
822
-
823
- On occasion one needs to access to the returned headers like etag. For those scenarios use the responseHeaders method.
824
-
825
- **Get a specific header**
826
-
827
- ```javascript
828
- let etag = rafObject.responseHeaders( 'etag' );
829
- ```
830
- **Get all headers**
831
-
832
- ```javascript
833
- let headers = rafObject.responseHeaders();
834
- ```
835
-
836
- #### status
837
-
838
- To get the http status returned from the call
839
-
840
- ```javascript
841
- let status = rafObject.status();
842
- ```
843
-
844
- #### statusInfo
845
-
846
- Use this to get any additional information that might have been returned for the status. Mostly useful if the job failed for some reason
847
-
848
- let info = rafObject.statusInfo() ;
849
- console.log( info );
850
-
851
- ### Less used Methods
852
-
853
- ####raw
854
- Returns the raw response from the server
855
-
856
- ####config
857
- The http payload sent to the server (useful for debugging)
858
-
859
-
860
- Running CAS actions
861
- -------------------
862
-
863
-
864
- In restAF you will access cas actions in the following manner.
865
-
866
- * Add casManagement to restAF using store.addServices.
867
- * Look thru the list of servers returned by casManagement and pick the one you want. More often than not there is probably only one cas server.
868
- * Create a session on that server. restAF will add a rel named 'execute' to the session's links returned from this step.
869
- * Make all cas action calls thru runAction method
870
- * restAF collects all the returned tables under a "tables" object to make handling of returned tables a bit easier
871
-
872
- Below is an example showing uploading of a csv to cas and then getting its info
873
-
874
- The example below uses the nodejs file i/o. If you are running from browser you will use the File Object.
875
-
876
- ```javascript
877
- async function example () {
878
- // setup
879
-
880
- await store.logon(payload);
881
- let {casManagement} = await store.addServices('casManagement');
882
- let servers = await store.apiCall(casManagement.links('servers'));
883
- let casserver = servers.itemsList(0);
884
- let session = await store.apiCall(servers.itemsCmd(casserver, 'createSession'),
885
- {data: {name: 'mysessionname'}});
886
- /
887
- // setup header for upload and the rest of the payload
888
- let JSON_Parameters = {
889
- casout: {
890
- caslib: 'casuser', /* a valid caslib */
891
- name : filename /* name of output file on cas server */
892
- },
893
-
894
- importOptions: {
895
- fileType: fileType /* type of the file being uploaded */
896
- }
897
- };
898
- let p = {
899
- headers: {'JSON-Parameters': JSON_Parameters},
900
- data : readFile(filename, fileType),/* read the csv file from somewhere */
901
- action : 'upload'
902
- };
903
-
904
- let actionResult = await store.runAction(session.links, payload);
905
- prtUtil.view(actionResult, 'Result of upload action');
906
-
907
- let deleteAction = await store.apiCall(session.links('delete'));
908
- return "All Done";
909
- }
910
-
911
- function readFile (filename, fileType) {
912
- return fs.readFileSync(`./data/${filename}.${fileType}`);
913
- }
914
- ```
915
-
916
- #### Handling tables returned by CAS
917
- Below is a sample program to print the rows in the CAS tables
918
-
919
- ```javascript
920
- let data = result.items('tables', <name of the table>);
921
- let itemRows = data.get('rows');
922
- let columns = [];
923
- data.get('schema').map(s => {
924
- columns.push(s.get('name'));
925
- });
926
-
927
- itemRows.map((r)=> {
928
- let row = {};
929
- r.map((value, j) => {
930
- row[columns[j]] = value;
931
- });
932
- console.log(JSON.stringify(row, null, 4));
933
- });
934
-
935
- ```
936
-
937
- Handling Pagination
938
- -------------------
939
-
940
- restAF handles all the pagination for you. Use the scrollCmds as described above. It assumes that you will use the scrollCmds API to retrieve more information.
941
-
942
- For example to get the next set of items do the following
943
-
944
- ```javascript
945
- let next = rafObject.scrollCmds( 'next' );
946
- if ( next === null ) {
947
- /\* tell user there is no more data */
948
- } else {
949
- store.apiCall( next )
950
- .then ( nextObj => {
951
- do whatever using teh rafObject methods
952
- } );
953
- .catch ( err => {
954
- handle error conditions
955
- } )
956
- }
957
- ```
958
-
959
- Replace next with prev,last ans first as appropriate.
960
-
961
- Here is an example of printing to console all the items from a collection
962
-
963
- ```javascript
964
- let store = restaf.initStore();
965
-
966
- // Pagination
967
-
968
- async function example (store, logonPayload, counter) {
969
- await store.logon(logonPayload);
970
- let {files} = await store.addServices('files');
971
-
972
- let filesList = await store.apiCall(files.links('files'));
973
- printList(filesList.itemsList());
974
- let next;
975
- // do this loop while the service returns the next link
976
- while(((next = filesList.scrollCmds('next')) !== null) ) {
977
- filesList = await store.apiCall(next);
978
- printList(filesList.itemsList());
979
- }
980
-
981
- return 'All Done';
982
- }
983
-
984
- const printList = (itemsList) => console.log(JSON.stringify(itemsList, null, 4));
985
-
986
- example(store, payload, 10)
987
- .then (status => console.log(status))
988
- .catch(err => console.log(err));
989
-
990
- ```
991
-
992
- Route property
993
- --------------
994
-
995
- Each rafObject has a route property. This is a string that is key to the data pointed to by rafObject.
996
- restAF uses the route to find the data associated with this key. If for whatever reason you need to maintain a "pointer" to a
997
- rafObject or to pass it in as a query parameter you can use this.
998
-
999
- Route is useful if you want to pass a specific rafObject to another part of your program through some routing mechanism( ex: react-router). Given a route you can get the
1000
- associated rafObject with the **rafObject** method
1001
-
1002
- ```javascript
1003
- let myObject = store.rafObject(route);
1004
- ```
1005
-
1006
- ## Authentication
1007
-
1008
- restAF relies on the Oauth2 authentication supported by SAS Viya.
1009
-
1010
- There are a few use cases as described below:
1011
-
1012
- ### Authenticated browser session: If you session is already authenticated then pass null to logon method
1013
-
1014
- ```javascript
1015
- store,logon( null)
1016
- .then ( msg => <do your stuff> )
1017
- .catch( err => <error handling> )
1018
- ```
1019
-
1020
- ### Using an existing token
1021
-
1022
- There are situations where a valid token might exist. In that case use the following payload to store.logon
1023
-
1024
- ```javascript
1025
- store.logon( {
1026
- host: "your viya server',
1027
- token: "your token
1028
- });
1029
- ```
1030
-
1031
- ### nodejs applications
1032
-
1033
- For nodejs applications you will need to use the password flow authentication method
1034
-
1035
- ```javascript
1036
- let payload = {
1037
- authType : 'password',
1038
- host : 'http://yourserver:portno',
1039
- user : username,
1040
- password : user password,
1041
- clientID : clientid, /* get this from your admin */
1042
- clientSecret: clientsecret /* get this from your admin */
1043
- } );
1044
- store.logon ( payload )
1045
- .then ( () => ...do whatever your app does ...)
1046
- .catch( err => ...do recovery ... )
1047
- ```
1048
-
1049
- ### Web Applications
1050
-
1051
- #### Implicit flow
1052
- For web applications it is recommended that you use implicit flow authentication.
1053
-
1054
- ```javascript
1055
- let payload = {
1056
- host : <Viya server host (ex: http://my.example.com)
1057
- clientID : <clientid>
1058
- redirect : <your redirect>,
1059
- authType : 'implicit',
1060
- };
1061
-
1062
- store.logon ( payload )
1063
- .then ( () => ...do whatever your app does ...)
1064
- .catch( err => ...do recovery ... )
1065
-
1066
- ```
1067
- In your redirect uri pass null for the logon
1068
-
1069
- ```javascript
1070
- store.logon(null)
1071
- .then(...)
1072
-
1073
- ```
1074
-
1075
-
1076
- ## Additional Store Methods
1077
-
1078
- ### connection
1079
-
1080
- let c = store.connection();
1081
- The connection method return information on the current connection.
1082
-
1083
- ```json
1084
- {
1085
- "type": "trusted",
1086
- "host": "http://your-viya-host",
1087
- "tokenType": "bearer",
1088
- "token": "... your Oauth token ..."
1089
- }
1090
- ```
1091
-
1092
-
1093
- ### getServices
1094
- ```javascript
1095
- let services = store.getServices();
1096
- ```
1097
-
1098
- This returns the list of services including "services" specific to restAF(see below)
1099
-
1100
- ### getService
1101
-
1102
- ```javascript
1103
- let services = store.getService('name of service');
1104
- ```
1105
-
1106
- This returns raf object for a service that was setup using addServices. \
1107
-
1108
- ### setAppData and getAppData
1109
-
1110
- Use this method to request restAF to store your data and to retrieve that data
1111
-
1112
- ```javascript
1113
- await store.setAppData( id, data )
1114
- where
1115
- id - id of this data (string)
1116
- data - an object that you want restAF to store
1117
- ```
1118
-
1119
- To retrieve the data use the getAppData method
1120
-
1121
- ```javascript
1122
- let mydata = store.getAppData( id );
1123
- ```
1124
-
1125
- To retieve a specific item pass
1126
-
1127
- ```javascript
1128
- let mydata1 = store.getAppData( id, name);
1129
- ```
1130
-
1131
- Unlike the other store api methods getAppData returns a standard
1132
- javaScript object and not an immutable object.
1133
-
1134
- See example appdata.js in the examples directory.
1135
-
1136
- Lifecycle of restAF store
1137
- ----------------------
1138
-
1139
- **Warning** The store will be lost when you close the browser session. While technically it is possible to save and restore the store this version of restAF does not support persistence.
1
+ # restAF- Building Applications with SAS REST API made simple
2
+
3
+ SAS® Viya® is like a Swiss Army knife when it comes to allowing all types of
4
+ clients to talk to it—Java, Python, Lua, R, and, of course, SAS®.
5
+ In addition to using these language-based APIs, SAS also publishes APIs that
6
+ conform to REST best practices. The well-designed APIs in SAS Viya make it relatively
7
+ easy to build complex web and non-web applications with SAS Viya as the engine of your application.
8
+ The applications can be solely based on SAS Viya capabilities or users can do a
9
+ mashup of the advanced SAS Viya analytics with their other products and capabilities.
10
+ restAF is a light-weight, UI-framework agnostic, JavaScript library designed to be
11
+ easy-to-use for app developers. restAF takes advantage of the consistency of the REST APIs
12
+ to reduce the responses from the server into a standard form, and it also manages the data on
13
+ the client side. This reduction enables developers to interact with the server in a repeating pattern
14
+ regardless of which service the application is using.
15
+
16
+ Use restAF to exploit SAS® Cloud Analytic Services (CAS), compute server, SAS® Visual Analytics,
17
+ and other key capabilities of SAS Viya in a very consistent manner with minimal coding.
18
+
19
+
20
+
21
+
22
+ ## Introduction
23
+
24
+ The concepts and usage of restAF are explained through examples. Addtional examples can be found in these repositories.
25
+
26
+ * [A collection of nodejs based examples](https://github.com/sassoftware/restaf-demos)
27
+ * [A collection of web apps using restAF](https://github.com/sassoftware/restaf-uidemos)
28
+ * [a SAS API explorer built with restAF](https://github.com/sassoftware/restaf-apiexplorer)
29
+ * [react components using restAF](https://github.com/sassoftware/restaf-uicomponents)
30
+
31
+ restAF makes writing applications with SAS REST API simple
32
+ * A small set of methods to make API calls
33
+ * Reduces the information to readily useable parts
34
+ * Manages the data for the application in a central store for one version of truth
35
+ * Short learning curve
36
+ * Results in productive application developers
37
+
38
+ With restAF, you focus on your application and not on the nitty-gritty details of setting up HTTP calls, processing the
39
+ returned results and HTTP codes, parsing the returned payload, managing the data, and so on.
40
+
41
+ ## Key technologies
42
+ restAF uses three key libraries(among others) that you are probably familiar with.
43
+
44
+ 1. [redux-saga](https://redux-saga.js.org/)
45
+ 2. [immutable-js](https://facebook.github.io/immutable-js/docs/#/)
46
+ 3. [axios](https://github.com/axios/axios)
47
+
48
+ I am extremely grateful to the developers of these packages for their wonderful contributions.
49
+
50
+
51
+ ## Accessing restAF
52
+
53
+ ### web Application
54
+ In your web application you can access restaf with the following script tag.
55
+
56
+ ```
57
+ <script src="https://unpkg.com/restaf/dist/restaf.min.js"></script>
58
+
59
+ ```
60
+ A global variable called restaf is available for use in your javascript programs.
61
+
62
+ ### nodejs application
63
+
64
+ ```
65
+ npm install restaf
66
+ ```
67
+
68
+ and then your program
69
+
70
+ ```
71
+ let restaf = require('restaf')
72
+
73
+ ```
74
+
75
+ ### Cloning and modifying restAF
76
+ You can clone as follows
77
+
78
+ ```
79
+ clone https://github.com/sassoftware/restaf
80
+ cd restaf
81
+ npm install
82
+ ```
83
+ To build the code run this command
84
+
85
+ ```
86
+ npm run build
87
+ ```
88
+
89
+ ## Background
90
+
91
+ restAF reduces the hypermedia returned from the REST API calls to a form that is suitable for rapid application development.
92
+ THe next few sections will explain the reduction through examples and how you would use it to write your applications.
93
+
94
+ REST API response is an hypermedia. In practical terms a hypermedia has the following:
95
+
96
+ * Links to
97
+ - What you can do to with this resource
98
+
99
+ - Where you can go next
100
+ - Examples: Get a list of items, create a new resource etc...
101
+ - Optionally information(links) on paginating through the data
102
+
103
+ * Data
104
+ - A collection of items
105
+ - String
106
+ - arrays, objects etc...
107
+ - Optionally links to operate on the returned data( delete, copy, etc...)
108
+
109
+
110
+ restAF stores all the response and returns an object referred to as **rafObject***. The application will use this object
111
+ to retrieve information from it use as it sees fit. The methods correspond to the various component parts of the
112
+ response discussed above.
113
+
114
+ ### Links
115
+ A link has several key parts:
116
+ 1. A URI for the next step (edit, delete, and so on).
117
+ 2. An identification of the link to indicate what this next step is. This is referred to as **rel** – short for “link relationship.”
118
+ You can discern the purpose of that link based on the rel. Some examples are:
119
+
120
+ - If the rel is “content,” you will guess that that this end point returns the content.
121
+ - If the rel is “execute,” you will guess that this end point is associated with some type of execution that is appropriate for that context.
122
+ - If the rel is “create,” you will rightly assume that the call will result in something being created.
123
+
124
+ 3. Media Types
125
+ - The media type used for the payload if the end point needs a payload.
126
+ - The media type of the returned data. This information can be used by applications to determine programmatically how to deal with the data either for
127
+ processing or display.
128
+
129
+ The links are reduced to an object called **rafLink**. You will use rel to locate a rafLink in the rafObject. The primary use of rafLink is as a parameter
130
+ to the api calls to the server.
131
+
132
+ ### Items
133
+ Items are the data. Items can be a collection of items, a text string, an array, SVG, and so on.
134
+ Some examples are as follows:
135
+
136
+ - A collection of items: Each item has some data and links to indicate what you can do with this item (delete for example).
137
+ - A string (examples: SVG, status of a job, and so on).
138
+ - An array (example: SAS logs).
139
+ - Some object.
140
+ - And, in some cases, no data only http return codes.
141
+
142
+ restAF manages the links and data for the application.
143
+
144
+ ## Basic flow of an application
145
+
146
+ The flow of an application using restAF is as follows:
147
+
148
+ 1. Initialize restAF - you can have only one instance of restAF per application. The initialization creates a **store** to maintain the data and handle asynchronous calls
149
+ to the server.
150
+ 2. Logon to the server - setups connection to the Viya Server
151
+ 3. Register the services - Inform restAF to setup structures(called folders)to handle data for each of the services your application will be using.
152
+ 4. In the main loop of your application make calls to the server thru store. The calls will return an object that the application will interact with to
153
+ retrieve the data(read-only) as and when they are needed in the application.
154
+
155
+
156
+ ### Introductory Example for a nodejs application
157
+
158
+ Here is a simple example running data step with compute service. In the example below ignore the following for now:
159
+
160
+ * prtUtil - some utility functions to dump information to console
161
+ * logonPayload - data for logging on to Viya Server - discussed later in this document
162
+
163
+ ```javascript
164
+ let restaf = require('restaf');
165
+ ```
166
+
167
+ Preamble: Application specific code to get configuration information
168
+ You might do it differently
169
+
170
+ ```javascript
171
+ let config = require('./config');
172
+ let logonPayload = config('raf.env');
173
+ ```
174
+
175
+
176
+ Step 1: Initialize the store
177
+ ```javascript
178
+ let store = restaf.initStore();
179
+ ```
180
+
181
+ Steps 2 and 3: Logon to the server and tell restAF which services you want to use
182
+ ```javascript
183
+ async function setup (store, logonPayload){
184
+ let msg = await store.logon(logonPayload);
185
+ let {compute} = await store.addServices('compute');
186
+ return compute;
187
+ }
188
+ ```
189
+
190
+ // Step 4: The main program
191
+
192
+ ```javascript
193
+ async function mainLoop (store, compute, code) {
194
+
195
+ // get the list of contexts for compute server
196
+ // This of contexts as your SAS configs
197
+ let contexts = await store.apiCall(compute.links('contexts'));
198
+
199
+ // lookup the name of the context and create a SAS session with that information
200
+ // In the example we pick the first context that is returned.
201
+ // the itemList function returns the list of contexts
202
+ let context = contexts.itemsList(0);
203
+ let createSession = contexts.itemsCmd(context, 'createSession');
204
+ let session = await store.apiCall(createSession);
205
+
206
+ // Define the payload
207
+ // This is your SAS program
208
+ let payload = {
209
+ data: {code: code}
210
+ };
211
+
212
+ // Execute the data step and wait for completion
213
+ // All calls to the server are managed by the store you created in step 1
214
+ let job = await store.apiCall(session.links('execute'), payload);
215
+ let status = await store.jobState(job, null, 5, 2);
216
+
217
+ // Check the final status of job and view the logs
218
+ if (status.data === 'running') {
219
+ throw `ERROR: Job did not complete in allotted time`;
220
+ } else {
221
+ let logLines = await store.apiCall(status.job.links('log'));
222
+ // print the log
223
+ logViewer(logLines);
224
+ }
225
+ return 'restAF is cool or what';
226
+ }
227
+ ```
228
+
229
+ Your program
230
+
231
+ ```javascript
232
+ let code = [`data _null_; do i = 1 to 100; x=1; end; run; `];
233
+
234
+ setup (store, logonPayload)
235
+ .then (compute => mainLoop(store, compute, code))
236
+ .catch(err =>console.log(err));
237
+
238
+
239
+ ```
240
+
241
+ **A Cool Fact** \- _Notice that in the example above you see no reference to links, content-type, accept type, url's etc... Except in some rare cases you do not have to worry about these - restAF takes care of all those house keeping activities. You just focus on what your app needs to do._
242
+
243
+ ## restAF Objects and restAF Links
244
+
245
+ restAF reduces the information returned by an API call an object called rafObject. This object has several properties amd methods.
246
+ One key reduction is the transformation of links into rafLinks objects. rafLink allows restAF to navigate its own structures and make API
247
+ calls on behalf of the application.
248
+
249
+ ## Initializing restAF
250
+
251
+ To initialize restAF you will make the following 2 calls:
252
+
253
+ ###initStore
254
+
255
+ initStore must be called first to get the store object. **Only one store per application** . This call initializes the store to hold all the application data.
256
+
257
+ ```javascript
258
+ let restaf = require( 'restaf' );
259
+ store = restaf.initStore();
260
+ ```
261
+
262
+ _If using the umd version of the library, the global 'restaf' will be set when the umd version is brought in using the script tag._
263
+
264
+ At this point there is no data in the store and it is not attached to any Viya Server.
265
+
266
+ ### logon
267
+
268
+ The next step is to associate Viya server with this instance of restAF.
269
+
270
+ ```javascript
271
+ store.logon( payload )
272
+ .then ( msg => console.log( msg ) )
273
+ .catch( err => console.log( err ) )
274
+ ```
275
+
276
+ payload is either null or is an object with information to logon to the server. If null,
277
+ restAF assumes that your browser session has been authenticated by other means. In an non-browser environment you have to
278
+ pass in the valid payload.
279
+
280
+ Below is a typical payload for a nodejs application
281
+
282
+ ```javascript
283
+
284
+ payload = {
285
+ authType : 'password',
286
+ host : 'http://yourserver:portno',
287
+ user : username,
288
+ password : user password,
289
+ clientID : clientid, /* get this from your admin */
290
+ clientSecret: clientsecret /* get this from your admin */
291
+ } );
292
+ ```
293
+
294
+ A friendly note to protect the payload information on your client machine
295
+ since it has all the key authentication information
296
+ in plain text.
297
+
298
+ In a browser use the initial logon payload will look as follows:
299
+
300
+ ```javascript
301
+ payload = {
302
+ authType: 'implicit',
303
+ host : 'http://yourserver:portno',
304
+ clientId: clientId, /* get this from your admin */
305
+ redirect: <The redirect that was specified when creating the clientId>
306
+ }
307
+ ```
308
+ In the url that is the target of the redirect you will pass a null for payload
309
+
310
+ ```javascript
311
+ payload = null
312
+ ```
313
+
314
+ restAF will parse the incoming location information and set up access to the server.
315
+
316
+
317
+ ##Adding Services
318
+
319
+ After the initialization steps restAF still does not know what information to manage.
320
+ Now populate restAF by registering the services you want to use in your application.
321
+
322
+ ### addServices
323
+
324
+ addServices method adds a folder for the each service.
325
+ It additionally calls the root end point for that service
326
+ and populates the folder with the links returned from that call.
327
+ The addServices method returns a restAF object( rafObject) which we will discuss next.
328
+
329
+ **All service names are case-sensitive**
330
+
331
+ ```javascript
332
+ let {serviceName1, serviceName2,....} = await store.addServices( 'serviceName1', 'serviceName2', .... );
333
+ ```
334
+
335
+ At this point serviceName1 is a rafObject with information for the root end points of that service.
336
+ #### Example
337
+
338
+ ```javascript
339
+ let {compute, casManagement} = await store.addServices( 'compute', 'casManagement' );
340
+ ```
341
+
342
+
343
+ ##restAF Object(rafObject)
344
+
345
+ In your application you will be dealing primarily with rafObjects and rafLinks. Every time a result is returned from an
346
+ API call(including addServices), restAF will "reduce" the information into a folder and return a rafObject
347
+
348
+ The folders are immutable objects based on [immutable.js.](https://facebook.github.io/immutable-js/) while rafObject is a
349
+ standard javaScript object.
350
+
351
+ The application will retrieve information using the methods of the rafObject.
352
+
353
+ ### restAF Links ( rafLinks )
354
+
355
+
356
+ restAF extends all the links returned by the server with information it needs to navigate the store and make the final calls.
357
+ For an application the most common use of rafLinks is as an argument to apiCall method of the store to navigate to a new resource.
358
+ The other common use is in associating links with UI components like buttons, navigators etc. This will be covered later in this document.
359
+
360
+ ###apiCall - Making REST API calls
361
+
362
+ The apiCall method on the store object is the most used method for making REST calls.
363
+
364
+ ```javascript
365
+ store.apiCall( rafLink <, payload>)
366
+ ```
367
+
368
+ rafLink is obtained using the links and itemsCmd methods on rafObject.
369
+
370
+ The payload(optional parameter based on the REST end point) is an object with the following optional keys
371
+
372
+ * data - If the REST call is a PUT or POST then specify the data payload
373
+ * qs - Use this to specify the query parameters
374
+ * headers - Use this to specify any special headers for this call. For example the upload action in cas requires the the JSON-Parameters header. Another example is file service's 'create' operation requires content-type to be specified
375
+ * action - if you are running a cas action, you **must** specify the action name. Recommend that you use a fully qualified action name ( actionset.actionname )
376
+ * All other information in the payload will be ignored silently. Since all the keys are optional restAF has no way to warn you about missing keys or
377
+ extraneous keys.
378
+
379
+
380
+ ### Examples of payload
381
+
382
+ _To run sas code with compute service_
383
+ ```json
384
+ { data: { code: \[ 'data a; x=1;run;' , 'proc print;run' \] }}
385
+ ```
386
+
387
+ _To run datastep in cas_
388
+
389
+ { action: 'datastep.runCode', data: { code: 'data a; x=1;run;' } }
390
+
391
+ _To run upload action for CAS_
392
+
393
+ ```javascript
394
+ let JSON_Parameters = {
395
+ casout: {
396
+ caslib: 'casuser', /* a valid caslib */
397
+ name : 'iris' /* name of output file on cas server */
398
+ },
399
+
400
+ importOptions: {
401
+ fileType: 'csv' /* type of the file being uploaded */
402
+ }
403
+ };
404
+ let payload = {
405
+ action : 'table.upload',
406
+ headers: { 'JSON-Parameters': JSON_Parameters },
407
+ data : readFile( 'iris', 'csv' )
408
+ }
409
+ ```
410
+
411
+ _To create a file with plain text_
412
+
413
+ ```javascript
414
+ let payload = {
415
+ data : 'my data',
416
+ headers: { 'content-type': 'text/plain' }
417
+ }
418
+ ```
419
+
420
+ ### apiCallAll - Making api Calls in parallel
421
+
422
+ apiCall method makes a single service call. The apiCallAll method on store allows the programmer to execute multiple service calls in parallel.
423
+ A typical use case will be running forecast actions in parallel in different cas sessions.
424
+ Another is running jobs in multiple compute server sessions. You can mix and match services in this single call( ex: create files, run cas jobs etc... but this would be an exception rather than a rule
425
+
426
+ ```javascript
427
+ store.apiCallAll( requestArray )
428
+ ```
429
+
430
+
431
+ * requestArray is an array with each row of the following format:
432
+
433
+ ```javascript
434
+ { rafLink: <the rafLink to be called>
435
+ payload: <standard payload as described earlier in this document>
436
+ }
437
+ ```
438
+
439
+
440
+ The method returns when all the calls in the requestArray return successfully or when one of them fails. In that sense apiCallAll behaves like PromiseAll.
441
+ The order of the results array is the same as the requestArray. See the example for running parallel sas compute jobs
442
+
443
+
444
+ ### jobState and jobStateAll
445
+
446
+ Some services might take a long time to run. Typically these services support a "state" link to query the state of the job. Note that not all services support the 'state' link. Services like compute service and JobExecution support this
447
+
448
+ In SAS API the state end point returns could be in one of these states.
449
+
450
+ * **running or pending** \- This means the job did not complete and is still running or pending.
451
+ * **Completed** The job completed but the actual state of the job will vary based on the service. Some possible completed states are:
452
+
453
+ * Job completed with no additional clarification(completed)
454
+ * Job completed with warnings(warning)
455
+ * Job failed or completed with errors(errors)
456
+ * Job failed(failed) - similar to the previous item but some services have chosen to return failed
457
+ * probably other completed states now or in later releases
458
+
459
+ Most of the services that have a "state" end point recommend that you call the 'self' end point after the job completes to get the latest results from the job run.
460
+ jobState and jobStateAll will make this additional call on 'self' if none of the jobs are still running. This saves you an additional call
461
+
462
+ At this point your code has to decide how best to proceed given these states.
463
+
464
+ ### To check the status of a single job use jobState method.
465
+
466
+ ```javascript
467
+ store.jobState( rafObject-for-job <,payload> <,maxTries > )
468
+ ```
469
+
470
+
471
+ Some services take a query parameter to support long polling - pass that information in the standard payload structure
472
+
473
+ maxTries is the number of times restAF will try if the job did not return a completed status
474
+
475
+ The returned object has the following form:
476
+
477
+ ```json
478
+ {
479
+ data : <completed|running|...|...>,
480
+ statusCode: <http code >,
481
+ job : <rafObject of the final job>
482
+ }
483
+ ```
484
+
485
+ An example
486
+ ```javascript
487
+ /* pass a timeout of 2 seconds and try a max of 5 times */
488
+ let status = await store.jobState( job, {qs: { timeout: 2} }, 5 );
489
+ if (status.data === 'running') {
490
+ throw \`ERROR: Job did not complete in allotted time\`;
491
+ } else {
492
+ viewer(status.job); /* some viewer of results results */
493
+ switch(status.data) {
494
+ case 'warning': console.log(\`Warning: check your log for warnings\`); break;
495
+ case 'error':
496
+ throw \`Please correct errors and rerun program\`;
497
+ default:
498
+ break;
499
+ }
500
+ }
501
+
502
+ ```
503
+ ### jobStateAll
504
+
505
+ jobStateAll method to check the status of multiple jobs the jobs with a single call
506
+
507
+ The returned value has the form
508
+ ```json
509
+ {
510
+ running: <No of jobs still running >
511
+ details: >details on the completion ( see note above and see below for the form of this object>
512
+ jobState: [
513
+ {
514
+ job : < the job rafObject. see note below >
515
+ statusCode: < The http code from the server >
516
+ data : < text returned on completion( 'complete' for example ) >
517
+ }
518
+ ]
519
+ }
520
+ ```
521
+
522
+ Some notes
523
+
524
+ * 'running' will be 0 if all jobs completed and >0 if one or more jobs are still running
525
+ * jobState will be an array for jobStateAll
526
+
527
+ The details object gives you a summary of the returned state. It is easier to explain its structure with some examples
528
+
529
+ For jobState the detail will have a single key/value. The key will be the state returned from the call. For example if the job returned "failed" then the detail object will be as follows
530
+
531
+ detail: {
532
+ failed: 1
533
+ }
534
+
535
+ For jobStateAll the detail could have multiple key/values. For example if you ran 4 jobs in parallel and 2 completed with state of 'warning' , 1 completed with state of 'completed' and one completed with state of 'error', then the detail will look as follows:
536
+
537
+ ```json
538
+ detail: {
539
+ warning : 2,
540
+ completed: 1,
541
+ error : 1
542
+ }
543
+ ```
544
+
545
+ ```javascript
546
+ store.jobStateAll( array of jobs rafObject, payload, maxTries )
547
+ .then ( status => {
548
+ if ( status.running !== 0 ) {
549
+ ...handle slow running jobs...
550
+ } else {
551
+ ... check the detail object and make decisions...
552
+ }
553
+
554
+ } )
555
+ ```
556
+
557
+ The payload parameter can be an array or single object. If single object then this payload will be applied to every rafObject in the first parameter.
558
+ The primary purpose of the payload is to set timeout as a query parameter
559
+
560
+ Maxtries works the same as in jobState
561
+
562
+ ### Submit and SubmitStatus methods- run api and handle results in "background".
563
+
564
+ REST APIs are great when the response to an api call is fast and instantaneous.
565
+ In SAS it is not uncommon to have jobs that take a bit to run.
566
+ For really long running jobs one should use our batch capabilities.
567
+ However many solutions have applications where the user submits requests
568
+ that can take from a few seconds to a minute or two to complete.
569
+
570
+ This presents a problem for the application writers.They have to poll the server at some intervals and
571
+ probably at many points in their application code.
572
+
573
+ The submit method is designed to handle this scenario in a more elegant way.
574
+
575
+ The parameters to submit are:
576
+
577
+ ```javascript
578
+ store.submit( rafLink, payload, delay, id , <OnCompletion exit>, <progress exit> )
579
+ ```
580
+
581
+ where:
582
+
583
+ - raflink - same as for apiCall
584
+
585
+ - payload - standard payload or null
586
+
587
+ - delay - restAF will check the server after every "delay" seconds for completion and drive the callbacks if specified
588
+
589
+ - id - specify an id(string). You will use this to retrieve the information the rafObject associated with this job
590
+
591
+ - onCompletion - if non-null restAF will call this exit on completion ( success or failure) of the job.
592
+
593
+ - progress - if non-null restAH will call this exist after every call to server to check the status.
594
+
595
+
596
+ The method signature of progress exit is
597
+
598
+ ```javascript
599
+ function progress ( data, id )
600
+ ```
601
+ where
602
+
603
+ - data - the current status of the job ( running, completed, failed etc...)
604
+ - id - the id you passed to the submit method
605
+
606
+ The function returns either true or false. If you return true, restAF will stop calling the server for the status
607
+ of the job.
608
+
609
+
610
+ The method signature of onCompletion is
611
+
612
+ ```javascript
613
+ function onCompletion( err, jobStatus, id );
614
+ ```
615
+ where
616
+
617
+ - err - if non-null then this is the error message
618
+ - jobStatus - this has the same structure as the return from jobStatus
619
+ - id - the id that you passed to the submit method
620
+
621
+ You will retrieve the final jobStatus using the **submitStatus** method of the store
622
+
623
+ ```javascript
624
+ let jobStatus = store.submitStatus(id);
625
+ ```
626
+ where jobStatus is the same as what jobState returns.
627
+
628
+ One could write part of the introductory example as follows:
629
+
630
+ ```javascript
631
+ let payload = {
632
+ data: {code: [`data _null_; do i = 1 to 10000000; end;run; `]}
633
+ };
634
+ store.submit( session.links('execute'), payload, 1,
635
+ 'myJob',onCompletion, progress);
636
+ ```
637
+
638
+ ## Details on restAF Object
639
+
640
+ ###Properties
641
+
642
+
643
+ All property names are case sensitive
644
+
645
+ 1. **type:** This indicates which of the results pattern this object represent
646
+
647
+ 1. **links:** Use the links method below to view and obtain rafLinks for further calls. If type is "links" then this object only has links for other end points.
648
+ 2. **itemsList:** The rafObject contains an array of items with id's.
649
+ 1. Use itemsList method to retrieve the id's of the items returned.
650
+ 2. Use the scrollCmds method to determine whether there are more data available and for making the pagination calls.
651
+ 3. Use the items function to retrieve specific items( or all items ) from the collection.
652
+ 4. use the itemsCmd method torRetrieve the cmds( links) associated with specific item
653
+
654
+ 3. **itemArray:** The returned value is an array of text. Use items methods to retrieve this data
655
+ 4. **items:** The exact object type is not known. Use items method to get the information and also use resultType property do decide how to handle that data
656
+
657
+ 3. **resultType:** The media type of the result
658
+ 4. **route:** This is an index into the store for this specific result (see section on route later in this document). This will be useful if you want to pass a reference to a rafObject.
659
+ 6. **status:** This the http code returned from the api call
660
+ 7. **statusInfo:** An object with details of the status
661
+ 8. **host** - the fully qualified Viay host the web app is logged on to.
662
+
663
+
664
+ Methods (function properties)
665
+ -----------------------------
666
+
667
+ Use these methods to interact with rafObject. These functions return either objects or string depending on the data. Remember these objects are immutable objects. In the document below the common usage patterns are described.
668
+
669
+ If the returned value is an rafObject you can use query parameters to drill into a specific value in that object. Examples are provided below
670
+
671
+ #### links method
672
+
673
+ The links method will return an immutable object of rafLinks. You will use this method when the type of the rafObject is "links". But sometimes there will be links even when the type is not "links". In those cases the links are usually actions that operate on the whole collection
674
+
675
+ ```javascript
676
+ rafLink = rafObject.links( relName );
677
+ ```
678
+
679
+
680
+ relName is the rel of the resource you are looking for. The call will return a rafLink that you can use in an apiCall as shown below
681
+
682
+ ```javascript
683
+ contentLink = fileObject.links( 'content' );
684
+ contentObject = store.apiCall ( contentLink );
685
+ ```
686
+
687
+ Sometimes you need the entire list of links. An example is displaying buttons with the titles of the links. Below is an example of printing the titles of all the links.
688
+
689
+ ```javascript
690
+ allLinks = fileObject.links();
691
+ allLinks.forEach( ( l, rel ) => {
692
+ console.log(\`Title: ${rel} rel: ${rel} \` );
693
+ }
694
+ ```
695
+
696
+ #### itemsList method
697
+
698
+ If the rafObject type is 'itemsList' use this method to get the immutable array of id's in the current result object. This is really a convenience method for UI ( ex: showing a list of the id's).
699
+
700
+ Below is an example listing all the id's
701
+
702
+ ```javascript
703
+ rafObject.itemsList().map( id => console.log(id) )
704
+ ```
705
+
706
+ you can also do the following:
707
+
708
+ ```javascript
709
+ console.log( JSON.stringify(rafObject.itemsList(), null, 4) );
710
+ ```
711
+
712
+
713
+ #### scrollCmds method
714
+
715
+ This method returns the rafLinks associated with pagination. This method can be used to get a rafLink associated with next, prev, last and first cmds(rel). At any given time the server may return some, all or none of these. To ensure safe programming, always check if the returned rafLink is non-null.
716
+
717
+ ```javascript
718
+ let nextCmd = rafObject.scrollCmds( 'next' );
719
+ if ( nextCmd !== null ) {
720
+ store.apiCall( nextCmd )
721
+ .then ( restafobj => {...do something...} )
722
+ } else {
723
+ console.log( 'No more data' )
724
+ }
725
+ ```
726
+
727
+ In an UI you would want to display buttons for scrolling. A typical code will look like this
728
+
729
+ ```javascript
730
+ let cmds = rafObject.scrollCmds();
731
+
732
+ cmds.forEach( ( c, cmd ) => {
733
+ ...make your scrolling menu...
734
+ } );
735
+ ```
736
+
737
+ For example here is the code for creating a menubar in Reactjs
738
+
739
+ ```javascript
740
+ function ButtonMenu( props ) {
741
+ let { cmds, onClick } = props;
742
+ let menu = [];
743
+ cmds.forEach( ( c, rel ) => {
744
+ menu.push( <button key={rel} onClick={onClick(...)}
745
+ className="button"> {rel} </button> );
746
+ } );
747
+
748
+ return (
749
+ <div>
750
+ {menu}
751
+ </div>
752
+ )
753
+ }
754
+ ```
755
+
756
+ #### items method
757
+
758
+ This method gives you access to the data that might have been returned. This could be log for compute server, cas results, tables, status, ods and so on. The combination of resultType and type should give you sufficient information on how to process this data. It is possible to write an intelligent UI that determines the "viewer" to use given these pieces of information. But in a typical scenario the application knows what it is expecting at any specific point in the flow of the application.
759
+
760
+ The items method takes arguments that determines what is returned.
761
+
762
+ ```javascript
763
+ let result = rafObject.items( <query> )
764
+ ```
765
+
766
+ Let us examine a few typical cases
767
+
768
+ **All Items**
769
+
770
+ ```javascript
771
+ let allItems = rafObject.items();
772
+ ```
773
+
774
+ If you get all the items then you need to write the code to manage the items based on the resultType
775
+
776
+ **Get a specific item using the id ( which you will get from itemsList() )**
777
+
778
+ ```javascript
779
+ let item = rafObject.items( idOfItem );
780
+ ```
781
+
782
+ **Getting data for an item in a collection **
783
+
784
+ ```javascript
785
+ let data = rafObject.item( idOfItem, 'data' );
786
+ ```
787
+
788
+
789
+ #### itemsCmd
790
+
791
+ Use this method to obtain the cmds associated with a particular item. Obtain the id of the item by using the itemList method.
792
+ The most common usage is to get the rafLink for a specific command for the selected item
793
+
794
+ **Get a specific command associated with a item with a specific id**
795
+
796
+ ```javascript
797
+ let item = rafObject.itemsCmd( idOfItem, 'name of command' );
798
+ ex:
799
+ let deleteCmd = rafObject.itemsCmd( '1234ID', 'delete' );
800
+
801
+ store.apiCall( deleteCmd )
802
+ .then ( f => { do whatever) }
803
+ .catch( err => { do whatever) }
804
+ ```
805
+
806
+ **Get all commands associated with a item with a specific id**
807
+
808
+ ```javascript
809
+ let rafLinks = rafObject.itemsCmd( idOfItem );
810
+ ```
811
+
812
+ You can step thru this object as follows
813
+
814
+ ```javascript
815
+ rafObject.itemsCmd( idOfItem ).forEach( ( c, key) => {
816
+ // c is the current cmd information
817
+ // key is the name of the command( createSession, delete etc... )
818
+ }
819
+ ```
820
+
821
+ #### responseHeaders
822
+
823
+ On occasion one needs to access to the returned headers like etag. For those scenarios use the responseHeaders method.
824
+
825
+ **Get a specific header**
826
+
827
+ ```javascript
828
+ let etag = rafObject.responseHeaders( 'etag' );
829
+ ```
830
+ **Get all headers**
831
+
832
+ ```javascript
833
+ let headers = rafObject.responseHeaders();
834
+ ```
835
+
836
+ #### status
837
+
838
+ To get the http status returned from the call
839
+
840
+ ```javascript
841
+ let status = rafObject.status();
842
+ ```
843
+
844
+ #### statusInfo
845
+
846
+ Use this to get any additional information that might have been returned for the status. Mostly useful if the job failed for some reason
847
+
848
+ let info = rafObject.statusInfo() ;
849
+ console.log( info );
850
+
851
+ ### Less used Methods
852
+
853
+ ####raw
854
+ Returns the raw response from the server
855
+
856
+ ####config
857
+ The http payload sent to the server (useful for debugging)
858
+
859
+
860
+ Running CAS actions
861
+ -------------------
862
+
863
+
864
+ In restAF you will access cas actions in the following manner.
865
+
866
+ * Add casManagement to restAF using store.addServices.
867
+ * Look thru the list of servers returned by casManagement and pick the one you want. More often than not there is probably only one cas server.
868
+ * Create a session on that server. restAF will add a rel named 'execute' to the session's links returned from this step.
869
+ * Make all cas action calls thru runAction method
870
+ * restAF collects all the returned tables under a "tables" object to make handling of returned tables a bit easier
871
+
872
+ Below is an example showing uploading of a csv to cas and then getting its info
873
+
874
+ The example below uses the nodejs file i/o. If you are running from browser you will use the File Object.
875
+
876
+ ```javascript
877
+ async function example () {
878
+ // setup
879
+
880
+ await store.logon(payload);
881
+ let {casManagement} = await store.addServices('casManagement');
882
+ let servers = await store.apiCall(casManagement.links('servers'));
883
+ let casserver = servers.itemsList(0);
884
+ let session = await store.apiCall(servers.itemsCmd(casserver, 'createSession'),
885
+ {data: {name: 'mysessionname'}});
886
+ /
887
+ // setup header for upload and the rest of the payload
888
+ let JSON_Parameters = {
889
+ casout: {
890
+ caslib: 'casuser', /* a valid caslib */
891
+ name : filename /* name of output file on cas server */
892
+ },
893
+
894
+ importOptions: {
895
+ fileType: fileType /* type of the file being uploaded */
896
+ }
897
+ };
898
+ let p = {
899
+ headers: {'JSON-Parameters': JSON_Parameters},
900
+ data : readFile(filename, fileType),/* read the csv file from somewhere */
901
+ action : 'upload'
902
+ };
903
+
904
+ let actionResult = await store.runAction(session.links, payload);
905
+ prtUtil.view(actionResult, 'Result of upload action');
906
+
907
+ let deleteAction = await store.apiCall(session.links('delete'));
908
+ return "All Done";
909
+ }
910
+
911
+ function readFile (filename, fileType) {
912
+ return fs.readFileSync(`./data/${filename}.${fileType}`);
913
+ }
914
+ ```
915
+
916
+ #### Handling tables returned by CAS
917
+ Below is a sample program to print the rows in the CAS tables
918
+
919
+ ```javascript
920
+ let data = result.items('tables', <name of the table>);
921
+ let itemRows = data.get('rows');
922
+ let columns = [];
923
+ data.get('schema').map(s => {
924
+ columns.push(s.get('name'));
925
+ });
926
+
927
+ itemRows.map((r)=> {
928
+ let row = {};
929
+ r.map((value, j) => {
930
+ row[columns[j]] = value;
931
+ });
932
+ console.log(JSON.stringify(row, null, 4));
933
+ });
934
+
935
+ ```
936
+
937
+ Handling Pagination
938
+ -------------------
939
+
940
+ restAF handles all the pagination for you. Use the scrollCmds as described above. It assumes that you will use the scrollCmds API to retrieve more information.
941
+
942
+ For example to get the next set of items do the following
943
+
944
+ ```javascript
945
+ let next = rafObject.scrollCmds( 'next' );
946
+ if ( next === null ) {
947
+ /\* tell user there is no more data */
948
+ } else {
949
+ store.apiCall( next )
950
+ .then ( nextObj => {
951
+ do whatever using teh rafObject methods
952
+ } );
953
+ .catch ( err => {
954
+ handle error conditions
955
+ } )
956
+ }
957
+ ```
958
+
959
+ Replace next with prev,last ans first as appropriate.
960
+
961
+ Here is an example of printing to console all the items from a collection
962
+
963
+ ```javascript
964
+ let store = restaf.initStore();
965
+
966
+ // Pagination
967
+
968
+ async function example (store, logonPayload, counter) {
969
+ await store.logon(logonPayload);
970
+ let {files} = await store.addServices('files');
971
+
972
+ let filesList = await store.apiCall(files.links('files'));
973
+ printList(filesList.itemsList());
974
+ let next;
975
+ // do this loop while the service returns the next link
976
+ while(((next = filesList.scrollCmds('next')) !== null) ) {
977
+ filesList = await store.apiCall(next);
978
+ printList(filesList.itemsList());
979
+ }
980
+
981
+ return 'All Done';
982
+ }
983
+
984
+ const printList = (itemsList) => console.log(JSON.stringify(itemsList, null, 4));
985
+
986
+ example(store, payload, 10)
987
+ .then (status => console.log(status))
988
+ .catch(err => console.log(err));
989
+
990
+ ```
991
+
992
+ Route property
993
+ --------------
994
+
995
+ Each rafObject has a route property. This is a string that is key to the data pointed to by rafObject.
996
+ restAF uses the route to find the data associated with this key. If for whatever reason you need to maintain a "pointer" to a
997
+ rafObject or to pass it in as a query parameter you can use this.
998
+
999
+ Route is useful if you want to pass a specific rafObject to another part of your program through some routing mechanism( ex: react-router). Given a route you can get the
1000
+ associated rafObject with the **rafObject** method
1001
+
1002
+ ```javascript
1003
+ let myObject = store.rafObject(route);
1004
+ ```
1005
+
1006
+ ## Authentication
1007
+
1008
+ restAF relies on the Oauth2 authentication supported by SAS Viya.
1009
+
1010
+ There are a few use cases as described below:
1011
+
1012
+ ### Authenticated browser session: If you session is already authenticated then pass null to logon method
1013
+
1014
+ ```javascript
1015
+ store,logon( null)
1016
+ .then ( msg => <do your stuff> )
1017
+ .catch( err => <error handling> )
1018
+ ```
1019
+
1020
+ ### Using an existing token
1021
+
1022
+ There are situations where a valid token might exist. In that case use the following payload to store.logon
1023
+
1024
+ ```javascript
1025
+ store.logon( {
1026
+ host: "your viya server',
1027
+ token: "your token
1028
+ });
1029
+ ```
1030
+
1031
+ ### nodejs applications
1032
+
1033
+ For nodejs applications you will need to use the password flow authentication method
1034
+
1035
+ ```javascript
1036
+ let payload = {
1037
+ authType : 'password',
1038
+ host : 'http://yourserver:portno',
1039
+ user : username,
1040
+ password : user password,
1041
+ clientID : clientid, /* get this from your admin */
1042
+ clientSecret: clientsecret /* get this from your admin */
1043
+ } );
1044
+ store.logon ( payload )
1045
+ .then ( () => ...do whatever your app does ...)
1046
+ .catch( err => ...do recovery ... )
1047
+ ```
1048
+
1049
+ ### Web Applications
1050
+
1051
+ #### Implicit flow
1052
+ For web applications it is recommended that you use implicit flow authentication.
1053
+
1054
+ ```javascript
1055
+ let payload = {
1056
+ host : <Viya server host (ex: http://my.example.com)
1057
+ clientID : <clientid>
1058
+ redirect : <your redirect>,
1059
+ authType : 'implicit',
1060
+ };
1061
+
1062
+ store.logon ( payload )
1063
+ .then ( () => ...do whatever your app does ...)
1064
+ .catch( err => ...do recovery ... )
1065
+
1066
+ ```
1067
+ In your redirect uri pass null for the logon
1068
+
1069
+ ```javascript
1070
+ store.logon(null)
1071
+ .then(...)
1072
+
1073
+ ```
1074
+
1075
+
1076
+ ## Additional Store Methods
1077
+
1078
+ ### connection
1079
+
1080
+ let c = store.connection();
1081
+ The connection method return information on the current connection.
1082
+
1083
+ ```json
1084
+ {
1085
+ "type": "trusted",
1086
+ "host": "http://your-viya-host",
1087
+ "tokenType": "bearer",
1088
+ "token": "... your Oauth token ..."
1089
+ }
1090
+ ```
1091
+
1092
+
1093
+ ### getServices
1094
+ ```javascript
1095
+ let services = store.getServices();
1096
+ ```
1097
+
1098
+ This returns the list of services including "services" specific to restAF(see below)
1099
+
1100
+ ### getService
1101
+
1102
+ ```javascript
1103
+ let services = store.getService('name of service');
1104
+ ```
1105
+
1106
+ This returns raf object for a service that was setup using addServices. \
1107
+
1108
+ ### setAppData and getAppData
1109
+
1110
+ Use this method to request restAF to store your data and to retrieve that data
1111
+
1112
+ ```javascript
1113
+ await store.setAppData( id, data )
1114
+ where
1115
+ id - id of this data (string)
1116
+ data - an object that you want restAF to store
1117
+ ```
1118
+
1119
+ To retrieve the data use the getAppData method
1120
+
1121
+ ```javascript
1122
+ let mydata = store.getAppData( id );
1123
+ ```
1124
+
1125
+ To retieve a specific item pass
1126
+
1127
+ ```javascript
1128
+ let mydata1 = store.getAppData( id, name);
1129
+ ```
1130
+
1131
+ Unlike the other store api methods getAppData returns a standard
1132
+ javaScript object and not an immutable object.
1133
+
1134
+ See example appdata.js in the examples directory.
1135
+
1136
+ Lifecycle of restAF store
1137
+ ----------------------
1138
+
1139
+ **Warning** The store will be lost when you close the browser session. While technically it is possible to save and restore the store this version of restAF does not support persistence.