@route-forge/core 2.0.0 → 2.1.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/dist/codegen.d.cts +1 -1
- package/dist/codegen.d.ts +1 -1
- package/dist/index.cjs +521 -436
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +47 -47
- package/dist/index.d.ts +47 -47
- package/dist/index.js +521 -436
- package/dist/index.js.map +1 -1
- package/dist/route-forge.global.js +521 -436
- 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-C51rXaiY.d.cts} +1 -1
- package/dist/{types-BvLdl02f.d.ts → types-C51rXaiY.d.ts} +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -490,39 +490,6 @@ async function resolveAdapter(opts) {
|
|
|
490
490
|
return createBuiltinHttp(opts.forgeInterceptors);
|
|
491
491
|
}
|
|
492
492
|
|
|
493
|
-
// src/resolveRouteName.ts
|
|
494
|
-
async function resolveRouteName(forge, level, prefix, suffix, separator = ".") {
|
|
495
|
-
if (!suffix) return prefix;
|
|
496
|
-
const joined = `${prefix}${separator}${suffix}`;
|
|
497
|
-
if (!suffix.startsWith(`${prefix}${separator}`)) return joined;
|
|
498
|
-
await forge.load(level);
|
|
499
|
-
if (forge.hasRoute(level, joined)) return joined;
|
|
500
|
-
if (forge.hasRoute(level, suffix)) return suffix;
|
|
501
|
-
throw new UnknownRouteError(joined, level);
|
|
502
|
-
}
|
|
503
|
-
function resolveRouteNameSync(forge, level, prefix, suffix, separator = ".") {
|
|
504
|
-
if (!suffix) return prefix;
|
|
505
|
-
const joined = `${prefix}${separator}${suffix}`;
|
|
506
|
-
if (!suffix.startsWith(`${prefix}${separator}`)) return joined;
|
|
507
|
-
if (forge.hasRoute(level, joined)) return joined;
|
|
508
|
-
if (forge.hasRoute(level, suffix)) return suffix;
|
|
509
|
-
throw new UnknownRouteError(joined, level);
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
// src/defineImmutableProps.ts
|
|
513
|
-
function defineImmutableProps(target, props) {
|
|
514
|
-
for (const key of Object.keys(props)) {
|
|
515
|
-
const val = props[key];
|
|
516
|
-
Object.defineProperty(target, key, {
|
|
517
|
-
value: val !== null && typeof val === "object" ? Object.freeze(val) : val,
|
|
518
|
-
writable: false,
|
|
519
|
-
enumerable: false,
|
|
520
|
-
configurable: false
|
|
521
|
-
});
|
|
522
|
-
}
|
|
523
|
-
return target;
|
|
524
|
-
}
|
|
525
|
-
|
|
526
493
|
// src/loading.ts
|
|
527
494
|
var LoadingTracker = class {
|
|
528
495
|
constructor() {
|
|
@@ -582,328 +549,308 @@ var LoadingTracker = class {
|
|
|
582
549
|
}
|
|
583
550
|
};
|
|
584
551
|
|
|
585
|
-
// src/
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
const explicitLevels = options.levels;
|
|
600
|
-
const explicitEager = options.eager;
|
|
601
|
-
const explicitEndpoint = options.endpoint;
|
|
602
|
-
let effectiveLevels = explicitLevels ?? [];
|
|
603
|
-
let effectiveEager = explicitEager ?? [];
|
|
604
|
-
let effectiveEndpoint = explicitEndpoint;
|
|
605
|
-
let effectiveUrlPrefix = "";
|
|
606
|
-
let summaryUnassigned;
|
|
607
|
-
let backendHasUnassignedLevel = false;
|
|
608
|
-
const fetchSummary = async () => {
|
|
609
|
-
try {
|
|
610
|
-
const base = baseURL.endsWith("/") ? baseURL.slice(0, -1) : baseURL;
|
|
611
|
-
const ep = explicitEndpoint.startsWith("/") ? explicitEndpoint : `/${explicitEndpoint}`;
|
|
612
|
-
const data = await fetchMeta("__forge__.summary", `${base}${ep}`);
|
|
613
|
-
return data;
|
|
614
|
-
} catch (e) {
|
|
615
|
-
if (explicitLevels && explicitLevels.length > 0) {
|
|
616
|
-
console.warn(
|
|
617
|
-
`[route-forge] summary endpoint unreachable: ${e.message}; using explicit levels`
|
|
618
|
-
);
|
|
619
|
-
return null;
|
|
620
|
-
}
|
|
621
|
-
throw new UnknownLevelError("(auto-discovery)");
|
|
622
|
-
}
|
|
623
|
-
};
|
|
624
|
-
let summaryPromise;
|
|
625
|
-
const summaryWaiters = [];
|
|
626
|
-
const whenSummary = () => summaryPromise ?? new Promise((resolve) => summaryWaiters.push(resolve));
|
|
627
|
-
const autoDiscoveryPromise = whenSummary().then((summary) => {
|
|
628
|
-
if (summary === null) {
|
|
629
|
-
return;
|
|
630
|
-
}
|
|
631
|
-
const schemaVersion = summary.schemaVersion ?? 1;
|
|
632
|
-
if (schemaVersion > 1) {
|
|
633
|
-
console.warn(
|
|
634
|
-
`[route-forge] backend schemaVersion=${schemaVersion} > client supported 1; some features may be unavailable`
|
|
635
|
-
);
|
|
636
|
-
}
|
|
637
|
-
if (summary.config.endpoint_prefix && summary.config.endpoint_prefix !== explicitEndpoint) {
|
|
638
|
-
console.warn(
|
|
639
|
-
`[route-forge] backend endpoint_prefix "${summary.config.endpoint_prefix}" overrides frontend endpoint "${explicitEndpoint}"`
|
|
640
|
-
);
|
|
641
|
-
effectiveEndpoint = summary.config.endpoint_prefix;
|
|
642
|
-
}
|
|
643
|
-
if (summary.config.url_prefix) {
|
|
644
|
-
effectiveUrlPrefix = summary.config.url_prefix.endsWith("/") ? summary.config.url_prefix.slice(0, -1) : summary.config.url_prefix;
|
|
645
|
-
}
|
|
646
|
-
const backendLevels = Object.keys(summary.levels);
|
|
647
|
-
backendHasUnassignedLevel = backendLevels.includes(UNASSIGNED_LEVEL);
|
|
648
|
-
if (Array.isArray(summary.unassigned) && summary.unassigned.length > 0 && !backendHasUnassignedLevel) {
|
|
649
|
-
summaryUnassigned = summary.unassigned;
|
|
650
|
-
}
|
|
651
|
-
const availableLevels = backendLevels.slice();
|
|
652
|
-
if (summaryUnassigned && !backendHasUnassignedLevel) {
|
|
653
|
-
availableLevels.push(UNASSIGNED_LEVEL);
|
|
552
|
+
// src/url-builder.ts
|
|
553
|
+
function buildUrl(level, ctx) {
|
|
554
|
+
const base = ctx.baseURL.endsWith("/") ? ctx.baseURL.slice(0, -1) : ctx.baseURL;
|
|
555
|
+
const ep = ctx.endpoint.startsWith("/") ? ctx.endpoint : `/${ctx.endpoint}`;
|
|
556
|
+
return `${base}${ep}/${encodeURIComponent(level)}`;
|
|
557
|
+
}
|
|
558
|
+
function buildRequestUrl(meta, params, ctx) {
|
|
559
|
+
const defaults = meta.parameter_defaults ?? {};
|
|
560
|
+
const missingRequired = [];
|
|
561
|
+
const values = {};
|
|
562
|
+
for (const p of meta.parameters) {
|
|
563
|
+
let v = params[p];
|
|
564
|
+
if ((v === void 0 || v === null) && p in defaults) {
|
|
565
|
+
v = defaults[p];
|
|
654
566
|
}
|
|
655
|
-
if (
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
if (removed.length > 0) {
|
|
659
|
-
console.warn(
|
|
660
|
-
`[route-forge] levels not in backend summary and dropped: ${removed.join(", ")}`
|
|
661
|
-
);
|
|
567
|
+
if (v === void 0 || v === null) {
|
|
568
|
+
if (!meta.uri.includes(`{${p}?}`)) {
|
|
569
|
+
missingRequired.push(p);
|
|
662
570
|
}
|
|
663
|
-
effectiveLevels = intersection;
|
|
664
571
|
} else {
|
|
665
|
-
|
|
572
|
+
values[p] = v;
|
|
666
573
|
}
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
574
|
+
}
|
|
575
|
+
if (missingRequired.length > 0) {
|
|
576
|
+
throw new MissingRouteParamError(meta.name, missingRequired);
|
|
577
|
+
}
|
|
578
|
+
let uri = meta.uri.replace(/\{([^{}]+)\}/g, (match, raw) => {
|
|
579
|
+
const optional = raw.endsWith("?");
|
|
580
|
+
const name = optional ? raw.slice(0, -1) : raw;
|
|
581
|
+
if (values[name] !== void 0) {
|
|
582
|
+
const val = values[name];
|
|
583
|
+
if (typeof val === "object") {
|
|
584
|
+
throw new ForgeError(
|
|
585
|
+
`Path parameter "${name}" must be a primitive value (string, number, boolean), got ${typeof val}`,
|
|
586
|
+
{ code: "RF_FE_003", route: meta.name, context: { param: name, value: val } }
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
return encodeURIComponent(String(val));
|
|
673
590
|
}
|
|
591
|
+
return optional ? "" : match;
|
|
674
592
|
});
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
const
|
|
691
|
-
const
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
593
|
+
uri = uri.replace(/\/+/g, "/").replace(/\/$/, "");
|
|
594
|
+
if (/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(ctx.urlPrefix)) {
|
|
595
|
+
const prefix2 = ctx.urlPrefix.endsWith("/") ? ctx.urlPrefix.slice(0, -1) : ctx.urlPrefix;
|
|
596
|
+
return uri.startsWith("/") ? `${prefix2}${uri}` : `${prefix2}/${uri}`;
|
|
597
|
+
}
|
|
598
|
+
const base = ctx.baseURL.endsWith("/") ? ctx.baseURL.slice(0, -1) : ctx.baseURL;
|
|
599
|
+
const prefix = ctx.urlPrefix;
|
|
600
|
+
return uri.startsWith("/") ? `${base}${prefix}${uri}` : `${base}${prefix}/${uri}`;
|
|
601
|
+
}
|
|
602
|
+
function pickMethod(meta) {
|
|
603
|
+
const m = meta.methods.find((x) => x.toUpperCase() !== "HEAD");
|
|
604
|
+
return (m ?? meta.methods[0] ?? "GET").toUpperCase();
|
|
605
|
+
}
|
|
606
|
+
function appendQuery(url, query) {
|
|
607
|
+
if (!query) return url;
|
|
608
|
+
const usp = new URLSearchParams();
|
|
609
|
+
for (const [k, v] of Object.entries(query)) {
|
|
610
|
+
if (v === void 0 || v === null) continue;
|
|
611
|
+
usp.append(k, String(v));
|
|
612
|
+
}
|
|
613
|
+
const qs = usp.toString();
|
|
614
|
+
if (!qs) return url;
|
|
615
|
+
return url.includes("?") ? `${url}&${qs}` : `${url}?${qs}`;
|
|
616
|
+
}
|
|
617
|
+
function resolveApiParams(input) {
|
|
618
|
+
const {
|
|
619
|
+
params: explicitParams,
|
|
620
|
+
query: rawQuery,
|
|
621
|
+
body: rawBody,
|
|
622
|
+
headers: rawHeaders,
|
|
623
|
+
timeout: perCallTimeout,
|
|
624
|
+
...flatRest
|
|
625
|
+
} = input;
|
|
626
|
+
const pathParams = explicitParams ? { ...explicitParams } : {};
|
|
627
|
+
for (const [k, v] of Object.entries(flatRest)) {
|
|
628
|
+
if (!(k in pathParams)) {
|
|
629
|
+
pathParams[k] = v;
|
|
701
630
|
}
|
|
702
631
|
}
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
632
|
+
let query;
|
|
633
|
+
let body;
|
|
634
|
+
let headers;
|
|
635
|
+
if (rawQuery !== void 0) {
|
|
636
|
+
if (typeof rawQuery === "object" && rawQuery !== null) {
|
|
637
|
+
query = rawQuery;
|
|
638
|
+
} else if (!("query" in pathParams)) {
|
|
639
|
+
pathParams.query = rawQuery;
|
|
711
640
|
}
|
|
712
641
|
}
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
summaryPromise = fetchSummary();
|
|
719
|
-
for (const resolve of summaryWaiters) resolve(summaryPromise);
|
|
720
|
-
});
|
|
721
|
-
let adapterResolved = false;
|
|
722
|
-
let adapterObj = null;
|
|
723
|
-
async function ensureAdapter() {
|
|
724
|
-
if (!adapterResolved) {
|
|
725
|
-
adapterObj = await adapterPromise.catch((e) => {
|
|
726
|
-
if (e instanceof AdapterNotFoundError) throw e;
|
|
727
|
-
return resolveAdapter({
|
|
728
|
-
adapter: "builtin",
|
|
729
|
-
forgeInterceptors: { request: requestInterceptors, response: responseInterceptors }
|
|
730
|
-
});
|
|
731
|
-
});
|
|
732
|
-
adapterResolved = true;
|
|
642
|
+
if (rawBody !== void 0) {
|
|
643
|
+
if (typeof rawBody !== "string" && typeof rawBody !== "number") {
|
|
644
|
+
body = rawBody;
|
|
645
|
+
} else if (!("body" in pathParams)) {
|
|
646
|
+
pathParams.body = rawBody;
|
|
733
647
|
}
|
|
734
|
-
return adapterObj;
|
|
735
648
|
}
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
if (!
|
|
740
|
-
|
|
649
|
+
if (rawHeaders !== void 0) {
|
|
650
|
+
if (typeof rawHeaders === "object" && rawHeaders !== null) {
|
|
651
|
+
headers = rawHeaders;
|
|
652
|
+
} else if (!("headers" in pathParams)) {
|
|
653
|
+
pathParams.headers = rawHeaders;
|
|
741
654
|
}
|
|
742
655
|
}
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
656
|
+
return { pathParams, query, body, headers, timeout: perCallTimeout };
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// src/auto-discovery.ts
|
|
660
|
+
var UNASSIGNED_LEVEL = "unassigned";
|
|
661
|
+
async function fetchSummary(inputs, baseURL, fetchMeta) {
|
|
662
|
+
const { explicitLevels, explicitEndpoint } = inputs;
|
|
663
|
+
try {
|
|
664
|
+
const base = baseURL.endsWith("/") ? baseURL.slice(0, -1) : baseURL;
|
|
665
|
+
const ep = explicitEndpoint.startsWith("/") ? explicitEndpoint : `/${explicitEndpoint}`;
|
|
666
|
+
const data = await fetchMeta("__forge__.summary", `${base}${ep}`);
|
|
667
|
+
return data;
|
|
668
|
+
} catch (e) {
|
|
669
|
+
if (explicitLevels && explicitLevels.length > 0) {
|
|
670
|
+
console.warn(
|
|
671
|
+
`[route-forge] summary endpoint unreachable: ${e.message}; using explicit levels`
|
|
748
672
|
);
|
|
673
|
+
return null;
|
|
749
674
|
}
|
|
675
|
+
throw new UnknownLevelError("(auto-discovery)");
|
|
750
676
|
}
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
677
|
+
}
|
|
678
|
+
function applySummaryToState(summary, state, inputs) {
|
|
679
|
+
const { explicitLevels, explicitEager, explicitEndpoint } = inputs;
|
|
680
|
+
const schemaVersion = summary.schemaVersion ?? 1;
|
|
681
|
+
if (schemaVersion > 1) {
|
|
682
|
+
console.warn(
|
|
683
|
+
`[route-forge] backend schemaVersion=${schemaVersion} > client supported 1; some features may be unavailable`
|
|
684
|
+
);
|
|
755
685
|
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
const
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
686
|
+
if (summary.config.endpoint_prefix && summary.config.endpoint_prefix !== explicitEndpoint) {
|
|
687
|
+
console.warn(
|
|
688
|
+
`[route-forge] backend endpoint_prefix "${summary.config.endpoint_prefix}" overrides frontend endpoint "${explicitEndpoint}"`
|
|
689
|
+
);
|
|
690
|
+
state.endpoint = summary.config.endpoint_prefix;
|
|
691
|
+
}
|
|
692
|
+
if (summary.config.url_prefix) {
|
|
693
|
+
state.urlPrefix = summary.config.url_prefix.endsWith("/") ? summary.config.url_prefix.slice(0, -1) : summary.config.url_prefix;
|
|
694
|
+
}
|
|
695
|
+
const backendLevels = Object.keys(summary.levels);
|
|
696
|
+
state.backendHasUnassignedLevel = backendLevels.includes(UNASSIGNED_LEVEL);
|
|
697
|
+
if (Array.isArray(summary.unassigned) && summary.unassigned.length > 0 && !state.backendHasUnassignedLevel) {
|
|
698
|
+
state.summaryUnassigned = summary.unassigned;
|
|
699
|
+
}
|
|
700
|
+
const availableLevels = backendLevels.slice();
|
|
701
|
+
if (state.summaryUnassigned && !state.backendHasUnassignedLevel) {
|
|
702
|
+
availableLevels.push(UNASSIGNED_LEVEL);
|
|
703
|
+
}
|
|
704
|
+
if (explicitLevels && explicitLevels.length > 0) {
|
|
705
|
+
const intersection = explicitLevels.filter((l) => availableLevels.includes(l));
|
|
706
|
+
const removed = explicitLevels.filter((l) => !availableLevels.includes(l));
|
|
707
|
+
if (removed.length > 0) {
|
|
708
|
+
console.warn(
|
|
709
|
+
`[route-forge] levels not in backend summary and dropped: ${removed.join(", ")}`
|
|
780
710
|
);
|
|
781
711
|
}
|
|
782
|
-
|
|
712
|
+
state.levels = intersection;
|
|
713
|
+
} else {
|
|
714
|
+
state.levels = availableLevels;
|
|
715
|
+
}
|
|
716
|
+
const backendEager = backendLevels.filter((lvl) => summary.levels[lvl]?.load === "eager");
|
|
717
|
+
if (!explicitEager) {
|
|
718
|
+
state.eager = backendEager;
|
|
719
|
+
} else {
|
|
720
|
+
const union = /* @__PURE__ */ new Set([...backendEager, ...explicitEager]);
|
|
721
|
+
state.eager = [...union];
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// src/route-store.ts
|
|
726
|
+
var RouteStore = class {
|
|
727
|
+
constructor(deps) {
|
|
728
|
+
this.inflight = /* @__PURE__ */ new Map();
|
|
729
|
+
/** 每个层级的失效代数,用于检测 loadOne 期间是否发生了 invalidate */
|
|
730
|
+
this.invalidationGens = /* @__PURE__ */ new Map();
|
|
731
|
+
this.cache = deps.cache;
|
|
732
|
+
this.state = deps.state;
|
|
733
|
+
this.baseURL = deps.baseURL;
|
|
734
|
+
this.fetchMeta = deps.fetchMeta;
|
|
735
|
+
this.autoDiscoveryPromise = deps.autoDiscoveryPromise;
|
|
736
|
+
this.getAutoDiscoveryError = deps.getAutoDiscoveryError;
|
|
737
|
+
}
|
|
738
|
+
assertLevelDeclared(level) {
|
|
739
|
+
if (!this.state.levels.includes(level)) {
|
|
740
|
+
throw new UnknownLevelError(level);
|
|
741
|
+
}
|
|
783
742
|
}
|
|
784
|
-
async
|
|
785
|
-
return await fetchMeta(
|
|
743
|
+
async fetchLevel(level) {
|
|
744
|
+
return await this.fetchMeta(
|
|
745
|
+
`__forge__.load.${level}`,
|
|
746
|
+
buildUrl(level, { baseURL: this.baseURL, endpoint: this.state.endpoint }),
|
|
747
|
+
level
|
|
748
|
+
);
|
|
786
749
|
}
|
|
787
|
-
async
|
|
750
|
+
async loadOne(level) {
|
|
751
|
+
const autoDiscoveryError = this.getAutoDiscoveryError();
|
|
788
752
|
if (autoDiscoveryError) throw autoDiscoveryError;
|
|
789
|
-
assertLevelDeclared(level);
|
|
790
|
-
if (cache.get(level)) return;
|
|
791
|
-
const existing = inflight.get(level);
|
|
753
|
+
this.assertLevelDeclared(level);
|
|
754
|
+
if (this.cache.get(level)) return;
|
|
755
|
+
const existing = this.inflight.get(level);
|
|
792
756
|
if (existing) return existing;
|
|
793
|
-
const gen = invalidationGens.get(level) ?? 0;
|
|
757
|
+
const gen = this.invalidationGens.get(level) ?? 0;
|
|
794
758
|
const p = (async () => {
|
|
795
759
|
try {
|
|
796
|
-
if (level === UNASSIGNED_LEVEL && !backendHasUnassignedLevel && summaryUnassigned) {
|
|
760
|
+
if (level === UNASSIGNED_LEVEL && !this.state.backendHasUnassignedLevel && this.state.summaryUnassigned) {
|
|
797
761
|
const routes = {};
|
|
798
|
-
for (const r of summaryUnassigned) {
|
|
762
|
+
for (const r of this.state.summaryUnassigned) {
|
|
799
763
|
routes[r.name] = { ...r, level: UNASSIGNED_LEVEL };
|
|
800
764
|
}
|
|
801
|
-
cache.set({ level: UNASSIGNED_LEVEL, routes, cache: null });
|
|
765
|
+
this.cache.set({ level: UNASSIGNED_LEVEL, routes, cache: null });
|
|
802
766
|
return;
|
|
803
767
|
}
|
|
804
|
-
const resp = await fetchLevel(level);
|
|
805
|
-
if ((invalidationGens.get(level) ?? 0) === gen) {
|
|
806
|
-
cache.set(resp);
|
|
768
|
+
const resp = await this.fetchLevel(level);
|
|
769
|
+
if ((this.invalidationGens.get(level) ?? 0) === gen) {
|
|
770
|
+
this.cache.set(resp);
|
|
807
771
|
}
|
|
808
772
|
} finally {
|
|
809
|
-
inflight.delete(level);
|
|
773
|
+
this.inflight.delete(level);
|
|
810
774
|
}
|
|
811
775
|
})();
|
|
812
|
-
inflight.set(level, p);
|
|
776
|
+
this.inflight.set(level, p);
|
|
813
777
|
return p;
|
|
814
778
|
}
|
|
815
|
-
async
|
|
816
|
-
await autoDiscoveryPromise;
|
|
779
|
+
async load(level) {
|
|
780
|
+
await this.autoDiscoveryPromise;
|
|
817
781
|
const list = Array.isArray(level) ? level : [level];
|
|
818
|
-
await Promise.all(list.map(loadOne));
|
|
782
|
+
await Promise.all(list.map((l) => this.loadOne(l)));
|
|
819
783
|
}
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
return buildRequestUrl(meta, params ?? {});
|
|
827
|
-
}
|
|
828
|
-
function buildRequestUrl(meta, params) {
|
|
829
|
-
const defaults = meta.parameter_defaults ?? {};
|
|
830
|
-
const missingRequired = [];
|
|
831
|
-
const values = {};
|
|
832
|
-
for (const p of meta.parameters) {
|
|
833
|
-
let v = params[p];
|
|
834
|
-
if ((v === void 0 || v === null) && p in defaults) {
|
|
835
|
-
v = defaults[p];
|
|
836
|
-
}
|
|
837
|
-
if (v === void 0 || v === null) {
|
|
838
|
-
if (!meta.uri.includes(`{${p}?}`)) {
|
|
839
|
-
missingRequired.push(p);
|
|
840
|
-
}
|
|
841
|
-
} else {
|
|
842
|
-
values[p] = v;
|
|
784
|
+
invalidate(level) {
|
|
785
|
+
if (level === void 0) {
|
|
786
|
+
this.cache.clear();
|
|
787
|
+
this.inflight.clear();
|
|
788
|
+
for (const lvl of this.state.levels) {
|
|
789
|
+
this.invalidationGens.set(lvl, (this.invalidationGens.get(lvl) ?? 0) + 1);
|
|
843
790
|
}
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
const optional = raw.endsWith("?");
|
|
850
|
-
const name = optional ? raw.slice(0, -1) : raw;
|
|
851
|
-
if (values[name] !== void 0) {
|
|
852
|
-
const val = values[name];
|
|
853
|
-
if (typeof val === "object") {
|
|
854
|
-
throw new ForgeError(
|
|
855
|
-
`Path parameter "${name}" must be a primitive value (string, number, boolean), got ${typeof val}`,
|
|
856
|
-
{ code: "RF_FE_003", route: meta.name, context: { param: name, value: val } }
|
|
857
|
-
);
|
|
858
|
-
}
|
|
859
|
-
return encodeURIComponent(String(val));
|
|
791
|
+
} else if (Array.isArray(level)) {
|
|
792
|
+
for (const lvl of level) {
|
|
793
|
+
this.cache.del(lvl);
|
|
794
|
+
this.inflight.delete(lvl);
|
|
795
|
+
this.invalidationGens.set(lvl, (this.invalidationGens.get(lvl) ?? 0) + 1);
|
|
860
796
|
}
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
const prefix2 = effectiveUrlPrefix.endsWith("/") ? effectiveUrlPrefix.slice(0, -1) : effectiveUrlPrefix;
|
|
866
|
-
return uri.startsWith("/") ? `${prefix2}${uri}` : `${prefix2}/${uri}`;
|
|
797
|
+
} else {
|
|
798
|
+
this.cache.del(level);
|
|
799
|
+
this.inflight.delete(level);
|
|
800
|
+
this.invalidationGens.set(level, (this.invalidationGens.get(level) ?? 0) + 1);
|
|
867
801
|
}
|
|
868
|
-
const base = baseURL.endsWith("/") ? baseURL.slice(0, -1) : baseURL;
|
|
869
|
-
const prefix = effectiveUrlPrefix;
|
|
870
|
-
return uri.startsWith("/") ? `${base}${prefix}${uri}` : `${base}${prefix}/${uri}`;
|
|
871
802
|
}
|
|
872
|
-
|
|
873
|
-
|
|
803
|
+
isLoaded(level) {
|
|
804
|
+
if (level) return this.cache.get(level) !== void 0;
|
|
805
|
+
return this.state.levels.length > 0 && this.state.levels.every((lvl) => this.cache.get(lvl) !== void 0);
|
|
806
|
+
}
|
|
807
|
+
findRouteMeta(level, name) {
|
|
808
|
+
const entry = this.cache.get(level);
|
|
874
809
|
const meta = entry?.routes[name];
|
|
875
810
|
if (meta) {
|
|
876
811
|
return { ...meta, level };
|
|
877
812
|
}
|
|
878
813
|
return void 0;
|
|
879
814
|
}
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
ctrl.abort(abortReason);
|
|
888
|
-
}
|
|
889
|
-
await autoDiscoveryPromise;
|
|
890
|
-
await load(level);
|
|
891
|
-
const meta = findRouteMeta(level, name);
|
|
892
|
-
if (!meta) {
|
|
893
|
-
throw new UnknownRouteError(name, level);
|
|
815
|
+
getRoutes(level) {
|
|
816
|
+
if (level !== void 0) {
|
|
817
|
+
const entry = this.cache.get(level);
|
|
818
|
+
const routes = entry?.routes ?? {};
|
|
819
|
+
const result2 = {};
|
|
820
|
+
for (const [k, v] of Object.entries(routes)) {
|
|
821
|
+
result2[k] = JSON.parse(JSON.stringify(v));
|
|
894
822
|
}
|
|
895
|
-
return
|
|
896
|
-
}
|
|
897
|
-
const
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
823
|
+
return result2;
|
|
824
|
+
}
|
|
825
|
+
const result = {};
|
|
826
|
+
for (const lvl of this.state.levels) {
|
|
827
|
+
const entry = this.cache.get(lvl);
|
|
828
|
+
if (entry) {
|
|
829
|
+
const levelRoutes = {};
|
|
830
|
+
for (const [k, v] of Object.entries(entry.routes)) {
|
|
831
|
+
levelRoutes[k] = JSON.parse(JSON.stringify(v));
|
|
832
|
+
}
|
|
833
|
+
result[lvl] = levelRoutes;
|
|
903
834
|
}
|
|
904
|
-
}
|
|
905
|
-
return
|
|
835
|
+
}
|
|
836
|
+
return result;
|
|
906
837
|
}
|
|
838
|
+
};
|
|
839
|
+
|
|
840
|
+
// src/http-runner.ts
|
|
841
|
+
function createHttpRunner(deps) {
|
|
842
|
+
const {
|
|
843
|
+
ensureAdapter,
|
|
844
|
+
requestInterceptors,
|
|
845
|
+
responseInterceptors,
|
|
846
|
+
load,
|
|
847
|
+
findRouteMeta,
|
|
848
|
+
baseURL,
|
|
849
|
+
state,
|
|
850
|
+
timeout,
|
|
851
|
+
loadingTracker,
|
|
852
|
+
autoDiscoveryPromise
|
|
853
|
+
} = deps;
|
|
907
854
|
async function doApiCall(meta, params, signal) {
|
|
908
855
|
const {
|
|
909
856
|
pathParams,
|
|
@@ -916,7 +863,10 @@ function createRouteForge(options) {
|
|
|
916
863
|
throw new RequestAbortedError(meta.name, meta.level, signal.reason);
|
|
917
864
|
}
|
|
918
865
|
const method = pickMethod(meta);
|
|
919
|
-
const urlWithQuery = appendQuery(
|
|
866
|
+
const urlWithQuery = appendQuery(
|
|
867
|
+
buildRequestUrl(meta, pathParams, { baseURL, urlPrefix: state.urlPrefix }),
|
|
868
|
+
query
|
|
869
|
+
);
|
|
920
870
|
const config = {
|
|
921
871
|
route: meta.name,
|
|
922
872
|
level: meta.level ?? "",
|
|
@@ -971,88 +921,77 @@ function createRouteForge(options) {
|
|
|
971
921
|
loadingTracker.stop();
|
|
972
922
|
}
|
|
973
923
|
}
|
|
974
|
-
function
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
for (const lvl of level) {
|
|
983
|
-
cache.del(lvl);
|
|
984
|
-
inflight.delete(lvl);
|
|
985
|
-
invalidationGens.set(lvl, (invalidationGens.get(lvl) ?? 0) + 1);
|
|
924
|
+
return function api(level, name, params = {}) {
|
|
925
|
+
let ctrl;
|
|
926
|
+
let abortedBeforeInit = false;
|
|
927
|
+
let abortReason;
|
|
928
|
+
const work = (async () => {
|
|
929
|
+
ctrl = new AbortController();
|
|
930
|
+
if (abortedBeforeInit) {
|
|
931
|
+
ctrl.abort(abortReason);
|
|
986
932
|
}
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
}
|
|
993
|
-
function isLoaded(level) {
|
|
994
|
-
if (level) return cache.get(level) !== void 0;
|
|
995
|
-
return effectiveLevels.length > 0 && effectiveLevels.every((lvl) => cache.get(lvl) !== void 0);
|
|
996
|
-
}
|
|
997
|
-
function hasRoute(level, name) {
|
|
998
|
-
assertDiscoveryReady();
|
|
999
|
-
return findRouteMeta(level, name) !== void 0;
|
|
1000
|
-
}
|
|
1001
|
-
function getRoutes(level) {
|
|
1002
|
-
if (level !== void 0) {
|
|
1003
|
-
const entry = cache.get(level);
|
|
1004
|
-
const routes = entry?.routes ?? {};
|
|
1005
|
-
const result2 = {};
|
|
1006
|
-
for (const [k, v] of Object.entries(routes)) {
|
|
1007
|
-
result2[k] = JSON.parse(JSON.stringify(v));
|
|
933
|
+
await autoDiscoveryPromise;
|
|
934
|
+
await load(level);
|
|
935
|
+
const meta = findRouteMeta(level, name);
|
|
936
|
+
if (!meta) {
|
|
937
|
+
throw new UnknownRouteError(name, level);
|
|
1008
938
|
}
|
|
1009
|
-
return
|
|
1010
|
-
}
|
|
1011
|
-
const
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
levelRoutes[k] = JSON.parse(JSON.stringify(v));
|
|
1018
|
-
}
|
|
1019
|
-
result[lvl] = levelRoutes;
|
|
939
|
+
return doApiCall(meta, params, ctrl.signal);
|
|
940
|
+
})();
|
|
941
|
+
const request = work;
|
|
942
|
+
request.abort = () => {
|
|
943
|
+
if (ctrl) {
|
|
944
|
+
ctrl.abort();
|
|
945
|
+
} else {
|
|
946
|
+
abortedBeforeInit = true;
|
|
1020
947
|
}
|
|
1021
|
-
}
|
|
1022
|
-
return
|
|
1023
|
-
}
|
|
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
|
-
|
|
948
|
+
};
|
|
949
|
+
return request;
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
// src/resolveRouteName.ts
|
|
954
|
+
async function resolveRouteName(forge, level, prefix, suffix, separator = ".") {
|
|
955
|
+
if (!suffix) return prefix;
|
|
956
|
+
const joined = `${prefix}${separator}${suffix}`;
|
|
957
|
+
if (!suffix.startsWith(`${prefix}${separator}`)) return joined;
|
|
958
|
+
await forge.load(level);
|
|
959
|
+
if (forge.hasRoute(level, joined)) return joined;
|
|
960
|
+
if (forge.hasRoute(level, suffix)) return suffix;
|
|
961
|
+
throw new UnknownRouteError(joined, level);
|
|
962
|
+
}
|
|
963
|
+
function resolveRouteNameSync(forge, level, prefix, suffix, separator = ".") {
|
|
964
|
+
if (!suffix) return prefix;
|
|
965
|
+
const joined = `${prefix}${separator}${suffix}`;
|
|
966
|
+
if (!suffix.startsWith(`${prefix}${separator}`)) return joined;
|
|
967
|
+
if (forge.hasRoute(level, joined)) return joined;
|
|
968
|
+
if (forge.hasRoute(level, suffix)) return suffix;
|
|
969
|
+
throw new UnknownRouteError(joined, level);
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// src/defineImmutableProps.ts
|
|
973
|
+
function defineImmutableProps(target, props) {
|
|
974
|
+
for (const key of Object.keys(props)) {
|
|
975
|
+
const val = props[key];
|
|
976
|
+
Object.defineProperty(target, key, {
|
|
977
|
+
value: val !== null && typeof val === "object" ? Object.freeze(val) : val,
|
|
978
|
+
writable: false,
|
|
979
|
+
enumerable: false,
|
|
980
|
+
configurable: false
|
|
981
|
+
});
|
|
1049
982
|
}
|
|
1050
|
-
|
|
983
|
+
return target;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
// src/bound-forge.ts
|
|
987
|
+
function createBoundForgeFactory(deps) {
|
|
988
|
+
const { load, api, route, hasRoute, getRoutes, invalidate, isLoaded, loadingTracker } = deps;
|
|
989
|
+
const resolver = { load, hasRoute };
|
|
1051
990
|
function createBoundForge(level, prefix) {
|
|
1052
991
|
const levelLoadedPromise = load(level);
|
|
1053
992
|
levelLoadedPromise.catch(() => {
|
|
1054
993
|
});
|
|
1055
|
-
const apiFn = prefix ? (name, params) => resolveRouteName(
|
|
994
|
+
const apiFn = prefix ? (name, params) => resolveRouteName(resolver, level, prefix, name).then(
|
|
1056
995
|
(resolved) => api(level, resolved, params)
|
|
1057
996
|
) : (name, params) => api(level, name, params);
|
|
1058
997
|
const callable = apiFn;
|
|
@@ -1060,8 +999,8 @@ function createRouteForge(options) {
|
|
|
1060
999
|
level,
|
|
1061
1000
|
...prefix !== void 0 ? { prefix } : {},
|
|
1062
1001
|
api: apiFn,
|
|
1063
|
-
route: prefix ? (name, params) => route(level, resolveRouteNameSync(
|
|
1064
|
-
url: prefix ? (name, params) => route(level, resolveRouteNameSync(
|
|
1002
|
+
route: prefix ? (name, params) => route(level, resolveRouteNameSync(resolver, level, prefix, name), params) : (name, params) => route(level, name, params),
|
|
1003
|
+
url: prefix ? (name, params) => route(level, resolveRouteNameSync(resolver, level, prefix, name), params) : (name, params) => route(level, name, params),
|
|
1065
1004
|
hasRoute: (name) => hasRoute(level, name),
|
|
1066
1005
|
getRoutes: () => getRoutes(level),
|
|
1067
1006
|
load: () => load(level),
|
|
@@ -1105,6 +1044,208 @@ function createRouteForge(options) {
|
|
|
1105
1044
|
attachBoundMethods(bound, levelLoadedPromise, level);
|
|
1106
1045
|
return bound;
|
|
1107
1046
|
}
|
|
1047
|
+
return createBoundForgeWithMethods;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
// src/forge.ts
|
|
1051
|
+
var DEFAULT_TIMEOUT = 3e4;
|
|
1052
|
+
var DEFAULT_CACHE_TTL = 3600;
|
|
1053
|
+
function createRouteForge(options) {
|
|
1054
|
+
if (!options.endpoint) throw new TypeError("options.endpoint is required");
|
|
1055
|
+
const {
|
|
1056
|
+
adapter = "auto",
|
|
1057
|
+
timeout = DEFAULT_TIMEOUT,
|
|
1058
|
+
baseURL = "",
|
|
1059
|
+
interceptors: declarativeInterceptors,
|
|
1060
|
+
cache: cacheOpts = {}
|
|
1061
|
+
} = options;
|
|
1062
|
+
const loadingTracker = new LoadingTracker();
|
|
1063
|
+
const explicitLevels = options.levels;
|
|
1064
|
+
const explicitEager = options.eager;
|
|
1065
|
+
const explicitEndpoint = options.endpoint;
|
|
1066
|
+
const discoveryState = {
|
|
1067
|
+
levels: explicitLevels ?? [],
|
|
1068
|
+
eager: explicitEager ?? [],
|
|
1069
|
+
endpoint: explicitEndpoint,
|
|
1070
|
+
urlPrefix: "",
|
|
1071
|
+
summaryUnassigned: void 0,
|
|
1072
|
+
backendHasUnassignedLevel: false
|
|
1073
|
+
};
|
|
1074
|
+
const discoveryInputs = { explicitLevels, explicitEager, explicitEndpoint };
|
|
1075
|
+
let autoDiscoveryCompleted = false;
|
|
1076
|
+
let resolveReady;
|
|
1077
|
+
let rejectReady;
|
|
1078
|
+
const readyPromise = new Promise((resolve, reject) => {
|
|
1079
|
+
resolveReady = resolve;
|
|
1080
|
+
rejectReady = reject;
|
|
1081
|
+
});
|
|
1082
|
+
readyPromise.catch(() => {
|
|
1083
|
+
});
|
|
1084
|
+
const cacheTtl = cacheOpts.ttl ?? DEFAULT_CACHE_TTL;
|
|
1085
|
+
const cacheStorage = cacheOpts.storage ?? "memory";
|
|
1086
|
+
const cache = new RouteCache({ storage: cacheStorage, ttl: cacheTtl });
|
|
1087
|
+
const requestInterceptors = new InterceptorManagerImpl();
|
|
1088
|
+
const responseInterceptors = new InterceptorManagerImpl();
|
|
1089
|
+
if (declarativeInterceptors?.request) {
|
|
1090
|
+
for (const entry of declarativeInterceptors.request) {
|
|
1091
|
+
if (typeof entry === "function") {
|
|
1092
|
+
requestInterceptors.use(entry);
|
|
1093
|
+
} else {
|
|
1094
|
+
const [onFulfilled, onRejected] = entry;
|
|
1095
|
+
requestInterceptors.use(onFulfilled, onRejected);
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
if (declarativeInterceptors?.response) {
|
|
1100
|
+
for (const entry of declarativeInterceptors.response) {
|
|
1101
|
+
if (typeof entry === "function") {
|
|
1102
|
+
responseInterceptors.use(entry);
|
|
1103
|
+
} else {
|
|
1104
|
+
const [onFulfilled, onRejected] = entry;
|
|
1105
|
+
responseInterceptors.use(onFulfilled, onRejected);
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
const adapterPromise = resolveAdapter({
|
|
1110
|
+
adapter,
|
|
1111
|
+
forgeInterceptors: { request: requestInterceptors, response: responseInterceptors }
|
|
1112
|
+
});
|
|
1113
|
+
let adapterResolved = false;
|
|
1114
|
+
let adapterObj = null;
|
|
1115
|
+
async function ensureAdapter() {
|
|
1116
|
+
if (!adapterResolved) {
|
|
1117
|
+
adapterObj = await adapterPromise.catch((e) => {
|
|
1118
|
+
if (e instanceof AdapterNotFoundError) throw e;
|
|
1119
|
+
return resolveAdapter({
|
|
1120
|
+
adapter: "builtin",
|
|
1121
|
+
forgeInterceptors: { request: requestInterceptors, response: responseInterceptors }
|
|
1122
|
+
});
|
|
1123
|
+
});
|
|
1124
|
+
adapterResolved = true;
|
|
1125
|
+
}
|
|
1126
|
+
return adapterObj;
|
|
1127
|
+
}
|
|
1128
|
+
function assertDiscoveryReady() {
|
|
1129
|
+
if (!autoDiscoveryCompleted && !explicitLevels?.length) {
|
|
1130
|
+
throw new ForgeError(
|
|
1131
|
+
"Route data not available. Auto-discovery has not completed. Use forge.ready() or forge.use(level) first.",
|
|
1132
|
+
{ code: "RF_FE_010" }
|
|
1133
|
+
);
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
async function fetchMeta(routeTag, url, level = "") {
|
|
1137
|
+
const adp = await ensureAdapter();
|
|
1138
|
+
const config = {
|
|
1139
|
+
route: routeTag,
|
|
1140
|
+
level,
|
|
1141
|
+
method: "GET",
|
|
1142
|
+
url,
|
|
1143
|
+
headers: { Accept: "application/json" },
|
|
1144
|
+
params: {},
|
|
1145
|
+
timeout,
|
|
1146
|
+
meta: {
|
|
1147
|
+
name: routeTag,
|
|
1148
|
+
uri: url,
|
|
1149
|
+
methods: ["GET"],
|
|
1150
|
+
parameters: [],
|
|
1151
|
+
level
|
|
1152
|
+
}
|
|
1153
|
+
};
|
|
1154
|
+
const doRawRequest = adp.requestRaw ?? adp.request;
|
|
1155
|
+
const resp = await doRawRequest(config);
|
|
1156
|
+
if (!resp || resp.status < 200 || resp.status >= 300) {
|
|
1157
|
+
throw new HTTPError(
|
|
1158
|
+
`Failed to fetch "${routeTag}": HTTP ${resp?.status}`,
|
|
1159
|
+
{ level, status: resp?.status, url, method: "GET" }
|
|
1160
|
+
);
|
|
1161
|
+
}
|
|
1162
|
+
return resp.data;
|
|
1163
|
+
}
|
|
1164
|
+
const autoDiscoveryPromise = fetchSummary(discoveryInputs, baseURL, fetchMeta).then((summary) => {
|
|
1165
|
+
if (summary === null) {
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
applySummaryToState(summary, discoveryState, discoveryInputs);
|
|
1169
|
+
});
|
|
1170
|
+
let autoDiscoveryError = null;
|
|
1171
|
+
autoDiscoveryPromise.catch((e) => {
|
|
1172
|
+
autoDiscoveryError = e;
|
|
1173
|
+
});
|
|
1174
|
+
const store = new RouteStore({
|
|
1175
|
+
cache,
|
|
1176
|
+
state: discoveryState,
|
|
1177
|
+
baseURL,
|
|
1178
|
+
fetchMeta,
|
|
1179
|
+
autoDiscoveryPromise,
|
|
1180
|
+
getAutoDiscoveryError: () => autoDiscoveryError
|
|
1181
|
+
});
|
|
1182
|
+
const load = (level) => store.load(level);
|
|
1183
|
+
const findRouteMeta = (level, name) => store.findRouteMeta(level, name);
|
|
1184
|
+
const invalidate = (level) => store.invalidate(level);
|
|
1185
|
+
const isLoaded = (level) => store.isLoaded(level);
|
|
1186
|
+
function getRoutes(level) {
|
|
1187
|
+
return level === void 0 ? store.getRoutes() : store.getRoutes(level);
|
|
1188
|
+
}
|
|
1189
|
+
function route(level, name, params) {
|
|
1190
|
+
assertDiscoveryReady();
|
|
1191
|
+
const meta = findRouteMeta(level, name);
|
|
1192
|
+
if (!meta) {
|
|
1193
|
+
throw new UnknownRouteError(name, level);
|
|
1194
|
+
}
|
|
1195
|
+
return buildRequestUrl(meta, params ?? {}, { baseURL, urlPrefix: discoveryState.urlPrefix });
|
|
1196
|
+
}
|
|
1197
|
+
const api = createHttpRunner({
|
|
1198
|
+
ensureAdapter,
|
|
1199
|
+
requestInterceptors,
|
|
1200
|
+
responseInterceptors,
|
|
1201
|
+
load,
|
|
1202
|
+
findRouteMeta,
|
|
1203
|
+
baseURL,
|
|
1204
|
+
state: discoveryState,
|
|
1205
|
+
timeout,
|
|
1206
|
+
loadingTracker,
|
|
1207
|
+
autoDiscoveryPromise
|
|
1208
|
+
});
|
|
1209
|
+
function hasRoute(level, name) {
|
|
1210
|
+
assertDiscoveryReady();
|
|
1211
|
+
return findRouteMeta(level, name) !== void 0;
|
|
1212
|
+
}
|
|
1213
|
+
void autoDiscoveryPromise.then(() => {
|
|
1214
|
+
autoDiscoveryCompleted = true;
|
|
1215
|
+
if (discoveryState.eager.length > 0) {
|
|
1216
|
+
return Promise.allSettled(discoveryState.eager.map((lvl) => load(lvl))).then((results) => {
|
|
1217
|
+
results.forEach((r, i) => {
|
|
1218
|
+
if (r.status === "rejected") {
|
|
1219
|
+
console.error(
|
|
1220
|
+
`[route-forge] eager load failed for level "${discoveryState.eager[i]}":`,
|
|
1221
|
+
r.reason
|
|
1222
|
+
);
|
|
1223
|
+
}
|
|
1224
|
+
});
|
|
1225
|
+
});
|
|
1226
|
+
}
|
|
1227
|
+
}).then(() => {
|
|
1228
|
+
resolveReady(forgeInstance);
|
|
1229
|
+
}).catch((e) => {
|
|
1230
|
+
rejectReady(e);
|
|
1231
|
+
});
|
|
1232
|
+
function ready(onFulfilled, onRejected) {
|
|
1233
|
+
if (onFulfilled) {
|
|
1234
|
+
const p = readyPromise.then(onFulfilled, onRejected);
|
|
1235
|
+
return p.then(() => forgeInstance);
|
|
1236
|
+
}
|
|
1237
|
+
return readyPromise;
|
|
1238
|
+
}
|
|
1239
|
+
const createBoundForgeWithMethods = createBoundForgeFactory({
|
|
1240
|
+
load,
|
|
1241
|
+
api,
|
|
1242
|
+
route,
|
|
1243
|
+
hasRoute,
|
|
1244
|
+
getRoutes,
|
|
1245
|
+
invalidate,
|
|
1246
|
+
isLoaded,
|
|
1247
|
+
loadingTracker
|
|
1248
|
+
});
|
|
1108
1249
|
const forgeInstance = {
|
|
1109
1250
|
api,
|
|
1110
1251
|
load,
|
|
@@ -1128,62 +1269,6 @@ function createRouteForge(options) {
|
|
|
1128
1269
|
};
|
|
1129
1270
|
return forgeInstance;
|
|
1130
1271
|
}
|
|
1131
|
-
function pickMethod(meta) {
|
|
1132
|
-
const m = meta.methods.find((x) => x.toUpperCase() !== "HEAD");
|
|
1133
|
-
return (m ?? meta.methods[0] ?? "GET").toUpperCase();
|
|
1134
|
-
}
|
|
1135
|
-
function appendQuery(url, query) {
|
|
1136
|
-
if (!query) return url;
|
|
1137
|
-
const usp = new URLSearchParams();
|
|
1138
|
-
for (const [k, v] of Object.entries(query)) {
|
|
1139
|
-
if (v === void 0 || v === null) continue;
|
|
1140
|
-
usp.append(k, String(v));
|
|
1141
|
-
}
|
|
1142
|
-
const qs = usp.toString();
|
|
1143
|
-
if (!qs) return url;
|
|
1144
|
-
return url.includes("?") ? `${url}&${qs}` : `${url}?${qs}`;
|
|
1145
|
-
}
|
|
1146
|
-
function resolveApiParams(input) {
|
|
1147
|
-
const {
|
|
1148
|
-
params: explicitParams,
|
|
1149
|
-
query: rawQuery,
|
|
1150
|
-
body: rawBody,
|
|
1151
|
-
headers: rawHeaders,
|
|
1152
|
-
timeout: perCallTimeout,
|
|
1153
|
-
...flatRest
|
|
1154
|
-
} = input;
|
|
1155
|
-
const pathParams = explicitParams ? { ...explicitParams } : {};
|
|
1156
|
-
for (const [k, v] of Object.entries(flatRest)) {
|
|
1157
|
-
if (!(k in pathParams)) {
|
|
1158
|
-
pathParams[k] = v;
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
|
-
let query;
|
|
1162
|
-
let body;
|
|
1163
|
-
let headers;
|
|
1164
|
-
if (rawQuery !== void 0) {
|
|
1165
|
-
if (typeof rawQuery === "object" && rawQuery !== null) {
|
|
1166
|
-
query = rawQuery;
|
|
1167
|
-
} else if (!("query" in pathParams)) {
|
|
1168
|
-
pathParams.query = rawQuery;
|
|
1169
|
-
}
|
|
1170
|
-
}
|
|
1171
|
-
if (rawBody !== void 0) {
|
|
1172
|
-
if (typeof rawBody !== "string" && typeof rawBody !== "number") {
|
|
1173
|
-
body = rawBody;
|
|
1174
|
-
} else if (!("body" in pathParams)) {
|
|
1175
|
-
pathParams.body = rawBody;
|
|
1176
|
-
}
|
|
1177
|
-
}
|
|
1178
|
-
if (rawHeaders !== void 0) {
|
|
1179
|
-
if (typeof rawHeaders === "object" && rawHeaders !== null) {
|
|
1180
|
-
headers = rawHeaders;
|
|
1181
|
-
} else if (!("headers" in pathParams)) {
|
|
1182
|
-
pathParams.headers = rawHeaders;
|
|
1183
|
-
}
|
|
1184
|
-
}
|
|
1185
|
-
return { pathParams, query, body, headers, timeout: perCallTimeout };
|
|
1186
|
-
}
|
|
1187
1272
|
|
|
1188
1273
|
exports.AdapterNotFoundError = AdapterNotFoundError;
|
|
1189
1274
|
exports.ForgeError = ForgeError;
|