Skip to content

Commit b519667

Browse files
committed
feat(config-files): add config files server cache interceptor
1 parent f680695 commit b519667

4 files changed

Lines changed: 86 additions & 53 deletions

File tree

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { HttpInterceptorFn } from '@angular/common/http';
2+
import { HTTP_SERVER_CACHE_KEY } from '@angularity/core/http';
3+
4+
import { CONFIG_FILE_REQUEST_CONTEXT } from './loader.http';
5+
6+
export const httpServerCacheConfigFilesInterceptor: HttpInterceptorFn = (
7+
req,
8+
next,
9+
) => {
10+
const context = req.context.get(CONFIG_FILE_REQUEST_CONTEXT);
11+
if (!context) return next(req);
12+
if (!context.def.id) return next(req);
13+
const key = `config-files:${context.def.id}`;
14+
req = req.clone({ context: req.context.set(HTTP_SERVER_CACHE_KEY, key) });
15+
return next(req);
16+
};
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import {
2+
HttpClient,
3+
HttpContext,
4+
HttpContextToken,
5+
} from '@angular/common/http';
6+
import { inject, Injectable, Injector } from '@angular/core';
7+
import { pendingUntilEvent } from '@angular/core/rxjs-interop';
8+
import { catchError, map, Observable, switchMap } from 'rxjs';
9+
10+
import { ConfigFileDefinition } from './definition';
11+
import { ConfigFileLoader, ConfigFileNotFoundException } from './loader';
12+
13+
/**
14+
* Context object that will be supplied to `CONFIG_FILE_REQUEST_CONTEXT`.
15+
*/
16+
export interface ConfigFileRequestContext {
17+
readonly def: ConfigFileDefinition<unknown, unknown>;
18+
}
19+
20+
/**
21+
* `HttpContextToken` that will be supplied a value in an endpoint request
22+
* sent by `HttpClientConfigFileLoader`.
23+
*/
24+
export const CONFIG_FILE_REQUEST_CONTEXT =
25+
new HttpContextToken<ConfigFileRequestContext | null>(() => null);
26+
27+
/**
28+
* Implementation of {@link ConfigFileLoader}
29+
* based on Angular's built-in {@link HttpClient}.
30+
*/
31+
@Injectable({
32+
providedIn: 'root',
33+
})
34+
export class HttpClientConfigFileLoader implements ConfigFileLoader {
35+
protected httpClient = inject(HttpClient);
36+
protected injector = inject(Injector);
37+
38+
load<T, Schema>(def: ConfigFileDefinition<T, Schema>): Observable<T> {
39+
const parser = this.injector.get(def.parser);
40+
const validator = this.injector.get(def.validator);
41+
42+
const context = new HttpContext().set(CONFIG_FILE_REQUEST_CONTEXT, { def });
43+
return this.fetch(def.path, context).pipe(
44+
map((res) => {
45+
if (!res) throw new ConfigFileNotFoundException(def.path);
46+
return res;
47+
}),
48+
switchMap(async (raw) => {
49+
const parsed = await parser.parse(raw);
50+
await validator.validate(def.schema, parsed);
51+
return parsed as T;
52+
}),
53+
pendingUntilEvent(this.injector),
54+
);
55+
}
56+
57+
protected fetch(
58+
path: string,
59+
context: HttpContext,
60+
): Observable<string | null> {
61+
return this.httpClient
62+
.get(path, { responseType: 'text', context })
63+
.pipe(catchError(() => [null]));
64+
}
65+
}
Lines changed: 3 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
1-
import {
2-
HttpClient,
3-
HttpContext,
4-
HttpContextToken,
5-
} from '@angular/common/http';
6-
import { inject, Injectable, Injector } from '@angular/core';
7-
import { pendingUntilEvent } from '@angular/core/rxjs-interop';
1+
import { inject, Injectable } from '@angular/core';
82
import { Exception } from '@angularity/core';
9-
import { catchError, map, Observable, switchMap } from 'rxjs';
3+
import { Observable } from 'rxjs';
104

115
import { CacheConfigFiles } from './behaviors';
126
import { ConfigFileDefinition } from './definition';
7+
import { HttpClientConfigFileLoader } from './loader.http';
138

149
/**
1510
* Service for loading configuration files.
@@ -35,48 +30,3 @@ export abstract class ConfigFileLoader {
3530
}
3631

3732
export class ConfigFileNotFoundException extends Exception {}
38-
39-
/**
40-
* `HttpContextToken` that will be set to `true` in an endpoint request
41-
* sent by `HttpClientConfigFileLoader`.
42-
*/
43-
export const IS_CONFIG_FILE_REQUEST = new HttpContextToken<boolean>(
44-
() => false,
45-
);
46-
47-
/**
48-
* Implementation of {@link ConfigFileLoader}
49-
* based on Angular's built-in {@link HttpClient}.
50-
*/
51-
@Injectable({
52-
providedIn: 'root',
53-
})
54-
export class HttpClientConfigFileLoader implements ConfigFileLoader {
55-
protected httpClient = inject(HttpClient);
56-
protected injector = inject(Injector);
57-
58-
load<T, Schema>(def: ConfigFileDefinition<T, Schema>): Observable<T> {
59-
const parser = this.injector.get(def.parser);
60-
const validator = this.injector.get(def.validator);
61-
62-
return this.fetch(def.path).pipe(
63-
map((res) => {
64-
if (!res) throw new ConfigFileNotFoundException(def.path);
65-
return res;
66-
}),
67-
switchMap(async (raw) => {
68-
const parsed = await parser.parse(raw);
69-
await validator.validate(def.schema, parsed);
70-
return parsed as T;
71-
}),
72-
pendingUntilEvent(this.injector),
73-
);
74-
}
75-
76-
protected fetch(path: string): Observable<string | null> {
77-
const context = new HttpContext().set(IS_CONFIG_FILE_REQUEST, true);
78-
return this.httpClient
79-
.get(path, { responseType: 'text', context })
80-
.pipe(catchError(() => [null]));
81-
}
82-
}

packages/config-files/src/public-api.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,7 @@ export * from './behaviors';
77
export * from './definition';
88
export * from './facade';
99
export * from './loader';
10+
export * from './loader.http';
11+
export * from './loader.http.server-cache';
1012
export * from './parser';
1113
export * from './validator';

0 commit comments

Comments
 (0)