diff --git a/package.json b/package.json index cbe80b8ddbf2f6daf9a1cf9750c655ea3e9da6fb..7fc37dc50103789b026ecfd4e5bc315c0fa1e813 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,5 @@ { "name": "irstea-ng-model", - "version": "1.0.0", "description": "Runtime libray for the composer package irstea/ng-model-generator-bundle.", "types": "dist/index.d.ts", "main": "dist/index.js", diff --git a/src/php/Resources/views/common.ts.twig b/src/php/Resources/views/common.ts.twig deleted file mode 100644 index cb466fa681f649859c2a5a1857110612cda7b04c..0000000000000000000000000000000000000000 --- a/src/php/Resources/views/common.ts.twig +++ /dev/null @@ -1,417 +0,0 @@ -{% extends '@NgModelGenerator/_layout.ts.twig' %} - -{% block content %} -import {HttpClient, HttpHeaders, HttpResponseBase} from '@angular/common/http'; -import { forkJoin, Observable } from 'rxjs'; - -/** - * Nom de la propriété d'une resource contenant son IRI. - */ -export const IRI_PROPERTY = '@id'; - -/** - * Nom de la propriété d'une resource contenant son type. - */ -export const TYPE_PROPERTY = '@type'; - -/** - * Nom de la propriété contenant les resource d'une collection. - */ -export const COLLECTION_MEMBERS = 'hydra:member'; - -/** - * Nom de la propriété contenant le nombre total d'objet d'une collection. - */ -export const COLLECTION_TOTAL_COUNT = 'hydra:totalItems'; - -/** - * IRI typé. - * - * Internationalized Resource Identifier - RFC 3987 - */ -const IRI = Symbol('IRI'); -/* Les IRI sont en fait des chaînes mais pour forcer un typage fort on les définit - * comme un type "opaque". De cette façon, il est impossible de mélanger - * IRI et chaînes, et le type générique R permet d'interdire les assignations entre - * IRI de resources différentes. - */ -export interface IRI<R extends Resource> { - readonly [IRI]?: R; -} - -/** - * Resource - */ -export interface Resource { - readonly [IRI_PROPERTY]: IRI<any>; - readonly [TYPE_PROPERTY]: string; - [property: string]: any; -}; - -/** - * Collection représente une collection de respoucres JSON-LD pour un type T donné. - */ -export interface Collection<R extends Resource> { - [COLLECTION_MEMBERS]: R[]; - [COLLECTION_TOTAL_COUNT]: number; - [property: string]: any; -} - -/** - * Retourne les membres d'une collection. - */ -export function getCollectionMembers<R extends Resource>(collection: Collection<R>): R[] { - return collection[COLLECTION_MEMBERS]; -} - -/** - * Retourne le nombre total d'items - * @param collection - */ -export function getCollectionTotalCount<R extends Resource>(collection: Collection<R>): number { - return collection[COLLECTION_TOTAL_COUNT]; -} - -/** - * Universally Unique IDentifier - RFC 4122 - */ -export type UUID = string; - -/** - * Teste si une donnée (chaîne) est formatée selon un UUID. - */ -export function isUUID(data: any): data is UUID { - return typeof data === 'string' && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu.test(data); -} -/** - * Full DateTime in ISO-8601 format. - */ -export type DateTime = string; - -/** - * Options supplémentaires pour les requêtes. - */ -export interface RequestOptions { - body?: any; - headers?: HttpHeaders | { - [header: string]: string | string[]; - }; - params?: { [param: string]: string | string[]; }; -} - -/** - * AbstractResourceCache est une classe abstraite qui définit l'interface d'un cache de ressources. - * - * Elle doit être implémentée puis être fournie en provider d'un module core. Par exemple: - * - * final class ResourceCache<T extends Resource> extends AbstractResourceCache<T> { - * // Implémentation - * } - * - * providers: [ - * [ provider: AbstractResourceCache, useClass: ResourceCache ], - * ] - */ -export abstract class AbstractResourceCache { - /** - * Récupère une ressource par son IRI. N'exécute la requête requestFactory que si on ne dispose - * pas d'une version en cache. - */ - public abstract get<R extends Resource>( - iri: IRI<R>, - requestFactory: () => Observable<R> - ): Observable<R>; - - /** - * Met à jour une ressource existante, rafraîchit le cache local avec la réponse. - */ - public abstract put<R extends Resource>(iri: IRI<R>, request: Observable<R>): Observable<R>; - - /** - * Crée une nouvelle ressource et met la ressource créée dans le cache. - */ - public abstract post<R extends Resource>(request: Observable<R>): Observable<R>; - - /** - * Supprime une ressource en distant et dans le cache. - */ - public abstract delete<R extends Resource>(iri: IRI<R>, request: Observable<HttpResponseBase>): Observable<HttpResponseBase>; - - /** - * Effectue une recherche et met en cache toutes les ressources récupérées. - */ - public abstract getAll<R extends Resource>( - request: Observable<Collection<R>> - ): Observable<Collection<R>>; - - /** - * Invalide une ressource en cache. - */ - public abstract invalidate<R extends Resource>(iri: IRI<R>): void; -} - -/** - * Type des paramètrès - */ -export type IRIParameters = string[] | string; - -/** - * Informations sur l'IRI d'une resource. - * - * P: types des paramètres. - */ -export class IRIMetadata<P extends IRIParameters> { - public constructor( - private readonly testPattern: RegExp, - private readonly capturePattern: RegExp, - private readonly template: (parameters: P) => string - ) {} - - public validate(path: string): boolean { - return this.testPattern.test(path); - } - - public generate(parameters: P): IRI<any> { - return this.template(parameters) as any; - } - - public parse(path: string): P { - const matches = this.capturePattern.exec(path); - if (!matches) { - throw new Error(`Invalid path: ${path} does not match ${this.capturePattern}`); - } - if (matches.length == 2) { - return matches[1] as any; - } - return matches.slice(1) as any; - } -} - -function getResourceType(that: any): string | null { - return ( - (that !== null && typeof that === 'object' && typeof that[TYPE_PROPERTY] === 'string' && that[TYPE_PROPERTY]) || - null - ); -} - -/** - * Metadonnées d'une ressource. - * - * R : type de resource, e.g. Person - * T : valeur de la propriété '@type', e.g. 'Person'. - */ -export class ResourceMetadata<R extends Resource, T extends string, P extends IRIParameters> { - public constructor( - public readonly type: T, - public readonly iri: IRIMetadata<P>, - private readonly requiredProperties: (keyof R)[], - private readonly types: ResourceMetadata<any, any, P>[] = [] - ) { - this.types.unshift(this); - } - - /** - * Vérifie si l'argument représente une ressource R ou dérivée. - */ - public isResource(that: any): that is R { - const type = getResourceType(that); - if (!type) { - return false; - } - return this.types.some(t => t.type === type); - } - - /** - * Vérifie si une propriété est obligatoire dans la resource R. - */ - public isRequired(property: keyof R): boolean { - return this.requiredProperties.includes(property); - } - - /** - * Génère une IRI à partir de ses paramètres. - */ - public generateIRI(parameters: P): IRI<R> { - return this.iri.generate(parameters); - } - - /** - * Extrait les paramètres d'une IRI. - */ - public getIRIParameters(iri: IRI<R>): P { - return this.iri.parse(iri as any); - } -} - -/** - * Classe de base d'un repository. - */ -export abstract class AbstractRepository<R extends Resource, T extends string, P extends IRIParameters> { - public constructor( - public readonly metadata: ResourceMetadata<R, T, P>, - protected readonly client: HttpClient, - protected readonly cache: AbstractResourceCache - ) {} - - /** - * Génère une IRI à partir de ses paramètres. - */ - public generateIRI(parameters: P): IRI<R> { - return this.metadata.generateIRI(parameters); - } - - /** - * Extrait les paramètres d'une IRI. - */ - public getIRIParameters(iri: IRI<R>): P { - return this.metadata.getIRIParameters(iri); - } -} - -/** - * Sur-type encadrant des API. - */ -export interface APIMeta { - [type: string]: { - resource: Resource; - metadata: ResourceMetadata<any, any, IRIParameters>; - repository: AbstractRepository<any, any, IRIParameters>; - iriParameters: IRIParameters; - }; -} - -/** - * Classe abstraite d'un registre des metadonnées des ressources. - */ -export interface APIMetadataRegistry<API extends APIMeta> { - /** - * Vérifie si on a des métadonnées pour le type de ressourcce indiqué. - */ - has<T extends keyof API>(type: T): type is T; - - /** - * (Construit et) retourne l'instance de métadonnées pour le type de resource 'type'. - */ - get<T extends keyof API>(type: T): API[T]['metadata']; -} - -/** - * Registre de métadonnées qui construit les instances à la demande (ce qui permet de gérer les - * dépendances entre métadonnées). - */ -export class LazyMetadataRegistry<API extends APIMeta> implements APIMetadataRegistry<API> { - private readonly instances: { [T in keyof API]?: API[T]['metadata'] } = {}; - - protected constructor( - private readonly builders: { readonly [T in keyof API]: (r?: APIMetadataRegistry<API>) => API[T]['metadata'] } - ) {} - - public has<T extends keyof API>(type: T): type is T { - return typeof type === 'string' && type in this.builders; - } - - public get<T extends keyof API>(type: T): API[T]['metadata'] { - if (!this.has(type)) { - throw new Error(`Invalid resource type: ${type}`); - } - return this.getOrCreate(type); - } - - protected getOrCreate<T extends keyof API>(type: T): API[T]['metadata'] { - if (!(type in this.instances)) { - this.instances[type] = this.builders[type](this); - } - return this.instances[type]; - } -} - -/** - * Classe abstraite de la façade de l'API. - */ -export interface APIRepositoryRegistry<API extends APIMeta> { - get<T extends keyof API>(type: T): API[T]['repository']; -} - -/** - * Service permettant d'accéder à l'API. - */ -export interface APIService<API extends APIMeta> { - /** - * Métadonnées de l'API. - */ - readonly metadata: APIMetadataRegistry<API>; - - /** - * Repositories de l'API - */ - readonly repositories: APIRepositoryRegistry<API>; - - /** - * Récupère une ressource par son IRI ou par son type et les paramètres de son IRI. - */ - get<R extends Resource>(iri: IRI<R>, options?: RequestOptions): Observable<R> ; - get<T extends keyof API>(type: T, parameters: API[T]['iriParameters'], options?: RequestOptions): Observable<API[T]['resource']> ; - - /** - * Récupère des ressources par leurs IRIs. - */ - getMany<R extends Resource>(iris: IRI<R>[], options?: RequestOptions): Observable<R[]>; - - /** - * Génère l'IRI d'une resource à partir de son type et des paramètres d'IRI. - */ - generateIRI<T extends keyof API, P extends string[]>(type: T, parameters: P): IRI<any>; - - /** - * Invalide le cache pour une IRI. - */ - invalidate<R extends Resource>(iri: IRI<R>): void; -} - -/** - * Implémentation de base d'une api - */ -export abstract class AbstractAPIService< - API extends APIMeta, - MR extends APIMetadataRegistry<API>, - RR extends APIRepositoryRegistry<API> -> implements APIService<API> { - public constructor( - public readonly metadata: MR, - public readonly repositories: RR, - private readonly cache: AbstractResourceCache, - private readonly client: HttpClient - ) {} - - public get<R extends Resource>(iri: IRI<R>, options?: RequestOptions): Observable<R>; - public get<T extends keyof API>(type: T, parameters: API[T]['iriParameters'], options?: RequestOptions): Observable<API[T]['resource']>; - - public get<T extends keyof API, R extends Resource = API[T]['resource']>( - typeOrIRI: T | IRI<R>, - parametersOrOptions?: API[T]['iriParameters'] | RequestOptions, - options?: RequestOptions - ): Observable<R> { - let iri: IRI<R>; - if (this.metadata.has(typeOrIRI as string)) { - iri = this.metadata.get(typeOrIRI as string).generateIRI(parametersOrOptions as API[T]['iriParameters']); - } else { - iri = typeOrIRI as IRI<R>; - options = parametersOrOptions as RequestOptions; - } - return this.cache.get(iri, () => this.client.get<R>(iri as any, options)); - } - - public getMany<R extends Resource>(iris: IRI<R>[], options?: RequestOptions): Observable<R[]> { - return forkJoin(iris.map(iri => this.get(iri, options))); - } - - public generateIRI<T extends keyof API>(type: T, parameters: API[T]['iriParameters']): IRI<API[T]['resource']> { - return this.metadata.get(type).generateIRI(parameters); - } - - public invalidate<R extends Resource>(iri: IRI<R>): void { - this.cache.invalidate(iri); - } -} - -{% endblock %} diff --git a/src/php/Resources/views/index.ts.twig b/src/php/Resources/views/index.ts.twig index 7940dacb54a4364d6f9a87267cece08a0d866763..2aabe9facaaabff836bd27f92c7718b81d99fe46 100644 --- a/src/php/Resources/views/index.ts.twig +++ b/src/php/Resources/views/index.ts.twig @@ -1,7 +1,6 @@ {% extends '@NgModelGenerator/_layout.ts.twig' %} {% block content %} -export * from './common'; export * from './resources'; export * from './repositories'; export * from './metadata'; diff --git a/src/php/Resources/views/resources.ts.twig b/src/php/Resources/views/resources.ts.twig index a60203d1d252fb8a41a38d9f97d7b254758eb473..baa8c14deeb31a9ea2b28d517d92756dc031d0c4 100644 --- a/src/php/Resources/views/resources.ts.twig +++ b/src/php/Resources/views/resources.ts.twig @@ -3,7 +3,7 @@ {% block content %} {% autoescape false %} // @ts-ignore -import { DateTime, IRI, UUID } from './common'; +import { DateTime, IRI, UUID } from 'irstea-ng-model'; /********************************************************************************* * Ressources