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