Commit 2ba2587e authored by Laurent Wouters's avatar Laurent Wouters
Browse files

[client] Refactored usage of schema hash for caching

parent 992246fe7c80
Pipeline #16305 failed with stages
in 4 minutes and 28 seconds
......@@ -3,37 +3,9 @@
contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
*/
import {
Schema,
EntitySchema,
getFromCardinality,
getToCardinality,
EntityAttributeSchema,
EntityRelationSchema,
} from './providers';
require('isomorphic-fetch');
import CryptoJS = require('crypto-js');
export function applySchema(jsonEntity: any): EntitySchema {
const adaptAttribute = (attr: any): EntityAttributeSchema => {
return {...attr, cardinality: getToCardinality(attr.cardinality)};
};
const adaptRelation = (relation: any): EntityRelationSchema => {
return {
...relation,
fromCardinality: getFromCardinality(relation.fromCardinality),
toCardinality: getToCardinality(relation.toCardinality),
};
};
return {
...jsonEntity,
attributes: jsonEntity.attributes.map(adaptAttribute),
relationsTo: jsonEntity.relationsTo.map(adaptRelation),
relationsFrom: jsonEntity.relationsFrom.map(adaptRelation),
};
}
/**
* Gets the passed URL ensuring it ends with a slash
* @param url Th input URL
......@@ -186,11 +158,17 @@ function doRequestFetch(
throw myResponse;
}
};
if (contentType === 'application/json') {
return response.json().then(toHttpResponse);
} else {
return response.text().then(toHttpResponse);
}
return response.text().then(content => {
if (
contentType === 'application/json' &&
content !== null &&
content.length > 0
) {
return toHttpResponse(JSON.parse(content));
} else {
return toHttpResponse(content);
}
});
});
}
......@@ -348,7 +326,7 @@ function getOptionsWithDefault(
): HttpRequestOptions {
return {
method: options.method ?? 'GET',
content: options.content ?? '',
content: options.content ?? null,
contentType: options.contentType ?? 'application/json',
accept: options.accept ?? 'application/json',
headers: new Headers(),
......@@ -684,6 +662,10 @@ export function rqlIoV2asSolutions(
* A client able to execute RQL queries
*/
export interface RqlClient {
/**
* The underlying HTTP client
*/
readonly httpClient: HttpClient;
/**
* Executes a RQL query
* @param query The RQL query
......@@ -712,15 +694,6 @@ export interface RqlClient {
params: {[key: string]: any},
viewId: string
): Promise<any>;
/**
* Fetch the cubicweb schema from a server supporting cubicweb_rqlcontroller
*/
getSchema(): Promise<Schema>;
/**
* Fetch the cubicweb schema hash from a server supporting cubicweb_rqlcontroller
*/
getSchemaHash(): Promise<string | null>;
/**
* Executes RQL queries in a transaction through RQL/IO v1.0
* @param queries The queries
......@@ -776,26 +749,6 @@ export class CwRqlClient implements RqlClient {
});
}
public getSchema(): Promise<Schema> {
return this.httpClient.request('rqlio/schema/', {}).then(response => {
return {
metadata: {
timestamp: new Date(),
},
entities: response.content['entities'].map(applySchema),
};
});
}
public getSchemaHash(): Promise<string | null> {
return this.httpClient
.request('rqlio/schema/', {
method: 'HEAD',
responseHeaders: ['Etag'],
})
.then(response => response.headers.get('Etag'));
}
public queryAndTransform(
query: string,
params: {[key: string]: any},
......
......@@ -3,7 +3,7 @@
contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
*/
import {RqlClient, RqlRow, RqlQuery} from './client';
import {RqlClient, RqlRow, RqlQuery, HttpClient} from './client';
/**
* A complete schema
......@@ -251,7 +251,7 @@ function undefinedToNull<X>(x: X | undefined): X | null {
*/
export interface EntitySchemasLoader {
load(): Promise<Schema>;
getSchemaHash(): Promise<string | null>;
getSchemaHash(): Promise<string>;
}
/**
......@@ -286,10 +286,6 @@ export class LocalStorageEntitySchemasLoader implements EntitySchemasLoader {
* @param isCacheUsable A validation function to check whether the cached schema is still usable
* @param onRetrieved A processing function to be applied on a schema that has been retrieved and is going to be stored in cache
*/
/**
* Promise used to fetch the most recent schema hash from the server
*/
public readonly getSchemaHash: () => Promise<string | null>;
constructor(
inner: EntitySchemasLoader,
storageKey: string,
......@@ -335,48 +331,97 @@ export class LocalStorageEntitySchemasLoader implements EntitySchemasLoader {
});
}
public getSchemaHash(): Promise<string> {
return this.inner.getSchemaHash();
}
/**
* Loads from the inner loader
*/
private loadFromInner(): Promise<Schema> {
return Promise.all([
this.inner.load().then(this.onRetrieved),
this.inner.getSchemaHash(),
]).then(([schema, hash]) => {
if (window !== undefined && hash !== null) {
window.localStorage.setItem(
this.storageKey,
JSON.stringify(schema)
);
window.localStorage.setItem(this.hashStorageKey, hash);
}
return schema;
});
return this.inner
.load()
.then(this.onRetrieved)
.then(schema => {
if (window !== undefined) {
window.localStorage.setItem(
this.storageKey,
JSON.stringify(schema)
);
window.localStorage.setItem(
this.hashStorageKey,
schema.metadata.hash
);
}
return schema!;
});
}
}
/**
* A loader that performs RQL queries to retrieve the schemas
*/
export class RqlEntitySchemasLoader implements EntitySchemasLoader {
export class HttpEntitySchemasLoader implements EntitySchemasLoader {
/**
* The RQL client to use
* The client to use
*/
public readonly rqlClient: RqlClient;
public readonly client: HttpClient;
constructor(rqlClient: RqlClient) {
this.rqlClient = rqlClient;
constructor(client: HttpClient) {
this.client = client;
}
/**
* Loads all entity schemas
*/
public load(): Promise<Schema> {
return this.rqlClient.getSchema();
return this.client
.request('rqlio/schema/', {
responseHeaders: ['Etag'],
})
.then(response => {
return {
metadata: {
timestamp: new Date(),
hash: response.headers.get('Etag'),
},
entities: response.content['entities'].map(
this.applySchema
),
};
});
}
public getSchemaHash(): Promise<string | null> {
return this.rqlClient.getSchemaHash();
public getSchemaHash(): Promise<string> {
return this.client
.request('rqlio/schema/', {
method: 'HEAD',
responseHeaders: ['Etag'],
})
.then(response => response.headers.get('Etag')!);
}
/**
* Transforms the retrieve schema in JSON form into an entity schema
* @param jsonEntity The retrieved schema
*/
private applySchema(jsonEntity: any): EntitySchema {
const adaptAttribute = (attr: any): EntityAttributeSchema => {
return {...attr, cardinality: getToCardinality(attr.cardinality)};
};
const adaptRelation = (relation: any): EntityRelationSchema => {
return {
...relation,
fromCardinality: getFromCardinality(relation.fromCardinality),
toCardinality: getToCardinality(relation.toCardinality),
};
};
return {
...jsonEntity,
attributes: jsonEntity.attributes.map(adaptAttribute),
relationsTo: jsonEntity.relationsTo.map(adaptRelation),
relationsFrom: jsonEntity.relationsFrom.map(adaptRelation),
};
}
}
......@@ -413,6 +458,10 @@ export class LoadableEntitySchemaProvider implements EntitySchemaProvider {
this.isLoaded = false;
}
public getSchemaHash(): Promise<string> {
return this.loader.getSchemaHash();
}
/**
* Loads the content of the
*/
......@@ -511,22 +560,11 @@ export class RqlEntitySchemaProvider extends LoadableEntitySchemaProvider {
*/
constructor(
rqlClient: RqlClient,
storageKey?: string,
hashStorageKey: string | null = null,
isCacheUsable: (schema: Schema) => Promise<boolean> = _ =>
new Promise((resolve, _) => resolve(true)),
onRetrieved: (schema: Schema) => Promise<Schema> = schema =>
new Promise((resolve, _) => resolve(schema))
loader: EntitySchemasLoader = new HttpEntitySchemasLoader(
rqlClient.httpClient
)
) {
super(
storageKey === undefined
? new RqlEntitySchemasLoader(rqlClient)
: new LocalStorageEntitySchemasLoader(
new RqlEntitySchemasLoader(rqlClient),
storageKey,
hashStorageKey || null
)
);
super(loader);
this.rqlClient = rqlClient;
}
......@@ -544,10 +582,6 @@ export class RqlEntitySchemaProvider extends LoadableEntitySchemaProvider {
});
});
}
public getSchemaHash(): Promise<string | null> {
return this.rqlClient.getSchemaHash();
}
}
/**
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment