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