@obsidiane/meridiane 2.0.10 → 2.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -2
- package/cli.js +21 -37
- package/package.json +3 -3
- package/src/README.md +9 -0
- package/{tools/app → src/application}/bridge-workflow.js +15 -13
- package/src/application/generate-bridge.js +50 -0
- package/{tools/app → src/application}/run-build.js +1 -1
- package/{tools/app → src/application}/run-dev.js +30 -36
- package/{tools/app → src/application}/run-generate.js +3 -3
- package/{tools → src}/domain/bridge-options.js +8 -21
- package/src/domain/list-values.js +6 -0
- package/src/generators/bridge/write-bridge-files.js +100 -0
- package/src/generators/models/interface-schema.js +116 -0
- package/src/generators/models/model-selection.js +148 -0
- package/src/generators/models/oas-contract.js +170 -0
- package/{tools/generator → src/generators}/models/oas-schema-name.js +6 -15
- package/src/generators/models/openapi-to-models.js +136 -0
- package/{tools/generator → src/generators}/models/schema-utils.js +27 -3
- package/src/generators/models/templates/type-alias.hbs +2 -0
- package/src/generators/models/type-expression.js +65 -0
- package/src/generators/models/type-resolver.js +240 -0
- package/src/generators/models/utils.js +89 -0
- package/src/generators/models/write-model-files.js +55 -0
- package/src/infrastructure/dev-project.js +48 -0
- package/{tools/infra → src/infrastructure}/exec.js +3 -2
- package/src/infrastructure/local-package-install.js +104 -0
- package/src/infrastructure/spec-loader.js +65 -0
- package/{tools/infra → src/infrastructure}/standalone-workspace.js +31 -29
- package/templates/{_lib_template → angular-bridge}/README.md +9 -0
- package/templates/angular-bridge/src/lib/bridge/rest/api-platform.adapter.ts +82 -0
- package/templates/angular-bridge/src/lib/bridge/rest/http-request.options.ts +30 -0
- package/templates/{_lib_template → angular-bridge}/src/lib/bridge/rest/query-builder.ts +10 -10
- package/templates/angular-bridge/src/lib/bridge/sse/mercure-connections.ts +255 -0
- package/templates/angular-bridge/src/lib/bridge/sse/mercure-payload.ts +82 -0
- package/templates/angular-bridge/src/lib/bridge/sse/mercure-url.builder.ts +39 -0
- package/templates/angular-bridge/src/lib/bridge/sse/mercure.adapter.ts +358 -0
- package/templates/{_lib_template → angular-bridge}/src/lib/bridge/sse/ref-count-topic.registry.ts +3 -12
- package/templates/angular-bridge/src/lib/bridge/sse/subscription-claims.ts +76 -0
- package/templates/angular-bridge/src/lib/bridge-options.ts +59 -0
- package/templates/{_lib_template → angular-bridge}/src/lib/facades/bridge.facade.ts +50 -51
- package/templates/{_lib_template → angular-bridge}/src/lib/facades/facade.factory.ts +0 -1
- package/templates/angular-bridge/src/lib/facades/facade.interface.ts +13 -0
- package/templates/{_lib_template → angular-bridge}/src/lib/facades/resource.facade.ts +24 -31
- package/templates/angular-bridge/src/lib/interceptors/bearer-auth.interceptor.ts +33 -0
- package/templates/angular-bridge/src/lib/interceptors/bridge-defaults.interceptor.ts +65 -0
- package/templates/angular-bridge/src/lib/interceptors/content-type.interceptor.ts +37 -0
- package/templates/angular-bridge/src/lib/interceptors/scoped.interceptor.ts +9 -0
- package/templates/angular-bridge/src/lib/interceptors/singleflight.interceptor.ts +46 -0
- package/templates/{_lib_template → angular-bridge}/src/lib/ports/realtime.port.ts +6 -2
- package/templates/{_lib_template → angular-bridge}/src/lib/ports/resource-repository.port.ts +7 -7
- package/templates/angular-bridge/src/lib/provide-bridge.ts +98 -0
- package/templates/angular-bridge/src/lib/utils/iri.ts +6 -0
- package/templates/angular-bridge/src/lib/utils/request-url.ts +31 -0
- package/templates/{_lib_template → angular-bridge}/src/public-api.ts +2 -0
- package/templates/_lib_template/src/lib/bridge/rest/api-platform.adapter.ts +0 -84
- package/templates/_lib_template/src/lib/bridge/rest/http-request.options.ts +0 -21
- package/templates/_lib_template/src/lib/bridge/sse/mercure-url.builder.ts +0 -17
- package/templates/_lib_template/src/lib/bridge/sse/mercure.adapter.ts +0 -806
- package/templates/_lib_template/src/lib/facades/facade.interface.ts +0 -31
- package/templates/_lib_template/src/lib/interceptors/bridge-defaults.interceptor.ts +0 -53
- package/templates/_lib_template/src/lib/interceptors/content-type.interceptor.ts +0 -49
- package/templates/_lib_template/src/lib/interceptors/singleflight.interceptor.ts +0 -55
- package/templates/_lib_template/src/lib/provide-bridge.ts +0 -186
- package/tools/build.js +0 -1
- package/tools/dev.js +0 -1
- package/tools/domain/paths.js +0 -10
- package/tools/generate.js +0 -1
- package/tools/generator/models/oas-contract.js +0 -159
- package/tools/generator/models/openapi-to-models.js +0 -240
- package/tools/generator/models/type-resolver.js +0 -107
- package/tools/generator/models/utils.js +0 -62
- package/tools/infra/dev-support.js +0 -189
- package/tools/infra/generate/bridge.js +0 -212
- package/tools/infra/spec-loader.js +0 -59
- /package/{tools → src}/domain/lib-name.js +0 -0
- /package/{tools/generator → src/generators}/models/handlebars.js +0 -0
- /package/{tools/generator → src/generators}/models/templates/index.hbs +0 -0
- /package/{tools/generator → src/generators}/models/templates/model.hbs +0 -0
- /package/{tools/infra → src/infrastructure}/json.js +0 -0
- /package/{tools/infra → src/infrastructure}/logger.js +0 -0
- /package/templates/{_lib_template → angular-bridge}/ng-package.json +0 -0
- /package/templates/{_lib_template → angular-bridge}/package.json +0 -0
- /package/templates/{_lib_template → angular-bridge}/src/lib/bridge/sse/eventsource-wrapper.ts +0 -0
- /package/templates/{_lib_template → angular-bridge}/src/lib/bridge/sse/mercure-topic.mapper.ts +0 -0
- /package/templates/{_lib_template → angular-bridge}/src/lib/bridge.types.ts +0 -0
- /package/templates/{_lib_template → angular-bridge}/src/lib/interceptors/bridge-debug.interceptor.ts +0 -0
- /package/templates/{_lib_template → angular-bridge}/src/lib/tokens.ts +0 -0
- /package/templates/{_lib_template → angular-bridge}/src/lib/utils/url.ts +0 -0
- /package/templates/{_lib_template → angular-bridge}/src/models/index.ts +0 -0
- /package/templates/{_lib_template → angular-bridge}/tsconfig.lib.json +0 -0
- /package/templates/{_lib_template → angular-bridge}/tsconfig.lib.prod.json +0 -0
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Collection,
|
|
3
|
-
HttpRequestConfig,
|
|
4
|
-
Iri,
|
|
5
|
-
IriRequired,
|
|
6
|
-
Item,
|
|
7
|
-
HttpCallOptions,
|
|
8
|
-
AnyQuery,
|
|
9
|
-
} from '../ports/resource-repository.port';
|
|
10
|
-
import {Observable} from 'rxjs';
|
|
11
|
-
import {WatchConnectionOptions} from '../bridge.types';
|
|
12
|
-
|
|
13
|
-
export interface Facade<T extends Item> {
|
|
14
|
-
getCollection$(query?: AnyQuery, opts?: HttpCallOptions): Observable<Collection<T>>;
|
|
15
|
-
|
|
16
|
-
get$(iri: IriRequired, opts?: HttpCallOptions): Observable<T>;
|
|
17
|
-
|
|
18
|
-
post$(payload: Partial<T>, opts?: HttpCallOptions): Observable<T>;
|
|
19
|
-
|
|
20
|
-
patch$(iri: IriRequired, changes: Partial<T>, opts?: HttpCallOptions): Observable<T>;
|
|
21
|
-
|
|
22
|
-
put$(iri: IriRequired, payload: Partial<T>, opts?: HttpCallOptions): Observable<T>;
|
|
23
|
-
|
|
24
|
-
delete$(iri: IriRequired, opts?: HttpCallOptions): Observable<void>;
|
|
25
|
-
|
|
26
|
-
request$<R = unknown, B = unknown>(req: HttpRequestConfig<B>): Observable<R>;
|
|
27
|
-
|
|
28
|
-
watch$(iri: Iri | Iri[], options?: WatchConnectionOptions): Observable<T>;
|
|
29
|
-
|
|
30
|
-
unwatch(iri: Iri | Iri[]): void;
|
|
31
|
-
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import {HttpInterceptorFn} from '@angular/common/http';
|
|
2
|
-
import {inject} from '@angular/core';
|
|
3
|
-
import {BRIDGE_DEFAULTS, BRIDGE_LOGGER} from '../tokens';
|
|
4
|
-
import {retry, timeout, timer} from 'rxjs';
|
|
5
|
-
|
|
6
|
-
const DEFAULT_RETRY_METHODS = ['GET', 'HEAD', 'OPTIONS'];
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Applies default headers, timeout and retry policy configured via `provideBridge({defaults: ...})`.
|
|
10
|
-
*/
|
|
11
|
-
export const bridgeDefaultsInterceptor: HttpInterceptorFn = (req, next) => {
|
|
12
|
-
const defaults = inject(BRIDGE_DEFAULTS, {optional: true}) ?? {};
|
|
13
|
-
const logger = inject(BRIDGE_LOGGER, {optional: true});
|
|
14
|
-
|
|
15
|
-
let nextReq = req;
|
|
16
|
-
|
|
17
|
-
if (defaults.headers) {
|
|
18
|
-
for (const [k, v] of Object.entries(defaults.headers)) {
|
|
19
|
-
if (!nextReq.headers.has(k)) {
|
|
20
|
-
nextReq = nextReq.clone({headers: nextReq.headers.set(k, v)});
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
let out$ = next(nextReq);
|
|
26
|
-
|
|
27
|
-
const timeoutMs = typeof defaults.timeoutMs === 'number' ? defaults.timeoutMs : undefined;
|
|
28
|
-
if (timeoutMs && timeoutMs > 0) {
|
|
29
|
-
out$ = out$.pipe(timeout({first: timeoutMs}));
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const retryCfg = defaults.retries;
|
|
33
|
-
const retryCount = typeof retryCfg === 'number' ? retryCfg : retryCfg?.count;
|
|
34
|
-
if (retryCount && retryCount > 0) {
|
|
35
|
-
const methods = (typeof retryCfg === 'object' && retryCfg.methods) ? retryCfg.methods : DEFAULT_RETRY_METHODS;
|
|
36
|
-
const normalizedMethod = req.method.toUpperCase();
|
|
37
|
-
const methodAllowList = new Set(methods.map((m) => m.toUpperCase()));
|
|
38
|
-
if (methodAllowList.has(normalizedMethod)) {
|
|
39
|
-
const delayMs = typeof retryCfg === 'object' ? (retryCfg.delayMs ?? 250) : 250;
|
|
40
|
-
out$ = out$.pipe(
|
|
41
|
-
retry({
|
|
42
|
-
count: retryCount,
|
|
43
|
-
delay: (_err, retryIndex) => {
|
|
44
|
-
logger?.debug?.('[Bridge] retry', {url: req.urlWithParams, method: req.method, retryIndex});
|
|
45
|
-
return timer(delayMs);
|
|
46
|
-
},
|
|
47
|
-
})
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return out$;
|
|
53
|
-
};
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import {HttpInterceptorFn} from '@angular/common/http';
|
|
2
|
-
|
|
3
|
-
export interface ApiMediaTypes {
|
|
4
|
-
accept: string;
|
|
5
|
-
post: string;
|
|
6
|
-
put: string;
|
|
7
|
-
patch: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export const DEFAULT_MEDIA_TYPES: ApiMediaTypes = {
|
|
11
|
-
accept: 'application/ld+json',
|
|
12
|
-
post: 'application/ld+json',
|
|
13
|
-
put: 'application/ld+json',
|
|
14
|
-
patch: 'application/merge-patch+json',
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export const contentTypeInterceptor: HttpInterceptorFn = (req, next) => {
|
|
18
|
-
const mediaTypes = DEFAULT_MEDIA_TYPES;
|
|
19
|
-
let headers = req.headers;
|
|
20
|
-
|
|
21
|
-
// Set Accept if missing.
|
|
22
|
-
if (!headers.has('Accept')) {
|
|
23
|
-
headers = headers.set('Accept', mediaTypes.accept);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Only methods with a body.
|
|
27
|
-
const isBodyMethod = ['POST', 'PUT', 'PATCH'].includes(req.method);
|
|
28
|
-
// Ignore FormData (browser sets multipart boundary).
|
|
29
|
-
const isFormData = typeof FormData !== 'undefined' && req.body instanceof FormData;
|
|
30
|
-
|
|
31
|
-
if (isBodyMethod && !isFormData && !headers.has('Content-Type')) {
|
|
32
|
-
let contentType: string;
|
|
33
|
-
|
|
34
|
-
switch (req.method) {
|
|
35
|
-
case 'PATCH':
|
|
36
|
-
contentType = mediaTypes.patch;
|
|
37
|
-
break;
|
|
38
|
-
case 'PUT':
|
|
39
|
-
contentType = mediaTypes.put;
|
|
40
|
-
break;
|
|
41
|
-
default:
|
|
42
|
-
contentType = mediaTypes.post;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
headers = headers.set('Content-Type', contentType);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return next(req.clone({headers}));
|
|
49
|
-
};
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import {HttpEvent, HttpInterceptorFn, HttpRequest} from '@angular/common/http';
|
|
2
|
-
import {finalize, Observable, shareReplay} from 'rxjs';
|
|
3
|
-
|
|
4
|
-
export type SingleFlightMode = 'off' | 'safe';
|
|
5
|
-
|
|
6
|
-
const DEFAULT_MODE: SingleFlightMode = 'safe';
|
|
7
|
-
const SAFE_METHODS = new Set(['GET', 'HEAD', 'OPTIONS']);
|
|
8
|
-
|
|
9
|
-
export function createSingleFlightInterceptor(mode: SingleFlightMode = DEFAULT_MODE): HttpInterceptorFn {
|
|
10
|
-
const inflight = new Map<string, Observable<HttpEvent<unknown>>>();
|
|
11
|
-
|
|
12
|
-
return (req, next) => {
|
|
13
|
-
if (mode === 'off') {
|
|
14
|
-
return next(req);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const method = req.method.toUpperCase();
|
|
18
|
-
if (!SAFE_METHODS.has(method)) {
|
|
19
|
-
return next(req);
|
|
20
|
-
}
|
|
21
|
-
if (req.reportProgress === true) {
|
|
22
|
-
return next(req);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const key = computeKey(req);
|
|
26
|
-
const existing = inflight.get(key);
|
|
27
|
-
if (existing) {
|
|
28
|
-
return existing;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const shared$ = next(req).pipe(
|
|
32
|
-
finalize(() => inflight.delete(key)),
|
|
33
|
-
shareReplay({bufferSize: 1, refCount: true})
|
|
34
|
-
);
|
|
35
|
-
|
|
36
|
-
inflight.set(key, shared$);
|
|
37
|
-
return shared$;
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function computeKey(req: HttpRequest<unknown>): string {
|
|
42
|
-
const auth = req.headers.get('Authorization') ?? '';
|
|
43
|
-
const accept = req.headers.get('Accept') ?? '';
|
|
44
|
-
const contentType = req.headers.get('Content-Type') ?? '';
|
|
45
|
-
const creds = req.withCredentials ? '1' : '0';
|
|
46
|
-
return [
|
|
47
|
-
req.method.toUpperCase(),
|
|
48
|
-
req.urlWithParams,
|
|
49
|
-
`rt=${req.responseType}`,
|
|
50
|
-
`wc=${creds}`,
|
|
51
|
-
`a=${auth}`,
|
|
52
|
-
`acc=${accept}`,
|
|
53
|
-
`ct=${contentType}`,
|
|
54
|
-
].join('::');
|
|
55
|
-
}
|
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
import {EnvironmentProviders, makeEnvironmentProviders} from '@angular/core';
|
|
2
|
-
import {HttpInterceptorFn, provideHttpClient, withFetch, withInterceptors} from '@angular/common/http';
|
|
3
|
-
import {
|
|
4
|
-
API_BASE_URL,
|
|
5
|
-
BRIDGE_DEFAULTS,
|
|
6
|
-
BRIDGE_HTTP_WITH_CREDENTIALS,
|
|
7
|
-
BRIDGE_LOGGER,
|
|
8
|
-
BRIDGE_MERCURE_WITH_CREDENTIALS,
|
|
9
|
-
MERCURE_CONNECTION_MODE,
|
|
10
|
-
MERCURE_HUB_URL,
|
|
11
|
-
MERCURE_MAX_URL_LENGTH,
|
|
12
|
-
MERCURE_TOPIC_MODE,
|
|
13
|
-
} from './tokens';
|
|
14
|
-
import {BridgeDefaults, BridgeLogger, MercureConnectionMode, MercureTopicMode} from './bridge.types';
|
|
15
|
-
import {contentTypeInterceptor} from './interceptors/content-type.interceptor';
|
|
16
|
-
import {bridgeDefaultsInterceptor} from './interceptors/bridge-defaults.interceptor';
|
|
17
|
-
import {bridgeDebugInterceptor} from './interceptors/bridge-debug.interceptor';
|
|
18
|
-
import {from, switchMap} from 'rxjs';
|
|
19
|
-
import {createSingleFlightInterceptor} from './interceptors/singleflight.interceptor';
|
|
20
|
-
|
|
21
|
-
export type BridgeAuth =
|
|
22
|
-
| string
|
|
23
|
-
| {type: 'bearer'; token: string}
|
|
24
|
-
| {type: 'bearer'; getToken: () => string | undefined | Promise<string | undefined>}
|
|
25
|
-
| HttpInterceptorFn;
|
|
26
|
-
|
|
27
|
-
export interface BridgeMercureOptions {
|
|
28
|
-
hubUrl?: string;
|
|
29
|
-
init?: RequestInit;
|
|
30
|
-
topicMode?: MercureTopicMode;
|
|
31
|
-
/**
|
|
32
|
-
* Connection strategy for SSE subscriptions.
|
|
33
|
-
* - `single`: one SSE connection per watch subscription
|
|
34
|
-
* - `auto` (default): shared connections with automatic URL-length sharding
|
|
35
|
-
*/
|
|
36
|
-
connectionMode?: MercureConnectionMode;
|
|
37
|
-
/**
|
|
38
|
-
* Maximum Mercure URL length before auto-sharding topics into multiple connections.
|
|
39
|
-
* Used only when `connectionMode: 'auto'`. Default: `1900`.
|
|
40
|
-
*/
|
|
41
|
-
maxUrlLength?: number;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export interface BridgeHttpOptions {
|
|
45
|
-
/**
|
|
46
|
-
* Enables cookies / credentials on HTTP requests by default.
|
|
47
|
-
*
|
|
48
|
-
* When omitted, the bridge preserves the historical behavior and derives the
|
|
49
|
-
* HTTP value from `mercure.init.credentials`.
|
|
50
|
-
*/
|
|
51
|
-
withCredentials?: boolean;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export interface BridgeOptions {
|
|
55
|
-
/** Base URL of the API (e.g. `http://localhost:8000`). */
|
|
56
|
-
baseUrl: string;
|
|
57
|
-
/** Auth strategy used to attach an Authorization header. */
|
|
58
|
-
auth?: BridgeAuth;
|
|
59
|
-
/** Default HTTP transport options. */
|
|
60
|
-
http?: BridgeHttpOptions;
|
|
61
|
-
/**
|
|
62
|
-
* Use `topicMode` to control the `topic=` values sent to the hub.
|
|
63
|
-
*/
|
|
64
|
-
mercure?: BridgeMercureOptions;
|
|
65
|
-
/** Default HTTP behaviour (headers, timeout, retries). */
|
|
66
|
-
defaults?: BridgeDefaults;
|
|
67
|
-
/**
|
|
68
|
-
* De-duplicates in-flight HTTP requests.
|
|
69
|
-
*
|
|
70
|
-
* - `true` (default): single-flight for safe methods (`GET/HEAD/OPTIONS`)
|
|
71
|
-
* - `false`: disabled (each call triggers a new request)
|
|
72
|
-
*/
|
|
73
|
-
singleFlight?: boolean;
|
|
74
|
-
/** Enables debug logging via the debug interceptor and console logger. */
|
|
75
|
-
debug?: boolean;
|
|
76
|
-
/** Extra `HttpInterceptorFn` applied after bridge interceptors. */
|
|
77
|
-
extraInterceptors?: HttpInterceptorFn[];
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/** Registers the bridge HTTP client, interceptors, Mercure realtime adapter and configuration tokens. */
|
|
81
|
-
export function provideBridge(opts: BridgeOptions): EnvironmentProviders {
|
|
82
|
-
const {
|
|
83
|
-
baseUrl,
|
|
84
|
-
auth,
|
|
85
|
-
http,
|
|
86
|
-
mercure,
|
|
87
|
-
defaults,
|
|
88
|
-
singleFlight = true,
|
|
89
|
-
debug = false,
|
|
90
|
-
extraInterceptors = [],
|
|
91
|
-
} = opts;
|
|
92
|
-
|
|
93
|
-
if (!baseUrl) {
|
|
94
|
-
throw new Error("provideBridge(): missing 'baseUrl'");
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const resolvedMercureInit: RequestInit = mercure?.init ?? {credentials: 'include' as RequestCredentials};
|
|
98
|
-
const resolvedMercureHubUrl = mercure?.hubUrl;
|
|
99
|
-
const resolvedMercureTopicMode: MercureTopicMode = mercure?.topicMode ?? 'url';
|
|
100
|
-
const resolvedMercureConnectionMode: MercureConnectionMode = mercure?.connectionMode ?? 'auto';
|
|
101
|
-
const resolvedMercureMaxUrlLength = normalizeMercureMaxUrlLength(mercure?.maxUrlLength, 1900);
|
|
102
|
-
const mercureWithCredentials = resolveWithCredentials(resolvedMercureInit);
|
|
103
|
-
const httpWithCredentials = http?.withCredentials ?? mercureWithCredentials;
|
|
104
|
-
|
|
105
|
-
const loggerProvider: BridgeLogger = createBridgeLogger(debug);
|
|
106
|
-
|
|
107
|
-
const interceptors: HttpInterceptorFn[] = [
|
|
108
|
-
contentTypeInterceptor,
|
|
109
|
-
bridgeDefaultsInterceptor,
|
|
110
|
-
...createAuthInterceptors(auth),
|
|
111
|
-
...(singleFlight ? [createSingleFlightInterceptor('safe')] : [createSingleFlightInterceptor('off')]),
|
|
112
|
-
...(debug ? [bridgeDebugInterceptor] : []),
|
|
113
|
-
...extraInterceptors,
|
|
114
|
-
];
|
|
115
|
-
|
|
116
|
-
return makeEnvironmentProviders([
|
|
117
|
-
provideHttpClient(
|
|
118
|
-
withFetch(),
|
|
119
|
-
withInterceptors(interceptors)
|
|
120
|
-
),
|
|
121
|
-
{provide: API_BASE_URL, useValue: baseUrl},
|
|
122
|
-
{provide: BRIDGE_HTTP_WITH_CREDENTIALS, useValue: httpWithCredentials},
|
|
123
|
-
{provide: BRIDGE_MERCURE_WITH_CREDENTIALS, useValue: mercureWithCredentials},
|
|
124
|
-
...(resolvedMercureHubUrl ? [{provide: MERCURE_HUB_URL, useValue: resolvedMercureHubUrl}] : []),
|
|
125
|
-
{provide: MERCURE_TOPIC_MODE, useValue: resolvedMercureTopicMode},
|
|
126
|
-
{provide: MERCURE_CONNECTION_MODE, useValue: resolvedMercureConnectionMode},
|
|
127
|
-
{provide: MERCURE_MAX_URL_LENGTH, useValue: resolvedMercureMaxUrlLength},
|
|
128
|
-
{provide: BRIDGE_DEFAULTS, useValue: defaults ?? {}},
|
|
129
|
-
{provide: BRIDGE_LOGGER, useValue: loggerProvider},
|
|
130
|
-
]);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function createBridgeLogger(debug: boolean): BridgeLogger {
|
|
134
|
-
const noop = () => undefined;
|
|
135
|
-
return {
|
|
136
|
-
debug: debug ? console.debug.bind(console) : noop,
|
|
137
|
-
info: debug ? console.info.bind(console) : noop,
|
|
138
|
-
warn: debug ? console.warn.bind(console) : noop,
|
|
139
|
-
error: console.error.bind(console),
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function resolveWithCredentials(init: RequestInit | undefined): boolean {
|
|
144
|
-
if (!init) return false;
|
|
145
|
-
const anyInit = init as RequestInit & {withCredentials?: boolean};
|
|
146
|
-
return anyInit.withCredentials === true || init.credentials === 'include';
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function normalizeMercureMaxUrlLength(input: unknown, fallback: number): number {
|
|
150
|
-
const value = typeof input === 'number' ? Math.floor(input) : fallback;
|
|
151
|
-
return Number.isFinite(value) && value >= 256 ? value : fallback;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function createAuthInterceptors(auth?: BridgeAuth): HttpInterceptorFn[] {
|
|
155
|
-
if (!auth) return [];
|
|
156
|
-
|
|
157
|
-
if (typeof auth === 'function') {
|
|
158
|
-
return [auth as HttpInterceptorFn];
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const bearer =
|
|
162
|
-
typeof auth === 'string'
|
|
163
|
-
? {type: 'bearer' as const, token: auth}
|
|
164
|
-
: (auth as Exclude<BridgeAuth, string | HttpInterceptorFn>);
|
|
165
|
-
|
|
166
|
-
if (bearer.type !== 'bearer') return [];
|
|
167
|
-
|
|
168
|
-
if ('token' in bearer) {
|
|
169
|
-
const token = bearer.token;
|
|
170
|
-
return [createBearerAuthInterceptor(() => token)];
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return [createBearerAuthInterceptor(bearer.getToken)];
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function createBearerAuthInterceptor(getToken: () => string | undefined | Promise<string | undefined>): HttpInterceptorFn {
|
|
177
|
-
return (req, next) => {
|
|
178
|
-
if (req.headers.has('Authorization')) return next(req);
|
|
179
|
-
return from(Promise.resolve(getToken())).pipe(
|
|
180
|
-
switchMap((token) => {
|
|
181
|
-
if (!token) return next(req);
|
|
182
|
-
return next(req.clone({headers: req.headers.set('Authorization', `Bearer ${token}`)}));
|
|
183
|
-
})
|
|
184
|
-
);
|
|
185
|
-
};
|
|
186
|
-
}
|
package/tools/build.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { runBuild } from './app/run-build.js';
|
package/tools/dev.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { runDev } from './app/run-dev.js';
|
package/tools/domain/paths.js
DELETED
package/tools/generate.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { runGenerate } from './app/run-generate.js';
|
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
import { schemaNameFromRef } from './schema-utils.js';
|
|
2
|
-
|
|
3
|
-
function resolveRefPointer(spec, ref) {
|
|
4
|
-
const s = String(ref || '');
|
|
5
|
-
if (!s.startsWith('#/')) return undefined;
|
|
6
|
-
const parts = s.slice(2).split('/').filter(Boolean);
|
|
7
|
-
let cur = spec;
|
|
8
|
-
for (const p of parts) {
|
|
9
|
-
if (!cur || typeof cur !== 'object') return undefined;
|
|
10
|
-
cur = cur[p];
|
|
11
|
-
}
|
|
12
|
-
return cur;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function deref(spec, maybeRef) {
|
|
16
|
-
if (!maybeRef || typeof maybeRef !== 'object') return maybeRef;
|
|
17
|
-
if (typeof maybeRef.$ref === 'string') return resolveRefPointer(spec, maybeRef.$ref) || maybeRef;
|
|
18
|
-
return maybeRef;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function canonicalizeMediaType(mediaType) {
|
|
22
|
-
return String(mediaType || '').split(';')[0].trim().toLowerCase();
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function getContentSchema(content, wantedLower) {
|
|
26
|
-
if (!content || typeof content !== 'object') return undefined;
|
|
27
|
-
for (const [k, v] of Object.entries(content)) {
|
|
28
|
-
const canon = canonicalizeMediaType(k);
|
|
29
|
-
if (canon === wantedLower) return v?.schema;
|
|
30
|
-
}
|
|
31
|
-
return undefined;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function is2xxStatus(code) {
|
|
35
|
-
const s = String(code || '');
|
|
36
|
-
if (/^[0-9]{3}$/.test(s)) return s.startsWith('2');
|
|
37
|
-
return /^2xx$/i.test(s);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Collects only component schema refs (`#/components/schemas/*`) found in a JSON schema.
|
|
42
|
-
* @param {any} schema
|
|
43
|
-
* @param {Set<string>} out
|
|
44
|
-
*/
|
|
45
|
-
function collectComponentSchemaRefs(schema, out) {
|
|
46
|
-
const seen = new Set();
|
|
47
|
-
|
|
48
|
-
function walk(node) {
|
|
49
|
-
if (!node || typeof node !== 'object') return;
|
|
50
|
-
if (seen.has(node)) return;
|
|
51
|
-
seen.add(node);
|
|
52
|
-
|
|
53
|
-
if (node.$ref && typeof node.$ref === 'string') {
|
|
54
|
-
const name = schemaNameFromRef(node.$ref);
|
|
55
|
-
if (name) out.add(name);
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (Array.isArray(node)) {
|
|
60
|
-
for (const it of node) walk(it);
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (node.allOf) walk(node.allOf);
|
|
65
|
-
if (node.oneOf) walk(node.oneOf);
|
|
66
|
-
if (node.anyOf) walk(node.anyOf);
|
|
67
|
-
if (node.items) walk(node.items);
|
|
68
|
-
if (node.properties) walk(node.properties);
|
|
69
|
-
if (node.additionalProperties) walk(node.additionalProperties);
|
|
70
|
-
|
|
71
|
-
for (const v of Object.values(node)) walk(v);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
walk(schema);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Extracts root schemas from endpoints for one media type.
|
|
79
|
-
* - requestBody is ignored for PATCH (merge-patch typed as Partial<> elsewhere)
|
|
80
|
-
* - responses keep only 2xx and default
|
|
81
|
-
*/
|
|
82
|
-
function listRootsFromPaths(spec, { mediaType } = {}) {
|
|
83
|
-
const wantedLower = canonicalizeMediaType(mediaType);
|
|
84
|
-
const roots = [];
|
|
85
|
-
|
|
86
|
-
const paths = spec?.paths;
|
|
87
|
-
if (!paths || typeof paths !== 'object') return roots;
|
|
88
|
-
|
|
89
|
-
for (const pathItem of Object.values(paths)) {
|
|
90
|
-
if (!pathItem || typeof pathItem !== 'object') continue;
|
|
91
|
-
for (const [method, op0] of Object.entries(pathItem)) {
|
|
92
|
-
const op = deref(spec, op0);
|
|
93
|
-
if (!op || typeof op !== 'object') continue;
|
|
94
|
-
|
|
95
|
-
const isPatch = String(method || '').toLowerCase() === 'patch';
|
|
96
|
-
|
|
97
|
-
if (!isPatch) {
|
|
98
|
-
const rb = deref(spec, op.requestBody);
|
|
99
|
-
const rbSchema = getContentSchema(rb?.content, wantedLower);
|
|
100
|
-
if (rbSchema) roots.push(rbSchema);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const responses = op.responses;
|
|
104
|
-
if (!responses || typeof responses !== 'object') continue;
|
|
105
|
-
for (const [code, resp] of Object.entries(responses)) {
|
|
106
|
-
if (!is2xxStatus(code) && String(code).toLowerCase() !== 'default') continue;
|
|
107
|
-
const resolved = deref(spec, resp);
|
|
108
|
-
const schema = getContentSchema(resolved?.content, wantedLower);
|
|
109
|
-
if (schema) roots.push(schema);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return roots;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Returns the transitive closure of schema dependencies (following component-schema $ref).
|
|
119
|
-
* @param {Iterable<string>} start
|
|
120
|
-
* @param {Record<string, any>} schemas
|
|
121
|
-
*/
|
|
122
|
-
export function expandWithSchemaDependencies(start, schemas) {
|
|
123
|
-
const out = new Set();
|
|
124
|
-
const queue = [];
|
|
125
|
-
|
|
126
|
-
for (const n of start) {
|
|
127
|
-
if (!n || out.has(n)) continue;
|
|
128
|
-
out.add(n);
|
|
129
|
-
queue.push(n);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
while (queue.length) {
|
|
133
|
-
const name = queue.pop();
|
|
134
|
-
const schema = schemas[name];
|
|
135
|
-
if (!schema) continue;
|
|
136
|
-
const refs = new Set();
|
|
137
|
-
collectComponentSchemaRefs(schema, refs);
|
|
138
|
-
for (const ref of refs) {
|
|
139
|
-
if (!ref || out.has(ref)) continue;
|
|
140
|
-
out.add(ref);
|
|
141
|
-
queue.push(ref);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return out;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Collects all component schemas used by endpoints for one media type.
|
|
150
|
-
* @param {any} spec
|
|
151
|
-
* @param {{ mediaType: string }} cfg
|
|
152
|
-
*/
|
|
153
|
-
export function collectUsedSchemasForMediaType(spec, cfg) {
|
|
154
|
-
const schemas = spec?.components?.schemas || {};
|
|
155
|
-
const roots = listRootsFromPaths(spec, cfg);
|
|
156
|
-
const initialRefs = new Set();
|
|
157
|
-
for (const r of roots) collectComponentSchemaRefs(r, initialRefs);
|
|
158
|
-
return expandWithSchemaDependencies(initialRefs, schemas);
|
|
159
|
-
}
|