@route-forge/core 3.0.0 → 3.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.cjs +89 -61
- package/dist/codegen.cjs.map +1 -1
- package/dist/codegen.d.cts +22 -13
- package/dist/codegen.d.ts +22 -13
- package/dist/codegen.js +89 -61
- package/dist/codegen.js.map +1 -1
- package/dist/index.cjs +308 -153
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +498 -19
- package/dist/index.d.ts +498 -19
- package/dist/index.js +308 -154
- package/dist/index.js.map +1 -1
- package/dist/manifest-bjy4P_B4.d.cts +76 -0
- package/dist/manifest-bjy4P_B4.d.ts +76 -0
- package/dist/route-forge.global.js +308 -153
- package/dist/route-forge.global.js.map +1 -1
- package/dist/route-forge.global.min.js +2 -2
- package/package.json +1 -1
- package/dist/types-B_vdcRqx.d.cts +0 -501
- package/dist/types-B_vdcRqx.d.ts +0 -501
|
@@ -132,6 +132,36 @@ var RouteForge = (function (exports) {
|
|
|
132
132
|
}
|
|
133
133
|
};
|
|
134
134
|
|
|
135
|
+
// src/interceptors/manager.ts
|
|
136
|
+
var InterceptorManagerImpl = class {
|
|
137
|
+
constructor() {
|
|
138
|
+
this.handlers = [];
|
|
139
|
+
this.nextId = 0;
|
|
140
|
+
}
|
|
141
|
+
use(onFulfilled, onRejected) {
|
|
142
|
+
const id = this.nextId++;
|
|
143
|
+
this.handlers.push({ id, onFulfilled, onRejected });
|
|
144
|
+
return id;
|
|
145
|
+
}
|
|
146
|
+
eject(id) {
|
|
147
|
+
const idx = this.handlers.findIndex((h) => h.id === id);
|
|
148
|
+
if (idx >= 0) this.handlers.splice(idx, 1);
|
|
149
|
+
}
|
|
150
|
+
clear() {
|
|
151
|
+
this.handlers = [];
|
|
152
|
+
}
|
|
153
|
+
forEach(fn) {
|
|
154
|
+
for (const h of this.handlers) fn(h);
|
|
155
|
+
}
|
|
156
|
+
/** 测试用:当前注册数量 */
|
|
157
|
+
get size() {
|
|
158
|
+
return this.handlers.length;
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
function createInterceptorManager() {
|
|
162
|
+
return new InterceptorManagerImpl();
|
|
163
|
+
}
|
|
164
|
+
|
|
135
165
|
// src/errors.ts
|
|
136
166
|
var ForgeError = class extends Error {
|
|
137
167
|
constructor(message, opts) {
|
|
@@ -248,32 +278,7 @@ var RouteForge = (function (exports) {
|
|
|
248
278
|
}
|
|
249
279
|
};
|
|
250
280
|
|
|
251
|
-
// src/interceptors.ts
|
|
252
|
-
var InterceptorManagerImpl = class {
|
|
253
|
-
constructor() {
|
|
254
|
-
this.handlers = [];
|
|
255
|
-
this.nextId = 0;
|
|
256
|
-
}
|
|
257
|
-
use(onFulfilled, onRejected) {
|
|
258
|
-
const id = this.nextId++;
|
|
259
|
-
this.handlers.push({ id, onFulfilled, onRejected });
|
|
260
|
-
return id;
|
|
261
|
-
}
|
|
262
|
-
eject(id) {
|
|
263
|
-
const idx = this.handlers.findIndex((h) => h.id === id);
|
|
264
|
-
if (idx >= 0) this.handlers.splice(idx, 1);
|
|
265
|
-
}
|
|
266
|
-
clear() {
|
|
267
|
-
this.handlers = [];
|
|
268
|
-
}
|
|
269
|
-
forEach(fn) {
|
|
270
|
-
for (const h of this.handlers) fn(h);
|
|
271
|
-
}
|
|
272
|
-
/** 测试用:当前注册数量 */
|
|
273
|
-
get size() {
|
|
274
|
-
return this.handlers.length;
|
|
275
|
-
}
|
|
276
|
-
};
|
|
281
|
+
// src/interceptors/runner.ts
|
|
277
282
|
async function runRequestInterceptors(manager, initial) {
|
|
278
283
|
const handlers = [];
|
|
279
284
|
manager.forEach((h) => handlers.push(h));
|
|
@@ -310,9 +315,8 @@ var RouteForge = (function (exports) {
|
|
|
310
315
|
}
|
|
311
316
|
return p;
|
|
312
317
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
}
|
|
318
|
+
|
|
319
|
+
// src/interceptors/normalize.ts
|
|
316
320
|
function normalizeInterceptorDeclaration(value) {
|
|
317
321
|
if (value === null || value === void 0) return {};
|
|
318
322
|
let rawResolve;
|
|
@@ -582,6 +586,69 @@ var RouteForge = (function (exports) {
|
|
|
582
586
|
return createBuiltinHttp(opts.forgeInterceptors);
|
|
583
587
|
}
|
|
584
588
|
|
|
589
|
+
// src/adapter-bootstrap.ts
|
|
590
|
+
function createAdapterBootstrap(deps) {
|
|
591
|
+
const { adapter, requestInterceptors, responseInterceptors, warnings } = deps;
|
|
592
|
+
const adapterPromise = resolveAdapter({
|
|
593
|
+
adapter,
|
|
594
|
+
forgeInterceptors: { request: requestInterceptors, response: responseInterceptors }
|
|
595
|
+
});
|
|
596
|
+
let adapterResolved = false;
|
|
597
|
+
let adapterObj = null;
|
|
598
|
+
return async function ensureAdapter() {
|
|
599
|
+
if (!adapterResolved) {
|
|
600
|
+
adapterObj = await adapterPromise.catch((e) => {
|
|
601
|
+
if (e instanceof AdapterNotFoundError) throw e;
|
|
602
|
+
if (warnings) {
|
|
603
|
+
console.warn(
|
|
604
|
+
`[route-forge] adapter initialization failed (${e?.message ?? String(e)}); falling back to builtin`
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
return resolveAdapter({
|
|
608
|
+
adapter: "builtin",
|
|
609
|
+
forgeInterceptors: { request: requestInterceptors, response: responseInterceptors }
|
|
610
|
+
});
|
|
611
|
+
});
|
|
612
|
+
adapterResolved = true;
|
|
613
|
+
}
|
|
614
|
+
return adapterObj;
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// src/ready-latch.ts
|
|
619
|
+
function createReadyLatch() {
|
|
620
|
+
let resolveReady;
|
|
621
|
+
let rejectReady;
|
|
622
|
+
const readyPromise = new Promise((resolve, reject) => {
|
|
623
|
+
resolveReady = resolve;
|
|
624
|
+
rejectReady = reject;
|
|
625
|
+
});
|
|
626
|
+
let settledValue;
|
|
627
|
+
readyPromise.catch(() => {
|
|
628
|
+
});
|
|
629
|
+
let readySettledOk = false;
|
|
630
|
+
readyPromise.then(
|
|
631
|
+
(value) => {
|
|
632
|
+
settledValue = value;
|
|
633
|
+
readySettledOk = true;
|
|
634
|
+
},
|
|
635
|
+
() => {
|
|
636
|
+
}
|
|
637
|
+
);
|
|
638
|
+
function ready(onFulfilled, onRejected) {
|
|
639
|
+
if (onFulfilled) {
|
|
640
|
+
return readyPromise.then(onFulfilled, onRejected).then(() => settledValue);
|
|
641
|
+
}
|
|
642
|
+
return readyPromise;
|
|
643
|
+
}
|
|
644
|
+
return {
|
|
645
|
+
resolve: (value) => resolveReady(value),
|
|
646
|
+
reject: (reason) => rejectReady(reason),
|
|
647
|
+
isReady: () => readySettledOk,
|
|
648
|
+
ready
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
|
|
585
652
|
// src/loading.ts
|
|
586
653
|
var LoadingTracker = class {
|
|
587
654
|
constructor() {
|
|
@@ -641,12 +708,107 @@ var RouteForge = (function (exports) {
|
|
|
641
708
|
}
|
|
642
709
|
};
|
|
643
710
|
|
|
644
|
-
// src/
|
|
711
|
+
// src/route-change.ts
|
|
712
|
+
var RouteChangeTracker = class {
|
|
713
|
+
constructor() {
|
|
714
|
+
this.subscribers = /* @__PURE__ */ new Set();
|
|
715
|
+
/** 本微任务周期内累积的变更层级(去重) */
|
|
716
|
+
this.pending = /* @__PURE__ */ new Set();
|
|
717
|
+
/** 是否已排定一次微任务 flush */
|
|
718
|
+
this.scheduled = false;
|
|
719
|
+
}
|
|
720
|
+
/** 订阅路由表数据变更;返回取消订阅函数 */
|
|
721
|
+
subscribe(cb) {
|
|
722
|
+
this.subscribers.add(cb);
|
|
723
|
+
return () => {
|
|
724
|
+
this.subscribers.delete(cb);
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
/** 记录某层级数据已变更;同一 tick 多次调用合并为一次投递(每层级各投一次) */
|
|
728
|
+
notify(level) {
|
|
729
|
+
if (this.subscribers.size === 0) return;
|
|
730
|
+
this.pending.add(level);
|
|
731
|
+
if (this.scheduled) return;
|
|
732
|
+
this.scheduled = true;
|
|
733
|
+
queueMicrotask(() => this.flush());
|
|
734
|
+
}
|
|
735
|
+
/** 一次性投递本周期累积的所有变更层级;单订阅者抛错不影响其它 */
|
|
736
|
+
flush() {
|
|
737
|
+
this.scheduled = false;
|
|
738
|
+
const levels = [...this.pending];
|
|
739
|
+
this.pending.clear();
|
|
740
|
+
for (const level of levels) {
|
|
741
|
+
for (const cb of this.subscribers) {
|
|
742
|
+
try {
|
|
743
|
+
cb(level);
|
|
744
|
+
} catch {
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
};
|
|
750
|
+
|
|
751
|
+
// src/url/utils.ts
|
|
752
|
+
function trimTrailingSlash(s) {
|
|
753
|
+
return s.endsWith("/") ? s.slice(0, -1) : s;
|
|
754
|
+
}
|
|
755
|
+
function withLeadingSlash(s) {
|
|
756
|
+
return s.startsWith("/") ? s : `/${s}`;
|
|
757
|
+
}
|
|
758
|
+
function joinBaseAndPath(base, path) {
|
|
759
|
+
return `${trimTrailingSlash(base)}${withLeadingSlash(path)}`;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// src/url/endpoint.ts
|
|
645
763
|
function buildUrl(level, ctx) {
|
|
646
|
-
|
|
647
|
-
const ep = ctx.endpoint.startsWith("/") ? ctx.endpoint : `/${ctx.endpoint}`;
|
|
648
|
-
return `${base}${ep}/${encodeURIComponent(level)}`;
|
|
764
|
+
return `${joinBaseAndPath(ctx.baseURL, withLeadingSlash(ctx.endpoint))}/${encodeURIComponent(level)}`;
|
|
649
765
|
}
|
|
766
|
+
|
|
767
|
+
// src/url/params.ts
|
|
768
|
+
function resolveApiParams(input) {
|
|
769
|
+
const {
|
|
770
|
+
params: explicitParams,
|
|
771
|
+
query: rawQuery,
|
|
772
|
+
body: rawBody,
|
|
773
|
+
headers: rawHeaders,
|
|
774
|
+
timeout: perCallTimeout,
|
|
775
|
+
signal: rawSignal,
|
|
776
|
+
...flatRest
|
|
777
|
+
} = input;
|
|
778
|
+
const pathParams = explicitParams ? { ...explicitParams } : {};
|
|
779
|
+
for (const [k, v] of Object.entries(flatRest)) {
|
|
780
|
+
if (!(k in pathParams)) {
|
|
781
|
+
pathParams[k] = v;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
let query;
|
|
785
|
+
let body;
|
|
786
|
+
let headers;
|
|
787
|
+
if (rawQuery !== void 0) {
|
|
788
|
+
if (typeof rawQuery === "object" && rawQuery !== null) {
|
|
789
|
+
query = rawQuery;
|
|
790
|
+
} else if (!("query" in pathParams)) {
|
|
791
|
+
pathParams.query = rawQuery;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
if (rawBody !== void 0) {
|
|
795
|
+
if (typeof rawBody !== "string" && typeof rawBody !== "number") {
|
|
796
|
+
body = rawBody;
|
|
797
|
+
} else if (!("body" in pathParams)) {
|
|
798
|
+
pathParams.body = rawBody;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
if (rawHeaders !== void 0) {
|
|
802
|
+
if (typeof rawHeaders === "object" && rawHeaders !== null) {
|
|
803
|
+
headers = rawHeaders;
|
|
804
|
+
} else if (!("headers" in pathParams)) {
|
|
805
|
+
pathParams.headers = rawHeaders;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
return { pathParams, query, body, headers, timeout: perCallTimeout, signal: rawSignal };
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
// src/url/request.ts
|
|
650
812
|
function buildRequestUrl(meta, params, ctx) {
|
|
651
813
|
const defaults = meta.parameter_defaults ?? {};
|
|
652
814
|
const missingRequired = [];
|
|
@@ -681,10 +843,10 @@ var RouteForge = (function (exports) {
|
|
|
681
843
|
});
|
|
682
844
|
uri = uri.replace(/\/+/g, "/").replace(/\/$/, "");
|
|
683
845
|
if (/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(ctx.urlPrefix)) {
|
|
684
|
-
const prefix2 =
|
|
846
|
+
const prefix2 = trimTrailingSlash(ctx.urlPrefix);
|
|
685
847
|
return uri.startsWith("/") ? `${prefix2}${uri}` : `${prefix2}/${uri}`;
|
|
686
848
|
}
|
|
687
|
-
const base =
|
|
849
|
+
const base = trimTrailingSlash(ctx.baseURL);
|
|
688
850
|
const prefix = ctx.urlPrefix;
|
|
689
851
|
return uri.startsWith("/") ? `${base}${prefix}${uri}` : `${base}${prefix}/${uri}`;
|
|
690
852
|
}
|
|
@@ -703,46 +865,18 @@ var RouteForge = (function (exports) {
|
|
|
703
865
|
if (!qs) return url;
|
|
704
866
|
return url.includes("?") ? `${url}&${qs}` : `${url}?${qs}`;
|
|
705
867
|
}
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
timeout: perCallTimeout,
|
|
713
|
-
...flatRest
|
|
714
|
-
} = input;
|
|
715
|
-
const pathParams = explicitParams ? { ...explicitParams } : {};
|
|
716
|
-
for (const [k, v] of Object.entries(flatRest)) {
|
|
717
|
-
if (!(k in pathParams)) {
|
|
718
|
-
pathParams[k] = v;
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
let query;
|
|
722
|
-
let body;
|
|
723
|
-
let headers;
|
|
724
|
-
if (rawQuery !== void 0) {
|
|
725
|
-
if (typeof rawQuery === "object" && rawQuery !== null) {
|
|
726
|
-
query = rawQuery;
|
|
727
|
-
} else if (!("query" in pathParams)) {
|
|
728
|
-
pathParams.query = rawQuery;
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
if (rawBody !== void 0) {
|
|
732
|
-
if (typeof rawBody !== "string" && typeof rawBody !== "number") {
|
|
733
|
-
body = rawBody;
|
|
734
|
-
} else if (!("body" in pathParams)) {
|
|
735
|
-
pathParams.body = rawBody;
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
if (rawHeaders !== void 0) {
|
|
739
|
-
if (typeof rawHeaders === "object" && rawHeaders !== null) {
|
|
740
|
-
headers = rawHeaders;
|
|
741
|
-
} else if (!("headers" in pathParams)) {
|
|
742
|
-
pathParams.headers = rawHeaders;
|
|
868
|
+
var RESERVED_API_KEYS = ["params", "query", "body", "headers", "timeout", "signal"];
|
|
869
|
+
function buildRouteUrl(meta, params, ctx) {
|
|
870
|
+
if (params) {
|
|
871
|
+
const hasReservedKey = RESERVED_API_KEYS.some((k) => k in params);
|
|
872
|
+
if (!hasReservedKey) {
|
|
873
|
+
return buildRequestUrl(meta, params, ctx);
|
|
743
874
|
}
|
|
875
|
+
} else {
|
|
876
|
+
return buildRequestUrl(meta, {}, ctx);
|
|
744
877
|
}
|
|
745
|
-
|
|
878
|
+
const { pathParams, query } = resolveApiParams(params);
|
|
879
|
+
return appendQuery(buildRequestUrl(meta, pathParams, ctx), query);
|
|
746
880
|
}
|
|
747
881
|
|
|
748
882
|
// src/auto-discovery.ts
|
|
@@ -750,9 +884,7 @@ var RouteForge = (function (exports) {
|
|
|
750
884
|
async function fetchSummary(inputs, baseURL, fetchMeta) {
|
|
751
885
|
const { explicitLevels, explicitEndpoint, warnings } = inputs;
|
|
752
886
|
const endpoint = explicitEndpoint ?? DEFAULT_ENDPOINT;
|
|
753
|
-
const
|
|
754
|
-
const ep = endpoint.startsWith("/") ? endpoint : `/${endpoint}`;
|
|
755
|
-
const url = `${base}${ep}`;
|
|
887
|
+
const url = joinBaseAndPath(baseURL, endpoint);
|
|
756
888
|
try {
|
|
757
889
|
const data = await fetchMeta("__forge__.summary", url);
|
|
758
890
|
return data;
|
|
@@ -858,6 +990,7 @@ var RouteForge = (function (exports) {
|
|
|
858
990
|
this.fetchMeta = deps.fetchMeta;
|
|
859
991
|
this.autoDiscoveryPromise = deps.autoDiscoveryPromise;
|
|
860
992
|
this.getAutoDiscoveryError = deps.getAutoDiscoveryError;
|
|
993
|
+
this.onChange = deps.onChange;
|
|
861
994
|
}
|
|
862
995
|
assertLevelDeclared(level) {
|
|
863
996
|
if (!this.state.levels.includes(level)) {
|
|
@@ -866,15 +999,9 @@ var RouteForge = (function (exports) {
|
|
|
866
999
|
}
|
|
867
1000
|
async fetchLevel(level) {
|
|
868
1001
|
const uri = this.state.levelRoutes[level]?.uri;
|
|
869
|
-
const url = uri ?
|
|
1002
|
+
const url = uri ? joinBaseAndPath(this.baseURL, uri) : buildUrl(level, { baseURL: this.baseURL, endpoint: this.state.endpoint });
|
|
870
1003
|
return await this.fetchMeta(`route-forge.${level}`, url, level);
|
|
871
1004
|
}
|
|
872
|
-
/** baseURL 与后端下发的绝对 path 拼接(规范化斜杠),与 buildUrl 的 base 处理一致 */
|
|
873
|
-
joinBaseAndPath(baseURL, path) {
|
|
874
|
-
const base = baseURL.endsWith("/") ? baseURL.slice(0, -1) : baseURL;
|
|
875
|
-
const p = path.startsWith("/") ? path : `/${path}`;
|
|
876
|
-
return `${base}${p}`;
|
|
877
|
-
}
|
|
878
1005
|
async loadOne(level) {
|
|
879
1006
|
const autoDiscoveryError = this.getAutoDiscoveryError();
|
|
880
1007
|
if (autoDiscoveryError) throw autoDiscoveryError;
|
|
@@ -889,6 +1016,7 @@ var RouteForge = (function (exports) {
|
|
|
889
1016
|
const resp = await this.fetchLevel(level);
|
|
890
1017
|
if ((this.invalidationGens.get(level) ?? 0) === gen) {
|
|
891
1018
|
this.cache.set(resp, this.state.cacheTtl);
|
|
1019
|
+
this.onChange?.(level);
|
|
892
1020
|
}
|
|
893
1021
|
} finally {
|
|
894
1022
|
this.inflight.delete(level);
|
|
@@ -902,6 +1030,36 @@ var RouteForge = (function (exports) {
|
|
|
902
1030
|
const list = Array.isArray(level) ? level : [level];
|
|
903
1031
|
await Promise.all(list.map((l) => this.loadOne(l)));
|
|
904
1032
|
}
|
|
1033
|
+
/**
|
|
1034
|
+
* 强制刷新:跳过缓存命中短路重新拉取指定层级,成功后覆盖缓存、失败保留旧值。
|
|
1035
|
+
* 与 invalidate→load 的区别——全程不清空缓存,故读取旧数据不受影响、无空窗。
|
|
1036
|
+
*/
|
|
1037
|
+
async revalidate(level) {
|
|
1038
|
+
await this.autoDiscoveryPromise;
|
|
1039
|
+
const list = Array.isArray(level) ? level : [level];
|
|
1040
|
+
await Promise.all(list.map((l) => this.revalidateOne(l)));
|
|
1041
|
+
}
|
|
1042
|
+
async revalidateOne(level) {
|
|
1043
|
+
const autoDiscoveryError = this.getAutoDiscoveryError();
|
|
1044
|
+
if (autoDiscoveryError) throw autoDiscoveryError;
|
|
1045
|
+
this.assertLevelDeclared(level);
|
|
1046
|
+
const existing = this.inflight.get(level);
|
|
1047
|
+
if (existing) return existing;
|
|
1048
|
+
const gen = this.invalidationGens.get(level) ?? 0;
|
|
1049
|
+
const p = (async () => {
|
|
1050
|
+
try {
|
|
1051
|
+
const resp = await this.fetchLevel(level);
|
|
1052
|
+
if ((this.invalidationGens.get(level) ?? 0) === gen) {
|
|
1053
|
+
this.cache.set(resp, this.state.cacheTtl);
|
|
1054
|
+
this.onChange?.(level);
|
|
1055
|
+
}
|
|
1056
|
+
} finally {
|
|
1057
|
+
this.inflight.delete(level);
|
|
1058
|
+
}
|
|
1059
|
+
})();
|
|
1060
|
+
this.inflight.set(level, p);
|
|
1061
|
+
return p;
|
|
1062
|
+
}
|
|
905
1063
|
invalidate(level) {
|
|
906
1064
|
if (level === void 0) {
|
|
907
1065
|
this.cache.clear();
|
|
@@ -909,16 +1067,19 @@ var RouteForge = (function (exports) {
|
|
|
909
1067
|
for (const lvl of this.state.levels) {
|
|
910
1068
|
this.invalidationGens.set(lvl, (this.invalidationGens.get(lvl) ?? 0) + 1);
|
|
911
1069
|
}
|
|
1070
|
+
for (const lvl of this.state.levels) this.onChange?.(lvl);
|
|
912
1071
|
} else if (Array.isArray(level)) {
|
|
913
1072
|
for (const lvl of level) {
|
|
914
1073
|
this.cache.del(lvl);
|
|
915
1074
|
this.inflight.delete(lvl);
|
|
916
1075
|
this.invalidationGens.set(lvl, (this.invalidationGens.get(lvl) ?? 0) + 1);
|
|
917
1076
|
}
|
|
1077
|
+
for (const lvl of level) this.onChange?.(lvl);
|
|
918
1078
|
} else {
|
|
919
1079
|
this.cache.del(level);
|
|
920
1080
|
this.inflight.delete(level);
|
|
921
1081
|
this.invalidationGens.set(level, (this.invalidationGens.get(level) ?? 0) + 1);
|
|
1082
|
+
this.onChange?.(level);
|
|
922
1083
|
}
|
|
923
1084
|
}
|
|
924
1085
|
isLoaded(level) {
|
|
@@ -933,6 +1094,16 @@ var RouteForge = (function (exports) {
|
|
|
933
1094
|
}
|
|
934
1095
|
return void 0;
|
|
935
1096
|
}
|
|
1097
|
+
/**
|
|
1098
|
+
* 该层级当前已加载的路由名列表(不深拷贝,仅读键名)。
|
|
1099
|
+
* 供错误候选与 prefix 解析使用——避免为取名字而 getRoutes 深拷贝整表。
|
|
1100
|
+
* 层级未声明抛 UnknownLevelError(与 getRoutes/route 一致)。
|
|
1101
|
+
*/
|
|
1102
|
+
routeNames(level) {
|
|
1103
|
+
this.assertLevelDeclared(level);
|
|
1104
|
+
const entry = this.cache.get(level);
|
|
1105
|
+
return entry ? Object.keys(entry.routes) : [];
|
|
1106
|
+
}
|
|
936
1107
|
getRoutes(level) {
|
|
937
1108
|
if (level !== void 0) {
|
|
938
1109
|
this.assertLevelDeclared(level);
|
|
@@ -940,7 +1111,7 @@ var RouteForge = (function (exports) {
|
|
|
940
1111
|
const routes = entry?.routes ?? {};
|
|
941
1112
|
const result2 = {};
|
|
942
1113
|
for (const [k, v] of Object.entries(routes)) {
|
|
943
|
-
result2[k] =
|
|
1114
|
+
result2[k] = structuredClone(v);
|
|
944
1115
|
}
|
|
945
1116
|
return result2;
|
|
946
1117
|
}
|
|
@@ -950,7 +1121,7 @@ var RouteForge = (function (exports) {
|
|
|
950
1121
|
if (entry) {
|
|
951
1122
|
const levelRoutes = {};
|
|
952
1123
|
for (const [k, v] of Object.entries(entry.routes)) {
|
|
953
|
-
levelRoutes[k] =
|
|
1124
|
+
levelRoutes[k] = structuredClone(v);
|
|
954
1125
|
}
|
|
955
1126
|
result[lvl] = levelRoutes;
|
|
956
1127
|
}
|
|
@@ -1051,18 +1222,37 @@ var RouteForge = (function (exports) {
|
|
|
1051
1222
|
let ctrl;
|
|
1052
1223
|
let abortedBeforeInit = false;
|
|
1053
1224
|
let abortReason;
|
|
1054
|
-
const
|
|
1055
|
-
|
|
1056
|
-
if (
|
|
1057
|
-
ctrl.abort(
|
|
1225
|
+
const externalSignal = params.signal;
|
|
1226
|
+
const onExternalAbort = () => {
|
|
1227
|
+
if (ctrl) {
|
|
1228
|
+
ctrl.abort(externalSignal?.reason);
|
|
1229
|
+
} else {
|
|
1230
|
+
abortedBeforeInit = true;
|
|
1231
|
+
abortReason = externalSignal?.reason;
|
|
1058
1232
|
}
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1233
|
+
};
|
|
1234
|
+
if (externalSignal?.aborted) {
|
|
1235
|
+
abortedBeforeInit = true;
|
|
1236
|
+
abortReason = externalSignal.reason;
|
|
1237
|
+
} else {
|
|
1238
|
+
externalSignal?.addEventListener("abort", onExternalAbort, { once: true });
|
|
1239
|
+
}
|
|
1240
|
+
const work = (async () => {
|
|
1241
|
+
try {
|
|
1242
|
+
ctrl = new AbortController();
|
|
1243
|
+
if (abortedBeforeInit) {
|
|
1244
|
+
ctrl.abort(abortReason);
|
|
1245
|
+
}
|
|
1246
|
+
await autoDiscoveryPromise;
|
|
1247
|
+
await load(level);
|
|
1248
|
+
const meta = findRouteMeta(level, name);
|
|
1249
|
+
if (!meta) {
|
|
1250
|
+
throw new UnknownRouteError(name, level, getRouteNames(level));
|
|
1251
|
+
}
|
|
1252
|
+
return await doApiCall(meta, params, ctrl.signal);
|
|
1253
|
+
} finally {
|
|
1254
|
+
externalSignal?.removeEventListener("abort", onExternalAbort);
|
|
1064
1255
|
}
|
|
1065
|
-
return doApiCall(meta, params, ctrl.signal);
|
|
1066
1256
|
})();
|
|
1067
1257
|
const request = work;
|
|
1068
1258
|
request.abort = () => {
|
|
@@ -1120,8 +1310,8 @@ var RouteForge = (function (exports) {
|
|
|
1120
1310
|
|
|
1121
1311
|
// src/bound-forge.ts
|
|
1122
1312
|
function createBoundForgeFactory(deps) {
|
|
1123
|
-
const { load, api, route, hasRoute, getRoutes, invalidate, isLoaded, loadingTracker } = deps;
|
|
1124
|
-
const resolver = { load, hasRoute, getRouteNames
|
|
1313
|
+
const { load, api, route, hasRoute, getRoutes, getRouteNames, invalidate, isLoaded, loadingTracker } = deps;
|
|
1314
|
+
const resolver = { load, hasRoute, getRouteNames };
|
|
1125
1315
|
function createBoundForge(level, prefix) {
|
|
1126
1316
|
const levelLoadedPromise = load(level);
|
|
1127
1317
|
levelLoadedPromise.catch(() => {
|
|
@@ -1196,6 +1386,7 @@ var RouteForge = (function (exports) {
|
|
|
1196
1386
|
} = options;
|
|
1197
1387
|
const warnings = options.warnings ?? true;
|
|
1198
1388
|
const loadingTracker = new LoadingTracker();
|
|
1389
|
+
const routesTracker = new RouteChangeTracker();
|
|
1199
1390
|
const explicitLevels = options.levels;
|
|
1200
1391
|
const explicitEager = options.eager;
|
|
1201
1392
|
const explicitEndpoint = options.endpoint;
|
|
@@ -1209,22 +1400,7 @@ var RouteForge = (function (exports) {
|
|
|
1209
1400
|
};
|
|
1210
1401
|
const discoveryInputs = { explicitLevels, explicitEager, explicitEndpoint, warnings };
|
|
1211
1402
|
let autoDiscoveryCompleted = false;
|
|
1212
|
-
|
|
1213
|
-
let rejectReady;
|
|
1214
|
-
const readyPromise = new Promise((resolve, reject) => {
|
|
1215
|
-
resolveReady = resolve;
|
|
1216
|
-
rejectReady = reject;
|
|
1217
|
-
});
|
|
1218
|
-
readyPromise.catch(() => {
|
|
1219
|
-
});
|
|
1220
|
-
let readySettledOk = false;
|
|
1221
|
-
readyPromise.then(
|
|
1222
|
-
() => {
|
|
1223
|
-
readySettledOk = true;
|
|
1224
|
-
},
|
|
1225
|
-
() => {
|
|
1226
|
-
}
|
|
1227
|
-
);
|
|
1403
|
+
const latch = createReadyLatch();
|
|
1228
1404
|
const cacheTtl = cacheOpts.ttl ?? DEFAULT_CACHE_TTL;
|
|
1229
1405
|
const cacheStorage = cacheOpts.storage ?? "memory";
|
|
1230
1406
|
const cache = new RouteCache({ storage: cacheStorage, ttl: cacheTtl });
|
|
@@ -1242,30 +1418,12 @@ var RouteForge = (function (exports) {
|
|
|
1242
1418
|
if (resDecl.onFulfilled || resDecl.onRejected) {
|
|
1243
1419
|
responseInterceptors.use(resDecl.onFulfilled, resDecl.onRejected);
|
|
1244
1420
|
}
|
|
1245
|
-
const
|
|
1421
|
+
const ensureAdapter = createAdapterBootstrap({
|
|
1246
1422
|
adapter,
|
|
1247
|
-
|
|
1423
|
+
requestInterceptors,
|
|
1424
|
+
responseInterceptors,
|
|
1425
|
+
warnings
|
|
1248
1426
|
});
|
|
1249
|
-
let adapterResolved = false;
|
|
1250
|
-
let adapterObj = null;
|
|
1251
|
-
async function ensureAdapter() {
|
|
1252
|
-
if (!adapterResolved) {
|
|
1253
|
-
adapterObj = await adapterPromise.catch((e) => {
|
|
1254
|
-
if (e instanceof AdapterNotFoundError) throw e;
|
|
1255
|
-
if (warnings) {
|
|
1256
|
-
console.warn(
|
|
1257
|
-
`[route-forge] adapter initialization failed (${e?.message ?? String(e)}); falling back to builtin`
|
|
1258
|
-
);
|
|
1259
|
-
}
|
|
1260
|
-
return resolveAdapter({
|
|
1261
|
-
adapter: "builtin",
|
|
1262
|
-
forgeInterceptors: { request: requestInterceptors, response: responseInterceptors }
|
|
1263
|
-
});
|
|
1264
|
-
});
|
|
1265
|
-
adapterResolved = true;
|
|
1266
|
-
}
|
|
1267
|
-
return adapterObj;
|
|
1268
|
-
}
|
|
1269
1427
|
function assertDiscoveryReady() {
|
|
1270
1428
|
if (!autoDiscoveryCompleted && !explicitLevels?.length) {
|
|
1271
1429
|
throw new DiscoveryNotReadyError();
|
|
@@ -1322,15 +1480,15 @@ var RouteForge = (function (exports) {
|
|
|
1322
1480
|
baseURL,
|
|
1323
1481
|
fetchMeta,
|
|
1324
1482
|
autoDiscoveryPromise,
|
|
1325
|
-
getAutoDiscoveryError: () => autoDiscoveryError
|
|
1483
|
+
getAutoDiscoveryError: () => autoDiscoveryError,
|
|
1484
|
+
onChange: (level) => routesTracker.notify(level)
|
|
1326
1485
|
});
|
|
1327
1486
|
const load = (level) => store.load(level);
|
|
1487
|
+
const revalidate = (level) => store.revalidate(level);
|
|
1328
1488
|
const findRouteMeta = (level, name) => store.findRouteMeta(level, name);
|
|
1329
1489
|
const invalidate = (level) => store.invalidate(level);
|
|
1330
1490
|
const isLoaded = (level) => store.isLoaded(level);
|
|
1331
|
-
|
|
1332
|
-
return level === void 0 ? store.getRoutes() : store.getRoutes(level);
|
|
1333
|
-
}
|
|
1491
|
+
const getRoutes = store.getRoutes.bind(store);
|
|
1334
1492
|
function getLevels() {
|
|
1335
1493
|
return [...discoveryState.levels];
|
|
1336
1494
|
}
|
|
@@ -1338,9 +1496,9 @@ var RouteForge = (function (exports) {
|
|
|
1338
1496
|
assertDiscoveryReady();
|
|
1339
1497
|
const meta = findRouteMeta(level, name);
|
|
1340
1498
|
if (!meta) {
|
|
1341
|
-
throw new UnknownRouteError(name, level,
|
|
1499
|
+
throw new UnknownRouteError(name, level, store.routeNames(level));
|
|
1342
1500
|
}
|
|
1343
|
-
return
|
|
1501
|
+
return buildRouteUrl(meta, params ?? {}, { baseURL, urlPrefix: discoveryState.urlPrefix });
|
|
1344
1502
|
}
|
|
1345
1503
|
const api = createHttpRunner({
|
|
1346
1504
|
ensureAdapter,
|
|
@@ -1348,7 +1506,7 @@ var RouteForge = (function (exports) {
|
|
|
1348
1506
|
responseInterceptors,
|
|
1349
1507
|
load,
|
|
1350
1508
|
findRouteMeta,
|
|
1351
|
-
getRouteNames: (lvl) =>
|
|
1509
|
+
getRouteNames: (lvl) => store.routeNames(lvl),
|
|
1352
1510
|
baseURL,
|
|
1353
1511
|
state: discoveryState,
|
|
1354
1512
|
timeout,
|
|
@@ -1374,23 +1532,17 @@ var RouteForge = (function (exports) {
|
|
|
1374
1532
|
});
|
|
1375
1533
|
}
|
|
1376
1534
|
}).then(() => {
|
|
1377
|
-
|
|
1535
|
+
latch.resolve(forgeInstance);
|
|
1378
1536
|
}).catch((e) => {
|
|
1379
|
-
|
|
1537
|
+
latch.reject(e);
|
|
1380
1538
|
});
|
|
1381
|
-
function ready(onFulfilled, onRejected) {
|
|
1382
|
-
if (onFulfilled) {
|
|
1383
|
-
const p = readyPromise.then(onFulfilled, onRejected);
|
|
1384
|
-
return p.then(() => forgeInstance);
|
|
1385
|
-
}
|
|
1386
|
-
return readyPromise;
|
|
1387
|
-
}
|
|
1388
1539
|
const createBoundForgeWithMethods = createBoundForgeFactory({
|
|
1389
1540
|
load,
|
|
1390
1541
|
api,
|
|
1391
1542
|
route,
|
|
1392
1543
|
hasRoute,
|
|
1393
1544
|
getRoutes,
|
|
1545
|
+
getRouteNames: (lvl) => store.routeNames(lvl),
|
|
1394
1546
|
invalidate,
|
|
1395
1547
|
isLoaded,
|
|
1396
1548
|
loadingTracker
|
|
@@ -1398,6 +1550,7 @@ var RouteForge = (function (exports) {
|
|
|
1398
1550
|
const forgeInstance = {
|
|
1399
1551
|
api,
|
|
1400
1552
|
load,
|
|
1553
|
+
revalidate,
|
|
1401
1554
|
route,
|
|
1402
1555
|
url: route,
|
|
1403
1556
|
invalidate,
|
|
@@ -1407,13 +1560,14 @@ var RouteForge = (function (exports) {
|
|
|
1407
1560
|
getLevels,
|
|
1408
1561
|
warnings,
|
|
1409
1562
|
isLoading: () => loadingTracker.isLoading(),
|
|
1410
|
-
isReady: () =>
|
|
1563
|
+
isReady: () => latch.isReady(),
|
|
1411
1564
|
onLoadingChange: (cb) => loadingTracker.subscribe(cb),
|
|
1565
|
+
onRoutesChange: (cb) => routesTracker.subscribe(cb),
|
|
1412
1566
|
interceptors: {
|
|
1413
1567
|
request: requestInterceptors,
|
|
1414
1568
|
response: responseInterceptors
|
|
1415
1569
|
},
|
|
1416
|
-
ready,
|
|
1570
|
+
ready: latch.ready,
|
|
1417
1571
|
use(level, prefix) {
|
|
1418
1572
|
if (level === void 0) return forgeInstance;
|
|
1419
1573
|
return createBoundForgeWithMethods(level, prefix);
|
|
@@ -1434,6 +1588,7 @@ var RouteForge = (function (exports) {
|
|
|
1434
1588
|
exports.NetworkError = NetworkError;
|
|
1435
1589
|
exports.RequestAbortedError = RequestAbortedError;
|
|
1436
1590
|
exports.RouteCache = RouteCache;
|
|
1591
|
+
exports.RouteChangeTracker = RouteChangeTracker;
|
|
1437
1592
|
exports.UnknownLevelError = UnknownLevelError;
|
|
1438
1593
|
exports.UnknownRouteError = UnknownRouteError;
|
|
1439
1594
|
exports.createInterceptorManager = createInterceptorManager;
|