# cwclientlibjs - client library for CubicWeb's rqlcontroller Javascript clone of the cwclientlib python library that wraps the rqlcontroller API offered by the cubicweb-rqlcontroller cube. To read about the rql client, go to [Section Client.ts](#client.ts). To read about the providers, go to [Section Providers.ts](#providers.ts). For internal use : [sonarqube analysis](https://sonarqube.k.intra.logilab.fr/dashboard?id=open-source-cwclientlibjs) ## Client.ts The [client](src/client.ts) namespace provides helpers to run RQL queries on a cubicweb instance with the rqlcontroller cube. The class [CwRqlClient](src/client.ts#L690) implements the interface [RqlClient](src/client.ts#L644) and provides the following functions: - **queryRows(query:string, params?:any)**: runs query on the cubicweb instance and returns the results as [RqlRows](src/client.ts#L553) in a Promise. This function calls transactionV1 - **queryBindings(query:string, params?)**: runs query on the cubicweb instance with and returns the results as [RqlSolutions](src/client.ts#L586) in a Promise. This function calls transactionV2 - **queryAndTransform(query:string, params?,viewId)**: Build the url corresponding to the query with a given viewId and returns the content in a Promise - **transactionV1(queries)**: runs a set of queries with the rqlio/1.0 interface and returns the query results or throws an error if something went wrong - **transactionV2(queries)**: runs a set of queries with the rqlio/2.0 interface and returns the query results as [RqlIoV2TransactionResponse](src/client.ts#L576) A RqlClient relies on an HttpClient. Two implementations are available [CwSimpleHttpClient](src/client.ts#L332) and [CwSigningHttpClient](src/client.ts#L462). ### CwSimpleHttpClient: anonymous request [CwSimpleHttpClient](src/client.ts#L332) requires a baseUrl (the base url of a cubicweb instance). A boolean can be added to allow cross origin requests. The CwSimpleHttpClient can perform a doLogin(login,password) operation on the cubicweb instance and perform the queries by using cookies (see [Known issues](#known-issues)). It can also be transformed into a CwSigningHttpClient (toSigningClient()) after a doLogin() operation. It requires that the CubicWeb user has at least an enabled token linked to his/her account. The CSRF token will be included if `allowsCrossOrigin` is false (the default). ```javascript import {providers, client} from '@logilab/cwclientlibjs'; // url is the base url of your cubicweb instance const url = 'http://my-cubicweb-instance-base-url/'; const rqlClient = new client.CwRqlClient( new client.CwSimpleHttpClient(url, true) ); const query = 'Any X, Y WHERE X is BlogEntry, X entry_of Y'; rqlClient.queryRows(query).then(res => { console.log(res); // [[123,1], [234, 2]] }); rqlClient.queryBindings(query).then(res => { console.log(res); // [{'X': 123, 'Y': 1}, {'X':234, 'Y':2}] }); rqlClient.queryAndTransform(query, 'rdf').then(res => { console.log(res); // equivalent to http://my-cubicweb-instance-base-url/view?rql=Any X, Y WHERE X is BlogEntry, X entry_of Y &vid=rdf }); rqlClient.transactionV1([query]).then(res => { console.log(res); // [[[123,1], [234, 2]]] }); rqlClient.transactionV2([query]).then(res => { console.log(res); // [{rows: [[123,1], [234, 2]], variables : ['X','Y']}] }); ``` ### CwSigningHttpClient: authenticated requests. [CwSigningHttpClient](src/client.ts#L462) requires a baseUrl (the base url of a cubicweb instance), a tokenName, a tokenValue and a hashMethod code. Each request will contain an Authorization header with the connection token and its hashed value. These tokens are managed on your CubicWeb instance under Profile > Actions > Add > Authentication Token. ```javascript import {providers, client} from '@logilab/cwclientlibjs'; // url is the base url of your cubicweb instance const url = 'http://my-cubicweb-instance-base-url/'; const rqlClient = new client.CwRqlClient( new client.CwSimpleHttpClient(url, "my-token-name", "1a2b3c4d5e6f...") ); const query = 'Any X, Y WHERE X is BlogEntry, X entry_of Y'; rqlClient.queryRows(query).then(res => { console.log(res); // [[123,1], [234, 2]] }); ``` ## Providers.ts The [provider](src/providers.ts) namespace provides two main objects: [EntitySchemaProvider](#entityschemaprovider) and [EntityProvider](#entityprovider). ### EntitySchemaProvider A [RqlEntitySchemaProvider](src/providers.ts#L593) provides an interface to load a CubicWeb instance Schema. A EntitySchemaProvider provides the following functions: - **getAllRelationsFor(typeEid:number)**: Outputs two lists: relationsFrom and relationsTo relative to the CWEType referenced by typeEid - **getEntitySchemaByName(typeName: string)**: Retrieves a CWEType (as EntitySchema) from its name - **getEntitySchemaById(typeEid: number)** : Retrieves a CWEType (as EntitySchema) from its eid - **getEntitySchemaFor(eid:number)** : Retrieves a CWEType (as EntitySchema) for a given instance number - **load()**: Loads the Schema An RqlEntitySchemaProvider can instantiate a [LocalStorageEntitySchemasLoader](src/providers.ts#L259) which will store the schema in the browser's window.localStorage, if given a 'storageKey' value. Additionnaly, two functions: **isCacheUsable** and **onRetrieved** can be given to the RqlEntitySchemaProvider, they can be used to deal with schema versioning. - **isCacheUsable** is called in a LocalStorageEntitySchemasLoader, it takes a `providers.Schema` as input and outputs a boolean Promise. If the promise is resolved with the value `true`, the schema will be loaded from the localStorage. Otherwise, if will be queried and loaded from the cubicweb instance again. - **onRetrieved** is called after the schema has been queried and loaded from the cubicweb instance. If no 'storageKey' value is given, a [RqlEntitySchemasLoader](src/providers.ts#L340) will be instantiated and the schema will be queried at every call of the `load` function. ```javascript import {providers, client} from '@logilab/cwclientlibjs'; // url is the base url of your cubicweb instance const url = 'http://my-cubicweb-instance-base-url/'; const rqlClient = new client.CwRqlClient(new client.CwSimpleHttpClient(url)); // Schema will not be cached const schemaProvider = new providers.RqlEntitySchemaProvider(rqlClient); // Schema will be cached const schemaProviderWithLocalStorage = new providers.RqlEntitySchemaProvider( rqlClient, 'mySchemaStorageKey' ); // load a schema schemaProvider.load().then((schema: providers.Schema) => { console.log(schema); }); // load a schema schemaProviderWithLocalStorage.load().then((schema: providers.Schema) => { console.log(schema); }); ``` A Schema is a json object with two entries: - a **metadata** object which is computed on the schema load. At the moment, the metadata contains the schema loading timestamp. - **entities**: a list of EntitySchema, each EntitySchema describes a CWEType, its attributes and to and from relations. Example instanciation of a schema object: ```json { "metadata": { "timestamp": "2020-04-20T09:35:49.183Z" }, "entities": [ { "eid": 30, "name": "BlogEntry", "modificationDate": "2009/09/01 12:15:04", "specializes": null, "attributes": [ {"eid": 78, "name": "title", "type": "String", "cardinality": 2} ], "relationsTo": [ { "eid": 6055, "name": "entry_of", "from": 30, "fromName": "BlogEntry", "fromCardinality": 1, "to": 6044, "toName": "Blog", "toCardinality": 1, "description": "" } ], "relationsFrom": [ { "eid": 711, "name": "tags", "from": 61, "fromName": "Tag", "fromCardinality": 1, "to": 30, "toName": "BlogEntry", "toCardinality": 1, "description": "indicates that an entity is classified by a given tag" } ] } ] } ``` ### EntityProvider Three entity Providers: - [RqlEntityProvider](src/providers.ts#L682): Provides access to entities through a RQL enpoint. An EntitySchemaProvider can be specified on implementation, for example to cache the Schema in the localStorage. - [EJsonExportEntityProvider](src/providers.ts#L812): Provides access to entities through the ejsonexport view of a RQL endpoint - [CachedEntityProvider](src/providers.ts#L859): Caches the access to entities provided by another provider Each EntityProvider provides the following functions: - **getEntity(eid:number)**: Gets an entity given its eid - **getEntityGivenSchema(eid:number, entitySchema: EntitySchema)**: Gets an entity given its expected entitySchema (CWEType, list of attributes, relations to and from) - **getEntityTitleGivenSchema(eid: number, entitySchema EntitySchema)** : Gets the title for an entity, i.e., the value of the first attribute in the list of the entitySchema attributes. ```javascript import {providers, client} from '@logilab/cwclientlibjs'; // url is the base url of your cubicweb instance const url = 'http://my-cubicweb-instance-base-url/'; const rqlClient = new client.CwRqlClient(new client.CwSimpleHttpClient(url)); // Schema will not be cached const schemaProvider = new providers.RqlEntitySchemaProvider( rqlClient, 'storageKey' ); const rqlEntityProvider = new providers.RqlEntityProvider( rqlClient, schemaProvider ); const eJsonEntityProvider = new providers.EJsonExportEntityProvider(rqlClient); const cachedEntityProvider = new providers.CachedEntityProvider( rqlEntityProvider ); cachedEntityProvider.getEntity(123456).then(entity => { console.log(entity); }); ``` An entity will be a json object, whose keys are defined by its EntitySchema (CWEType) ```json { "@eid": 123456, "@type": "BlogEntry", "title": "Super Blog Post", "entry_of_BlogEntry_Blog": [11111], "tags_Tag_BlogEntry": [765432, 99999], "modification_date": "2010/03/01 18:45:52" } ``` ## Tests The tests relies on an instance with the `test-server` cube to run. The Dockerfile located in `test-server` creates an image with an instance ready for use. Building it is as simple as: ```bash docker build -t cw-test-server . ``` You can then run the instance using `docker run`: ```bash docker run -p 8080:8080 cw-test-server ``` Once the instance is running, launch the tests: ```bash npm run test ``` By default the test instance is running on "http://localhost:8080". If you need to start it on a different host/port, use the `CW_BASE_URL` environment variable. ## Known issues There seems to be a problem with Cookie handling: in the [doRequestFetch function](src/client.ts#L107). The headers of the request seem to be empty for every response. Therefore, the login functionality is broken for the moment as it requires to access the `Set-Cookie` fields of HTTP Responses.