@cedvict/http-guardian 0.0.1-next.0
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/LICENSE +1 -0
- package/README.md +44 -0
- package/TESTING.md +12 -0
- package/dist/index.d.ts +266 -0
- package/dist/index.js +660 -0
- package/dist/index.js.map +1 -0
- package/package.json +65 -0
package/LICENSE
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
MIT
|
package/README.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# @cedvict/http-guardian
|
|
2
|
+
|
|
3
|
+
Core HTTP client (Promises) built on `fetch`.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
```bash
|
|
7
|
+
npm i @cedvict/http-guardian
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Quick example (REST classic envelope)
|
|
11
|
+
```ts
|
|
12
|
+
import { createHttpClient, bearerAuth, createApiParserRestClassic, memoryCache } from "@cedvict/http-guardian";
|
|
13
|
+
|
|
14
|
+
const api = createHttpClient({
|
|
15
|
+
baseUrl: "https://api.example.com",
|
|
16
|
+
auth: bearerAuth(() => localStorage.getItem("token")),
|
|
17
|
+
parser: createApiParserRestClassic(),
|
|
18
|
+
cache: memoryCache({ maxEntries: 500 })
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const res = await api.get<{ id: string; name: string }>("/me", {
|
|
22
|
+
cache: { ttlMs: 30_000, tags: ["me"] }
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
if (res.ok) console.log(res.data.name);
|
|
26
|
+
else console.error(res.errors);
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Presets
|
|
30
|
+
- `createApiParserRestClassic()`
|
|
31
|
+
- `createApiParserLaravel()`
|
|
32
|
+
- `createApiParserNest()`
|
|
33
|
+
- `createApiParserGraphQL({ allowPartialData? })`
|
|
34
|
+
|
|
35
|
+
## Auth modes
|
|
36
|
+
- `bearerAuth(() => token)`
|
|
37
|
+
- `cookieAuth({ credentials: "include", csrf: { headerName, getToken } })`
|
|
38
|
+
- `noAuth()`
|
|
39
|
+
|
|
40
|
+
## Redirects
|
|
41
|
+
- `redirects: { mode: "follow" | "manual" | "error", maxHops, onRedirect }`
|
|
42
|
+
|
|
43
|
+
## Testing
|
|
44
|
+
See `TESTING.md`.
|
package/TESTING.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Test suite
|
|
2
|
+
|
|
3
|
+
Run from repo root:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm -w @cedvict/http-guardian run test:unit
|
|
7
|
+
npm -w @cedvict/http-guardian run test:integration
|
|
8
|
+
npm -w @cedvict/http-guardian run test:robustness
|
|
9
|
+
npm -w @cedvict/http-guardian run test:security
|
|
10
|
+
npm -w @cedvict/http-guardian run build
|
|
11
|
+
npm -w @cedvict/http-guardian run bench
|
|
12
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
type AuthConfig = {
|
|
2
|
+
mode: "bearer";
|
|
3
|
+
getToken: () => string | null | Promise<string | null>;
|
|
4
|
+
headerName?: string;
|
|
5
|
+
prefix?: string;
|
|
6
|
+
} | {
|
|
7
|
+
mode: "cookie";
|
|
8
|
+
credentials?: RequestCredentials;
|
|
9
|
+
csrf?: {
|
|
10
|
+
headerName: string;
|
|
11
|
+
getToken: () => string | null | Promise<string | null>;
|
|
12
|
+
};
|
|
13
|
+
} | {
|
|
14
|
+
mode: "none";
|
|
15
|
+
};
|
|
16
|
+
declare function bearerAuth(getToken: () => string | null | Promise<string | null>, opts?: {
|
|
17
|
+
headerName?: string;
|
|
18
|
+
prefix?: string;
|
|
19
|
+
}): AuthConfig;
|
|
20
|
+
declare function cookieAuth(opts?: {
|
|
21
|
+
credentials?: RequestCredentials;
|
|
22
|
+
csrf?: {
|
|
23
|
+
headerName: string;
|
|
24
|
+
getToken: () => string | null | Promise<string | null>;
|
|
25
|
+
};
|
|
26
|
+
}): AuthConfig;
|
|
27
|
+
declare function noAuth(): AuthConfig;
|
|
28
|
+
|
|
29
|
+
type AppError = {
|
|
30
|
+
message: string;
|
|
31
|
+
code?: string;
|
|
32
|
+
field?: string;
|
|
33
|
+
details?: unknown;
|
|
34
|
+
};
|
|
35
|
+
declare class NetworkError extends Error {
|
|
36
|
+
readonly cause?: unknown | undefined;
|
|
37
|
+
name: string;
|
|
38
|
+
constructor(message: string, cause?: unknown | undefined);
|
|
39
|
+
}
|
|
40
|
+
declare class TimeoutError extends Error {
|
|
41
|
+
name: string;
|
|
42
|
+
constructor(message?: string);
|
|
43
|
+
}
|
|
44
|
+
declare class RedirectError extends Error {
|
|
45
|
+
readonly fromUrl: string;
|
|
46
|
+
readonly toUrl?: string | undefined;
|
|
47
|
+
name: string;
|
|
48
|
+
constructor(message: string, fromUrl: string, toUrl?: string | undefined);
|
|
49
|
+
}
|
|
50
|
+
declare class ParseError extends Error {
|
|
51
|
+
readonly raw?: unknown | undefined;
|
|
52
|
+
readonly cause?: unknown | undefined;
|
|
53
|
+
name: string;
|
|
54
|
+
constructor(message: string, raw?: unknown | undefined, cause?: unknown | undefined);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
type NotifyEvent = {
|
|
58
|
+
type: "http-error";
|
|
59
|
+
level: "error" | "warning";
|
|
60
|
+
title: string;
|
|
61
|
+
message: string;
|
|
62
|
+
status: number;
|
|
63
|
+
errors?: AppError[];
|
|
64
|
+
url: string;
|
|
65
|
+
} | {
|
|
66
|
+
type: "network-error";
|
|
67
|
+
level: "error";
|
|
68
|
+
title: string;
|
|
69
|
+
message: string;
|
|
70
|
+
url: string;
|
|
71
|
+
} | {
|
|
72
|
+
type: "parse-error";
|
|
73
|
+
level: "error";
|
|
74
|
+
title: string;
|
|
75
|
+
message: string;
|
|
76
|
+
url: string;
|
|
77
|
+
};
|
|
78
|
+
type Notifier = {
|
|
79
|
+
notify: (event: NotifyEvent) => void;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
type RequestContext = {
|
|
83
|
+
method: HttpMethod;
|
|
84
|
+
url: string;
|
|
85
|
+
headers: Headers;
|
|
86
|
+
credentials?: RequestCredentials;
|
|
87
|
+
redirect?: RequestRedirect;
|
|
88
|
+
body?: BodyInit;
|
|
89
|
+
signal?: AbortSignal;
|
|
90
|
+
timeoutMs?: number;
|
|
91
|
+
attempt: number;
|
|
92
|
+
auth: AuthConfig;
|
|
93
|
+
noAuth?: boolean;
|
|
94
|
+
notifier?: Notifier;
|
|
95
|
+
idempotencyKey?: string;
|
|
96
|
+
_fetch: typeof fetch;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
type Guard = (ctx: RequestContext, next: (ctx: RequestContext) => Promise<Response>) => Promise<Response>;
|
|
100
|
+
|
|
101
|
+
type CacheEntry = {
|
|
102
|
+
expiresAt: number;
|
|
103
|
+
value: unknown;
|
|
104
|
+
tags?: string[];
|
|
105
|
+
};
|
|
106
|
+
type CacheStore = {
|
|
107
|
+
get: (key: string) => CacheEntry | undefined;
|
|
108
|
+
set: (key: string, entry: CacheEntry) => void;
|
|
109
|
+
delete: (key: string) => void;
|
|
110
|
+
invalidateByTag: (tag: string) => number;
|
|
111
|
+
clear: () => void;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
type ShapeParser = {
|
|
115
|
+
isSuccess: (raw: unknown, status: number) => boolean;
|
|
116
|
+
getData: (raw: unknown) => unknown;
|
|
117
|
+
getErrors: (raw: unknown, status: number) => AppError[];
|
|
118
|
+
};
|
|
119
|
+
declare function createShapeParser(p: ShapeParser): ShapeParser;
|
|
120
|
+
|
|
121
|
+
type Schema<T> = {
|
|
122
|
+
parse: (input: unknown) => T;
|
|
123
|
+
};
|
|
124
|
+
declare function schema<T>(impl: Schema<T>): Schema<T>;
|
|
125
|
+
/**
|
|
126
|
+
* Optional helper to wrap a zod-like schema without adding a dependency.
|
|
127
|
+
*/
|
|
128
|
+
declare function fromZod<T>(zodSchema: {
|
|
129
|
+
parse?: (i: unknown) => T;
|
|
130
|
+
safeParse?: (i: unknown) => {
|
|
131
|
+
success: true;
|
|
132
|
+
data: T;
|
|
133
|
+
} | {
|
|
134
|
+
success: false;
|
|
135
|
+
error: unknown;
|
|
136
|
+
};
|
|
137
|
+
}): Schema<T>;
|
|
138
|
+
|
|
139
|
+
type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
140
|
+
type RedirectMode = "follow" | "manual" | "error";
|
|
141
|
+
type RedirectPolicy = {
|
|
142
|
+
mode: RedirectMode;
|
|
143
|
+
maxHops?: number;
|
|
144
|
+
onRedirect?: (info: {
|
|
145
|
+
fromUrl: string;
|
|
146
|
+
toUrl: string;
|
|
147
|
+
status: number;
|
|
148
|
+
hop: number;
|
|
149
|
+
method: HttpMethod;
|
|
150
|
+
}) => {
|
|
151
|
+
action: "follow";
|
|
152
|
+
} | {
|
|
153
|
+
action: "deny";
|
|
154
|
+
reason?: string;
|
|
155
|
+
} | {
|
|
156
|
+
action: "modify";
|
|
157
|
+
url: string;
|
|
158
|
+
};
|
|
159
|
+
};
|
|
160
|
+
type CacheOptions = {
|
|
161
|
+
ttlMs: number;
|
|
162
|
+
key?: string;
|
|
163
|
+
tags?: string[];
|
|
164
|
+
policy?: "networkFirst" | "cacheFirst" | "cacheOnly" | "networkOnly" | "staleWhileRevalidate";
|
|
165
|
+
dedupe?: boolean;
|
|
166
|
+
};
|
|
167
|
+
type RequestOptions<T> = {
|
|
168
|
+
query?: Record<string, string | number | boolean | null | undefined>;
|
|
169
|
+
headers?: Record<string, string | undefined>;
|
|
170
|
+
json?: unknown;
|
|
171
|
+
body?: BodyInit;
|
|
172
|
+
signal?: AbortSignal;
|
|
173
|
+
timeoutMs?: number;
|
|
174
|
+
cache?: CacheOptions;
|
|
175
|
+
dataSchema?: Schema<T>;
|
|
176
|
+
notifier?: Notifier;
|
|
177
|
+
noAuth?: boolean;
|
|
178
|
+
idempotencyKey?: string;
|
|
179
|
+
};
|
|
180
|
+
type ClientResult<T> = {
|
|
181
|
+
ok: true;
|
|
182
|
+
status: number;
|
|
183
|
+
headers: Headers;
|
|
184
|
+
data: T;
|
|
185
|
+
raw: unknown;
|
|
186
|
+
url: string;
|
|
187
|
+
} | {
|
|
188
|
+
ok: false;
|
|
189
|
+
status: number;
|
|
190
|
+
headers: Headers;
|
|
191
|
+
errors: AppError[];
|
|
192
|
+
raw: unknown;
|
|
193
|
+
url: string;
|
|
194
|
+
};
|
|
195
|
+
type HttpClientOptions = {
|
|
196
|
+
baseUrl: string;
|
|
197
|
+
fetch?: typeof fetch;
|
|
198
|
+
auth: AuthConfig;
|
|
199
|
+
parser: ShapeParser;
|
|
200
|
+
cache?: CacheStore;
|
|
201
|
+
notifier?: Notifier;
|
|
202
|
+
redirects?: RedirectPolicy;
|
|
203
|
+
defaults?: {
|
|
204
|
+
headers?: Record<string, string>;
|
|
205
|
+
timeoutMs?: number;
|
|
206
|
+
shouldNotify?: (result: {
|
|
207
|
+
ok: boolean;
|
|
208
|
+
status: number;
|
|
209
|
+
}) => boolean;
|
|
210
|
+
};
|
|
211
|
+
guards?: Guard[];
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
declare function createHttpClient(opts: HttpClientOptions): {
|
|
215
|
+
get: <T>(path: string, ro?: RequestOptions<T>) => Promise<ClientResult<T>>;
|
|
216
|
+
post: <T>(path: string, ro?: RequestOptions<T>) => Promise<ClientResult<T>>;
|
|
217
|
+
put: <T>(path: string, ro?: RequestOptions<T>) => Promise<ClientResult<T>>;
|
|
218
|
+
patch: <T>(path: string, ro?: RequestOptions<T>) => Promise<ClientResult<T>>;
|
|
219
|
+
delete: <T>(path: string, ro?: RequestOptions<T>) => Promise<ClientResult<T>>;
|
|
220
|
+
cache: {
|
|
221
|
+
invalidateByTag: (tag: string) => number;
|
|
222
|
+
clear: () => void | undefined;
|
|
223
|
+
};
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Simple retry guard for transient HTTP statuses.
|
|
228
|
+
* Note: this doesn't retry network errors thrown by fetch itself (those are handled by the client wrapper).
|
|
229
|
+
*/
|
|
230
|
+
declare function retryGuard(opts?: {
|
|
231
|
+
retries?: number;
|
|
232
|
+
retryOn?: number[];
|
|
233
|
+
baseDelayMs?: number;
|
|
234
|
+
maxDelayMs?: number;
|
|
235
|
+
}): Guard;
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Simple in-memory LRU-ish cache with tag invalidation.
|
|
239
|
+
*/
|
|
240
|
+
declare function memoryCache(opts?: {
|
|
241
|
+
maxEntries?: number;
|
|
242
|
+
}): CacheStore;
|
|
243
|
+
|
|
244
|
+
declare function createApiParserRestClassic(opts?: {
|
|
245
|
+
successField?: string;
|
|
246
|
+
dataField?: string;
|
|
247
|
+
errorField?: string;
|
|
248
|
+
messageField?: string;
|
|
249
|
+
}): ShapeParser;
|
|
250
|
+
declare function createApiParserLaravel(opts?: {
|
|
251
|
+
dataField?: string;
|
|
252
|
+
}): ShapeParser;
|
|
253
|
+
declare function createApiParserNest(): ShapeParser;
|
|
254
|
+
declare function createApiParserGraphQL(opts?: {
|
|
255
|
+
allowPartialData?: boolean;
|
|
256
|
+
}): ShapeParser;
|
|
257
|
+
|
|
258
|
+
declare const consoleNotifier: Notifier;
|
|
259
|
+
declare const alertNotifier: Notifier;
|
|
260
|
+
/**
|
|
261
|
+
* Returns an HTML string you can inject in your own UI layer (Tailwind classes).
|
|
262
|
+
* Note: this package does not manipulate the DOM by itself.
|
|
263
|
+
*/
|
|
264
|
+
declare function tailwindAlertRenderer(event: NotifyEvent): string;
|
|
265
|
+
|
|
266
|
+
export { type AppError, type AuthConfig, type CacheEntry, type CacheOptions, type CacheStore, type ClientResult, type Guard, type HttpClientOptions, type HttpMethod, NetworkError, type Notifier, type NotifyEvent, ParseError, RedirectError, type RedirectMode, type RedirectPolicy, type RequestContext, type RequestOptions, type Schema, type ShapeParser, TimeoutError, alertNotifier, bearerAuth, consoleNotifier, cookieAuth, createApiParserGraphQL, createApiParserLaravel, createApiParserNest, createApiParserRestClassic, createHttpClient, createShapeParser, fromZod, memoryCache, noAuth, retryGuard, schema, tailwindAlertRenderer };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,660 @@
|
|
|
1
|
+
// src/internal/compose.ts
|
|
2
|
+
function composeGuards(guards, terminal) {
|
|
3
|
+
return (ctx) => {
|
|
4
|
+
let idx = -1;
|
|
5
|
+
const dispatch = (i, c) => {
|
|
6
|
+
if (i <= idx) return Promise.reject(new Error("composeGuards: next() called multiple times"));
|
|
7
|
+
idx = i;
|
|
8
|
+
const guard = guards[i];
|
|
9
|
+
if (!guard) return terminal(c);
|
|
10
|
+
return guard(c, (nextCtx) => dispatch(i + 1, nextCtx));
|
|
11
|
+
};
|
|
12
|
+
return dispatch(0, ctx);
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// src/internal/url.ts
|
|
17
|
+
function joinUrl(baseUrl, path) {
|
|
18
|
+
if (!baseUrl) return path;
|
|
19
|
+
if (path.startsWith("http://") || path.startsWith("https://")) return path;
|
|
20
|
+
const b = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
|
|
21
|
+
const p = path.startsWith("/") ? path : `/${path}`;
|
|
22
|
+
return `${b}${p}`;
|
|
23
|
+
}
|
|
24
|
+
function withQuery(url, query) {
|
|
25
|
+
if (!query) return url;
|
|
26
|
+
const u = new URL(url);
|
|
27
|
+
for (const [k, v] of Object.entries(query)) {
|
|
28
|
+
if (v === void 0 || v === null) continue;
|
|
29
|
+
u.searchParams.set(k, String(v));
|
|
30
|
+
}
|
|
31
|
+
return u.toString();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// src/internal/authGuard.ts
|
|
35
|
+
function sanitizeHeaderValue(v) {
|
|
36
|
+
if (/[\r\n]/.test(v)) return null;
|
|
37
|
+
return v;
|
|
38
|
+
}
|
|
39
|
+
function authGuard(auth) {
|
|
40
|
+
return async (ctx, next) => {
|
|
41
|
+
if (ctx.noAuth) return next(ctx);
|
|
42
|
+
if (auth.mode === "none") return next(ctx);
|
|
43
|
+
if (auth.mode === "bearer") {
|
|
44
|
+
const token = await auth.getToken();
|
|
45
|
+
if (token) {
|
|
46
|
+
const headerName = auth.headerName ?? "Authorization";
|
|
47
|
+
const prefix = auth.prefix ?? "Bearer";
|
|
48
|
+
const safe = sanitizeHeaderValue(`${prefix} ${token}`);
|
|
49
|
+
if (safe) ctx.headers.set(headerName, safe);
|
|
50
|
+
}
|
|
51
|
+
return next(ctx);
|
|
52
|
+
}
|
|
53
|
+
ctx.credentials = auth.credentials ?? "include";
|
|
54
|
+
if (auth.csrf) {
|
|
55
|
+
const csrf = await auth.csrf.getToken();
|
|
56
|
+
if (csrf) ctx.headers.set(auth.csrf.headerName, csrf);
|
|
57
|
+
}
|
|
58
|
+
return next(ctx);
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// src/internal/stableStringify.ts
|
|
63
|
+
function stableStringify(value) {
|
|
64
|
+
return JSON.stringify(sortValue(value));
|
|
65
|
+
}
|
|
66
|
+
function sortValue(v) {
|
|
67
|
+
if (v === null || typeof v !== "object") return v;
|
|
68
|
+
if (Array.isArray(v)) return v.map(sortValue);
|
|
69
|
+
const proto = Object.getPrototypeOf(v);
|
|
70
|
+
if (proto !== Object.prototype && proto !== null) return v;
|
|
71
|
+
const out = {};
|
|
72
|
+
for (const key of Object.keys(v).sort()) out[key] = sortValue(v[key]);
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/cache/key.ts
|
|
77
|
+
function makeCacheKey(parts) {
|
|
78
|
+
if (parts.keyOverride) return parts.keyOverride;
|
|
79
|
+
const headerObj = {};
|
|
80
|
+
if (parts.headers) {
|
|
81
|
+
const accept = parts.headers.get("accept");
|
|
82
|
+
const contentType = parts.headers.get("content-type");
|
|
83
|
+
if (accept) headerObj["accept"] = accept;
|
|
84
|
+
if (contentType) headerObj["content-type"] = contentType;
|
|
85
|
+
}
|
|
86
|
+
return stableStringify({
|
|
87
|
+
m: parts.method,
|
|
88
|
+
u: parts.url,
|
|
89
|
+
h: headerObj,
|
|
90
|
+
b: parts.body ?? null,
|
|
91
|
+
i: parts.idempotencyKey ?? null
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// src/cache/inFlight.ts
|
|
96
|
+
var InFlight = class {
|
|
97
|
+
map = /* @__PURE__ */ new Map();
|
|
98
|
+
get(key) {
|
|
99
|
+
return this.map.get(key);
|
|
100
|
+
}
|
|
101
|
+
set(key, p) {
|
|
102
|
+
this.map.set(key, p);
|
|
103
|
+
p.finally(() => {
|
|
104
|
+
if (this.map.get(key) === p) this.map.delete(key);
|
|
105
|
+
}).catch(() => {
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
clear() {
|
|
109
|
+
this.map.clear();
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// src/notify/defaults.ts
|
|
114
|
+
var consoleNotifier = {
|
|
115
|
+
notify: (e) => {
|
|
116
|
+
console[e.level === "error" ? "error" : "warn"](`[${e.type}] ${e.title}: ${e.message}`, e);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
var alertNotifier = {
|
|
120
|
+
notify: (e) => {
|
|
121
|
+
if (typeof window !== "undefined" && typeof window.alert === "function") window.alert(`${e.title}
|
|
122
|
+
|
|
123
|
+
${e.message}`);
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
function tailwindAlertRenderer(event) {
|
|
127
|
+
const base = "rounded-lg border p-4 shadow-sm";
|
|
128
|
+
const color = event.level === "warning" ? "border-amber-200 bg-amber-50 text-amber-900" : "border-red-200 bg-red-50 text-red-900";
|
|
129
|
+
const title = escapeHtml(event.title);
|
|
130
|
+
const msg = escapeHtml(event.message);
|
|
131
|
+
return `<div class="${base} ${color}"><div class="font-semibold">${title}</div><div class="mt-1 text-sm">${msg}</div></div>`;
|
|
132
|
+
}
|
|
133
|
+
function escapeHtml(s) {
|
|
134
|
+
return s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// src/client/errors.ts
|
|
138
|
+
var NetworkError = class extends Error {
|
|
139
|
+
constructor(message, cause) {
|
|
140
|
+
super(message);
|
|
141
|
+
this.cause = cause;
|
|
142
|
+
}
|
|
143
|
+
name = "NetworkError";
|
|
144
|
+
};
|
|
145
|
+
var TimeoutError = class extends Error {
|
|
146
|
+
name = "TimeoutError";
|
|
147
|
+
constructor(message = "Request timed out") {
|
|
148
|
+
super(message);
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
var RedirectError = class extends Error {
|
|
152
|
+
constructor(message, fromUrl, toUrl) {
|
|
153
|
+
super(message);
|
|
154
|
+
this.fromUrl = fromUrl;
|
|
155
|
+
this.toUrl = toUrl;
|
|
156
|
+
}
|
|
157
|
+
name = "RedirectError";
|
|
158
|
+
};
|
|
159
|
+
var ParseError = class extends Error {
|
|
160
|
+
constructor(message, raw, cause) {
|
|
161
|
+
super(message);
|
|
162
|
+
this.raw = raw;
|
|
163
|
+
this.cause = cause;
|
|
164
|
+
}
|
|
165
|
+
name = "ParseError";
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// src/client/httpClient.ts
|
|
169
|
+
var REDIRECT_STATUSES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
|
|
170
|
+
function createHttpClient(opts) {
|
|
171
|
+
const cache = opts.cache;
|
|
172
|
+
const inFlight = new InFlight();
|
|
173
|
+
const defaultNotifier = opts.notifier ?? consoleNotifier;
|
|
174
|
+
const shouldNotify = opts.defaults?.shouldNotify ?? ((r) => !r.ok);
|
|
175
|
+
const baseGuards = [authGuard(opts.auth), ...opts.guards ?? []];
|
|
176
|
+
async function terminal(ctx) {
|
|
177
|
+
const controller = ctx.timeoutMs ? new AbortController() : void 0;
|
|
178
|
+
const timeout = ctx.timeoutMs ? setTimeout(() => controller?.abort(new TimeoutError()), ctx.timeoutMs) : void 0;
|
|
179
|
+
try {
|
|
180
|
+
const init = {
|
|
181
|
+
method: ctx.method,
|
|
182
|
+
headers: ctx.headers,
|
|
183
|
+
...ctx.body !== void 0 ? { body: ctx.body } : {},
|
|
184
|
+
...ctx.signal ? { signal: controller ? anySignal([ctx.signal, controller.signal]) : ctx.signal } : controller ? { signal: controller.signal } : {},
|
|
185
|
+
...ctx.credentials !== void 0 ? { credentials: ctx.credentials } : {},
|
|
186
|
+
...ctx.redirect !== void 0 ? { redirect: ctx.redirect } : {}
|
|
187
|
+
};
|
|
188
|
+
return await ctx._fetch(ctx.url, init);
|
|
189
|
+
} finally {
|
|
190
|
+
if (timeout) clearTimeout(timeout);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
const pipeline = composeGuards(baseGuards, terminal);
|
|
194
|
+
async function request(method, path, ro) {
|
|
195
|
+
const requestUrl = withQuery(joinUrl(opts.baseUrl, path), ro?.query);
|
|
196
|
+
const headers = new Headers();
|
|
197
|
+
if (opts.defaults?.headers) for (const [k, v] of Object.entries(opts.defaults.headers)) headers.set(k, v);
|
|
198
|
+
if (ro?.headers) {
|
|
199
|
+
for (const [k, v] of Object.entries(ro.headers)) if (v !== void 0) headers.set(k, v);
|
|
200
|
+
}
|
|
201
|
+
let body;
|
|
202
|
+
let bodyForKey = void 0;
|
|
203
|
+
if (ro?.json !== void 0) {
|
|
204
|
+
if (!headers.has("content-type")) headers.set("content-type", "application/json");
|
|
205
|
+
if (!headers.has("accept")) headers.set("accept", "application/json");
|
|
206
|
+
body = JSON.stringify(ro.json);
|
|
207
|
+
bodyForKey = ro.json;
|
|
208
|
+
} else if (ro?.body !== void 0) {
|
|
209
|
+
body = ro.body;
|
|
210
|
+
bodyForKey = "[body]";
|
|
211
|
+
if (!headers.has("accept")) headers.set("accept", "application/json");
|
|
212
|
+
} else {
|
|
213
|
+
if (!headers.has("accept")) headers.set("accept", "application/json");
|
|
214
|
+
}
|
|
215
|
+
const timeoutMs = ro?.timeoutMs ?? opts.defaults?.timeoutMs;
|
|
216
|
+
const notifier = ro?.notifier ?? defaultNotifier;
|
|
217
|
+
const cacheOpts = ro?.cache;
|
|
218
|
+
const wantsCache = method === "GET" && !!cache && !!cacheOpts;
|
|
219
|
+
const dedupe = cacheOpts?.dedupe ?? true;
|
|
220
|
+
const canDedupeWrite = method !== "GET" && !!ro?.idempotencyKey;
|
|
221
|
+
const inflightKey = (wantsCache || canDedupeWrite) && dedupe ? makeCacheKey({ method, url: requestUrl, headers, ...method === "GET" ? {} : { body: bodyForKey }, ...ro?.idempotencyKey !== void 0 ? { idempotencyKey: ro.idempotencyKey } : {}, ...cacheOpts?.key !== void 0 ? { keyOverride: cacheOpts.key } : {} }) : void 0;
|
|
222
|
+
if (inflightKey) {
|
|
223
|
+
const existing = inFlight.get(inflightKey);
|
|
224
|
+
if (existing) {
|
|
225
|
+
try {
|
|
226
|
+
const stored = await existing;
|
|
227
|
+
return asClientResult(stored, requestUrl, opts, ro, notifier, shouldNotify);
|
|
228
|
+
} catch {
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
const promise = (async () => {
|
|
233
|
+
if (wantsCache) {
|
|
234
|
+
const key = inflightKey;
|
|
235
|
+
const cached = cache.get(key);
|
|
236
|
+
const policy = cacheOpts?.policy ?? "cacheFirst";
|
|
237
|
+
if (cached) {
|
|
238
|
+
if (policy === "networkOnly") ; else if (policy === "staleWhileRevalidate") {
|
|
239
|
+
void refreshInBackground(key);
|
|
240
|
+
return { kind: "cache-hit", url: requestUrl, status: 200, headers: new Headers(), raw: cached.value };
|
|
241
|
+
} else if (policy === "cacheOnly" || policy === "cacheFirst") {
|
|
242
|
+
return { kind: "cache-hit", url: requestUrl, status: 200, headers: new Headers(), raw: cached.value };
|
|
243
|
+
}
|
|
244
|
+
} else if (policy === "cacheOnly") {
|
|
245
|
+
return { kind: "cache-miss", url: requestUrl, status: 0, headers: new Headers(), raw: null };
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
const res = await fetchWithRedirects({
|
|
249
|
+
opts,
|
|
250
|
+
pipeline,
|
|
251
|
+
method,
|
|
252
|
+
url: requestUrl,
|
|
253
|
+
headers,
|
|
254
|
+
...body !== void 0 ? { body } : {},
|
|
255
|
+
...ro?.signal ? { signal: ro.signal } : {},
|
|
256
|
+
...timeoutMs !== void 0 ? { timeoutMs } : {},
|
|
257
|
+
...ro?.noAuth ? { noAuth: true } : {}
|
|
258
|
+
});
|
|
259
|
+
const raw = await decodeResponse(res);
|
|
260
|
+
const envelope = { kind: "network", url: res.url || requestUrl, status: res.status, headers: res.headers, raw };
|
|
261
|
+
if (wantsCache && cacheOpts && res.ok) {
|
|
262
|
+
const key = inflightKey;
|
|
263
|
+
cache.set(key, { expiresAt: Date.now() + cacheOpts.ttlMs, value: raw, ...cacheOpts.tags ? { tags: cacheOpts.tags } : {} });
|
|
264
|
+
}
|
|
265
|
+
return envelope;
|
|
266
|
+
async function refreshInBackground(key) {
|
|
267
|
+
try {
|
|
268
|
+
const res2 = await fetchWithRedirects({
|
|
269
|
+
opts,
|
|
270
|
+
pipeline,
|
|
271
|
+
method,
|
|
272
|
+
url: requestUrl,
|
|
273
|
+
headers,
|
|
274
|
+
...body !== void 0 ? { body } : {},
|
|
275
|
+
...ro?.signal ? { signal: ro.signal } : {},
|
|
276
|
+
...timeoutMs !== void 0 ? { timeoutMs } : {},
|
|
277
|
+
...ro?.noAuth ? { noAuth: true } : {}
|
|
278
|
+
});
|
|
279
|
+
if (!res2.ok) return;
|
|
280
|
+
const raw2 = await decodeResponse(res2);
|
|
281
|
+
cache.set(key, { expiresAt: Date.now() + cacheOpts.ttlMs, value: raw2, ...cacheOpts.tags ? { tags: cacheOpts.tags } : {} });
|
|
282
|
+
} catch {
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
})();
|
|
286
|
+
if (inflightKey) inFlight.set(inflightKey, promise);
|
|
287
|
+
try {
|
|
288
|
+
const stored = await promise;
|
|
289
|
+
return asClientResult(stored, requestUrl, opts, ro, notifier, shouldNotify);
|
|
290
|
+
} catch (e) {
|
|
291
|
+
const err = e instanceof TimeoutError ? e : new NetworkError("Network request failed", e);
|
|
292
|
+
if (shouldNotify({ ok: false, status: 0 })) notifier.notify({ type: "network-error", level: "error", title: "Network error", message: err.message, url: requestUrl });
|
|
293
|
+
return { ok: false, status: 0, headers: new Headers(), errors: [{ message: err.message, details: e }], raw: null, url: requestUrl };
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return {
|
|
297
|
+
get: (path, ro) => request("GET", path, ro),
|
|
298
|
+
post: (path, ro) => request("POST", path, ro),
|
|
299
|
+
put: (path, ro) => request("PUT", path, ro),
|
|
300
|
+
patch: (path, ro) => request("PATCH", path, ro),
|
|
301
|
+
delete: (path, ro) => request("DELETE", path, ro),
|
|
302
|
+
cache: { invalidateByTag: (tag) => cache?.invalidateByTag(tag) ?? 0, clear: () => cache?.clear() }
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
async function fetchWithRedirects(args) {
|
|
306
|
+
const policy = args.opts.redirects ?? { mode: "follow", maxHops: 5 };
|
|
307
|
+
const maxHops = policy.maxHops ?? 5;
|
|
308
|
+
if (policy.mode === "follow") return args.pipeline(makeCtx(args, "follow"));
|
|
309
|
+
if (policy.mode === "error") return args.pipeline(makeCtx(args, "error"));
|
|
310
|
+
let currentUrl = args.url;
|
|
311
|
+
let method = args.method;
|
|
312
|
+
let body = args.body;
|
|
313
|
+
for (let hop = 0; hop <= maxHops; hop++) {
|
|
314
|
+
const res = await args.pipeline(
|
|
315
|
+
makeCtx({
|
|
316
|
+
...args,
|
|
317
|
+
url: currentUrl,
|
|
318
|
+
method,
|
|
319
|
+
...body !== void 0 ? { body } : {}
|
|
320
|
+
}, "manual")
|
|
321
|
+
);
|
|
322
|
+
if (!REDIRECT_STATUSES.has(res.status)) return res;
|
|
323
|
+
const loc = res.headers.get("location");
|
|
324
|
+
if (!loc) throw new RedirectError("Redirect without Location header", currentUrl);
|
|
325
|
+
const nextUrl = new URL(loc, currentUrl).toString();
|
|
326
|
+
const decision = policy.onRedirect?.({ fromUrl: currentUrl, toUrl: nextUrl, status: res.status, hop, method });
|
|
327
|
+
if (decision?.action === "deny") throw new RedirectError(decision.reason ?? "Redirect denied by policy", currentUrl, nextUrl);
|
|
328
|
+
const finalUrl = decision?.action === "modify" ? decision.url : nextUrl;
|
|
329
|
+
stripAuthOnCrossOriginRedirect(args.opts, args.url, finalUrl, args.headers);
|
|
330
|
+
if (res.status === 303) {
|
|
331
|
+
method = "GET";
|
|
332
|
+
body = void 0;
|
|
333
|
+
} else if ((res.status === 301 || res.status === 302) && method !== "GET") {
|
|
334
|
+
method = "GET";
|
|
335
|
+
body = void 0;
|
|
336
|
+
}
|
|
337
|
+
currentUrl = finalUrl;
|
|
338
|
+
if (hop === maxHops) throw new RedirectError(`Too many redirects (>${maxHops})`, args.url, currentUrl);
|
|
339
|
+
}
|
|
340
|
+
return args.pipeline(makeCtx(args, "manual"));
|
|
341
|
+
}
|
|
342
|
+
function stripAuthOnCrossOriginRedirect(opts, initialUrl, toUrl, headers) {
|
|
343
|
+
try {
|
|
344
|
+
if (opts.auth.mode !== "bearer") return;
|
|
345
|
+
const initialOrigin = new URL(initialUrl).origin;
|
|
346
|
+
const toOrigin = new URL(toUrl).origin;
|
|
347
|
+
if (initialOrigin !== toOrigin) {
|
|
348
|
+
const headerName = opts.auth.headerName ?? "Authorization";
|
|
349
|
+
headers.delete(headerName);
|
|
350
|
+
}
|
|
351
|
+
} catch {
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
function makeCtx(args, redirect) {
|
|
355
|
+
return {
|
|
356
|
+
method: args.method,
|
|
357
|
+
url: args.url,
|
|
358
|
+
headers: args.headers,
|
|
359
|
+
...args.body !== void 0 ? { body: args.body } : {},
|
|
360
|
+
...args.signal ? { signal: args.signal } : {},
|
|
361
|
+
...args.timeoutMs !== void 0 ? { timeoutMs: args.timeoutMs } : {},
|
|
362
|
+
...args.noAuth ? { noAuth: true } : {},
|
|
363
|
+
attempt: 0,
|
|
364
|
+
auth: args.opts.auth,
|
|
365
|
+
redirect,
|
|
366
|
+
_fetch: args.opts.fetch ?? fetch
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
async function decodeResponse(res) {
|
|
370
|
+
const ct = res.headers.get("content-type") ?? "";
|
|
371
|
+
if (ct.includes("application/json")) {
|
|
372
|
+
try {
|
|
373
|
+
return await res.json();
|
|
374
|
+
} catch (e) {
|
|
375
|
+
throw new ParseError("Failed to parse JSON response", void 0, e);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
try {
|
|
379
|
+
return await res.text();
|
|
380
|
+
} catch (e) {
|
|
381
|
+
throw new ParseError("Failed to read response body", void 0, e);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
function asClientResult(envelope, requestUrl, opts, ro, notifier, shouldNotify) {
|
|
385
|
+
if (envelope?.kind === "cache-miss") {
|
|
386
|
+
const errors2 = [{ message: "Cache miss" }];
|
|
387
|
+
if (shouldNotify({ ok: false, status: 0 })) notifier.notify({ type: "http-error", level: "warning", title: "Cache", message: "Cache miss", status: 0, errors: errors2, url: requestUrl });
|
|
388
|
+
return { ok: false, status: 0, headers: new Headers(), errors: errors2, raw: envelope.raw, url: requestUrl };
|
|
389
|
+
}
|
|
390
|
+
const status = envelope.status ?? 0;
|
|
391
|
+
const raw = envelope.raw;
|
|
392
|
+
const resHeaders = envelope.headers ?? new Headers();
|
|
393
|
+
const finalUrl = envelope.url ?? requestUrl;
|
|
394
|
+
const okByParser = opts.parser.isSuccess(raw, status);
|
|
395
|
+
if (okByParser && status >= 200 && status < 400) {
|
|
396
|
+
const dataRaw = opts.parser.getData(raw);
|
|
397
|
+
try {
|
|
398
|
+
const data = ro?.dataSchema ? ro.dataSchema.parse(dataRaw) : dataRaw;
|
|
399
|
+
return { ok: true, status, headers: resHeaders, data, raw, url: finalUrl };
|
|
400
|
+
} catch (e) {
|
|
401
|
+
const msg2 = e instanceof Error ? e.message : "Schema parse failed";
|
|
402
|
+
if (shouldNotify({ ok: false, status })) notifier.notify({ type: "parse-error", level: "error", title: "Parse error", message: msg2, url: finalUrl });
|
|
403
|
+
return { ok: false, status, headers: resHeaders, errors: [{ message: msg2, details: e }], raw, url: finalUrl };
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
const errors = opts.parser.getErrors(raw, status);
|
|
407
|
+
const msg = errors[0]?.message ?? (status ? `HTTP ${status}` : "Request failed");
|
|
408
|
+
if (shouldNotify({ ok: false, status })) notifier.notify({ type: "http-error", level: status >= 500 ? "error" : "warning", title: "Request failed", message: msg, status, errors, url: finalUrl });
|
|
409
|
+
return { ok: false, status, headers: resHeaders, errors, raw, url: finalUrl };
|
|
410
|
+
}
|
|
411
|
+
function anySignal(signals) {
|
|
412
|
+
const valid = signals.filter(Boolean);
|
|
413
|
+
const controller = new AbortController();
|
|
414
|
+
const onAbort = () => controller.abort();
|
|
415
|
+
for (const s of valid) {
|
|
416
|
+
if (s.aborted) controller.abort();
|
|
417
|
+
s.addEventListener("abort", onAbort, { once: true });
|
|
418
|
+
}
|
|
419
|
+
return controller.signal;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// src/auth/authConfig.ts
|
|
423
|
+
function bearerAuth(getToken, opts) {
|
|
424
|
+
return {
|
|
425
|
+
mode: "bearer",
|
|
426
|
+
getToken,
|
|
427
|
+
...opts?.headerName !== void 0 ? { headerName: opts.headerName } : {},
|
|
428
|
+
...opts?.prefix !== void 0 ? { prefix: opts.prefix } : {}
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
function cookieAuth(opts) {
|
|
432
|
+
return {
|
|
433
|
+
mode: "cookie",
|
|
434
|
+
...opts?.credentials !== void 0 ? { credentials: opts.credentials } : {},
|
|
435
|
+
...opts?.csrf !== void 0 ? { csrf: opts.csrf } : {}
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
function noAuth() {
|
|
439
|
+
return { mode: "none" };
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// src/guards/retryGuard.ts
|
|
443
|
+
function retryGuard(opts) {
|
|
444
|
+
const retries = opts?.retries ?? 0;
|
|
445
|
+
const retryOn = new Set(opts?.retryOn ?? [502, 503, 504]);
|
|
446
|
+
const baseDelay = opts?.baseDelayMs ?? 150;
|
|
447
|
+
const maxDelay = opts?.maxDelayMs ?? 1500;
|
|
448
|
+
return async (ctx, next) => {
|
|
449
|
+
let last;
|
|
450
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
451
|
+
ctx.attempt = attempt;
|
|
452
|
+
last = await next(ctx);
|
|
453
|
+
if (!retryOn.has(last.status)) return last;
|
|
454
|
+
if (attempt === retries) return last;
|
|
455
|
+
const delay = Math.min(maxDelay, baseDelay * 2 ** attempt);
|
|
456
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
457
|
+
}
|
|
458
|
+
return last;
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// src/cache/memoryCache.ts
|
|
463
|
+
function memoryCache(opts) {
|
|
464
|
+
const max = opts?.maxEntries ?? 1e3;
|
|
465
|
+
const map = /* @__PURE__ */ new Map();
|
|
466
|
+
const tagIndex = /* @__PURE__ */ new Map();
|
|
467
|
+
function touch(key) {
|
|
468
|
+
const v = map.get(key);
|
|
469
|
+
if (!v) return;
|
|
470
|
+
map.delete(key);
|
|
471
|
+
map.set(key, v);
|
|
472
|
+
}
|
|
473
|
+
function deindexTags(key, entry) {
|
|
474
|
+
const tags = entry?.tags;
|
|
475
|
+
if (!tags?.length) return;
|
|
476
|
+
for (const t of tags) {
|
|
477
|
+
const set = tagIndex.get(t);
|
|
478
|
+
if (!set) continue;
|
|
479
|
+
set.delete(key);
|
|
480
|
+
if (set.size === 0) tagIndex.delete(t);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
function indexTags(key, tags) {
|
|
484
|
+
if (!tags?.length) return;
|
|
485
|
+
for (const t of tags) {
|
|
486
|
+
let set = tagIndex.get(t);
|
|
487
|
+
if (!set) {
|
|
488
|
+
set = /* @__PURE__ */ new Set();
|
|
489
|
+
tagIndex.set(t, set);
|
|
490
|
+
}
|
|
491
|
+
set.add(key);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
function del(key) {
|
|
495
|
+
const prev = map.get(key);
|
|
496
|
+
if (prev) deindexTags(key, prev);
|
|
497
|
+
map.delete(key);
|
|
498
|
+
}
|
|
499
|
+
function ensureLimit() {
|
|
500
|
+
while (map.size > max) {
|
|
501
|
+
const firstKey = map.keys().next().value;
|
|
502
|
+
if (!firstKey) break;
|
|
503
|
+
del(firstKey);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
return {
|
|
507
|
+
get(key) {
|
|
508
|
+
const entry = map.get(key);
|
|
509
|
+
if (!entry) return void 0;
|
|
510
|
+
if (Date.now() > entry.expiresAt) {
|
|
511
|
+
del(key);
|
|
512
|
+
return void 0;
|
|
513
|
+
}
|
|
514
|
+
touch(key);
|
|
515
|
+
return entry;
|
|
516
|
+
},
|
|
517
|
+
set(key, entry) {
|
|
518
|
+
const prev = map.get(key);
|
|
519
|
+
if (prev) deindexTags(key, prev);
|
|
520
|
+
map.set(key, entry);
|
|
521
|
+
indexTags(key, entry.tags);
|
|
522
|
+
ensureLimit();
|
|
523
|
+
},
|
|
524
|
+
delete: del,
|
|
525
|
+
invalidateByTag(tag) {
|
|
526
|
+
const set = tagIndex.get(tag);
|
|
527
|
+
if (!set) return 0;
|
|
528
|
+
const keys = Array.from(set);
|
|
529
|
+
let n = 0;
|
|
530
|
+
for (const k of keys) {
|
|
531
|
+
if (map.has(k)) {
|
|
532
|
+
del(k);
|
|
533
|
+
n++;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
return n;
|
|
537
|
+
},
|
|
538
|
+
clear() {
|
|
539
|
+
map.clear();
|
|
540
|
+
tagIndex.clear();
|
|
541
|
+
}
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// src/parsing/shapeParser.ts
|
|
546
|
+
function createShapeParser(p) {
|
|
547
|
+
return p;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// src/parsing/schemaAdapter.ts
|
|
551
|
+
function schema(impl) {
|
|
552
|
+
return impl;
|
|
553
|
+
}
|
|
554
|
+
function fromZod(zodSchema) {
|
|
555
|
+
if (typeof zodSchema.parse === "function") return { parse: (i) => zodSchema.parse(i) };
|
|
556
|
+
if (typeof zodSchema.safeParse === "function") {
|
|
557
|
+
return {
|
|
558
|
+
parse: (i) => {
|
|
559
|
+
const r = zodSchema.safeParse(i);
|
|
560
|
+
if (r && r.success) return r.data;
|
|
561
|
+
throw new Error("Schema validation failed");
|
|
562
|
+
}
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
throw new Error("Unsupported zod-like schema: expected parse or safeParse");
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// src/parsing/presets.ts
|
|
569
|
+
function createApiParserRestClassic(opts) {
|
|
570
|
+
const successField = opts?.successField ?? "success";
|
|
571
|
+
const dataField = opts?.dataField ?? "data";
|
|
572
|
+
const errorField = opts?.errorField ?? "error";
|
|
573
|
+
const messageField = opts?.messageField ?? "message";
|
|
574
|
+
return createShapeParser({
|
|
575
|
+
isSuccess: (raw, status) => {
|
|
576
|
+
if (raw && typeof raw === "object" && successField in raw) return Boolean(raw[successField]);
|
|
577
|
+
return status >= 200 && status < 400;
|
|
578
|
+
},
|
|
579
|
+
getData: (raw) => raw && typeof raw === "object" && dataField in raw ? raw[dataField] : raw,
|
|
580
|
+
getErrors: (raw, status) => {
|
|
581
|
+
if (!raw || typeof raw !== "object") return status ? [{ message: `HTTP ${status}` }] : [{ message: "Request failed" }];
|
|
582
|
+
const r = raw;
|
|
583
|
+
const err = r[errorField] ?? r[messageField] ?? r["errors"];
|
|
584
|
+
if (!err) return status ? [{ message: `HTTP ${status}` }] : [{ message: "Request failed" }];
|
|
585
|
+
if (typeof err === "string") return [{ message: err }];
|
|
586
|
+
if (Array.isArray(err)) return err.map((e) => ({ message: String(e) }));
|
|
587
|
+
if (typeof err === "object") {
|
|
588
|
+
if (typeof err.message === "string") return [{ message: err.message, ...err.code ? { code: String(err.code) } : {}, details: err }];
|
|
589
|
+
return [{ message: String(r[messageField] ?? "Request failed"), details: err }];
|
|
590
|
+
}
|
|
591
|
+
return [{ message: String(err) }];
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
function createApiParserLaravel(opts) {
|
|
596
|
+
const dataField = opts?.dataField ?? "data";
|
|
597
|
+
return createShapeParser({
|
|
598
|
+
isSuccess: (_raw, status) => status >= 200 && status < 400,
|
|
599
|
+
getData: (raw) => raw && typeof raw === "object" && dataField in raw ? raw[dataField] : raw,
|
|
600
|
+
getErrors: (raw, status) => laravelErrors(raw, status)
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
function laravelErrors(raw, status) {
|
|
604
|
+
if (!raw || typeof raw !== "object") return status ? [{ message: `HTTP ${status}` }] : [{ message: "Request failed" }];
|
|
605
|
+
const r = raw;
|
|
606
|
+
if (r.errors && typeof r.errors === "object") {
|
|
607
|
+
const out = [];
|
|
608
|
+
for (const [field, msgs] of Object.entries(r.errors)) {
|
|
609
|
+
if (Array.isArray(msgs)) for (const m of msgs) out.push({ field, message: String(m) });
|
|
610
|
+
else out.push({ field, message: String(msgs) });
|
|
611
|
+
}
|
|
612
|
+
if (out.length) return out;
|
|
613
|
+
}
|
|
614
|
+
const msg = typeof r.message === "string" ? r.message : status ? `HTTP ${status}` : "Request failed";
|
|
615
|
+
return [{ message: msg, details: r }];
|
|
616
|
+
}
|
|
617
|
+
function createApiParserNest() {
|
|
618
|
+
return createShapeParser({
|
|
619
|
+
isSuccess: (_raw, status) => status >= 200 && status < 400,
|
|
620
|
+
getData: (raw) => raw,
|
|
621
|
+
getErrors: (raw, status) => nestErrors(raw, status)
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
function nestErrors(raw, status) {
|
|
625
|
+
if (!raw || typeof raw !== "object") return status ? [{ message: `HTTP ${status}` }] : [{ message: "Request failed" }];
|
|
626
|
+
const r = raw;
|
|
627
|
+
const msg = r.message;
|
|
628
|
+
if (Array.isArray(msg)) return msg.map((m) => ({ message: String(m), details: r }));
|
|
629
|
+
if (typeof msg === "string") return [{ message: msg, ...r.error ? { code: String(r.error) } : {}, details: r }];
|
|
630
|
+
return [{ message: status ? `HTTP ${status}` : "Request failed", details: r }];
|
|
631
|
+
}
|
|
632
|
+
function createApiParserGraphQL(opts) {
|
|
633
|
+
const allowPartial = opts?.allowPartialData ?? false;
|
|
634
|
+
return createShapeParser({
|
|
635
|
+
isSuccess: (raw, status) => {
|
|
636
|
+
if (status < 200 || status >= 400) return false;
|
|
637
|
+
if (!raw || typeof raw !== "object") return false;
|
|
638
|
+
const r = raw;
|
|
639
|
+
const hasErrors = Array.isArray(r.errors) && r.errors.length > 0;
|
|
640
|
+
if (hasErrors && !allowPartial) return false;
|
|
641
|
+
return "data" in r;
|
|
642
|
+
},
|
|
643
|
+
getData: (raw) => raw && typeof raw === "object" ? raw.data : raw,
|
|
644
|
+
getErrors: (raw, status) => {
|
|
645
|
+
if (!raw || typeof raw !== "object") return status ? [{ message: `HTTP ${status}` }] : [{ message: "GraphQL request failed" }];
|
|
646
|
+
const r = raw;
|
|
647
|
+
const errs = Array.isArray(r.errors) ? r.errors : [];
|
|
648
|
+
if (!errs.length) return status >= 400 ? [{ message: `HTTP ${status}` }] : [{ message: "GraphQL request failed" }];
|
|
649
|
+
return errs.map((e) => ({
|
|
650
|
+
message: typeof e?.message === "string" ? e.message : "GraphQL error",
|
|
651
|
+
code: e?.extensions?.code ? String(e.extensions.code) : void 0,
|
|
652
|
+
details: e
|
|
653
|
+
}));
|
|
654
|
+
}
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
export { NetworkError, ParseError, RedirectError, TimeoutError, alertNotifier, bearerAuth, consoleNotifier, cookieAuth, createApiParserGraphQL, createApiParserLaravel, createApiParserNest, createApiParserRestClassic, createHttpClient, createShapeParser, fromZod, memoryCache, noAuth, retryGuard, schema, tailwindAlertRenderer };
|
|
659
|
+
//# sourceMappingURL=index.js.map
|
|
660
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/internal/compose.ts","../src/internal/url.ts","../src/internal/authGuard.ts","../src/internal/stableStringify.ts","../src/cache/key.ts","../src/cache/inFlight.ts","../src/notify/defaults.ts","../src/client/errors.ts","../src/client/httpClient.ts","../src/auth/authConfig.ts","../src/guards/retryGuard.ts","../src/cache/memoryCache.ts","../src/parsing/shapeParser.ts","../src/parsing/schemaAdapter.ts","../src/parsing/presets.ts"],"names":["errors","msg"],"mappings":"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","file":"index.js","sourcesContent":["import type { Guard } from \"../guards/types.js\";\nimport type { RequestContext } from \"./context.js\";\n\n/**\n * Koa-like middleware composition with correct TypeScript typing.\n */\nexport function composeGuards(\n guards: Guard[],\n terminal: (ctx: RequestContext) => Promise<Response>\n): (ctx: RequestContext) => Promise<Response> {\n return (ctx: RequestContext) => {\n let idx = -1;\n\n const dispatch = (i: number, c: RequestContext): Promise<Response> => {\n if (i <= idx) return Promise.reject(new Error(\"composeGuards: next() called multiple times\"));\n idx = i;\n const guard = guards[i];\n if (!guard) return terminal(c);\n return guard(c, (nextCtx) => dispatch(i + 1, nextCtx));\n };\n\n return dispatch(0, ctx);\n };\n}\n","export function joinUrl(baseUrl: string, path: string): string {\n if (!baseUrl) return path;\n if (path.startsWith(\"http://\") || path.startsWith(\"https://\")) return path;\n const b = baseUrl.endsWith(\"/\") ? baseUrl.slice(0, -1) : baseUrl;\n const p = path.startsWith(\"/\") ? path : `/${path}`;\n return `${b}${p}`;\n}\n\nexport function withQuery(url: string, query?: Record<string, string | number | boolean | null | undefined>): string {\n if (!query) return url;\n const u = new URL(url);\n for (const [k, v] of Object.entries(query)) {\n if (v === undefined || v === null) continue;\n u.searchParams.set(k, String(v));\n }\n return u.toString();\n}\n","import type { Guard } from \"../guards/types.js\";\nimport type { AuthConfig } from \"../auth/authConfig.js\";\n\nfunction sanitizeHeaderValue(v: string): string | null {\n // Prevent header injection via CRLF\n if (/[\\r\\n]/.test(v)) return null;\n return v;\n}\n\nexport function authGuard(auth: AuthConfig): Guard {\n return async (ctx, next) => {\n if (ctx.noAuth) return next(ctx);\n if (auth.mode === \"none\") return next(ctx);\n\n if (auth.mode === \"bearer\") {\n const token = await auth.getToken();\n if (token) {\n const headerName = auth.headerName ?? \"Authorization\";\n const prefix = auth.prefix ?? \"Bearer\";\n const safe = sanitizeHeaderValue(`${prefix} ${token}`);\n if (safe) ctx.headers.set(headerName, safe);\n }\n return next(ctx);\n }\n\n ctx.credentials = auth.credentials ?? \"include\";\n if (auth.csrf) {\n const csrf = await auth.csrf.getToken();\n if (csrf) ctx.headers.set(auth.csrf.headerName, csrf);\n }\n return next(ctx);\n };\n}\n","export function stableStringify(value: unknown): string {\n return JSON.stringify(sortValue(value));\n}\n\nfunction sortValue(v: any): any {\n if (v === null || typeof v !== \"object\") return v;\n if (Array.isArray(v)) return v.map(sortValue);\n\n const proto = Object.getPrototypeOf(v);\n if (proto !== Object.prototype && proto !== null) return v;\n\n const out: Record<string, any> = {};\n for (const key of Object.keys(v).sort()) out[key] = sortValue(v[key]);\n return out;\n}\n","import { stableStringify } from \"../internal/stableStringify.js\";\n\nexport function makeCacheKey(parts: {\n method: string;\n url: string;\n headers?: Headers;\n body?: unknown;\n idempotencyKey?: string;\n keyOverride?: string;\n}): string {\n if (parts.keyOverride) return parts.keyOverride;\n\n const headerObj: Record<string, string> = {};\n if (parts.headers) {\n const accept = parts.headers.get(\"accept\");\n const contentType = parts.headers.get(\"content-type\");\n if (accept) headerObj[\"accept\"] = accept;\n if (contentType) headerObj[\"content-type\"] = contentType;\n }\n\n return stableStringify({\n m: parts.method,\n u: parts.url,\n h: headerObj,\n b: parts.body ?? null,\n i: parts.idempotencyKey ?? null,\n });\n}\n","/**\n * In-flight request de-duplication.\n */\nexport class InFlight {\n private readonly map = new Map<string, Promise<unknown>>();\n\n get(key: string) {\n return this.map.get(key);\n }\n\n set(key: string, p: Promise<unknown>) {\n this.map.set(key, p);\n p.finally(() => {\n if (this.map.get(key) === p) this.map.delete(key);\n }).catch(() => {});\n }\n\n clear() {\n this.map.clear();\n }\n}\n","import type { Notifier, NotifyEvent } from \"./types.js\";\n\nexport const consoleNotifier: Notifier = {\n notify: (e: NotifyEvent) => {\n console[e.level === \"error\" ? \"error\" : \"warn\"](`[${e.type}] ${e.title}: ${e.message}`, e);\n },\n};\n\nexport const alertNotifier: Notifier = {\n notify: (e: NotifyEvent) => {\n if (typeof window !== \"undefined\" && typeof window.alert === \"function\") window.alert(`${e.title}\\n\\n${e.message}`);\n },\n};\n\n/**\n * Returns an HTML string you can inject in your own UI layer (Tailwind classes).\n * Note: this package does not manipulate the DOM by itself.\n */\nexport function tailwindAlertRenderer(event: NotifyEvent): string {\n const base = \"rounded-lg border p-4 shadow-sm\";\n const color =\n event.level === \"warning\"\n ? \"border-amber-200 bg-amber-50 text-amber-900\"\n : \"border-red-200 bg-red-50 text-red-900\";\n\n const title = escapeHtml(event.title);\n const msg = escapeHtml(event.message);\n\n return `<div class=\"${base} ${color}\"><div class=\"font-semibold\">${title}</div><div class=\"mt-1 text-sm\">${msg}</div></div>`;\n}\n\nfunction escapeHtml(s: string): string {\n return s.replace(/[&<>\"']/g, (c) => ({ \"&\": \"&\", \"<\": \"<\", \">\": \">\", '\"': \""\", \"'\": \"'\" }[c]!));\n}\n","export type AppError = {\n message: string;\n code?: string;\n field?: string;\n details?: unknown;\n};\n\nexport class NetworkError extends Error {\n name = \"NetworkError\";\n constructor(message: string, public readonly cause?: unknown) {\n super(message);\n }\n}\n\nexport class TimeoutError extends Error {\n name = \"TimeoutError\";\n constructor(message = \"Request timed out\") {\n super(message);\n }\n}\n\nexport class RedirectError extends Error {\n name = \"RedirectError\";\n constructor(message: string, public readonly fromUrl: string, public readonly toUrl?: string) {\n super(message);\n }\n}\n\nexport class ParseError extends Error {\n name = \"ParseError\";\n constructor(message: string, public readonly raw?: unknown, public readonly cause?: unknown) {\n super(message);\n }\n}\n","import type { HttpClientOptions, RequestOptions, ClientResult, HttpMethod } from \"./types.js\";\nimport { composeGuards } from \"../internal/compose.js\";\nimport { joinUrl, withQuery } from \"../internal/url.js\";\nimport type { RequestContext } from \"../internal/context.js\";\nimport { authGuard } from \"../internal/authGuard.js\";\nimport { makeCacheKey } from \"../cache/key.js\";\nimport { InFlight } from \"../cache/inFlight.js\";\nimport { consoleNotifier } from \"../notify/defaults.js\";\nimport { NetworkError, TimeoutError, RedirectError, ParseError, type AppError } from \"./errors.js\";\n\nconst REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);\n\nexport function createHttpClient(opts: HttpClientOptions) {\n const cache = opts.cache;\n const inFlight = new InFlight();\n\n const defaultNotifier = opts.notifier ?? consoleNotifier;\n const shouldNotify = opts.defaults?.shouldNotify ?? ((r) => !r.ok);\n\n const baseGuards = [authGuard(opts.auth), ...(opts.guards ?? [])];\n\n async function terminal(ctx: RequestContext): Promise<Response> {\n const controller = ctx.timeoutMs ? new AbortController() : undefined;\n const timeout = ctx.timeoutMs ? setTimeout(() => controller?.abort(new TimeoutError()), ctx.timeoutMs) : undefined;\n\n try {\n const init: RequestInit = {\n method: ctx.method,\n headers: ctx.headers,\n ...(ctx.body !== undefined ? { body: ctx.body } : {}),\n ...(ctx.signal\n ? { signal: controller ? anySignal([ctx.signal, controller.signal]) : ctx.signal }\n : (controller ? { signal: controller.signal } : {})),\n ...(ctx.credentials !== undefined ? { credentials: ctx.credentials } : {}),\n ...(ctx.redirect !== undefined ? { redirect: ctx.redirect } : {}),\n};\n\nreturn await ctx._fetch(ctx.url, init);\n\n } finally {\n if (timeout) clearTimeout(timeout);\n }\n }\n\n const pipeline = composeGuards(baseGuards, terminal);\n\n async function request<T>(method: HttpMethod, path: string, ro?: RequestOptions<T>): Promise<ClientResult<T>> {\n const requestUrl = withQuery(joinUrl(opts.baseUrl, path), ro?.query);\n\n const headers = new Headers();\n if (opts.defaults?.headers) for (const [k, v] of Object.entries(opts.defaults.headers)) headers.set(k, v);\n if (ro?.headers) for (const [k, v] of Object.entries(ro.headers)) if (v !== undefined) headers.set(k, v);\n\n let body: BodyInit | undefined;\n let bodyForKey: unknown = undefined;\n\n if (ro?.json !== undefined) {\n if (!headers.has(\"content-type\")) headers.set(\"content-type\", \"application/json\");\n if (!headers.has(\"accept\")) headers.set(\"accept\", \"application/json\");\n body = JSON.stringify(ro.json);\n bodyForKey = ro.json;\n } else if (ro?.body !== undefined) {\n body = ro.body;\n bodyForKey = \"[body]\";\n if (!headers.has(\"accept\")) headers.set(\"accept\", \"application/json\");\n } else {\n if (!headers.has(\"accept\")) headers.set(\"accept\", \"application/json\");\n }\n\n const timeoutMs = ro?.timeoutMs ?? opts.defaults?.timeoutMs;\n const notifier = ro?.notifier ?? defaultNotifier;\n\n const cacheOpts = ro?.cache;\n const wantsCache = method === \"GET\" && !!cache && !!cacheOpts;\n const dedupe = cacheOpts?.dedupe ?? true;\n\n const canDedupeWrite = method !== \"GET\" && !!ro?.idempotencyKey;\n const inflightKey =\n (wantsCache || canDedupeWrite) && dedupe\n ? makeCacheKey({ method, url: requestUrl, headers, ...(method === \"GET\" ? {} : { body: bodyForKey }), ...(ro?.idempotencyKey !== undefined ? { idempotencyKey: ro.idempotencyKey } : {}), ...(cacheOpts?.key !== undefined ? { keyOverride: cacheOpts.key } : {}) })\n : undefined;\n\n if (inflightKey) {\n const existing = inFlight.get(inflightKey);\n if (existing) {\n try {\n const stored = await existing;\n return asClientResult<T>(stored, requestUrl, opts, ro, notifier, shouldNotify);\n } catch {\n // ignore and proceed\n }\n }\n }\n\n const promise = (async () => {\n if (wantsCache) {\n const key = inflightKey!;\n const cached = cache!.get(key);\n const policy = cacheOpts?.policy ?? \"cacheFirst\";\n\n if (cached) {\n if (policy === \"networkOnly\") {\n // ignore\n } else if (policy === \"staleWhileRevalidate\") {\n void refreshInBackground(key);\n return { kind: \"cache-hit\", url: requestUrl, status: 200, headers: new Headers(), raw: cached.value };\n } else if (policy === \"cacheOnly\" || policy === \"cacheFirst\") {\n return { kind: \"cache-hit\", url: requestUrl, status: 200, headers: new Headers(), raw: cached.value };\n }\n } else if (policy === \"cacheOnly\") {\n return { kind: \"cache-miss\", url: requestUrl, status: 0, headers: new Headers(), raw: null };\n }\n }\n\n const res = await fetchWithRedirects({\n opts,\n pipeline,\n method,\n url: requestUrl,\n headers,\n ...(body !== undefined ? { body } : {}),\n ...(ro?.signal ? { signal: ro.signal } : {}),\n ...(timeoutMs !== undefined ? { timeoutMs } : {}),\n ...(ro?.noAuth ? { noAuth: true } : {}),\n });\n\n const raw = await decodeResponse(res);\n const envelope = { kind: \"network\", url: res.url || requestUrl, status: res.status, headers: res.headers, raw };\n\n if (wantsCache && cacheOpts && res.ok) {\n const key = inflightKey!;\n cache!.set(key, { expiresAt: Date.now() + cacheOpts.ttlMs, value: raw, ...(cacheOpts.tags ? { tags: cacheOpts.tags } : {}) });\n }\n\n return envelope;\n\n async function refreshInBackground(key: string) {\n try {\n const res2 = await fetchWithRedirects({\n opts,\n pipeline,\n method,\n url: requestUrl,\n headers,\n ...(body !== undefined ? { body } : {}),\n ...(ro?.signal ? { signal: ro.signal } : {}),\n ...(timeoutMs !== undefined ? { timeoutMs } : {}),\n ...(ro?.noAuth ? { noAuth: true } : {}),\n });\n if (!res2.ok) return;\n const raw2 = await decodeResponse(res2);\n cache!.set(key, { expiresAt: Date.now() + cacheOpts!.ttlMs, value: raw2, ...(cacheOpts!.tags ? { tags: cacheOpts!.tags } : {}) });\n } catch {\n // ignore\n }\n }\n })();\n\n if (inflightKey) inFlight.set(inflightKey, promise);\n\n try {\n const stored = await promise;\n return asClientResult<T>(stored, requestUrl, opts, ro, notifier, shouldNotify);\n } catch (e) {\n const err = e instanceof TimeoutError ? e : new NetworkError(\"Network request failed\", e);\n if (shouldNotify({ ok: false, status: 0 })) notifier.notify({ type: \"network-error\", level: \"error\", title: \"Network error\", message: err.message, url: requestUrl });\n return { ok: false, status: 0, headers: new Headers(), errors: [{ message: err.message, details: e }], raw: null, url: requestUrl };\n }\n }\n\n return {\n get: <T>(path: string, ro?: RequestOptions<T>) => request<T>(\"GET\", path, ro),\n post: <T>(path: string, ro?: RequestOptions<T>) => request<T>(\"POST\", path, ro),\n put: <T>(path: string, ro?: RequestOptions<T>) => request<T>(\"PUT\", path, ro),\n patch: <T>(path: string, ro?: RequestOptions<T>) => request<T>(\"PATCH\", path, ro),\n delete: <T>(path: string, ro?: RequestOptions<T>) => request<T>(\"DELETE\", path, ro),\n cache: { invalidateByTag: (tag: string) => cache?.invalidateByTag(tag) ?? 0, clear: () => cache?.clear() },\n };\n}\n\nasync function fetchWithRedirects(args: {\n opts: HttpClientOptions;\n pipeline: (ctx: RequestContext) => Promise<Response>;\n method: HttpMethod;\n url: string;\n headers: Headers;\n body?: BodyInit;\n signal?: AbortSignal;\n timeoutMs?: number;\n noAuth?: boolean;\n}): Promise<Response> {\n const policy = args.opts.redirects ?? { mode: \"follow\" as const, maxHops: 5 };\n const maxHops = policy.maxHops ?? 5;\n\n if (policy.mode === \"follow\") return args.pipeline(makeCtx(args, \"follow\"));\n if (policy.mode === \"error\") return args.pipeline(makeCtx(args, \"error\"));\n\n let currentUrl = args.url;\n let method: HttpMethod = args.method;\n let body = args.body;\n\n for (let hop = 0; hop <= maxHops; hop++) {\n const res = await args.pipeline(\n makeCtx({\n ...args,\n url: currentUrl,\n method,\n ...(body !== undefined ? { body } : {}),\n }, \"manual\")\n );\n if (!REDIRECT_STATUSES.has(res.status)) return res;\n\n const loc = res.headers.get(\"location\");\n if (!loc) throw new RedirectError(\"Redirect without Location header\", currentUrl);\n const nextUrl = new URL(loc, currentUrl).toString();\n\n const decision = policy.onRedirect?.({ fromUrl: currentUrl, toUrl: nextUrl, status: res.status, hop, method });\n if (decision?.action === \"deny\") throw new RedirectError(decision.reason ?? \"Redirect denied by policy\", currentUrl, nextUrl);\n const finalUrl = decision?.action === \"modify\" ? decision.url : nextUrl;\n\n // Security: avoid leaking bearer auth to a different origin on redirects (manual mode).\n stripAuthOnCrossOriginRedirect(args.opts, args.url, finalUrl, args.headers);\n\n if (res.status === 303) { method = \"GET\"; body = undefined; }\n else if ((res.status === 301 || res.status === 302) && method !== \"GET\") { method = \"GET\"; body = undefined; }\n\n currentUrl = finalUrl;\n if (hop === maxHops) throw new RedirectError(`Too many redirects (>${maxHops})`, args.url, currentUrl);\n }\n\n return args.pipeline(makeCtx(args, \"manual\"));\n}\n\nfunction stripAuthOnCrossOriginRedirect(opts: HttpClientOptions, initialUrl: string, toUrl: string, headers: Headers) {\n try {\n if (opts.auth.mode !== \"bearer\") return;\n const initialOrigin = new URL(initialUrl).origin;\n const toOrigin = new URL(toUrl).origin;\n if (initialOrigin !== toOrigin) {\n const headerName = opts.auth.headerName ?? \"Authorization\";\n headers.delete(headerName);\n }\n } catch {\n // ignore\n }\n}\n\nfunction makeCtx(\n args: {\n opts: HttpClientOptions;\n method: HttpMethod;\n url: string;\n headers: Headers;\n body?: BodyInit;\n signal?: AbortSignal;\n timeoutMs?: number;\n noAuth?: boolean;\n },\n redirect: RequestRedirect\n): RequestContext {\n return {\n method: args.method,\n url: args.url,\n headers: args.headers,\n\n ...(args.body !== undefined ? { body: args.body } : {}),\n ...(args.signal ? { signal: args.signal } : {}),\n ...(args.timeoutMs !== undefined ? { timeoutMs: args.timeoutMs } : {}),\n ...(args.noAuth ? { noAuth: true } : {}),\n\n attempt: 0,\n auth: args.opts.auth,\n redirect,\n _fetch: args.opts.fetch ?? fetch,\n };\n}\n\n\nasync function decodeResponse(res: Response): Promise<unknown> {\n const ct = res.headers.get(\"content-type\") ?? \"\";\n if (ct.includes(\"application/json\")) {\n try { return await res.json(); }\n catch (e) { throw new ParseError(\"Failed to parse JSON response\", undefined, e); }\n }\n try { return await res.text(); }\n catch (e) { throw new ParseError(\"Failed to read response body\", undefined, e); }\n}\n\nfunction asClientResult<T>(\n envelope: any,\n requestUrl: string,\n opts: HttpClientOptions,\n ro: RequestOptions<T> | undefined,\n notifier: import(\"../notify/types.js\").Notifier,\n shouldNotify: (r: { ok: boolean; status: number }) => boolean\n): ClientResult<T> {\n if (envelope?.kind === \"cache-miss\") {\n const errors: AppError[] = [{ message: \"Cache miss\" }];\n if (shouldNotify({ ok: false, status: 0 })) notifier.notify({ type: \"http-error\", level: \"warning\", title: \"Cache\", message: \"Cache miss\", status: 0, errors, url: requestUrl });\n return { ok: false, status: 0, headers: new Headers(), errors, raw: envelope.raw, url: requestUrl };\n }\n\n const status = envelope.status ?? 0;\n const raw = envelope.raw;\n const resHeaders: Headers = envelope.headers ?? new Headers();\n const finalUrl = envelope.url ?? requestUrl;\n\n const okByParser = opts.parser.isSuccess(raw, status);\n\n if (okByParser && status >= 200 && status < 400) {\n const dataRaw = opts.parser.getData(raw);\n try {\n const data = ro?.dataSchema ? ro.dataSchema.parse(dataRaw) : (dataRaw as T);\n return { ok: true, status, headers: resHeaders, data, raw, url: finalUrl };\n } catch (e) {\n const msg = e instanceof Error ? e.message : \"Schema parse failed\";\n if (shouldNotify({ ok: false, status })) notifier.notify({ type: \"parse-error\", level: \"error\", title: \"Parse error\", message: msg, url: finalUrl });\n return { ok: false, status, headers: resHeaders, errors: [{ message: msg, details: e }], raw, url: finalUrl };\n }\n }\n\n const errors = opts.parser.getErrors(raw, status);\n const msg = errors[0]?.message ?? (status ? `HTTP ${status}` : \"Request failed\");\n if (shouldNotify({ ok: false, status })) notifier.notify({ type: \"http-error\", level: status >= 500 ? \"error\" : \"warning\", title: \"Request failed\", message: msg, status, errors, url: finalUrl });\n return { ok: false, status, headers: resHeaders, errors, raw, url: finalUrl };\n}\n\nfunction anySignal(signals: Array<AbortSignal | undefined>): AbortSignal {\n const valid = signals.filter(Boolean) as AbortSignal[];\n const controller = new AbortController();\n const onAbort = () => controller.abort();\n for (const s of valid) {\n if (s.aborted) controller.abort();\n s.addEventListener(\"abort\", onAbort, { once: true });\n }\n return controller.signal;\n}\n","export type AuthConfig =\n | {\n mode: \"bearer\";\n getToken: () => string | null | Promise<string | null>;\n headerName?: string;\n prefix?: string;\n }\n | {\n mode: \"cookie\";\n credentials?: RequestCredentials;\n csrf?: { headerName: string; getToken: () => string | null | Promise<string | null> };\n }\n | { mode: \"none\" };\n\nexport function bearerAuth(\n getToken: () => string | null | Promise<string | null>,\n opts?: { headerName?: string; prefix?: string }\n): AuthConfig {\n return {\n mode: \"bearer\",\n getToken,\n ...(opts?.headerName !== undefined ? { headerName: opts.headerName } : {}),\n ...(opts?.prefix !== undefined ? { prefix: opts.prefix } : {}),\n };\n}\n\nexport function cookieAuth(opts?: {\n credentials?: RequestCredentials;\n csrf?: { headerName: string; getToken: () => string | null | Promise<string | null> };\n}): AuthConfig {\n return {\n mode: \"cookie\",\n ...(opts?.credentials !== undefined ? { credentials: opts.credentials } : {}),\n ...(opts?.csrf !== undefined ? { csrf: opts.csrf } : {}),\n };\n}\n\nexport function noAuth(): AuthConfig {\n return { mode: \"none\" };\n}\n","import type { Guard } from \"./types.js\";\n\n/**\n * Simple retry guard for transient HTTP statuses.\n * Note: this doesn't retry network errors thrown by fetch itself (those are handled by the client wrapper).\n */\nexport function retryGuard(opts?: { retries?: number; retryOn?: number[]; baseDelayMs?: number; maxDelayMs?: number }): Guard {\n const retries = opts?.retries ?? 0;\n const retryOn = new Set(opts?.retryOn ?? [502, 503, 504]);\n const baseDelay = opts?.baseDelayMs ?? 150;\n const maxDelay = opts?.maxDelayMs ?? 1500;\n\n return async (ctx, next) => {\n let last: Response | undefined;\n for (let attempt = 0; attempt <= retries; attempt++) {\n ctx.attempt = attempt;\n last = await next(ctx);\n if (!retryOn.has(last.status)) return last;\n if (attempt === retries) return last;\n const delay = Math.min(maxDelay, baseDelay * 2 ** attempt);\n await new Promise((r) => setTimeout(r, delay));\n }\n return last!;\n };\n}\n","import type { CacheEntry, CacheStore } from \"./types.js\";\n\n/**\n * Simple in-memory LRU-ish cache with tag invalidation.\n */\nexport function memoryCache(opts?: { maxEntries?: number }): CacheStore {\n const max = opts?.maxEntries ?? 1000;\n const map = new Map<string, CacheEntry>();\n const tagIndex = new Map<string, Set<string>>();\n\n function touch(key: string) {\n const v = map.get(key);\n if (!v) return;\n map.delete(key);\n map.set(key, v);\n }\n\n function deindexTags(key: string, entry?: CacheEntry) {\n const tags = entry?.tags;\n if (!tags?.length) return;\n for (const t of tags) {\n const set = tagIndex.get(t);\n if (!set) continue;\n set.delete(key);\n if (set.size === 0) tagIndex.delete(t);\n }\n }\n\n function indexTags(key: string, tags?: string[]) {\n if (!tags?.length) return;\n for (const t of tags) {\n let set = tagIndex.get(t);\n if (!set) {\n set = new Set();\n tagIndex.set(t, set);\n }\n set.add(key);\n }\n }\n\n function del(key: string) {\n const prev = map.get(key);\n if (prev) deindexTags(key, prev);\n map.delete(key);\n }\n\n function ensureLimit() {\n while (map.size > max) {\n const firstKey = map.keys().next().value as string | undefined;\n if (!firstKey) break;\n del(firstKey);\n }\n }\n\n return {\n get(key) {\n const entry = map.get(key);\n if (!entry) return undefined;\n if (Date.now() > entry.expiresAt) {\n del(key);\n return undefined;\n }\n touch(key);\n return entry;\n },\n set(key, entry) {\n const prev = map.get(key);\n if (prev) deindexTags(key, prev);\n map.set(key, entry);\n indexTags(key, entry.tags);\n ensureLimit();\n },\n delete: del,\n invalidateByTag(tag) {\n const set = tagIndex.get(tag);\n if (!set) return 0;\n const keys = Array.from(set);\n let n = 0;\n for (const k of keys) {\n if (map.has(k)) {\n del(k);\n n++;\n }\n }\n return n;\n },\n clear() {\n map.clear();\n tagIndex.clear();\n },\n };\n}\n","import type { AppError } from \"../client/errors.js\";\n\nexport type ShapeParser = {\n isSuccess: (raw: unknown, status: number) => boolean;\n getData: (raw: unknown) => unknown;\n getErrors: (raw: unknown, status: number) => AppError[];\n};\n\nexport function createShapeParser(p: ShapeParser): ShapeParser {\n return p;\n}\n","export type Schema<T> = { parse: (input: unknown) => T };\n\nexport function schema<T>(impl: Schema<T>): Schema<T> {\n return impl;\n}\n\n/**\n * Optional helper to wrap a zod-like schema without adding a dependency.\n */\nexport function fromZod<T>(zodSchema: {\n parse?: (i: unknown) => T;\n safeParse?: (i: unknown) => { success: true; data: T } | { success: false; error: unknown };\n}): Schema<T> {\n if (typeof zodSchema.parse === \"function\") return { parse: (i) => zodSchema.parse!(i) };\n if (typeof zodSchema.safeParse === \"function\") {\n return {\n parse: (i) => {\n const r = zodSchema.safeParse!(i) as any;\n if (r && r.success) return r.data;\n throw new Error(\"Schema validation failed\");\n },\n };\n }\n throw new Error(\"Unsupported zod-like schema: expected parse or safeParse\");\n}\n","import type { AppError } from \"../client/errors.js\";\nimport { createShapeParser, type ShapeParser } from \"./shapeParser.js\";\n\nexport function createApiParserRestClassic(opts?: {\n successField?: string;\n dataField?: string;\n errorField?: string;\n messageField?: string;\n}): ShapeParser {\n const successField = opts?.successField ?? \"success\";\n const dataField = opts?.dataField ?? \"data\";\n const errorField = opts?.errorField ?? \"error\";\n const messageField = opts?.messageField ?? \"message\";\n\n return createShapeParser({\n isSuccess: (raw, status) => {\n if (raw && typeof raw === \"object\" && successField in (raw as any)) return Boolean((raw as any)[successField]);\n return status >= 200 && status < 400;\n },\n getData: (raw) => (raw && typeof raw === \"object\" && dataField in (raw as any)) ? (raw as any)[dataField] : raw,\n getErrors: (raw, status) => {\n if (!raw || typeof raw !== \"object\") return status ? [{ message: `HTTP ${status}` }] : [{ message: \"Request failed\" }];\n const r: any = raw;\n const err = r[errorField] ?? r[messageField] ?? r[\"errors\"];\n if (!err) return status ? [{ message: `HTTP ${status}` }] : [{ message: \"Request failed\" }];\n\n if (typeof err === \"string\") return [{ message: err }];\n if (Array.isArray(err)) return err.map((e) => ({ message: String(e) }));\n\n if (typeof err === \"object\") {\n if (typeof err.message === \"string\") return [{ message: err.message, ...(err.code ? { code: String(err.code) } : {}), details: err }];\n return [{ message: String(r[messageField] ?? \"Request failed\"), details: err }];\n }\n return [{ message: String(err) }];\n },\n });\n}\n\nexport function createApiParserLaravel(opts?: { dataField?: string }): ShapeParser {\n const dataField = opts?.dataField ?? \"data\";\n return createShapeParser({\n isSuccess: (_raw, status) => status >= 200 && status < 400,\n getData: (raw) => (raw && typeof raw === \"object\" && dataField in (raw as any)) ? (raw as any)[dataField] : raw,\n getErrors: (raw, status) => laravelErrors(raw, status),\n });\n}\n\nfunction laravelErrors(raw: unknown, status: number): AppError[] {\n if (!raw || typeof raw !== \"object\") return status ? [{ message: `HTTP ${status}` }] : [{ message: \"Request failed\" }];\n const r: any = raw;\n\n if (r.errors && typeof r.errors === \"object\") {\n const out: AppError[] = [];\n for (const [field, msgs] of Object.entries(r.errors)) {\n if (Array.isArray(msgs)) for (const m of msgs) out.push({ field, message: String(m) });\n else out.push({ field, message: String(msgs) });\n }\n if (out.length) return out;\n }\n\n const msg = typeof r.message === \"string\" ? r.message : (status ? `HTTP ${status}` : \"Request failed\");\n return [{ message: msg, details: r }];\n}\n\nexport function createApiParserNest(): ShapeParser {\n return createShapeParser({\n isSuccess: (_raw, status) => status >= 200 && status < 400,\n getData: (raw) => raw,\n getErrors: (raw, status) => nestErrors(raw, status),\n });\n}\n\nfunction nestErrors(raw: unknown, status: number): AppError[] {\n if (!raw || typeof raw !== \"object\") return status ? [{ message: `HTTP ${status}` }] : [{ message: \"Request failed\" }];\n const r: any = raw;\n const msg = r.message;\n\n if (Array.isArray(msg)) return msg.map((m: any) => ({ message: String(m), details: r }));\n if (typeof msg === \"string\") return [{ message: msg, ...(r.error ? { code: String(r.error) } : {}), details: r }];\n\n return [{ message: status ? `HTTP ${status}` : \"Request failed\", details: r }];\n}\n\nexport function createApiParserGraphQL(opts?: { allowPartialData?: boolean }): ShapeParser {\n const allowPartial = opts?.allowPartialData ?? false;\n\n return createShapeParser({\n isSuccess: (raw, status) => {\n if (status < 200 || status >= 400) return false;\n if (!raw || typeof raw !== \"object\") return false;\n const r: any = raw;\n const hasErrors = Array.isArray(r.errors) && r.errors.length > 0;\n if (hasErrors && !allowPartial) return false;\n return \"data\" in r;\n },\n getData: (raw) => (raw && typeof raw === \"object\") ? (raw as any).data : raw,\n getErrors: (raw, status) => {\n if (!raw || typeof raw !== \"object\") return status ? [{ message: `HTTP ${status}` }] : [{ message: \"GraphQL request failed\" }];\n const r: any = raw;\n const errs = Array.isArray(r.errors) ? r.errors : [];\n if (!errs.length) return status >= 400 ? [{ message: `HTTP ${status}` }] : [{ message: \"GraphQL request failed\" }];\n return errs.map((e: any) => ({\n message: typeof e?.message === \"string\" ? e.message : \"GraphQL error\",\n code: e?.extensions?.code ? String(e.extensions.code) : undefined,\n details: e,\n }));\n },\n });\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cedvict/http-guardian",
|
|
3
|
+
"version": "0.0.1-next.0",
|
|
4
|
+
"description": "Configurable HTTP client for TypeScript: auth (bearer/cookie), guards, cache+dedupe, dynamic envelope parsing presets, notifications, redirect handling.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"default": "./dist/index.js"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"main": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"README.md",
|
|
17
|
+
"LICENSE",
|
|
18
|
+
"TESTING.md"
|
|
19
|
+
],
|
|
20
|
+
"sideEffects": false,
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=18"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"http",
|
|
26
|
+
"fetch",
|
|
27
|
+
"typescript",
|
|
28
|
+
"client",
|
|
29
|
+
"cache",
|
|
30
|
+
"auth",
|
|
31
|
+
"guards",
|
|
32
|
+
"sdk",
|
|
33
|
+
"laravel",
|
|
34
|
+
"nestjs",
|
|
35
|
+
"graphql",
|
|
36
|
+
"rxjs"
|
|
37
|
+
],
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "git+https://github.com/cedvict/http-guardian.git"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsup",
|
|
45
|
+
"dev": "tsup --watch",
|
|
46
|
+
"test": "vitest run",
|
|
47
|
+
"test:unit": "vitest run test/unit",
|
|
48
|
+
"test:integration": "vitest run test/integration",
|
|
49
|
+
"test:robustness": "vitest run test/robustness",
|
|
50
|
+
"test:security": "vitest run test/security",
|
|
51
|
+
"bench": "node ./test/perf/bench.mjs",
|
|
52
|
+
"lint": "eslint . --max-warnings=0",
|
|
53
|
+
"typecheck": "tsc -p tsconfig.typecheck.json --noEmit",
|
|
54
|
+
"prepublishOnly": "npm run test && npm run lint && npm run typecheck && npm run build"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@types/node": "^20.0.0",
|
|
58
|
+
"eslint": "^9.0.0",
|
|
59
|
+
"typescript": "^5.4.0",
|
|
60
|
+
"tsup": "^8.0.0",
|
|
61
|
+
"vitest": "^1.5.0",
|
|
62
|
+
"@typescript-eslint/parser": "^8.0.0",
|
|
63
|
+
"@typescript-eslint/eslint-plugin": "^8.0.0"
|
|
64
|
+
}
|
|
65
|
+
}
|