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