@route-forge/core 1.0.0 → 1.0.2
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 +13 -2
- package/dist/codegen.cjs +5 -1
- package/dist/codegen.cjs.map +1 -1
- package/dist/codegen.d.cts +1 -1
- package/dist/codegen.d.ts +1 -1
- package/dist/codegen.js +5 -1
- package/dist/codegen.js.map +1 -1
- package/dist/index.cjs +126 -42
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +126 -42
- package/dist/index.js.map +1 -1
- package/dist/{types-yzY9-FWd.d.cts → types-DExMMUu1.d.cts} +13 -2
- package/dist/{types-yzY9-FWd.d.ts → types-DExMMUu1.d.ts} +13 -2
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -26,17 +26,19 @@ var RouteCache = class {
|
|
|
26
26
|
return this.getFromMemory(level);
|
|
27
27
|
}
|
|
28
28
|
const raw = this.backend.getItem(this.key(level));
|
|
29
|
-
if (
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
if (raw) {
|
|
30
|
+
try {
|
|
31
|
+
const entry = JSON.parse(raw);
|
|
32
|
+
if (this.isExpired(entry)) {
|
|
33
|
+
this.del(level);
|
|
34
|
+
return void 0;
|
|
35
|
+
}
|
|
36
|
+
return entry;
|
|
37
|
+
} catch {
|
|
34
38
|
return void 0;
|
|
35
39
|
}
|
|
36
|
-
return entry;
|
|
37
|
-
} catch {
|
|
38
|
-
return void 0;
|
|
39
40
|
}
|
|
41
|
+
return this.getFromMemory(level);
|
|
40
42
|
}
|
|
41
43
|
getFromMemory(level) {
|
|
42
44
|
const entry = this.memory.get(level);
|
|
@@ -238,6 +240,10 @@ function createInterceptorManager() {
|
|
|
238
240
|
}
|
|
239
241
|
|
|
240
242
|
// src/adapters/builtin-http.ts
|
|
243
|
+
function isPassthroughBody(body) {
|
|
244
|
+
if (body === null) return false;
|
|
245
|
+
return typeof FormData !== "undefined" && body instanceof FormData || typeof URLSearchParams !== "undefined" && body instanceof URLSearchParams || typeof Blob !== "undefined" && body instanceof Blob || typeof ArrayBuffer !== "undefined" && (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) || typeof ReadableStream !== "undefined" && body instanceof ReadableStream;
|
|
246
|
+
}
|
|
241
247
|
function createBuiltinHttp(forgeInterceptors) {
|
|
242
248
|
const requestMgr = forgeInterceptors?.request ?? createInterceptorManager();
|
|
243
249
|
const responseMgr = forgeInterceptors?.response ?? createInterceptorManager();
|
|
@@ -251,15 +257,33 @@ function createBuiltinHttp(forgeInterceptors) {
|
|
|
251
257
|
url = url.includes("?") ? `${url}&${qs}` : `${url}?${qs}`;
|
|
252
258
|
}
|
|
253
259
|
}
|
|
260
|
+
const headers = new Headers(finalConfig.headers);
|
|
254
261
|
const fetchInit = {
|
|
255
262
|
method: finalConfig.method,
|
|
256
|
-
headers
|
|
263
|
+
headers
|
|
257
264
|
};
|
|
258
265
|
if (signal) fetchInit.signal = signal;
|
|
259
266
|
if (finalConfig.body !== void 0 && !["GET", "HEAD"].includes(finalConfig.method.toUpperCase())) {
|
|
260
|
-
|
|
267
|
+
if (typeof finalConfig.body === "string" || isPassthroughBody(finalConfig.body)) {
|
|
268
|
+
fetchInit.body = finalConfig.body;
|
|
269
|
+
} else {
|
|
270
|
+
fetchInit.body = JSON.stringify(finalConfig.body);
|
|
271
|
+
if (!headers.has("Content-Type")) {
|
|
272
|
+
headers.set("Content-Type", "application/json");
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
let res;
|
|
277
|
+
try {
|
|
278
|
+
res = await fetch(url, fetchInit);
|
|
279
|
+
} catch (e) {
|
|
280
|
+
throw new NetworkError(
|
|
281
|
+
e instanceof Error ? e.message : String(e),
|
|
282
|
+
finalConfig.route,
|
|
283
|
+
finalConfig.level,
|
|
284
|
+
e
|
|
285
|
+
);
|
|
261
286
|
}
|
|
262
|
-
const res = await fetch(url, fetchInit);
|
|
263
287
|
const text = await res.text();
|
|
264
288
|
let data = text;
|
|
265
289
|
const contentType = res.headers.get("content-type") ?? "";
|
|
@@ -279,9 +303,21 @@ function createBuiltinHttp(forgeInterceptors) {
|
|
|
279
303
|
data,
|
|
280
304
|
config: finalConfig
|
|
281
305
|
};
|
|
306
|
+
const source = res.status >= 200 && res.status < 300 ? Promise.resolve(responseData) : Promise.reject(
|
|
307
|
+
new HTTPError(
|
|
308
|
+
`HTTP ${res.status} for route "${finalConfig.route}" (${finalConfig.method} ${url})`,
|
|
309
|
+
{
|
|
310
|
+
route: finalConfig.route,
|
|
311
|
+
level: finalConfig.level,
|
|
312
|
+
status: res.status,
|
|
313
|
+
url,
|
|
314
|
+
method: finalConfig.method
|
|
315
|
+
}
|
|
316
|
+
)
|
|
317
|
+
);
|
|
282
318
|
return runResponseInterceptors(
|
|
283
319
|
responseMgr,
|
|
284
|
-
|
|
320
|
+
source
|
|
285
321
|
);
|
|
286
322
|
}
|
|
287
323
|
const get = (url, config) => request({ ...config, url, method: "GET" });
|
|
@@ -305,6 +341,15 @@ function createBuiltinHttp(forgeInterceptors) {
|
|
|
305
341
|
}
|
|
306
342
|
|
|
307
343
|
// src/adapters/axios.ts
|
|
344
|
+
function toHeaders(raw) {
|
|
345
|
+
if (!raw) return new Headers();
|
|
346
|
+
const init = typeof raw.toJSON === "function" ? raw.toJSON() : raw;
|
|
347
|
+
try {
|
|
348
|
+
return new Headers(init);
|
|
349
|
+
} catch {
|
|
350
|
+
return new Headers();
|
|
351
|
+
}
|
|
352
|
+
}
|
|
308
353
|
async function wrapAxiosAdapter() {
|
|
309
354
|
let axios;
|
|
310
355
|
try {
|
|
@@ -321,23 +366,41 @@ async function wrapAxiosAdapter() {
|
|
|
321
366
|
if (!axios || typeof axios.request !== "function") return null;
|
|
322
367
|
async function request(config) {
|
|
323
368
|
const headers = { ...config.headers };
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
369
|
+
try {
|
|
370
|
+
const res = await axios.request({
|
|
371
|
+
url: config.url,
|
|
372
|
+
method: config.method,
|
|
373
|
+
headers,
|
|
374
|
+
data: config.body,
|
|
375
|
+
// axios 会处理 baseURL/transformRequest 等 defaults;timeout 透传保证超时语义与 builtin 一致
|
|
376
|
+
timeout: config.timeout
|
|
377
|
+
});
|
|
378
|
+
return {
|
|
379
|
+
route: config.route,
|
|
380
|
+
level: config.level,
|
|
381
|
+
method: config.method,
|
|
382
|
+
url: config.url,
|
|
383
|
+
status: res.status,
|
|
384
|
+
headers: toHeaders(res.headers),
|
|
385
|
+
data: res.data,
|
|
386
|
+
config
|
|
387
|
+
};
|
|
388
|
+
} catch (e) {
|
|
389
|
+
if (e && e.response) {
|
|
390
|
+
const resp = e.response;
|
|
391
|
+
return {
|
|
392
|
+
route: config.route,
|
|
393
|
+
level: config.level,
|
|
394
|
+
method: config.method,
|
|
395
|
+
url: config.url,
|
|
396
|
+
status: resp.status,
|
|
397
|
+
headers: toHeaders(resp.headers),
|
|
398
|
+
data: resp.data,
|
|
399
|
+
config
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
throw e;
|
|
403
|
+
}
|
|
341
404
|
}
|
|
342
405
|
return { request, interceptors: void 0 };
|
|
343
406
|
}
|
|
@@ -469,6 +532,12 @@ function createRouteForge(options) {
|
|
|
469
532
|
if (summary === null) {
|
|
470
533
|
return;
|
|
471
534
|
}
|
|
535
|
+
const schemaVersion = summary.schemaVersion ?? 1;
|
|
536
|
+
if (schemaVersion > 1) {
|
|
537
|
+
console.warn(
|
|
538
|
+
`[route-forge] backend schemaVersion=${schemaVersion} > client supported 1; some features may be unavailable`
|
|
539
|
+
);
|
|
540
|
+
}
|
|
472
541
|
if (summary.config.endpoint_prefix && summary.config.endpoint_prefix !== explicitEndpoint) {
|
|
473
542
|
console.warn(
|
|
474
543
|
`[route-forge] backend endpoint_prefix "${summary.config.endpoint_prefix}" overrides frontend endpoint "${explicitEndpoint}"`
|
|
@@ -578,7 +647,10 @@ function createRouteForge(options) {
|
|
|
578
647
|
};
|
|
579
648
|
const resp = await adp.request(config);
|
|
580
649
|
if (!resp || resp.status < 200 || resp.status >= 300) {
|
|
581
|
-
throw new
|
|
650
|
+
throw new HTTPError(
|
|
651
|
+
`Failed to load level "${level}": HTTP ${resp?.status}`,
|
|
652
|
+
{ level, status: resp?.status, url: buildUrl(level), method: "GET" }
|
|
653
|
+
);
|
|
582
654
|
}
|
|
583
655
|
const data = resp.data;
|
|
584
656
|
return data;
|
|
@@ -612,27 +684,33 @@ function createRouteForge(options) {
|
|
|
612
684
|
return buildRequestUrl(meta, params ?? {});
|
|
613
685
|
}
|
|
614
686
|
function buildRequestUrl(meta, params) {
|
|
615
|
-
let uri = meta.uri;
|
|
616
687
|
const defaults = meta.parameter_defaults ?? {};
|
|
617
688
|
const missingRequired = [];
|
|
689
|
+
const values = {};
|
|
618
690
|
for (const p of meta.parameters) {
|
|
619
691
|
let v = params[p];
|
|
620
692
|
if ((v === void 0 || v === null) && p in defaults) {
|
|
621
693
|
v = defaults[p];
|
|
622
694
|
}
|
|
623
695
|
if (v === void 0 || v === null) {
|
|
624
|
-
if (uri.includes(`{${p}?}`)) {
|
|
625
|
-
|
|
626
|
-
continue;
|
|
696
|
+
if (!meta.uri.includes(`{${p}?}`)) {
|
|
697
|
+
missingRequired.push(p);
|
|
627
698
|
}
|
|
628
|
-
missingRequired.push(p);
|
|
629
699
|
} else {
|
|
630
|
-
|
|
700
|
+
values[p] = v;
|
|
631
701
|
}
|
|
632
702
|
}
|
|
633
703
|
if (missingRequired.length > 0) {
|
|
634
704
|
throw new MissingRouteParamError(meta.name, missingRequired);
|
|
635
705
|
}
|
|
706
|
+
let uri = meta.uri.replace(/\{([^{}]+)\}/g, (match, raw) => {
|
|
707
|
+
const optional = raw.endsWith("?");
|
|
708
|
+
const name = optional ? raw.slice(0, -1) : raw;
|
|
709
|
+
if (values[name] !== void 0) {
|
|
710
|
+
return encodeURIComponent(String(values[name]));
|
|
711
|
+
}
|
|
712
|
+
return optional ? "" : match;
|
|
713
|
+
});
|
|
636
714
|
uri = uri.replace(/\/+/g, "/").replace(/\/$/, "");
|
|
637
715
|
if (effectiveUrlPrefix.includes("://")) {
|
|
638
716
|
const prefix2 = effectiveUrlPrefix.endsWith("/") ? effectiveUrlPrefix.slice(0, -1) : effectiveUrlPrefix;
|
|
@@ -660,7 +738,7 @@ function createRouteForge(options) {
|
|
|
660
738
|
return doApiCall(meta, params);
|
|
661
739
|
}
|
|
662
740
|
async function doApiCall(meta, params) {
|
|
663
|
-
const { pathParams, query, body, headers } = resolveApiParams(params);
|
|
741
|
+
const { pathParams, query, body, headers, timeout: perCallTimeout } = resolveApiParams(params);
|
|
664
742
|
const method = pickMethod(meta);
|
|
665
743
|
const urlWithQuery = appendQuery(buildRequestUrl(meta, pathParams), query);
|
|
666
744
|
const config = {
|
|
@@ -671,7 +749,7 @@ function createRouteForge(options) {
|
|
|
671
749
|
headers: { Accept: "application/json", ...headers ?? {} },
|
|
672
750
|
body,
|
|
673
751
|
params: pathParams,
|
|
674
|
-
timeout,
|
|
752
|
+
timeout: perCallTimeout ?? timeout,
|
|
675
753
|
meta
|
|
676
754
|
};
|
|
677
755
|
const adp = await ensureAdapter();
|
|
@@ -711,12 +789,17 @@ function createRouteForge(options) {
|
|
|
711
789
|
}
|
|
712
790
|
}
|
|
713
791
|
function invalidate(level) {
|
|
714
|
-
if (level)
|
|
715
|
-
|
|
792
|
+
if (level === void 0) {
|
|
793
|
+
cache.clear();
|
|
794
|
+
} else if (Array.isArray(level)) {
|
|
795
|
+
for (const lvl of level) cache.del(lvl);
|
|
796
|
+
} else {
|
|
797
|
+
cache.del(level);
|
|
798
|
+
}
|
|
716
799
|
}
|
|
717
800
|
function isLoaded(level) {
|
|
718
801
|
if (level) return cache.get(level) !== void 0;
|
|
719
|
-
return effectiveLevels.every((lvl) => cache.get(lvl) !== void 0);
|
|
802
|
+
return effectiveLevels.length > 0 && effectiveLevels.every((lvl) => cache.get(lvl) !== void 0);
|
|
720
803
|
}
|
|
721
804
|
function hasRoute(level, name) {
|
|
722
805
|
return findRouteMeta(level, name) !== void 0;
|
|
@@ -790,6 +873,7 @@ function resolveApiParams(input) {
|
|
|
790
873
|
query: rawQuery,
|
|
791
874
|
body: rawBody,
|
|
792
875
|
headers: rawHeaders,
|
|
876
|
+
timeout: perCallTimeout,
|
|
793
877
|
...flatRest
|
|
794
878
|
} = input;
|
|
795
879
|
const pathParams = explicitParams ? { ...explicitParams } : {};
|
|
@@ -822,7 +906,7 @@ function resolveApiParams(input) {
|
|
|
822
906
|
pathParams.headers = rawHeaders;
|
|
823
907
|
}
|
|
824
908
|
}
|
|
825
|
-
return { pathParams, query, body, headers };
|
|
909
|
+
return { pathParams, query, body, headers, timeout: perCallTimeout };
|
|
826
910
|
}
|
|
827
911
|
|
|
828
912
|
export { AdapterNotFoundError, ForgeError, HTTPError, InterceptorManagerImpl, InvalidInterceptorReturnError, LoadingTracker, MissingRouteParamError, NetworkError, RouteCache, UnknownLevelError, UnknownRouteError, createInterceptorManager, createRouteForge };
|