@route-forge/core 2.0.0 → 2.2.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 +282 -210
- package/README_zh.md +408 -0
- package/dist/codegen.cjs +2 -26
- package/dist/codegen.cjs.map +1 -1
- package/dist/codegen.d.cts +2 -9
- package/dist/codegen.d.ts +2 -9
- package/dist/codegen.js +2 -26
- package/dist/codegen.js.map +1 -1
- package/dist/index.cjs +584 -446
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +57 -49
- package/dist/index.d.ts +57 -49
- package/dist/index.js +584 -446
- package/dist/index.js.map +1 -1
- package/dist/route-forge.global.js +584 -446
- package/dist/route-forge.global.js.map +1 -1
- package/dist/route-forge.global.min.js +2 -2
- package/dist/{types-BvLdl02f.d.cts → types-CDKE8rw-.d.cts} +42 -23
- package/dist/{types-BvLdl02f.d.ts → types-CDKE8rw-.d.ts} +42 -23
- package/package.json +20 -3
|
@@ -64,12 +64,28 @@ var RouteForge = (function (exports) {
|
|
|
64
64
|
}
|
|
65
65
|
return entry;
|
|
66
66
|
}
|
|
67
|
-
|
|
67
|
+
/**
|
|
68
|
+
* 写入某层级缓存。后端 TTL 唯一来源为摘要 config.cache_ttl(全局),经参数 backendTtl 传入:
|
|
69
|
+
* - null → 后端声明"不缓存":不写 storage,仅留内存镜像供 route() 同步读(load 层"每次重取"由调用方跳过 get 短路实现)
|
|
70
|
+
* - 0 → 永久缓存(内存 + storage)
|
|
71
|
+
* - undefined → 后端未下发该字段:用前端兜底 TTL(options.cache.ttl)
|
|
72
|
+
* - 正整数 → min(后端, 前端兜底):后端为上限,前端只能缩短不能延长(SPEC §5.3)
|
|
73
|
+
*/
|
|
74
|
+
set(resp, backendTtl) {
|
|
68
75
|
let ttl;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
76
|
+
let persist;
|
|
77
|
+
if (backendTtl === null) {
|
|
78
|
+
ttl = 0;
|
|
79
|
+
persist = false;
|
|
80
|
+
} else if (backendTtl === void 0) {
|
|
72
81
|
ttl = this.fallbackTtl;
|
|
82
|
+
persist = true;
|
|
83
|
+
} else if (backendTtl === 0) {
|
|
84
|
+
ttl = 0;
|
|
85
|
+
persist = true;
|
|
86
|
+
} else {
|
|
87
|
+
ttl = Math.min(backendTtl, this.fallbackTtl);
|
|
88
|
+
persist = true;
|
|
73
89
|
}
|
|
74
90
|
const entry = {
|
|
75
91
|
level: resp.level,
|
|
@@ -78,7 +94,7 @@ var RouteForge = (function (exports) {
|
|
|
78
94
|
cachedAt: Date.now()
|
|
79
95
|
};
|
|
80
96
|
this.memory.set(resp.level, entry);
|
|
81
|
-
if (this.storage === "memory" || !this.backend) {
|
|
97
|
+
if (!persist || this.storage === "memory" || !this.backend) {
|
|
82
98
|
return;
|
|
83
99
|
}
|
|
84
100
|
try {
|
|
@@ -491,39 +507,6 @@ var RouteForge = (function (exports) {
|
|
|
491
507
|
return createBuiltinHttp(opts.forgeInterceptors);
|
|
492
508
|
}
|
|
493
509
|
|
|
494
|
-
// src/resolveRouteName.ts
|
|
495
|
-
async function resolveRouteName(forge, level, prefix, suffix, separator = ".") {
|
|
496
|
-
if (!suffix) return prefix;
|
|
497
|
-
const joined = `${prefix}${separator}${suffix}`;
|
|
498
|
-
if (!suffix.startsWith(`${prefix}${separator}`)) return joined;
|
|
499
|
-
await forge.load(level);
|
|
500
|
-
if (forge.hasRoute(level, joined)) return joined;
|
|
501
|
-
if (forge.hasRoute(level, suffix)) return suffix;
|
|
502
|
-
throw new UnknownRouteError(joined, level);
|
|
503
|
-
}
|
|
504
|
-
function resolveRouteNameSync(forge, level, prefix, suffix, separator = ".") {
|
|
505
|
-
if (!suffix) return prefix;
|
|
506
|
-
const joined = `${prefix}${separator}${suffix}`;
|
|
507
|
-
if (!suffix.startsWith(`${prefix}${separator}`)) return joined;
|
|
508
|
-
if (forge.hasRoute(level, joined)) return joined;
|
|
509
|
-
if (forge.hasRoute(level, suffix)) return suffix;
|
|
510
|
-
throw new UnknownRouteError(joined, level);
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
// src/defineImmutableProps.ts
|
|
514
|
-
function defineImmutableProps(target, props) {
|
|
515
|
-
for (const key of Object.keys(props)) {
|
|
516
|
-
const val = props[key];
|
|
517
|
-
Object.defineProperty(target, key, {
|
|
518
|
-
value: val !== null && typeof val === "object" ? Object.freeze(val) : val,
|
|
519
|
-
writable: false,
|
|
520
|
-
enumerable: false,
|
|
521
|
-
configurable: false
|
|
522
|
-
});
|
|
523
|
-
}
|
|
524
|
-
return target;
|
|
525
|
-
}
|
|
526
|
-
|
|
527
510
|
// src/loading.ts
|
|
528
511
|
var LoadingTracker = class {
|
|
529
512
|
constructor() {
|
|
@@ -583,328 +566,333 @@ var RouteForge = (function (exports) {
|
|
|
583
566
|
}
|
|
584
567
|
};
|
|
585
568
|
|
|
586
|
-
// src/
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
const explicitLevels = options.levels;
|
|
601
|
-
const explicitEager = options.eager;
|
|
602
|
-
const explicitEndpoint = options.endpoint;
|
|
603
|
-
let effectiveLevels = explicitLevels ?? [];
|
|
604
|
-
let effectiveEager = explicitEager ?? [];
|
|
605
|
-
let effectiveEndpoint = explicitEndpoint;
|
|
606
|
-
let effectiveUrlPrefix = "";
|
|
607
|
-
let summaryUnassigned;
|
|
608
|
-
let backendHasUnassignedLevel = false;
|
|
609
|
-
const fetchSummary = async () => {
|
|
610
|
-
try {
|
|
611
|
-
const base = baseURL.endsWith("/") ? baseURL.slice(0, -1) : baseURL;
|
|
612
|
-
const ep = explicitEndpoint.startsWith("/") ? explicitEndpoint : `/${explicitEndpoint}`;
|
|
613
|
-
const data = await fetchMeta("__forge__.summary", `${base}${ep}`);
|
|
614
|
-
return data;
|
|
615
|
-
} catch (e) {
|
|
616
|
-
if (explicitLevels && explicitLevels.length > 0) {
|
|
617
|
-
console.warn(
|
|
618
|
-
`[route-forge] summary endpoint unreachable: ${e.message}; using explicit levels`
|
|
619
|
-
);
|
|
620
|
-
return null;
|
|
621
|
-
}
|
|
622
|
-
throw new UnknownLevelError("(auto-discovery)");
|
|
623
|
-
}
|
|
624
|
-
};
|
|
625
|
-
let summaryPromise;
|
|
626
|
-
const summaryWaiters = [];
|
|
627
|
-
const whenSummary = () => summaryPromise ?? new Promise((resolve) => summaryWaiters.push(resolve));
|
|
628
|
-
const autoDiscoveryPromise = whenSummary().then((summary) => {
|
|
629
|
-
if (summary === null) {
|
|
630
|
-
return;
|
|
631
|
-
}
|
|
632
|
-
const schemaVersion = summary.schemaVersion ?? 1;
|
|
633
|
-
if (schemaVersion > 1) {
|
|
634
|
-
console.warn(
|
|
635
|
-
`[route-forge] backend schemaVersion=${schemaVersion} > client supported 1; some features may be unavailable`
|
|
636
|
-
);
|
|
637
|
-
}
|
|
638
|
-
if (summary.config.endpoint_prefix && summary.config.endpoint_prefix !== explicitEndpoint) {
|
|
639
|
-
console.warn(
|
|
640
|
-
`[route-forge] backend endpoint_prefix "${summary.config.endpoint_prefix}" overrides frontend endpoint "${explicitEndpoint}"`
|
|
641
|
-
);
|
|
642
|
-
effectiveEndpoint = summary.config.endpoint_prefix;
|
|
643
|
-
}
|
|
644
|
-
if (summary.config.url_prefix) {
|
|
645
|
-
effectiveUrlPrefix = summary.config.url_prefix.endsWith("/") ? summary.config.url_prefix.slice(0, -1) : summary.config.url_prefix;
|
|
646
|
-
}
|
|
647
|
-
const backendLevels = Object.keys(summary.levels);
|
|
648
|
-
backendHasUnassignedLevel = backendLevels.includes(UNASSIGNED_LEVEL);
|
|
649
|
-
if (Array.isArray(summary.unassigned) && summary.unassigned.length > 0 && !backendHasUnassignedLevel) {
|
|
650
|
-
summaryUnassigned = summary.unassigned;
|
|
651
|
-
}
|
|
652
|
-
const availableLevels = backendLevels.slice();
|
|
653
|
-
if (summaryUnassigned && !backendHasUnassignedLevel) {
|
|
654
|
-
availableLevels.push(UNASSIGNED_LEVEL);
|
|
569
|
+
// src/url-builder.ts
|
|
570
|
+
function buildUrl(level, ctx) {
|
|
571
|
+
const base = ctx.baseURL.endsWith("/") ? ctx.baseURL.slice(0, -1) : ctx.baseURL;
|
|
572
|
+
const ep = ctx.endpoint.startsWith("/") ? ctx.endpoint : `/${ctx.endpoint}`;
|
|
573
|
+
return `${base}${ep}/${encodeURIComponent(level)}`;
|
|
574
|
+
}
|
|
575
|
+
function buildRequestUrl(meta, params, ctx) {
|
|
576
|
+
const defaults = meta.parameter_defaults ?? {};
|
|
577
|
+
const missingRequired = [];
|
|
578
|
+
const values = {};
|
|
579
|
+
for (const p of meta.parameters) {
|
|
580
|
+
let v = params[p];
|
|
581
|
+
if ((v === void 0 || v === null) && p in defaults) {
|
|
582
|
+
v = defaults[p];
|
|
655
583
|
}
|
|
656
|
-
if (
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
if (removed.length > 0) {
|
|
660
|
-
console.warn(
|
|
661
|
-
`[route-forge] levels not in backend summary and dropped: ${removed.join(", ")}`
|
|
662
|
-
);
|
|
584
|
+
if (v === void 0 || v === null) {
|
|
585
|
+
if (!meta.uri.includes(`{${p}?}`)) {
|
|
586
|
+
missingRequired.push(p);
|
|
663
587
|
}
|
|
664
|
-
effectiveLevels = intersection;
|
|
665
588
|
} else {
|
|
666
|
-
|
|
589
|
+
values[p] = v;
|
|
667
590
|
}
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
591
|
+
}
|
|
592
|
+
if (missingRequired.length > 0) {
|
|
593
|
+
throw new MissingRouteParamError(meta.name, missingRequired);
|
|
594
|
+
}
|
|
595
|
+
let uri = meta.uri.replace(/\{([^{}]+)\}/g, (match, raw) => {
|
|
596
|
+
const optional = raw.endsWith("?");
|
|
597
|
+
const name = optional ? raw.slice(0, -1) : raw;
|
|
598
|
+
if (values[name] !== void 0) {
|
|
599
|
+
const val = values[name];
|
|
600
|
+
if (typeof val === "object") {
|
|
601
|
+
throw new ForgeError(
|
|
602
|
+
`Path parameter "${name}" must be a primitive value (string, number, boolean), got ${typeof val}`,
|
|
603
|
+
{ code: "RF_FE_003", route: meta.name, context: { param: name, value: val } }
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
return encodeURIComponent(String(val));
|
|
674
607
|
}
|
|
608
|
+
return optional ? "" : match;
|
|
675
609
|
});
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
const
|
|
692
|
-
const
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
610
|
+
uri = uri.replace(/\/+/g, "/").replace(/\/$/, "");
|
|
611
|
+
if (/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(ctx.urlPrefix)) {
|
|
612
|
+
const prefix2 = ctx.urlPrefix.endsWith("/") ? ctx.urlPrefix.slice(0, -1) : ctx.urlPrefix;
|
|
613
|
+
return uri.startsWith("/") ? `${prefix2}${uri}` : `${prefix2}/${uri}`;
|
|
614
|
+
}
|
|
615
|
+
const base = ctx.baseURL.endsWith("/") ? ctx.baseURL.slice(0, -1) : ctx.baseURL;
|
|
616
|
+
const prefix = ctx.urlPrefix;
|
|
617
|
+
return uri.startsWith("/") ? `${base}${prefix}${uri}` : `${base}${prefix}/${uri}`;
|
|
618
|
+
}
|
|
619
|
+
function pickMethod(meta) {
|
|
620
|
+
const m = meta.methods.find((x) => x.toUpperCase() !== "HEAD");
|
|
621
|
+
return (m ?? meta.methods[0] ?? "GET").toUpperCase();
|
|
622
|
+
}
|
|
623
|
+
function appendQuery(url, query) {
|
|
624
|
+
if (!query) return url;
|
|
625
|
+
const usp = new URLSearchParams();
|
|
626
|
+
for (const [k, v] of Object.entries(query)) {
|
|
627
|
+
if (v === void 0 || v === null) continue;
|
|
628
|
+
usp.append(k, String(v));
|
|
629
|
+
}
|
|
630
|
+
const qs = usp.toString();
|
|
631
|
+
if (!qs) return url;
|
|
632
|
+
return url.includes("?") ? `${url}&${qs}` : `${url}?${qs}`;
|
|
633
|
+
}
|
|
634
|
+
function resolveApiParams(input) {
|
|
635
|
+
const {
|
|
636
|
+
params: explicitParams,
|
|
637
|
+
query: rawQuery,
|
|
638
|
+
body: rawBody,
|
|
639
|
+
headers: rawHeaders,
|
|
640
|
+
timeout: perCallTimeout,
|
|
641
|
+
...flatRest
|
|
642
|
+
} = input;
|
|
643
|
+
const pathParams = explicitParams ? { ...explicitParams } : {};
|
|
644
|
+
for (const [k, v] of Object.entries(flatRest)) {
|
|
645
|
+
if (!(k in pathParams)) {
|
|
646
|
+
pathParams[k] = v;
|
|
702
647
|
}
|
|
703
648
|
}
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
649
|
+
let query;
|
|
650
|
+
let body;
|
|
651
|
+
let headers;
|
|
652
|
+
if (rawQuery !== void 0) {
|
|
653
|
+
if (typeof rawQuery === "object" && rawQuery !== null) {
|
|
654
|
+
query = rawQuery;
|
|
655
|
+
} else if (!("query" in pathParams)) {
|
|
656
|
+
pathParams.query = rawQuery;
|
|
712
657
|
}
|
|
713
658
|
}
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
summaryPromise = fetchSummary();
|
|
720
|
-
for (const resolve of summaryWaiters) resolve(summaryPromise);
|
|
721
|
-
});
|
|
722
|
-
let adapterResolved = false;
|
|
723
|
-
let adapterObj = null;
|
|
724
|
-
async function ensureAdapter() {
|
|
725
|
-
if (!adapterResolved) {
|
|
726
|
-
adapterObj = await adapterPromise.catch((e) => {
|
|
727
|
-
if (e instanceof AdapterNotFoundError) throw e;
|
|
728
|
-
return resolveAdapter({
|
|
729
|
-
adapter: "builtin",
|
|
730
|
-
forgeInterceptors: { request: requestInterceptors, response: responseInterceptors }
|
|
731
|
-
});
|
|
732
|
-
});
|
|
733
|
-
adapterResolved = true;
|
|
659
|
+
if (rawBody !== void 0) {
|
|
660
|
+
if (typeof rawBody !== "string" && typeof rawBody !== "number") {
|
|
661
|
+
body = rawBody;
|
|
662
|
+
} else if (!("body" in pathParams)) {
|
|
663
|
+
pathParams.body = rawBody;
|
|
734
664
|
}
|
|
735
|
-
return adapterObj;
|
|
736
665
|
}
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
if (!
|
|
741
|
-
|
|
666
|
+
if (rawHeaders !== void 0) {
|
|
667
|
+
if (typeof rawHeaders === "object" && rawHeaders !== null) {
|
|
668
|
+
headers = rawHeaders;
|
|
669
|
+
} else if (!("headers" in pathParams)) {
|
|
670
|
+
pathParams.headers = rawHeaders;
|
|
742
671
|
}
|
|
743
672
|
}
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
673
|
+
return { pathParams, query, body, headers, timeout: perCallTimeout };
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// src/auto-discovery.ts
|
|
677
|
+
async function fetchSummary(inputs, baseURL, fetchMeta) {
|
|
678
|
+
const { explicitLevels, explicitEndpoint } = inputs;
|
|
679
|
+
if (!explicitEndpoint) {
|
|
680
|
+
throw new UnknownLevelError("(auto-discovery)");
|
|
681
|
+
}
|
|
682
|
+
try {
|
|
683
|
+
const base = baseURL.endsWith("/") ? baseURL.slice(0, -1) : baseURL;
|
|
684
|
+
const ep = explicitEndpoint.startsWith("/") ? explicitEndpoint : `/${explicitEndpoint}`;
|
|
685
|
+
const data = await fetchMeta("__forge__.summary", `${base}${ep}`);
|
|
686
|
+
return data;
|
|
687
|
+
} catch (e) {
|
|
688
|
+
if (explicitLevels && explicitLevels.length > 0) {
|
|
689
|
+
console.warn(
|
|
690
|
+
`[route-forge] summary endpoint unreachable: ${e.message}; using explicit levels`
|
|
749
691
|
);
|
|
692
|
+
return null;
|
|
750
693
|
}
|
|
694
|
+
throw new UnknownLevelError("(auto-discovery)");
|
|
751
695
|
}
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
696
|
+
}
|
|
697
|
+
function normalizeCacheTtl(raw) {
|
|
698
|
+
if (typeof raw === "number" && raw < 0) return null;
|
|
699
|
+
return raw;
|
|
700
|
+
}
|
|
701
|
+
function applySummaryToState(summary, state, inputs) {
|
|
702
|
+
const { explicitLevels, explicitEager, explicitEndpoint } = inputs;
|
|
703
|
+
const schemeVersion = summary.schemeVersion ?? 1;
|
|
704
|
+
if (schemeVersion > 1) {
|
|
705
|
+
console.warn(
|
|
706
|
+
`[route-forge] backend schemeVersion=${schemeVersion} > client supported 1; some features may be unavailable`
|
|
707
|
+
);
|
|
756
708
|
}
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
level,
|
|
762
|
-
method: "GET",
|
|
763
|
-
url,
|
|
764
|
-
headers: { Accept: "application/json" },
|
|
765
|
-
params: {},
|
|
766
|
-
timeout,
|
|
767
|
-
meta: {
|
|
768
|
-
name: routeTag,
|
|
769
|
-
uri: url,
|
|
770
|
-
methods: ["GET"],
|
|
771
|
-
parameters: [],
|
|
772
|
-
level
|
|
773
|
-
}
|
|
774
|
-
};
|
|
775
|
-
const doRawRequest = adp.requestRaw ?? adp.request;
|
|
776
|
-
const resp = await doRawRequest(config);
|
|
777
|
-
if (!resp || resp.status < 200 || resp.status >= 300) {
|
|
778
|
-
throw new HTTPError(
|
|
779
|
-
`Failed to fetch "${routeTag}": HTTP ${resp?.status}`,
|
|
780
|
-
{ level, status: resp?.status, url, method: "GET" }
|
|
709
|
+
if (summary.config.endpoint_prefix && summary.config.endpoint_prefix !== explicitEndpoint) {
|
|
710
|
+
if (explicitEndpoint) {
|
|
711
|
+
console.warn(
|
|
712
|
+
`[route-forge] backend endpoint_prefix "${summary.config.endpoint_prefix}" overrides frontend endpoint "${explicitEndpoint}"`
|
|
781
713
|
);
|
|
782
714
|
}
|
|
783
|
-
|
|
715
|
+
state.endpoint = summary.config.endpoint_prefix;
|
|
716
|
+
}
|
|
717
|
+
if (summary.config.url_prefix) {
|
|
718
|
+
state.urlPrefix = summary.config.url_prefix.endsWith("/") ? summary.config.url_prefix.slice(0, -1) : summary.config.url_prefix;
|
|
719
|
+
}
|
|
720
|
+
state.cacheTtl = normalizeCacheTtl(summary.config.cache_ttl);
|
|
721
|
+
const backendLevels = Object.keys(summary.levels);
|
|
722
|
+
const levelRoutes = {};
|
|
723
|
+
for (const lvl of backendLevels) {
|
|
724
|
+
const r = summary.levels[lvl]?.route;
|
|
725
|
+
if (r && typeof r.uri === "string") {
|
|
726
|
+
levelRoutes[lvl] = { uri: r.uri, methods: r.methods ?? ["GET", "HEAD"] };
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
state.levelRoutes = levelRoutes;
|
|
730
|
+
if (explicitLevels && explicitLevels.length > 0) {
|
|
731
|
+
const intersection = explicitLevels.filter((l) => backendLevels.includes(l));
|
|
732
|
+
const removed = explicitLevels.filter((l) => !backendLevels.includes(l));
|
|
733
|
+
if (removed.length > 0) {
|
|
734
|
+
console.warn(
|
|
735
|
+
`[route-forge] levels not in backend summary and dropped: ${removed.join(", ")}`
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
state.levels = intersection;
|
|
739
|
+
} else {
|
|
740
|
+
state.levels = backendLevels.slice();
|
|
741
|
+
}
|
|
742
|
+
const backendEager = backendLevels.filter((lvl) => summary.levels[lvl]?.load === "eager");
|
|
743
|
+
if (!explicitEager) {
|
|
744
|
+
state.eager = backendEager;
|
|
745
|
+
} else {
|
|
746
|
+
const union = /* @__PURE__ */ new Set([...backendEager, ...explicitEager]);
|
|
747
|
+
state.eager = [...union];
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// src/embedded-summary.ts
|
|
752
|
+
var EMBEDDED_GLOBAL_KEY = "__ROUTE_FORGE__";
|
|
753
|
+
var memo;
|
|
754
|
+
function readEmbeddedSummary() {
|
|
755
|
+
if (memo !== void 0) return memo;
|
|
756
|
+
if (typeof window === "undefined") {
|
|
757
|
+
memo = null;
|
|
758
|
+
return null;
|
|
759
|
+
}
|
|
760
|
+
let raw;
|
|
761
|
+
try {
|
|
762
|
+
raw = window[EMBEDDED_GLOBAL_KEY];
|
|
763
|
+
} catch {
|
|
764
|
+
raw = void 0;
|
|
765
|
+
}
|
|
766
|
+
memo = raw !== null && typeof raw === "object" ? raw : null;
|
|
767
|
+
return memo;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// src/route-store.ts
|
|
771
|
+
var RouteStore = class {
|
|
772
|
+
constructor(deps) {
|
|
773
|
+
this.inflight = /* @__PURE__ */ new Map();
|
|
774
|
+
/** 每个层级的失效代数,用于检测 loadOne 期间是否发生了 invalidate */
|
|
775
|
+
this.invalidationGens = /* @__PURE__ */ new Map();
|
|
776
|
+
this.cache = deps.cache;
|
|
777
|
+
this.state = deps.state;
|
|
778
|
+
this.baseURL = deps.baseURL;
|
|
779
|
+
this.fetchMeta = deps.fetchMeta;
|
|
780
|
+
this.autoDiscoveryPromise = deps.autoDiscoveryPromise;
|
|
781
|
+
this.getAutoDiscoveryError = deps.getAutoDiscoveryError;
|
|
782
|
+
}
|
|
783
|
+
assertLevelDeclared(level) {
|
|
784
|
+
if (!this.state.levels.includes(level)) {
|
|
785
|
+
throw new UnknownLevelError(level);
|
|
786
|
+
}
|
|
784
787
|
}
|
|
785
|
-
async
|
|
786
|
-
|
|
788
|
+
async fetchLevel(level) {
|
|
789
|
+
const uri = this.state.levelRoutes[level]?.uri;
|
|
790
|
+
const url = uri ? this.joinBaseAndPath(this.baseURL, uri) : buildUrl(level, { baseURL: this.baseURL, endpoint: this.state.endpoint });
|
|
791
|
+
return await this.fetchMeta(`route-forge.${level}`, url, level);
|
|
792
|
+
}
|
|
793
|
+
/** baseURL 与后端下发的绝对 path 拼接(规范化斜杠),与 buildUrl 的 base 处理一致 */
|
|
794
|
+
joinBaseAndPath(baseURL, path) {
|
|
795
|
+
const base = baseURL.endsWith("/") ? baseURL.slice(0, -1) : baseURL;
|
|
796
|
+
const p = path.startsWith("/") ? path : `/${path}`;
|
|
797
|
+
return `${base}${p}`;
|
|
787
798
|
}
|
|
788
|
-
async
|
|
799
|
+
async loadOne(level) {
|
|
800
|
+
const autoDiscoveryError = this.getAutoDiscoveryError();
|
|
789
801
|
if (autoDiscoveryError) throw autoDiscoveryError;
|
|
790
|
-
assertLevelDeclared(level);
|
|
791
|
-
|
|
792
|
-
|
|
802
|
+
this.assertLevelDeclared(level);
|
|
803
|
+
const noCache = this.state.cacheTtl === null;
|
|
804
|
+
if (!noCache && this.cache.get(level)) return;
|
|
805
|
+
const existing = this.inflight.get(level);
|
|
793
806
|
if (existing) return existing;
|
|
794
|
-
const gen = invalidationGens.get(level) ?? 0;
|
|
807
|
+
const gen = this.invalidationGens.get(level) ?? 0;
|
|
795
808
|
const p = (async () => {
|
|
796
809
|
try {
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
routes[r.name] = { ...r, level: UNASSIGNED_LEVEL };
|
|
801
|
-
}
|
|
802
|
-
cache.set({ level: UNASSIGNED_LEVEL, routes, cache: null });
|
|
803
|
-
return;
|
|
804
|
-
}
|
|
805
|
-
const resp = await fetchLevel(level);
|
|
806
|
-
if ((invalidationGens.get(level) ?? 0) === gen) {
|
|
807
|
-
cache.set(resp);
|
|
810
|
+
const resp = await this.fetchLevel(level);
|
|
811
|
+
if ((this.invalidationGens.get(level) ?? 0) === gen) {
|
|
812
|
+
this.cache.set(resp, this.state.cacheTtl);
|
|
808
813
|
}
|
|
809
814
|
} finally {
|
|
810
|
-
inflight.delete(level);
|
|
815
|
+
this.inflight.delete(level);
|
|
811
816
|
}
|
|
812
817
|
})();
|
|
813
|
-
inflight.set(level, p);
|
|
818
|
+
this.inflight.set(level, p);
|
|
814
819
|
return p;
|
|
815
820
|
}
|
|
816
|
-
async
|
|
817
|
-
await autoDiscoveryPromise;
|
|
821
|
+
async load(level) {
|
|
822
|
+
await this.autoDiscoveryPromise;
|
|
818
823
|
const list = Array.isArray(level) ? level : [level];
|
|
819
|
-
await Promise.all(list.map(loadOne));
|
|
824
|
+
await Promise.all(list.map((l) => this.loadOne(l)));
|
|
820
825
|
}
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
return buildRequestUrl(meta, params ?? {});
|
|
828
|
-
}
|
|
829
|
-
function buildRequestUrl(meta, params) {
|
|
830
|
-
const defaults = meta.parameter_defaults ?? {};
|
|
831
|
-
const missingRequired = [];
|
|
832
|
-
const values = {};
|
|
833
|
-
for (const p of meta.parameters) {
|
|
834
|
-
let v = params[p];
|
|
835
|
-
if ((v === void 0 || v === null) && p in defaults) {
|
|
836
|
-
v = defaults[p];
|
|
826
|
+
invalidate(level) {
|
|
827
|
+
if (level === void 0) {
|
|
828
|
+
this.cache.clear();
|
|
829
|
+
this.inflight.clear();
|
|
830
|
+
for (const lvl of this.state.levels) {
|
|
831
|
+
this.invalidationGens.set(lvl, (this.invalidationGens.get(lvl) ?? 0) + 1);
|
|
837
832
|
}
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
values[p] = v;
|
|
833
|
+
} else if (Array.isArray(level)) {
|
|
834
|
+
for (const lvl of level) {
|
|
835
|
+
this.cache.del(lvl);
|
|
836
|
+
this.inflight.delete(lvl);
|
|
837
|
+
this.invalidationGens.set(lvl, (this.invalidationGens.get(lvl) ?? 0) + 1);
|
|
844
838
|
}
|
|
839
|
+
} else {
|
|
840
|
+
this.cache.del(level);
|
|
841
|
+
this.inflight.delete(level);
|
|
842
|
+
this.invalidationGens.set(level, (this.invalidationGens.get(level) ?? 0) + 1);
|
|
845
843
|
}
|
|
846
|
-
if (missingRequired.length > 0) {
|
|
847
|
-
throw new MissingRouteParamError(meta.name, missingRequired);
|
|
848
|
-
}
|
|
849
|
-
let uri = meta.uri.replace(/\{([^{}]+)\}/g, (match, raw) => {
|
|
850
|
-
const optional = raw.endsWith("?");
|
|
851
|
-
const name = optional ? raw.slice(0, -1) : raw;
|
|
852
|
-
if (values[name] !== void 0) {
|
|
853
|
-
const val = values[name];
|
|
854
|
-
if (typeof val === "object") {
|
|
855
|
-
throw new ForgeError(
|
|
856
|
-
`Path parameter "${name}" must be a primitive value (string, number, boolean), got ${typeof val}`,
|
|
857
|
-
{ code: "RF_FE_003", route: meta.name, context: { param: name, value: val } }
|
|
858
|
-
);
|
|
859
|
-
}
|
|
860
|
-
return encodeURIComponent(String(val));
|
|
861
|
-
}
|
|
862
|
-
return optional ? "" : match;
|
|
863
|
-
});
|
|
864
|
-
uri = uri.replace(/\/+/g, "/").replace(/\/$/, "");
|
|
865
|
-
if (/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(effectiveUrlPrefix)) {
|
|
866
|
-
const prefix2 = effectiveUrlPrefix.endsWith("/") ? effectiveUrlPrefix.slice(0, -1) : effectiveUrlPrefix;
|
|
867
|
-
return uri.startsWith("/") ? `${prefix2}${uri}` : `${prefix2}/${uri}`;
|
|
868
|
-
}
|
|
869
|
-
const base = baseURL.endsWith("/") ? baseURL.slice(0, -1) : baseURL;
|
|
870
|
-
const prefix = effectiveUrlPrefix;
|
|
871
|
-
return uri.startsWith("/") ? `${base}${prefix}${uri}` : `${base}${prefix}/${uri}`;
|
|
872
844
|
}
|
|
873
|
-
|
|
874
|
-
|
|
845
|
+
isLoaded(level) {
|
|
846
|
+
if (level) return this.cache.get(level) !== void 0;
|
|
847
|
+
return this.state.levels.length > 0 && this.state.levels.every((lvl) => this.cache.get(lvl) !== void 0);
|
|
848
|
+
}
|
|
849
|
+
findRouteMeta(level, name) {
|
|
850
|
+
const entry = this.cache.get(level);
|
|
875
851
|
const meta = entry?.routes[name];
|
|
876
852
|
if (meta) {
|
|
877
853
|
return { ...meta, level };
|
|
878
854
|
}
|
|
879
855
|
return void 0;
|
|
880
856
|
}
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
ctrl.abort(abortReason);
|
|
889
|
-
}
|
|
890
|
-
await autoDiscoveryPromise;
|
|
891
|
-
await load(level);
|
|
892
|
-
const meta = findRouteMeta(level, name);
|
|
893
|
-
if (!meta) {
|
|
894
|
-
throw new UnknownRouteError(name, level);
|
|
857
|
+
getRoutes(level) {
|
|
858
|
+
if (level !== void 0) {
|
|
859
|
+
const entry = this.cache.get(level);
|
|
860
|
+
const routes = entry?.routes ?? {};
|
|
861
|
+
const result2 = {};
|
|
862
|
+
for (const [k, v] of Object.entries(routes)) {
|
|
863
|
+
result2[k] = JSON.parse(JSON.stringify(v));
|
|
895
864
|
}
|
|
896
|
-
return
|
|
897
|
-
}
|
|
898
|
-
const
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
865
|
+
return result2;
|
|
866
|
+
}
|
|
867
|
+
const result = {};
|
|
868
|
+
for (const lvl of this.state.levels) {
|
|
869
|
+
const entry = this.cache.get(lvl);
|
|
870
|
+
if (entry) {
|
|
871
|
+
const levelRoutes = {};
|
|
872
|
+
for (const [k, v] of Object.entries(entry.routes)) {
|
|
873
|
+
levelRoutes[k] = JSON.parse(JSON.stringify(v));
|
|
874
|
+
}
|
|
875
|
+
result[lvl] = levelRoutes;
|
|
904
876
|
}
|
|
905
|
-
}
|
|
906
|
-
return
|
|
877
|
+
}
|
|
878
|
+
return result;
|
|
907
879
|
}
|
|
880
|
+
};
|
|
881
|
+
|
|
882
|
+
// src/http-runner.ts
|
|
883
|
+
function createHttpRunner(deps) {
|
|
884
|
+
const {
|
|
885
|
+
ensureAdapter,
|
|
886
|
+
requestInterceptors,
|
|
887
|
+
responseInterceptors,
|
|
888
|
+
load,
|
|
889
|
+
findRouteMeta,
|
|
890
|
+
baseURL,
|
|
891
|
+
state,
|
|
892
|
+
timeout,
|
|
893
|
+
loadingTracker,
|
|
894
|
+
autoDiscoveryPromise
|
|
895
|
+
} = deps;
|
|
908
896
|
async function doApiCall(meta, params, signal) {
|
|
909
897
|
const {
|
|
910
898
|
pathParams,
|
|
@@ -917,7 +905,10 @@ var RouteForge = (function (exports) {
|
|
|
917
905
|
throw new RequestAbortedError(meta.name, meta.level, signal.reason);
|
|
918
906
|
}
|
|
919
907
|
const method = pickMethod(meta);
|
|
920
|
-
const urlWithQuery = appendQuery(
|
|
908
|
+
const urlWithQuery = appendQuery(
|
|
909
|
+
buildRequestUrl(meta, pathParams, { baseURL, urlPrefix: state.urlPrefix }),
|
|
910
|
+
query
|
|
911
|
+
);
|
|
921
912
|
const config = {
|
|
922
913
|
route: meta.name,
|
|
923
914
|
level: meta.level ?? "",
|
|
@@ -972,88 +963,77 @@ var RouteForge = (function (exports) {
|
|
|
972
963
|
loadingTracker.stop();
|
|
973
964
|
}
|
|
974
965
|
}
|
|
975
|
-
function
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
for (const lvl of level) {
|
|
984
|
-
cache.del(lvl);
|
|
985
|
-
inflight.delete(lvl);
|
|
986
|
-
invalidationGens.set(lvl, (invalidationGens.get(lvl) ?? 0) + 1);
|
|
966
|
+
return function api(level, name, params = {}) {
|
|
967
|
+
let ctrl;
|
|
968
|
+
let abortedBeforeInit = false;
|
|
969
|
+
let abortReason;
|
|
970
|
+
const work = (async () => {
|
|
971
|
+
ctrl = new AbortController();
|
|
972
|
+
if (abortedBeforeInit) {
|
|
973
|
+
ctrl.abort(abortReason);
|
|
987
974
|
}
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
}
|
|
994
|
-
function isLoaded(level) {
|
|
995
|
-
if (level) return cache.get(level) !== void 0;
|
|
996
|
-
return effectiveLevels.length > 0 && effectiveLevels.every((lvl) => cache.get(lvl) !== void 0);
|
|
997
|
-
}
|
|
998
|
-
function hasRoute(level, name) {
|
|
999
|
-
assertDiscoveryReady();
|
|
1000
|
-
return findRouteMeta(level, name) !== void 0;
|
|
1001
|
-
}
|
|
1002
|
-
function getRoutes(level) {
|
|
1003
|
-
if (level !== void 0) {
|
|
1004
|
-
const entry = cache.get(level);
|
|
1005
|
-
const routes = entry?.routes ?? {};
|
|
1006
|
-
const result2 = {};
|
|
1007
|
-
for (const [k, v] of Object.entries(routes)) {
|
|
1008
|
-
result2[k] = JSON.parse(JSON.stringify(v));
|
|
975
|
+
await autoDiscoveryPromise;
|
|
976
|
+
await load(level);
|
|
977
|
+
const meta = findRouteMeta(level, name);
|
|
978
|
+
if (!meta) {
|
|
979
|
+
throw new UnknownRouteError(name, level);
|
|
1009
980
|
}
|
|
1010
|
-
return
|
|
1011
|
-
}
|
|
1012
|
-
const
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
levelRoutes[k] = JSON.parse(JSON.stringify(v));
|
|
1019
|
-
}
|
|
1020
|
-
result[lvl] = levelRoutes;
|
|
981
|
+
return doApiCall(meta, params, ctrl.signal);
|
|
982
|
+
})();
|
|
983
|
+
const request = work;
|
|
984
|
+
request.abort = () => {
|
|
985
|
+
if (ctrl) {
|
|
986
|
+
ctrl.abort();
|
|
987
|
+
} else {
|
|
988
|
+
abortedBeforeInit = true;
|
|
1021
989
|
}
|
|
1022
|
-
}
|
|
1023
|
-
return
|
|
1024
|
-
}
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
990
|
+
};
|
|
991
|
+
return request;
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
// src/resolveRouteName.ts
|
|
996
|
+
async function resolveRouteName(forge, level, prefix, suffix, separator = ".") {
|
|
997
|
+
if (!suffix) return prefix;
|
|
998
|
+
const joined = `${prefix}${separator}${suffix}`;
|
|
999
|
+
if (!suffix.startsWith(`${prefix}${separator}`)) return joined;
|
|
1000
|
+
await forge.load(level);
|
|
1001
|
+
if (forge.hasRoute(level, joined)) return joined;
|
|
1002
|
+
if (forge.hasRoute(level, suffix)) return suffix;
|
|
1003
|
+
throw new UnknownRouteError(joined, level);
|
|
1004
|
+
}
|
|
1005
|
+
function resolveRouteNameSync(forge, level, prefix, suffix, separator = ".") {
|
|
1006
|
+
if (!suffix) return prefix;
|
|
1007
|
+
const joined = `${prefix}${separator}${suffix}`;
|
|
1008
|
+
if (!suffix.startsWith(`${prefix}${separator}`)) return joined;
|
|
1009
|
+
if (forge.hasRoute(level, joined)) return joined;
|
|
1010
|
+
if (forge.hasRoute(level, suffix)) return suffix;
|
|
1011
|
+
throw new UnknownRouteError(joined, level);
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
// src/defineImmutableProps.ts
|
|
1015
|
+
function defineImmutableProps(target, props) {
|
|
1016
|
+
for (const key of Object.keys(props)) {
|
|
1017
|
+
const val = props[key];
|
|
1018
|
+
Object.defineProperty(target, key, {
|
|
1019
|
+
value: val !== null && typeof val === "object" ? Object.freeze(val) : val,
|
|
1020
|
+
writable: false,
|
|
1021
|
+
enumerable: false,
|
|
1022
|
+
configurable: false
|
|
1023
|
+
});
|
|
1050
1024
|
}
|
|
1051
|
-
|
|
1025
|
+
return target;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
// src/bound-forge.ts
|
|
1029
|
+
function createBoundForgeFactory(deps) {
|
|
1030
|
+
const { load, api, route, hasRoute, getRoutes, invalidate, isLoaded, loadingTracker } = deps;
|
|
1031
|
+
const resolver = { load, hasRoute };
|
|
1052
1032
|
function createBoundForge(level, prefix) {
|
|
1053
1033
|
const levelLoadedPromise = load(level);
|
|
1054
1034
|
levelLoadedPromise.catch(() => {
|
|
1055
1035
|
});
|
|
1056
|
-
const apiFn = prefix ? (name, params) => resolveRouteName(
|
|
1036
|
+
const apiFn = prefix ? (name, params) => resolveRouteName(resolver, level, prefix, name).then(
|
|
1057
1037
|
(resolved) => api(level, resolved, params)
|
|
1058
1038
|
) : (name, params) => api(level, name, params);
|
|
1059
1039
|
const callable = apiFn;
|
|
@@ -1061,8 +1041,8 @@ var RouteForge = (function (exports) {
|
|
|
1061
1041
|
level,
|
|
1062
1042
|
...prefix !== void 0 ? { prefix } : {},
|
|
1063
1043
|
api: apiFn,
|
|
1064
|
-
route: prefix ? (name, params) => route(level, resolveRouteNameSync(
|
|
1065
|
-
url: prefix ? (name, params) => route(level, resolveRouteNameSync(
|
|
1044
|
+
route: prefix ? (name, params) => route(level, resolveRouteNameSync(resolver, level, prefix, name), params) : (name, params) => route(level, name, params),
|
|
1045
|
+
url: prefix ? (name, params) => route(level, resolveRouteNameSync(resolver, level, prefix, name), params) : (name, params) => route(level, name, params),
|
|
1066
1046
|
hasRoute: (name) => hasRoute(level, name),
|
|
1067
1047
|
getRoutes: () => getRoutes(level),
|
|
1068
1048
|
load: () => load(level),
|
|
@@ -1106,6 +1086,220 @@ var RouteForge = (function (exports) {
|
|
|
1106
1086
|
attachBoundMethods(bound, levelLoadedPromise, level);
|
|
1107
1087
|
return bound;
|
|
1108
1088
|
}
|
|
1089
|
+
return createBoundForgeWithMethods;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
// src/forge.ts
|
|
1093
|
+
var DEFAULT_TIMEOUT = 3e4;
|
|
1094
|
+
var DEFAULT_CACHE_TTL = 3600;
|
|
1095
|
+
function createRouteForge(options) {
|
|
1096
|
+
const bootstrapSummary = readEmbeddedSummary() ?? options.summary ?? null;
|
|
1097
|
+
if (!bootstrapSummary && !options.endpoint) {
|
|
1098
|
+
throw new TypeError(
|
|
1099
|
+
"createRouteForge: \u9700\u8981 options.endpoint\uFF0C\u6216 options.summary\uFF0C\u6216\u9875\u9762\u5185\u5D4C window.__ROUTE_FORGE__"
|
|
1100
|
+
);
|
|
1101
|
+
}
|
|
1102
|
+
const {
|
|
1103
|
+
adapter = "auto",
|
|
1104
|
+
timeout = DEFAULT_TIMEOUT,
|
|
1105
|
+
baseURL = "",
|
|
1106
|
+
interceptors: declarativeInterceptors,
|
|
1107
|
+
cache: cacheOpts = {}
|
|
1108
|
+
} = options;
|
|
1109
|
+
const loadingTracker = new LoadingTracker();
|
|
1110
|
+
const explicitLevels = options.levels;
|
|
1111
|
+
const explicitEager = options.eager;
|
|
1112
|
+
const explicitEndpoint = options.endpoint;
|
|
1113
|
+
const discoveryState = {
|
|
1114
|
+
levels: explicitLevels ?? [],
|
|
1115
|
+
eager: explicitEager ?? [],
|
|
1116
|
+
endpoint: explicitEndpoint ?? bootstrapSummary?.config?.endpoint_prefix ?? "",
|
|
1117
|
+
urlPrefix: "",
|
|
1118
|
+
cacheTtl: void 0,
|
|
1119
|
+
levelRoutes: {}
|
|
1120
|
+
};
|
|
1121
|
+
const discoveryInputs = { explicitLevels, explicitEager, explicitEndpoint };
|
|
1122
|
+
let autoDiscoveryCompleted = false;
|
|
1123
|
+
let resolveReady;
|
|
1124
|
+
let rejectReady;
|
|
1125
|
+
const readyPromise = new Promise((resolve, reject) => {
|
|
1126
|
+
resolveReady = resolve;
|
|
1127
|
+
rejectReady = reject;
|
|
1128
|
+
});
|
|
1129
|
+
readyPromise.catch(() => {
|
|
1130
|
+
});
|
|
1131
|
+
const cacheTtl = cacheOpts.ttl ?? DEFAULT_CACHE_TTL;
|
|
1132
|
+
const cacheStorage = cacheOpts.storage ?? "memory";
|
|
1133
|
+
const cache = new RouteCache({ storage: cacheStorage, ttl: cacheTtl });
|
|
1134
|
+
const requestInterceptors = new InterceptorManagerImpl();
|
|
1135
|
+
const responseInterceptors = new InterceptorManagerImpl();
|
|
1136
|
+
if (declarativeInterceptors?.request) {
|
|
1137
|
+
for (const entry of declarativeInterceptors.request) {
|
|
1138
|
+
if (typeof entry === "function") {
|
|
1139
|
+
requestInterceptors.use(entry);
|
|
1140
|
+
} else {
|
|
1141
|
+
const [onFulfilled, onRejected] = entry;
|
|
1142
|
+
requestInterceptors.use(onFulfilled, onRejected);
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
if (declarativeInterceptors?.response) {
|
|
1147
|
+
for (const entry of declarativeInterceptors.response) {
|
|
1148
|
+
if (typeof entry === "function") {
|
|
1149
|
+
responseInterceptors.use(entry);
|
|
1150
|
+
} else {
|
|
1151
|
+
const [onFulfilled, onRejected] = entry;
|
|
1152
|
+
responseInterceptors.use(onFulfilled, onRejected);
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
const adapterPromise = resolveAdapter({
|
|
1157
|
+
adapter,
|
|
1158
|
+
forgeInterceptors: { request: requestInterceptors, response: responseInterceptors }
|
|
1159
|
+
});
|
|
1160
|
+
let adapterResolved = false;
|
|
1161
|
+
let adapterObj = null;
|
|
1162
|
+
async function ensureAdapter() {
|
|
1163
|
+
if (!adapterResolved) {
|
|
1164
|
+
adapterObj = await adapterPromise.catch((e) => {
|
|
1165
|
+
if (e instanceof AdapterNotFoundError) throw e;
|
|
1166
|
+
return resolveAdapter({
|
|
1167
|
+
adapter: "builtin",
|
|
1168
|
+
forgeInterceptors: { request: requestInterceptors, response: responseInterceptors }
|
|
1169
|
+
});
|
|
1170
|
+
});
|
|
1171
|
+
adapterResolved = true;
|
|
1172
|
+
}
|
|
1173
|
+
return adapterObj;
|
|
1174
|
+
}
|
|
1175
|
+
function assertDiscoveryReady() {
|
|
1176
|
+
if (!autoDiscoveryCompleted && !explicitLevels?.length) {
|
|
1177
|
+
throw new ForgeError(
|
|
1178
|
+
"Route data not available. Auto-discovery has not completed. Use forge.ready() or forge.use(level) first.",
|
|
1179
|
+
{ code: "RF_FE_010" }
|
|
1180
|
+
);
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
async function fetchMeta(routeTag, url, level = "") {
|
|
1184
|
+
const adp = await ensureAdapter();
|
|
1185
|
+
const config = {
|
|
1186
|
+
route: routeTag,
|
|
1187
|
+
level,
|
|
1188
|
+
method: "GET",
|
|
1189
|
+
url,
|
|
1190
|
+
headers: { Accept: "application/json" },
|
|
1191
|
+
params: {},
|
|
1192
|
+
timeout,
|
|
1193
|
+
meta: {
|
|
1194
|
+
name: routeTag,
|
|
1195
|
+
uri: url,
|
|
1196
|
+
methods: ["GET"],
|
|
1197
|
+
parameters: [],
|
|
1198
|
+
level
|
|
1199
|
+
}
|
|
1200
|
+
};
|
|
1201
|
+
const doRawRequest = adp.requestRaw ?? adp.request;
|
|
1202
|
+
const resp = await doRawRequest(config);
|
|
1203
|
+
if (!resp || resp.status < 200 || resp.status >= 300) {
|
|
1204
|
+
throw new HTTPError(
|
|
1205
|
+
`Failed to fetch "${routeTag}": HTTP ${resp?.status}`,
|
|
1206
|
+
{ level, status: resp?.status, url, method: "GET" }
|
|
1207
|
+
);
|
|
1208
|
+
}
|
|
1209
|
+
return resp.data;
|
|
1210
|
+
}
|
|
1211
|
+
let autoDiscoveryPromise;
|
|
1212
|
+
if (bootstrapSummary) {
|
|
1213
|
+
applySummaryToState(bootstrapSummary, discoveryState, discoveryInputs);
|
|
1214
|
+
autoDiscoveryCompleted = true;
|
|
1215
|
+
autoDiscoveryPromise = Promise.resolve();
|
|
1216
|
+
} else {
|
|
1217
|
+
autoDiscoveryPromise = fetchSummary(discoveryInputs, baseURL, fetchMeta).then((summary) => {
|
|
1218
|
+
if (summary === null) {
|
|
1219
|
+
return;
|
|
1220
|
+
}
|
|
1221
|
+
applySummaryToState(summary, discoveryState, discoveryInputs);
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1224
|
+
let autoDiscoveryError = null;
|
|
1225
|
+
autoDiscoveryPromise.catch((e) => {
|
|
1226
|
+
autoDiscoveryError = e;
|
|
1227
|
+
});
|
|
1228
|
+
const store = new RouteStore({
|
|
1229
|
+
cache,
|
|
1230
|
+
state: discoveryState,
|
|
1231
|
+
baseURL,
|
|
1232
|
+
fetchMeta,
|
|
1233
|
+
autoDiscoveryPromise,
|
|
1234
|
+
getAutoDiscoveryError: () => autoDiscoveryError
|
|
1235
|
+
});
|
|
1236
|
+
const load = (level) => store.load(level);
|
|
1237
|
+
const findRouteMeta = (level, name) => store.findRouteMeta(level, name);
|
|
1238
|
+
const invalidate = (level) => store.invalidate(level);
|
|
1239
|
+
const isLoaded = (level) => store.isLoaded(level);
|
|
1240
|
+
function getRoutes(level) {
|
|
1241
|
+
return level === void 0 ? store.getRoutes() : store.getRoutes(level);
|
|
1242
|
+
}
|
|
1243
|
+
function route(level, name, params) {
|
|
1244
|
+
assertDiscoveryReady();
|
|
1245
|
+
const meta = findRouteMeta(level, name);
|
|
1246
|
+
if (!meta) {
|
|
1247
|
+
throw new UnknownRouteError(name, level);
|
|
1248
|
+
}
|
|
1249
|
+
return buildRequestUrl(meta, params ?? {}, { baseURL, urlPrefix: discoveryState.urlPrefix });
|
|
1250
|
+
}
|
|
1251
|
+
const api = createHttpRunner({
|
|
1252
|
+
ensureAdapter,
|
|
1253
|
+
requestInterceptors,
|
|
1254
|
+
responseInterceptors,
|
|
1255
|
+
load,
|
|
1256
|
+
findRouteMeta,
|
|
1257
|
+
baseURL,
|
|
1258
|
+
state: discoveryState,
|
|
1259
|
+
timeout,
|
|
1260
|
+
loadingTracker,
|
|
1261
|
+
autoDiscoveryPromise
|
|
1262
|
+
});
|
|
1263
|
+
function hasRoute(level, name) {
|
|
1264
|
+
assertDiscoveryReady();
|
|
1265
|
+
return findRouteMeta(level, name) !== void 0;
|
|
1266
|
+
}
|
|
1267
|
+
void autoDiscoveryPromise.then(() => {
|
|
1268
|
+
autoDiscoveryCompleted = true;
|
|
1269
|
+
if (discoveryState.eager.length > 0) {
|
|
1270
|
+
return Promise.allSettled(discoveryState.eager.map((lvl) => load(lvl))).then((results) => {
|
|
1271
|
+
results.forEach((r, i) => {
|
|
1272
|
+
if (r.status === "rejected") {
|
|
1273
|
+
console.error(
|
|
1274
|
+
`[route-forge] eager load failed for level "${discoveryState.eager[i]}":`,
|
|
1275
|
+
r.reason
|
|
1276
|
+
);
|
|
1277
|
+
}
|
|
1278
|
+
});
|
|
1279
|
+
});
|
|
1280
|
+
}
|
|
1281
|
+
}).then(() => {
|
|
1282
|
+
resolveReady(forgeInstance);
|
|
1283
|
+
}).catch((e) => {
|
|
1284
|
+
rejectReady(e);
|
|
1285
|
+
});
|
|
1286
|
+
function ready(onFulfilled, onRejected) {
|
|
1287
|
+
if (onFulfilled) {
|
|
1288
|
+
const p = readyPromise.then(onFulfilled, onRejected);
|
|
1289
|
+
return p.then(() => forgeInstance);
|
|
1290
|
+
}
|
|
1291
|
+
return readyPromise;
|
|
1292
|
+
}
|
|
1293
|
+
const createBoundForgeWithMethods = createBoundForgeFactory({
|
|
1294
|
+
load,
|
|
1295
|
+
api,
|
|
1296
|
+
route,
|
|
1297
|
+
hasRoute,
|
|
1298
|
+
getRoutes,
|
|
1299
|
+
invalidate,
|
|
1300
|
+
isLoaded,
|
|
1301
|
+
loadingTracker
|
|
1302
|
+
});
|
|
1109
1303
|
const forgeInstance = {
|
|
1110
1304
|
api,
|
|
1111
1305
|
load,
|
|
@@ -1129,62 +1323,6 @@ var RouteForge = (function (exports) {
|
|
|
1129
1323
|
};
|
|
1130
1324
|
return forgeInstance;
|
|
1131
1325
|
}
|
|
1132
|
-
function pickMethod(meta) {
|
|
1133
|
-
const m = meta.methods.find((x) => x.toUpperCase() !== "HEAD");
|
|
1134
|
-
return (m ?? meta.methods[0] ?? "GET").toUpperCase();
|
|
1135
|
-
}
|
|
1136
|
-
function appendQuery(url, query) {
|
|
1137
|
-
if (!query) return url;
|
|
1138
|
-
const usp = new URLSearchParams();
|
|
1139
|
-
for (const [k, v] of Object.entries(query)) {
|
|
1140
|
-
if (v === void 0 || v === null) continue;
|
|
1141
|
-
usp.append(k, String(v));
|
|
1142
|
-
}
|
|
1143
|
-
const qs = usp.toString();
|
|
1144
|
-
if (!qs) return url;
|
|
1145
|
-
return url.includes("?") ? `${url}&${qs}` : `${url}?${qs}`;
|
|
1146
|
-
}
|
|
1147
|
-
function resolveApiParams(input) {
|
|
1148
|
-
const {
|
|
1149
|
-
params: explicitParams,
|
|
1150
|
-
query: rawQuery,
|
|
1151
|
-
body: rawBody,
|
|
1152
|
-
headers: rawHeaders,
|
|
1153
|
-
timeout: perCallTimeout,
|
|
1154
|
-
...flatRest
|
|
1155
|
-
} = input;
|
|
1156
|
-
const pathParams = explicitParams ? { ...explicitParams } : {};
|
|
1157
|
-
for (const [k, v] of Object.entries(flatRest)) {
|
|
1158
|
-
if (!(k in pathParams)) {
|
|
1159
|
-
pathParams[k] = v;
|
|
1160
|
-
}
|
|
1161
|
-
}
|
|
1162
|
-
let query;
|
|
1163
|
-
let body;
|
|
1164
|
-
let headers;
|
|
1165
|
-
if (rawQuery !== void 0) {
|
|
1166
|
-
if (typeof rawQuery === "object" && rawQuery !== null) {
|
|
1167
|
-
query = rawQuery;
|
|
1168
|
-
} else if (!("query" in pathParams)) {
|
|
1169
|
-
pathParams.query = rawQuery;
|
|
1170
|
-
}
|
|
1171
|
-
}
|
|
1172
|
-
if (rawBody !== void 0) {
|
|
1173
|
-
if (typeof rawBody !== "string" && typeof rawBody !== "number") {
|
|
1174
|
-
body = rawBody;
|
|
1175
|
-
} else if (!("body" in pathParams)) {
|
|
1176
|
-
pathParams.body = rawBody;
|
|
1177
|
-
}
|
|
1178
|
-
}
|
|
1179
|
-
if (rawHeaders !== void 0) {
|
|
1180
|
-
if (typeof rawHeaders === "object" && rawHeaders !== null) {
|
|
1181
|
-
headers = rawHeaders;
|
|
1182
|
-
} else if (!("headers" in pathParams)) {
|
|
1183
|
-
pathParams.headers = rawHeaders;
|
|
1184
|
-
}
|
|
1185
|
-
}
|
|
1186
|
-
return { pathParams, query, body, headers, timeout: perCallTimeout };
|
|
1187
|
-
}
|
|
1188
1326
|
|
|
1189
1327
|
exports.AdapterNotFoundError = AdapterNotFoundError;
|
|
1190
1328
|
exports.ForgeError = ForgeError;
|