@route-forge/core 0.2.0 → 1.0.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/README.md +155 -3
- package/dist/codegen.cjs +31 -17
- package/dist/codegen.cjs.map +1 -1
- package/dist/codegen.d.cts +3 -2
- package/dist/codegen.d.ts +3 -2
- package/dist/codegen.js +31 -17
- package/dist/codegen.js.map +1 -1
- package/dist/index.cjs +223 -72
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -10
- package/dist/index.d.ts +4 -10
- package/dist/index.js +223 -72
- package/dist/index.js.map +1 -1
- package/dist/types-yzY9-FWd.d.cts +330 -0
- package/dist/types-yzY9-FWd.d.ts +330 -0
- package/package.json +1 -1
- package/dist/types-DpbxJ6Yk.d.cts +0 -186
- package/dist/types-DpbxJ6Yk.d.ts +0 -186
package/dist/index.cjs
CHANGED
|
@@ -50,7 +50,12 @@ var RouteCache = class {
|
|
|
50
50
|
return entry;
|
|
51
51
|
}
|
|
52
52
|
set(resp) {
|
|
53
|
-
|
|
53
|
+
let ttl;
|
|
54
|
+
if (resp.cache !== void 0 && resp.cache !== null) {
|
|
55
|
+
ttl = resp.cache > 0 ? Math.min(resp.cache, this.fallbackTtl) : resp.cache;
|
|
56
|
+
} else {
|
|
57
|
+
ttl = this.fallbackTtl;
|
|
58
|
+
}
|
|
54
59
|
const entry = {
|
|
55
60
|
level: resp.level,
|
|
56
61
|
routes: resp.routes,
|
|
@@ -135,14 +140,6 @@ var MissingRouteParamError = class extends ForgeError {
|
|
|
135
140
|
});
|
|
136
141
|
}
|
|
137
142
|
};
|
|
138
|
-
var InsufficientAuthError = class extends ForgeError {
|
|
139
|
-
constructor(level) {
|
|
140
|
-
super(`Insufficient auth: level "${level}" requires login`, {
|
|
141
|
-
code: "RF_FE_004",
|
|
142
|
-
level
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
};
|
|
146
143
|
var AdapterNotFoundError = class extends ForgeError {
|
|
147
144
|
constructor(adapter) {
|
|
148
145
|
super(`Adapter "${adapter}" not available; install axios or use 'builtin'`, {
|
|
@@ -369,6 +366,65 @@ async function resolveAdapter(opts) {
|
|
|
369
366
|
return createBuiltinHttp(opts.forgeInterceptors);
|
|
370
367
|
}
|
|
371
368
|
|
|
369
|
+
// src/loading.ts
|
|
370
|
+
var LoadingTracker = class {
|
|
371
|
+
constructor() {
|
|
372
|
+
/** 当前并发请求计数 */
|
|
373
|
+
this.count = 0;
|
|
374
|
+
/** 订阅者集合 */
|
|
375
|
+
this.subscribers = /* @__PURE__ */ new Set();
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* 开始一次加载(计数器 +1)
|
|
379
|
+
*/
|
|
380
|
+
start() {
|
|
381
|
+
this.count++;
|
|
382
|
+
this.notify();
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* 结束一次加载(计数器 -1)
|
|
386
|
+
*/
|
|
387
|
+
stop() {
|
|
388
|
+
this.count = Math.max(0, this.count - 1);
|
|
389
|
+
this.notify();
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* 查询当前是否处于加载中
|
|
393
|
+
*/
|
|
394
|
+
isLoading() {
|
|
395
|
+
return this.count > 0;
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* 获取当前并发计数
|
|
399
|
+
*/
|
|
400
|
+
getCount() {
|
|
401
|
+
return this.count;
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* 订阅加载状态变更
|
|
405
|
+
* @returns 取消订阅函数
|
|
406
|
+
*/
|
|
407
|
+
subscribe(cb) {
|
|
408
|
+
this.subscribers.add(cb);
|
|
409
|
+
return () => {
|
|
410
|
+
this.subscribers.delete(cb);
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
/** 通知所有订阅者 */
|
|
414
|
+
notify() {
|
|
415
|
+
const event = {
|
|
416
|
+
loading: this.count > 0,
|
|
417
|
+
count: this.count
|
|
418
|
+
};
|
|
419
|
+
for (const cb of this.subscribers) {
|
|
420
|
+
try {
|
|
421
|
+
cb(event);
|
|
422
|
+
} catch {
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
|
|
372
428
|
// src/forge.ts
|
|
373
429
|
var DEFAULT_TIMEOUT = 3e4;
|
|
374
430
|
var DEFAULT_CACHE_TTL = 3600;
|
|
@@ -378,18 +434,18 @@ function createRouteForge(options) {
|
|
|
378
434
|
adapter = "auto",
|
|
379
435
|
timeout = DEFAULT_TIMEOUT,
|
|
380
436
|
baseURL = "",
|
|
381
|
-
auth,
|
|
382
437
|
interceptors: declarativeInterceptors,
|
|
383
438
|
cache: cacheOpts = {}
|
|
384
439
|
} = options;
|
|
440
|
+
const loadingTracker = new LoadingTracker();
|
|
385
441
|
const explicitLevels = options.levels;
|
|
386
442
|
const explicitEager = options.eager;
|
|
387
443
|
const explicitStrict = options.strict ?? false;
|
|
388
444
|
const explicitEndpoint = options.endpoint;
|
|
389
445
|
let effectiveLevels = explicitLevels ?? [];
|
|
390
446
|
let effectiveEager = explicitEager ?? [];
|
|
391
|
-
let effectiveStrict = explicitStrict;
|
|
392
447
|
let effectiveEndpoint = explicitEndpoint;
|
|
448
|
+
let effectiveUrlPrefix = "";
|
|
393
449
|
const summaryPromise = (async () => {
|
|
394
450
|
try {
|
|
395
451
|
const summaryUrl = explicitEndpoint;
|
|
@@ -421,11 +477,13 @@ function createRouteForge(options) {
|
|
|
421
477
|
);
|
|
422
478
|
effectiveEndpoint = summary.config.endpoint_prefix;
|
|
423
479
|
}
|
|
480
|
+
if (summary.config.url_prefix) {
|
|
481
|
+
effectiveUrlPrefix = summary.config.url_prefix.endsWith("/") ? summary.config.url_prefix.slice(0, -1) : summary.config.url_prefix;
|
|
482
|
+
}
|
|
424
483
|
if (summary.config.strict_mode && !explicitStrict) {
|
|
425
484
|
console.warn(
|
|
426
485
|
"[route-forge] backend strict_mode=true overrides frontend strict=false; forcing strict=true"
|
|
427
486
|
);
|
|
428
|
-
effectiveStrict = true;
|
|
429
487
|
}
|
|
430
488
|
const backendLevels = Object.keys(summary.levels);
|
|
431
489
|
if (explicitLevels && explicitLevels.length > 0) {
|
|
@@ -440,8 +498,12 @@ function createRouteForge(options) {
|
|
|
440
498
|
} else {
|
|
441
499
|
effectiveLevels = backendLevels;
|
|
442
500
|
}
|
|
501
|
+
const backendEager = backendLevels.filter((lvl) => summary.levels[lvl]?.load === "eager");
|
|
443
502
|
if (!explicitEager) {
|
|
444
|
-
effectiveEager =
|
|
503
|
+
effectiveEager = backendEager;
|
|
504
|
+
} else {
|
|
505
|
+
const union = /* @__PURE__ */ new Set([...backendEager, ...explicitEager]);
|
|
506
|
+
effectiveEager = [...union];
|
|
445
507
|
}
|
|
446
508
|
});
|
|
447
509
|
autoDiscoveryPromise.catch(() => {
|
|
@@ -452,15 +514,23 @@ function createRouteForge(options) {
|
|
|
452
514
|
const requestInterceptors = new InterceptorManagerImpl();
|
|
453
515
|
const responseInterceptors = new InterceptorManagerImpl();
|
|
454
516
|
if (declarativeInterceptors?.request) {
|
|
455
|
-
for (const
|
|
456
|
-
|
|
457
|
-
|
|
517
|
+
for (const entry of declarativeInterceptors.request) {
|
|
518
|
+
if (typeof entry === "function") {
|
|
519
|
+
requestInterceptors.use(entry);
|
|
520
|
+
} else {
|
|
521
|
+
const [onFulfilled, onRejected] = entry;
|
|
522
|
+
requestInterceptors.use(onFulfilled, onRejected);
|
|
523
|
+
}
|
|
458
524
|
}
|
|
459
525
|
}
|
|
460
526
|
if (declarativeInterceptors?.response) {
|
|
461
|
-
for (const
|
|
462
|
-
|
|
463
|
-
|
|
527
|
+
for (const entry of declarativeInterceptors.response) {
|
|
528
|
+
if (typeof entry === "function") {
|
|
529
|
+
responseInterceptors.use(entry);
|
|
530
|
+
} else {
|
|
531
|
+
const [onFulfilled, onRejected] = entry;
|
|
532
|
+
responseInterceptors.use(onFulfilled, onRejected);
|
|
533
|
+
}
|
|
464
534
|
}
|
|
465
535
|
}
|
|
466
536
|
const adapterPromise = resolveAdapter({
|
|
@@ -480,14 +550,6 @@ function createRouteForge(options) {
|
|
|
480
550
|
return adapterObj;
|
|
481
551
|
}
|
|
482
552
|
const inflight = /* @__PURE__ */ new Map();
|
|
483
|
-
function isAuthRequired(level) {
|
|
484
|
-
return Boolean(auth?.levels?.[level]);
|
|
485
|
-
}
|
|
486
|
-
function assertAuth(level) {
|
|
487
|
-
if (isAuthRequired(level) && auth?.state && !auth.state()) {
|
|
488
|
-
throw new InsufficientAuthError(level);
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
553
|
function assertLevelDeclared(level) {
|
|
492
554
|
if (!effectiveLevels.includes(level)) {
|
|
493
555
|
throw new UnknownLevelError(level);
|
|
@@ -525,7 +587,6 @@ function createRouteForge(options) {
|
|
|
525
587
|
}
|
|
526
588
|
async function loadOne(level) {
|
|
527
589
|
assertLevelDeclared(level);
|
|
528
|
-
assertAuth(level);
|
|
529
590
|
if (cache.get(level)) return;
|
|
530
591
|
const existing = inflight.get(level);
|
|
531
592
|
if (existing) return existing;
|
|
@@ -548,24 +609,40 @@ function createRouteForge(options) {
|
|
|
548
609
|
function route(level, name, params) {
|
|
549
610
|
const meta = findRouteMeta(level, name);
|
|
550
611
|
if (!meta) {
|
|
551
|
-
|
|
552
|
-
return "";
|
|
612
|
+
throw new UnknownRouteError(name, level);
|
|
553
613
|
}
|
|
554
614
|
return buildRequestUrl(meta, params ?? {});
|
|
555
615
|
}
|
|
556
616
|
function buildRequestUrl(meta, params) {
|
|
557
617
|
let uri = meta.uri;
|
|
618
|
+
const defaults = meta.parameter_defaults ?? {};
|
|
619
|
+
const missingRequired = [];
|
|
558
620
|
for (const p of meta.parameters) {
|
|
559
|
-
|
|
560
|
-
if (v === void 0) {
|
|
561
|
-
|
|
562
|
-
|
|
621
|
+
let v = params[p];
|
|
622
|
+
if ((v === void 0 || v === null) && p in defaults) {
|
|
623
|
+
v = defaults[p];
|
|
624
|
+
}
|
|
625
|
+
if (v === void 0 || v === null) {
|
|
626
|
+
if (uri.includes(`{${p}?}`)) {
|
|
627
|
+
uri = uri.replace(`{${p}?}`, "");
|
|
628
|
+
continue;
|
|
629
|
+
}
|
|
630
|
+
missingRequired.push(p);
|
|
563
631
|
} else {
|
|
564
|
-
uri = uri.replace(`{${p}}`, encodeURIComponent(String(v)));
|
|
632
|
+
uri = uri.replace(`{${p}?}`, encodeURIComponent(String(v))).replace(`{${p}}`, encodeURIComponent(String(v)));
|
|
565
633
|
}
|
|
566
634
|
}
|
|
635
|
+
if (missingRequired.length > 0) {
|
|
636
|
+
throw new MissingRouteParamError(meta.name, missingRequired);
|
|
637
|
+
}
|
|
638
|
+
uri = uri.replace(/\/+/g, "/").replace(/\/$/, "");
|
|
639
|
+
if (effectiveUrlPrefix.includes("://")) {
|
|
640
|
+
const prefix2 = effectiveUrlPrefix.endsWith("/") ? effectiveUrlPrefix.slice(0, -1) : effectiveUrlPrefix;
|
|
641
|
+
return uri.startsWith("/") ? `${prefix2}${uri}` : `${prefix2}/${uri}`;
|
|
642
|
+
}
|
|
567
643
|
const base = baseURL.endsWith("/") ? baseURL.slice(0, -1) : baseURL;
|
|
568
|
-
|
|
644
|
+
const prefix = effectiveUrlPrefix;
|
|
645
|
+
return uri.startsWith("/") ? `${base}${prefix}${uri}` : `${base}${prefix}/${uri}`;
|
|
569
646
|
}
|
|
570
647
|
function findRouteMeta(level, name) {
|
|
571
648
|
const entry = cache.get(level);
|
|
@@ -580,19 +657,12 @@ function createRouteForge(options) {
|
|
|
580
657
|
await load(level);
|
|
581
658
|
const meta = findRouteMeta(level, name);
|
|
582
659
|
if (!meta) {
|
|
583
|
-
|
|
584
|
-
return void 0;
|
|
660
|
+
throw new UnknownRouteError(name, level);
|
|
585
661
|
}
|
|
586
662
|
return doApiCall(meta, params);
|
|
587
663
|
}
|
|
588
664
|
async function doApiCall(meta, params) {
|
|
589
|
-
|
|
590
|
-
const { query, body, headers, ...pathParams } = params;
|
|
591
|
-
for (const p of meta.parameters) {
|
|
592
|
-
if (pathParams[p] === void 0) {
|
|
593
|
-
if (effectiveStrict) throw new MissingRouteParamError(meta.name, [p]);
|
|
594
|
-
}
|
|
595
|
-
}
|
|
665
|
+
const { pathParams, query, body, headers } = resolveApiParams(params);
|
|
596
666
|
const method = pickMethod(meta);
|
|
597
667
|
const urlWithQuery = appendQuery(buildRequestUrl(meta, pathParams), query);
|
|
598
668
|
const config = {
|
|
@@ -608,39 +678,74 @@ function createRouteForge(options) {
|
|
|
608
678
|
};
|
|
609
679
|
const adp = await ensureAdapter();
|
|
610
680
|
const finalConfig = adp.runsInterceptors ? config : await runRequestInterceptors(requestInterceptors, config);
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
route
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
681
|
+
loadingTracker.start();
|
|
682
|
+
try {
|
|
683
|
+
const source = adp.request(finalConfig).then(
|
|
684
|
+
(resp) => {
|
|
685
|
+
if (resp.status < 200 || resp.status >= 300) {
|
|
686
|
+
throw new HTTPError(
|
|
687
|
+
`HTTP ${resp.status} for route "${resp.route}" (${resp.method} ${resp.url})`,
|
|
688
|
+
{
|
|
689
|
+
route: resp.route,
|
|
690
|
+
level: resp.level,
|
|
691
|
+
status: resp.status,
|
|
692
|
+
url: resp.url,
|
|
693
|
+
method: resp.method
|
|
694
|
+
}
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
return resp;
|
|
698
|
+
},
|
|
699
|
+
(err) => {
|
|
700
|
+
if (err instanceof ForgeError) throw err;
|
|
701
|
+
throw new NetworkError(
|
|
702
|
+
err instanceof Error ? err.message : String(err),
|
|
703
|
+
meta.name,
|
|
704
|
+
meta.level,
|
|
705
|
+
err
|
|
623
706
|
);
|
|
624
707
|
}
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
meta.name,
|
|
632
|
-
meta.level,
|
|
633
|
-
err
|
|
634
|
-
);
|
|
635
|
-
}
|
|
636
|
-
);
|
|
637
|
-
if (adp.runsInterceptors) return source;
|
|
638
|
-
return runResponseInterceptors(responseInterceptors, source);
|
|
708
|
+
);
|
|
709
|
+
const result = adp.runsInterceptors ? await source : await runResponseInterceptors(responseInterceptors, source);
|
|
710
|
+
return result;
|
|
711
|
+
} finally {
|
|
712
|
+
loadingTracker.stop();
|
|
713
|
+
}
|
|
639
714
|
}
|
|
640
715
|
function invalidate(level) {
|
|
641
716
|
if (level) cache.del(level);
|
|
642
717
|
else cache.clear();
|
|
643
718
|
}
|
|
719
|
+
function isLoaded(level) {
|
|
720
|
+
if (level) return cache.get(level) !== void 0;
|
|
721
|
+
return effectiveLevels.every((lvl) => cache.get(lvl) !== void 0);
|
|
722
|
+
}
|
|
723
|
+
function hasRoute(level, name) {
|
|
724
|
+
return findRouteMeta(level, name) !== void 0;
|
|
725
|
+
}
|
|
726
|
+
function getRoutes(level) {
|
|
727
|
+
if (level !== void 0) {
|
|
728
|
+
const entry = cache.get(level);
|
|
729
|
+
const routes = entry?.routes ?? {};
|
|
730
|
+
const result2 = {};
|
|
731
|
+
for (const [k, v] of Object.entries(routes)) {
|
|
732
|
+
result2[k] = { ...v };
|
|
733
|
+
}
|
|
734
|
+
return result2;
|
|
735
|
+
}
|
|
736
|
+
const result = {};
|
|
737
|
+
for (const lvl of effectiveLevels) {
|
|
738
|
+
const entry = cache.get(lvl);
|
|
739
|
+
if (entry) {
|
|
740
|
+
const levelRoutes = {};
|
|
741
|
+
for (const [k, v] of Object.entries(entry.routes)) {
|
|
742
|
+
levelRoutes[k] = { ...v };
|
|
743
|
+
}
|
|
744
|
+
result[lvl] = levelRoutes;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
return result;
|
|
748
|
+
}
|
|
644
749
|
void autoDiscoveryPromise.then(() => {
|
|
645
750
|
if (effectiveEager.length > 0) {
|
|
646
751
|
void Promise.all(effectiveEager.map((lvl) => load(lvl))).catch((e) => {
|
|
@@ -653,7 +758,13 @@ function createRouteForge(options) {
|
|
|
653
758
|
api,
|
|
654
759
|
load,
|
|
655
760
|
route,
|
|
761
|
+
url: route,
|
|
656
762
|
invalidate,
|
|
763
|
+
isLoaded,
|
|
764
|
+
hasRoute,
|
|
765
|
+
getRoutes,
|
|
766
|
+
isLoading: () => loadingTracker.isLoading(),
|
|
767
|
+
onLoadingChange: (cb) => loadingTracker.subscribe(cb),
|
|
657
768
|
interceptors: {
|
|
658
769
|
request: requestInterceptors,
|
|
659
770
|
response: responseInterceptors
|
|
@@ -675,13 +786,53 @@ function appendQuery(url, query) {
|
|
|
675
786
|
if (!qs) return url;
|
|
676
787
|
return url.includes("?") ? `${url}&${qs}` : `${url}?${qs}`;
|
|
677
788
|
}
|
|
789
|
+
function resolveApiParams(input) {
|
|
790
|
+
const {
|
|
791
|
+
params: explicitParams,
|
|
792
|
+
query: rawQuery,
|
|
793
|
+
body: rawBody,
|
|
794
|
+
headers: rawHeaders,
|
|
795
|
+
...flatRest
|
|
796
|
+
} = input;
|
|
797
|
+
const pathParams = explicitParams ? { ...explicitParams } : {};
|
|
798
|
+
for (const [k, v] of Object.entries(flatRest)) {
|
|
799
|
+
if (!(k in pathParams)) {
|
|
800
|
+
pathParams[k] = v;
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
let query;
|
|
804
|
+
let body;
|
|
805
|
+
let headers;
|
|
806
|
+
if (rawQuery !== void 0) {
|
|
807
|
+
if (typeof rawQuery === "object" && rawQuery !== null) {
|
|
808
|
+
query = rawQuery;
|
|
809
|
+
} else if (!("query" in pathParams)) {
|
|
810
|
+
pathParams.query = rawQuery;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
if (rawBody !== void 0) {
|
|
814
|
+
if (typeof rawBody !== "string" && typeof rawBody !== "number") {
|
|
815
|
+
body = rawBody;
|
|
816
|
+
} else if (!("body" in pathParams)) {
|
|
817
|
+
pathParams.body = rawBody;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
if (rawHeaders !== void 0) {
|
|
821
|
+
if (typeof rawHeaders === "object" && rawHeaders !== null) {
|
|
822
|
+
headers = rawHeaders;
|
|
823
|
+
} else if (!("headers" in pathParams)) {
|
|
824
|
+
pathParams.headers = rawHeaders;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
return { pathParams, query, body, headers };
|
|
828
|
+
}
|
|
678
829
|
|
|
679
830
|
exports.AdapterNotFoundError = AdapterNotFoundError;
|
|
680
831
|
exports.ForgeError = ForgeError;
|
|
681
832
|
exports.HTTPError = HTTPError;
|
|
682
|
-
exports.InsufficientAuthError = InsufficientAuthError;
|
|
683
833
|
exports.InterceptorManagerImpl = InterceptorManagerImpl;
|
|
684
834
|
exports.InvalidInterceptorReturnError = InvalidInterceptorReturnError;
|
|
835
|
+
exports.LoadingTracker = LoadingTracker;
|
|
685
836
|
exports.MissingRouteParamError = MissingRouteParamError;
|
|
686
837
|
exports.NetworkError = NetworkError;
|
|
687
838
|
exports.RouteCache = RouteCache;
|