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