Commit b26a200b authored by Guillaume Perréal's avatar Guillaume Perréal
Browse files

Version intermédiaire.

parent 49d6de7c
No related merge requests found
Showing with 407 additions and 553 deletions
+407 -553
...@@ -150,10 +150,9 @@ final class ModelGenerator ...@@ -150,10 +150,9 @@ final class ModelGenerator
} }
sort($context['repoImports']); sort($context['repoImports']);
$this->generateFile($writer, 'resources.ts', $context); foreach (glob(__DIR__ . '/Resources/views/output/*.ts.twig') as $template) {
$this->generateFile($writer, 'repositories.ts', $context); $this->generateFile($writer, basename($template, '.twig'), $context);
$this->generateFile($writer, 'metadata.ts', $context); }
$this->generateFile($writer, 'index.ts', $context);
} }
/** /**
...@@ -166,7 +165,7 @@ final class ModelGenerator ...@@ -166,7 +165,7 @@ final class ModelGenerator
$fileWriter = $writer->newFile($path); $fileWriter = $writer->newFile($path);
try { try {
$template = sprintf('@NgModelGenerator/%s.twig', basename($path)); $template = sprintf('@NgModelGenerator/output/%s.twig', basename($path));
$context['filename'] = $path; $context['filename'] = $path;
$fileWriter->write($this->twigEnv->render($template, $context)); $fileWriter->write($this->twigEnv->render($template, $context));
} finally { } finally {
......
{% extends '@NgModelGenerator/_layout.ts.twig' %}
{% import '@NgModelGenerator/_macros.ts.twig' as m %}
{% block content %}
{% autoescape false %}
import { Injectable, Provider } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import {
APIMeta,
APIRepositoryRegistry,
AbstractAPIService,
AbstractResourceCache,
IRIMetadata,
LazyMetadataRegistry,
ResourceMetadata,
UUID,
} from '@devatscience/ng-model-runtime';
import {
{% for repo in repositories %}
{{ repo.usage }}{% if not loop.last %},{% endif %}
{%- endfor %}
} from './repositories';
import {
{% for repo in repositories %}
{{ repo.resourceName }}{% if not loop.last %},{% endif %}
{%- endfor %}
} from './resources';
/**
* Interface permettant de faire le lien entre le "@type", la ressource, les metadonnées et son repository.
*/
export interface AppAPI extends APIMeta {
{%- for repo in repositories %}
{{ repo.resourceName | objectKey }}: {
resource: {{ repo.resourceName }};
repository: {{ repo.usage }};
metadata: {{ m.metadataType(repo) }};
iriParameters: {{ m.iriParameterType(repo) }};
};
{% endfor %}
}
/**
* Metadonnées des ressources.
*/
@Injectable()
export class AppMetadata extends LazyMetadataRegistry<AppAPI> {
public constructor() {
super({
{%- for repo in repositories %}
{%- set name = repo.resourceName -%}
{%- set subTypes -%}
{%- for type in repo.atTypes if type.name != name %}this.{{ type.name }}, {% endfor -%}
{%- endset %}
{{ name | objectKey }}: () => new {{ m.metadataType(repo) }}(
{{ name | quoteString }},
new IRIMetadata(
/^{{ repo.iri.testPattern }}$/u,
/^{{ repo.iri.capturePattern }}$/u,
(
{%- if repo.iri.parameters | length > 1 %}[{% endif -%}
{%- for param in repo.iri.parameters -%}
{{ param.name }}{% if not loop.last %}, {% endif -%}
{%- endfor -%}
{%- if repo.iri.parameters | length > 1 %}]{% endif -%}
: {{ m.iriParameterType(repo) }}) => {{ repo.iri.usage }}
),
[ {% if repo.resource.properties is defined -%}
{%- for name, property in repo.resource.properties if not property.nullable %}{{ property.name | quoteString }}, {% endfor -%}
{%- endif %}],
{%- if subTypes %}
[ {{ subTypes }} ]
{%- endif %}
),
{%- endfor %}
});
};
{% for repo in repositories %}
public get {{ repo.resourceName }}(): {{ m.metadataType(repo) }} { return this.getOrCreate({{ repo.resourceName | quoteString }}); }
{% endfor %}
}
/**
* Repositories.
*/
@Injectable()
export class AppRepositories implements APIRepositoryRegistry<AppAPI> {
{% for repo in repositories %}
public readonly {{ repo.resourceName }} = new {{ repo.name }}(this.metadata.{{ repo.resourceName }}, this.client, this.cache);
{% endfor %}
public constructor(
public readonly metadata: AppMetadata,
private readonly cache: AbstractResourceCache,
private readonly client: HttpClient
) {}
public get<T extends keyof AppAPI>(type: T): AppAPI[T]['repository'] {
return (this as any)[type];
}
}
/**
* Le service d'API
*/
@Injectable()
export class AppAPIService extends AbstractAPIService<AppAPI, AppMetadata, AppRepositories> {
public constructor(
metadata: AppMetadata,
repositories: AppRepositories,
cache: AbstractResourceCache,
client: HttpClient
) {
super(metadata, repositories, cache, client);
}
}
/* Provider factories */
{% for repo in repositories %}
export function {{ repo.usage }}Factory(reps: AppRepositories): {{ repo.usage }} { return reps.{{ repo.resourceName }}; }
{% endfor %}
/**
* Providers Angular
*/
export const PROVIDERS: Provider[] = [
AppMetadata,
AppRepositories,
AppAPIService,
{% for repo in repositories %}
{ provide: {{ repo.usage }}, useFactory: {{ repo.usage }}Factory, deps: [AppRepositories] },
{% endfor %}
];
{% endautoescape -%}
{% endblock content %}
{% extends '@NgModelGenerator/_layout.ts.twig' %}
{% block content %}
export * from '@devatscience/ng-model-runtime';
{% endblock content %}
{% extends '@NgModelGenerator/_layout.ts.twig' %}
{% import '@NgModelGenerator/_macros.ts.twig' as m %}
{% block content %}
{% autoescape false %}
import { Injectable, Injector } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import {
AbstractResourceCache,
Collection,
GetIRIParameters,
GetResourceType,
GetResourceTypeString,
IRI,
IRIMetadata,
RequestOptions,
Resource,
ResourceMetadata,
UUID,
} from '@devatscience/ng-model-runtime';
import { Observable, forkJoin } from 'rxjs';
import {
{% for repo in repositories %}
{{ repo.usage }}{% if not loop.last %},{% endif %}
{%- endfor %}
} from './repositories';
import {
{% for repo in repositories %}
{{ repo.resourceName }}{% if not loop.last %},{% endif %}
{%- endfor %}
} from './resources';
/**
* Catalogue des repositories.
*/
@Injectable({providedIn: 'root'})
export class AppRepositories {
public constructor(private readonly injector: Injector) {}
{% for repo in repositories %}
public get {{ repo.resourceName }}() { return this.injector.get({{ repo.usage }}); }
{% endfor %}
}
/**
* Facade de l'API.
*/
@Injectable({providedIn: 'root'})
export class AppAPIService {
public constructor(
public readonly repositories: AppRepositories,
private readonly cache: AbstractResourceCache,
private readonly client: HttpClient,
) {
}
public get<R extends AppRepositories[keyof AppRepositories]['get']>>(
iri: IRI<R>,
options?: RequestOptions,
): Observable<R>;
public get<T extends keyof AppMetadata>(
type: T,
parameters: GetIRIParameters<AppMetadata[T]>,
options?: RequestOptions,
): Observable<GetResourceType<AppMetadata[T]>>;
public get<T extends keyof AppMetadata, R extends Resource = GetResourceType<AppMetadata[T]>>(
typeOrIRI: T | IRI<R>,
parametersOrOptions?: GetIRIParameters<AppMetadata[T]> | RequestOptions,
options?: RequestOptions,
): Observable<R> {
let iri: IRI<R>;
if (this.metadata[typeOrIRI as string]) {
iri = this.metadata[typeOrIRI as string]
.generateIRI(parametersOrOptions as GetIRIParameters<AppMetadata[T]>);
} 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 AppMetadata>(
type: T,
parameters: GetIRIParameters<AppMetadata[T]>,
): IRI<GetResourceType<AppMetadata[T]>> {
return this.metadata[type].generateIRI(parameters);
}
public invalidate<R extends Resource>(iri: IRI<R>): void {
this.cache.invalidate(iri);
}
}
{% endautoescape %}
{% endblock content %}
{% extends '@NgModelGenerator/_layout.ts.twig' %} {% extends '@NgModelGenerator/_layout.ts.twig' %}
{% block content %} {% block content %}
export * from '@devatscience/ng-model-runtime'; export * from './compat';
export * from './resources';
export * from './repositories';
export * from './metadata'; export * from './metadata';
export * from './providers';
export * from './repositories';
export * from './resources';
{% endblock content %} {% endblock content %}
...@@ -5,16 +5,20 @@ ...@@ -5,16 +5,20 @@
{% block content %} {% block content %}
{% autoescape false %} {% autoescape false %}
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { HttpResponseBase } from '@angular/common/http'; import { Injectable } from "@angular/core";
import { import {
AbstractRepository, AbstractRepository,
Collection, Collection,
IRI, IRI,
IRIMetadata,
RequestOptions, RequestOptions,
ResourceMetadata,
UUID, UUID,
hasProperty hasProperty,
Client,
InvalidIRIError
} from '@devatscience/ng-model-runtime'; } from '@devatscience/ng-model-runtime';
import { {% for name in repoImports %} import { {% for name in repoImports %}
{{ name }}{% if not loop.last %}, {% endif %} {{ name }}{% if not loop.last %}, {% endif %}
{%- endfor %} {%- endfor %}
...@@ -24,7 +28,50 @@ import { {% for name in repoImports %} ...@@ -24,7 +28,50 @@ import { {% for name in repoImports %}
* Repositories * Repositories
*********************************************************************************/ *********************************************************************************/
{% for repo in repositories %} {% for repo in repositories %}
export class {{ repo.name }} extends AbstractRepository<{{ repo.resource.usage }}, {{ repo.resourceName | quoteString }}, {{ m.iriParameterType(repo) }}> { @Injectable({
providedIn: "root",
deps: [Client],
useFactory: (client: Client) => new {{ repo.name }}(client)
})
export class {{ repo.name }} extends AbstractRepository<
{{- repo.resourceName }}, '{{ repo.resourceName }}', {{ m.iriParameterType(repo) -}}
> {
public constructor(protected readonly client: Client) {
super(
'{{ repo.resourceName }}',
[
{% if repo.resource.properties is defined -%}
{%- for name, property in repo.resource.properties -%}
{% if not property.nullable -%}
{{ property.name | quoteString }},
{% endif -%}
{% endfor -%}
{% endif -%}
],
/^{{ repo.iri.capturePattern }}$/u
);
}
public generateIRI(
{%- for param in repo.iri.parameters -%}
{{ param.name }}: {{ param.type.usage }}{% if not loop.last %}, {% endif -%}
{%- endfor -%}
): IRI<{{ repo.resourceName }}> {
return {{ repo.iri.usage }};
}
public getIRIParameters(iri: IRI<{{ repo.name }}>): {{ m.iriParameterType(repo) }} {
const captures = this.iriPattern.exec(iri);
if (!captures) {
throw new InvalidIRIError(`invalid {{ repo.resourceName }} URI: "${iri}"`);
}
{% if repo.iri.parameters | length > 1 -%}
return captures.slice(1);
{%- else %}
return captures[1];
{%- endif %}
}
{% for op in repo.operations %} {% for op in repo.operations %}
{% if op.description -%} {% if op.description -%}
......
import { forkJoin, Observable } from 'rxjs';
import { HttpClient, HttpResponseBase } from '@angular/common/http';
import { IRI, Resource, Collection, RequestOptions } from './types';
import { IRIParameters, ResourceMetadata } from './metadata';
/**
* 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;
}
/**
* 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] as API[T]['metadata'];
}
}
/**
* 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);
}
}
import { Injectable } from '@angular/core';
import {
HttpClient as NgHttpClient,
HttpHeaders,
HttpResponse,
} from '@angular/common/http';
import { Observable } from 'rxjs';
import { take } from 'rxjs/operators';
import { Collection, IRI, Resource } from './types';
export interface RequestOptions {
headers?:
| HttpHeaders
| {
[header: string]: string | string[];
};
params?: { [param: string]: string | string[] };
}
export interface BodyRequestOptions<B = any> extends RequestOptions {
body?: B;
}
export abstract class Client {
public abstract getItem<R extends Resource>(
iri: IRI<R>,
options: RequestOptions,
): Observable<HttpResponse<R>>;
public abstract putItem<R extends Resource, B = R>(
iri: IRI<R>,
body: B,
options: BodyRequestOptions<B>,
): Observable<HttpResponse<R>>;
public abstract postItem<R extends Resource, B = R>(
iri: IRI<R>,
body: B,
options: BodyRequestOptions<B>,
): Observable<HttpResponse<R>>;
public abstract deleteItem<R extends Resource>(
iri: IRI<R>,
options: RequestOptions,
): Observable<HttpResponse<void>>;
public abstract getCollection<R extends Resource>(
iri: string,
options: RequestOptions,
): Observable<HttpResponse<Collection<R>>>;
public abstract purgeItem<R extends Resource>(iri: IRI<R>): void;
public abstract purgeCollection(iri: string): void;
public abstract request<T, B = any>(
method: string,
url: string,
body?: B,
options?: BodyRequestOptions<B>,
): Observable<HttpResponse<T>>;
}
@Injectable({
providedIn: 'root',
deps: [NgHttpClient],
useFactory: (httpClient: NgHttpClient) => new HttpClient(httpClient),
})
export class HttpClient extends Client {
public constructor(private readonly httpClient: NgHttpClient) {
super();
}
public deleteItem<R extends Resource>(
iri: IRI<R>,
options: RequestOptions,
): Observable<HttpResponse<void>> {
return this.request('DELETE', iri.toString(), undefined, options);
}
public getCollection<R extends Resource>(
iri: string,
options: RequestOptions,
): Observable<HttpResponse<Collection<R>>> {
return this.request('GET', iri, undefined, options);
}
public getItem<R extends Resource>(
iri: IRI<R>,
options: RequestOptions,
): Observable<HttpResponse<R>> {
return this.request('GET', iri.toString(), undefined, options);
}
public postItem<R extends Resource, B = R>(
iri: IRI<R>,
body: B,
options: BodyRequestOptions<B>,
): Observable<HttpResponse<R>> {
return this.request('POST', iri.toString(), body, options);
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
public purgeCollection(iri: string): void {
// NOOP
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
public purgeItem<R extends Resource>(iri: IRI<R>): void {
// NOOP
}
public putItem<R extends Resource, B = R>(
iri: IRI<R>,
body: B,
options: BodyRequestOptions<B>,
): Observable<HttpResponse<R>> {
return this.request('PUT', iri.toString(), body, options);
}
public request<T, B = any>(
method: string,
uri: string,
body?: B,
options: BodyRequestOptions<B> = {},
): Observable<HttpResponse<T>> {
return this.httpClient
.request<T>(method, uri, {
...options,
body: options.body || body,
reportProgress: false,
observe: 'response',
responseType: 'json',
})
.pipe(take(1));
}
}
export * from './api';
export * from './cache'; export * from './cache';
export * from './client';
export * from './helpers'; export * from './helpers';
export * from './metadata';
export * from './types'; export * from './types';
/**
* Type des paramètrès
*/
import { IRI, Resource, TYPE_PROPERTY } from './types';
type IRIParameter = string | number;
export type IRIParameters = IRIParameter[] | IRIParameter;
/**
* 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 hasTypeProperty(that: unknown): that is { [TYPE_PROPERTY]: unknown } {
return typeof that === 'object' && that !== null && TYPE_PROPERTY in that;
}
function getResourceType(that: unknown): string | null {
if (hasTypeProperty(that)) {
const type = that[TYPE_PROPERTY];
if (typeof type === 'string') {
return type;
}
}
return 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, any>[] = [],
) {
this.types.unshift(this);
}
/**
* Vérifie si l'argument représente une ressource R ou dérivée.
*/
public isResource(that: unknown): 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);
}
}
import { IRI_PROPERTY } from './index'; import { IRI_PROPERTY } from './index';
export class InvalidIRIError extends Error {}
export class APICacheError extends Error {} export class APICacheError extends Error {}
export class MissingIRIError extends APICacheError { export class MissingIRIError extends APICacheError {
......
export * from './collection'; export * from './collection';
export * from './errors'; export * from './errors';
export * from './misc'; export * from './misc';
export * from './request';
export * from './resource'; export * from './resource';
export * from './repository';
import { isDateTime } from './misc'; import { isDateTime } from './misc';
import cases from 'jest-in-case';
describe('isDateTime', () => { describe('isDateTime', () => {
const itShouldRejectType = (value: unknown) => const itShouldRejectType = (value: unknown) =>
......
import { IRI, Resource } from './resource';
type IRIParameters = string | string[];
export interface Repository<
R extends Resource,
T extends string,
P extends IRIParameters
> {
readonly resourceType: T;
readonly requiredProperties: ReadonlyArray<string>;
readonly iriPattern: RegExp;
isRequired(propertyName: string): boolean;
generateIRI(parameters: P): IRI<R>;
getIRIParameters(iri: IRI<R>): P;
}
export type ResourceType<X> = X extends Repository<infer R, any, any>
? R
: never;
export type ResourceTypeString<X> = X extends Repository<any, infer T, any>
? T
: never;
export type IRIParameterType<X> = X extends Repository<any, any, infer P>
? P
: never;
export abstract class AbstractRepository<
R extends Resource,
T extends string,
P extends IRIParameters
> implements Repository<R, T, P> {
protected constructor(
public readonly resourceType: T,
public readonly requiredProperties: ReadonlyArray<string>,
public readonly iriPattern: RegExp,
) {}
public isRequired(propertyName: string): boolean {
return this.requiredProperties.indexOf(propertyName) >= 0;
}
public abstract generateIRI(parameters: P): IRI<R>;
public abstract getIRIParameters(iri: IRI<R>): P;
}
import { HttpHeaders } from '@angular/common/http';
/**
* Options supplémentaires pour les requêtes.
*/
export interface RequestOptions {
body?: any;
headers?:
| HttpHeaders
| {
[header: string]: string | string[];
};
params?: { [param: string]: string | string[] };
}
Supports Markdown
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