@route-forge/core 1.2.2 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -10
- package/dist/codegen.d.cts +1 -1
- package/dist/codegen.d.ts +1 -1
- package/dist/index.cjs +136 -49
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +136 -49
- package/dist/index.js.map +1 -1
- package/dist/route-forge.global.js +136 -49
- package/dist/route-forge.global.js.map +1 -1
- package/dist/route-forge.global.min.js +2 -2
- package/dist/{types-BcaNLA_l.d.cts → types-Cd1c1OdJ.d.cts} +53 -60
- package/dist/{types-BcaNLA_l.d.ts → types-Cd1c1OdJ.d.ts} +53 -60
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -210,26 +210,69 @@ const forge = createRouteForge({
|
|
|
210
210
|
})
|
|
211
211
|
```
|
|
212
212
|
|
|
213
|
-
### `forge.ready`
|
|
213
|
+
### `forge.ready()` 方法
|
|
214
214
|
|
|
215
|
-
auto-discovery + eager load 完成后 resolve
|
|
215
|
+
auto-discovery + eager load 完成后 resolve,返回 `Promise<RouteForge>`(resolve 值为 forge 自身),适合 async/await 风格:
|
|
216
216
|
|
|
217
217
|
```ts
|
|
218
218
|
const forge = createRouteForge({ endpoint: '/_forge/routes' })
|
|
219
|
-
|
|
219
|
+
|
|
220
|
+
// 无参模式:直接 await
|
|
221
|
+
await forge.ready()
|
|
220
222
|
// 路由数据已就绪,可安全调用 route() / hasRoute()
|
|
223
|
+
|
|
224
|
+
// 回调模式:onFulfilled / onRejected
|
|
225
|
+
forge.ready(
|
|
226
|
+
(f) => { console.log('ready!', f) },
|
|
227
|
+
(err) => { console.error(err) }
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
// 链式调用:ready 返回 forge 自身
|
|
231
|
+
const bound = await forge.ready().then(f => f.use('admin'))
|
|
221
232
|
```
|
|
222
233
|
|
|
223
|
-
### `
|
|
234
|
+
### `forge.use(level?, prefix?)` 层级绑定
|
|
224
235
|
|
|
225
|
-
|
|
236
|
+
`forge.use()` 是 core 层唯一的 level 绑定入口,返回 `BoundForge` 对象:
|
|
226
237
|
|
|
227
238
|
```ts
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
})
|
|
231
|
-
//
|
|
232
|
-
|
|
239
|
+
// 绑定层级 — 自动触发 load,提供快捷方法
|
|
240
|
+
const bound = forge.use('admin')
|
|
241
|
+
bound('users.show', { user: 1 }) // 可直接调用(= bound.api())
|
|
242
|
+
bound.route('users.show') // URL 生成
|
|
243
|
+
bound.level // → 'admin'
|
|
244
|
+
bound.levelLoaded // Promise<void>
|
|
245
|
+
|
|
246
|
+
// 绑定层级 + 前缀 — 路由名自动拼接
|
|
247
|
+
const bound = forge.use('admin', 'users')
|
|
248
|
+
bound('show', { user: 1 }) // → forge.api('admin', 'users.show', ...)
|
|
249
|
+
|
|
250
|
+
// BoundForge 独有方法
|
|
251
|
+
await bound.onLevelLoaded() // 等待 level 加载完成
|
|
252
|
+
const prefixed = bound.useRoutePrefix('posts') // 追加前缀
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### IIFE 浏览器用法
|
|
256
|
+
|
|
257
|
+
通过 `<script>` 标签引入后,`RouteForge` 全局可用:
|
|
258
|
+
|
|
259
|
+
```html
|
|
260
|
+
<script src="https://unpkg.com/@route-forge/core"></script>
|
|
261
|
+
<script>
|
|
262
|
+
const forge = RouteForge.createRouteForge({
|
|
263
|
+
endpoint: '/_forge/routes',
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
// 等待就绪后绑定层级
|
|
267
|
+
forge.ready().then(function(f) {
|
|
268
|
+
const admin = f.use('admin')
|
|
269
|
+
return admin.onLevelLoaded()
|
|
270
|
+
}).then(function(bound) {
|
|
271
|
+
return bound('users.show', { user: 1 })
|
|
272
|
+
}).then(function(data) {
|
|
273
|
+
console.log(data)
|
|
274
|
+
})
|
|
275
|
+
</script>
|
|
233
276
|
```
|
|
234
277
|
|
|
235
278
|
### Auto-discovery 守卫
|
package/dist/codegen.d.cts
CHANGED
package/dist/codegen.d.ts
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -467,6 +467,39 @@ async function resolveAdapter(opts) {
|
|
|
467
467
|
return createBuiltinHttp(opts.forgeInterceptors);
|
|
468
468
|
}
|
|
469
469
|
|
|
470
|
+
// src/resolveRouteName.ts
|
|
471
|
+
async function resolveRouteName(forge, level, prefix, suffix, separator = ".") {
|
|
472
|
+
if (!suffix) return prefix;
|
|
473
|
+
const joined = `${prefix}${separator}${suffix}`;
|
|
474
|
+
if (!suffix.startsWith(`${prefix}${separator}`)) return joined;
|
|
475
|
+
await forge.load(level);
|
|
476
|
+
if (forge.hasRoute(level, joined)) return joined;
|
|
477
|
+
if (forge.hasRoute(level, suffix)) return suffix;
|
|
478
|
+
throw new UnknownRouteError(joined, level);
|
|
479
|
+
}
|
|
480
|
+
function resolveRouteNameSync(forge, level, prefix, suffix, separator = ".") {
|
|
481
|
+
if (!suffix) return prefix;
|
|
482
|
+
const joined = `${prefix}${separator}${suffix}`;
|
|
483
|
+
if (!suffix.startsWith(`${prefix}${separator}`)) return joined;
|
|
484
|
+
if (forge.hasRoute(level, joined)) return joined;
|
|
485
|
+
if (forge.hasRoute(level, suffix)) return suffix;
|
|
486
|
+
throw new UnknownRouteError(joined, level);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// src/defineImmutableProps.ts
|
|
490
|
+
function defineImmutableProps(target, props) {
|
|
491
|
+
for (const key of Object.keys(props)) {
|
|
492
|
+
const val = props[key];
|
|
493
|
+
Object.defineProperty(target, key, {
|
|
494
|
+
value: val !== null && typeof val === "object" ? Object.freeze(val) : val,
|
|
495
|
+
writable: false,
|
|
496
|
+
enumerable: false,
|
|
497
|
+
configurable: false
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
return target;
|
|
501
|
+
}
|
|
502
|
+
|
|
470
503
|
// src/loading.ts
|
|
471
504
|
var LoadingTracker = class {
|
|
472
505
|
constructor() {
|
|
@@ -633,7 +666,6 @@ function createRouteForge(options) {
|
|
|
633
666
|
const readyPromise = new Promise((resolve) => {
|
|
634
667
|
resolveReady = resolve;
|
|
635
668
|
});
|
|
636
|
-
const levelLoadedListeners = /* @__PURE__ */ new Map();
|
|
637
669
|
const cacheTtl = cacheOpts.ttl ?? DEFAULT_CACHE_TTL;
|
|
638
670
|
const cacheStorage = cacheOpts.storage ?? "memory";
|
|
639
671
|
const cache = new RouteCache({ storage: cacheStorage, ttl: cacheTtl });
|
|
@@ -685,7 +717,7 @@ function createRouteForge(options) {
|
|
|
685
717
|
function assertDiscoveryReady() {
|
|
686
718
|
if (!autoDiscoveryCompleted && !explicitLevels?.length) {
|
|
687
719
|
throw new ForgeError(
|
|
688
|
-
"Route data not available. Auto-discovery has not completed. Use
|
|
720
|
+
"Route data not available. Auto-discovery has not completed. Use forge.ready() or forge.use(level) first.",
|
|
689
721
|
{ code: "RF_FE_010" }
|
|
690
722
|
);
|
|
691
723
|
}
|
|
@@ -738,15 +770,11 @@ function createRouteForge(options) {
|
|
|
738
770
|
routes[r.name] = { ...r, level: UNASSIGNED_LEVEL };
|
|
739
771
|
}
|
|
740
772
|
cache.set({ level: UNASSIGNED_LEVEL, routes, cache: null });
|
|
741
|
-
const listeners = levelLoadedListeners.get(level);
|
|
742
|
-
if (listeners) listeners.forEach((cb) => cb());
|
|
743
773
|
return;
|
|
744
774
|
}
|
|
745
775
|
const resp = await fetchLevel(level);
|
|
746
776
|
if ((invalidationGens.get(level) ?? 0) === gen) {
|
|
747
777
|
cache.set(resp);
|
|
748
|
-
const listeners = levelLoadedListeners.get(level);
|
|
749
|
-
if (listeners) listeners.forEach((cb) => cb());
|
|
750
778
|
}
|
|
751
779
|
} finally {
|
|
752
780
|
inflight.delete(level);
|
|
@@ -820,23 +848,40 @@ function createRouteForge(options) {
|
|
|
820
848
|
}
|
|
821
849
|
return void 0;
|
|
822
850
|
}
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
851
|
+
function api(level, name, params = {}) {
|
|
852
|
+
let ctrl;
|
|
853
|
+
let abortedBeforeInit = false;
|
|
854
|
+
let abortReason;
|
|
855
|
+
const work = (async () => {
|
|
856
|
+
ctrl = new AbortController();
|
|
857
|
+
if (abortedBeforeInit) {
|
|
858
|
+
ctrl.abort(abortReason);
|
|
859
|
+
}
|
|
860
|
+
await autoDiscoveryPromise;
|
|
861
|
+
await load(level);
|
|
862
|
+
const meta = findRouteMeta(level, name);
|
|
863
|
+
if (!meta) {
|
|
864
|
+
throw new UnknownRouteError(name, level);
|
|
865
|
+
}
|
|
866
|
+
return doApiCall(meta, params, ctrl.signal);
|
|
867
|
+
})();
|
|
868
|
+
const request = work;
|
|
869
|
+
request.abort = () => {
|
|
870
|
+
if (ctrl) {
|
|
871
|
+
ctrl.abort();
|
|
872
|
+
} else {
|
|
873
|
+
abortedBeforeInit = true;
|
|
874
|
+
}
|
|
875
|
+
};
|
|
876
|
+
return request;
|
|
831
877
|
}
|
|
832
|
-
async function doApiCall(meta, params) {
|
|
878
|
+
async function doApiCall(meta, params, signal) {
|
|
833
879
|
const {
|
|
834
880
|
pathParams,
|
|
835
881
|
query,
|
|
836
882
|
body,
|
|
837
883
|
headers,
|
|
838
|
-
timeout: perCallTimeout
|
|
839
|
-
signal
|
|
884
|
+
timeout: perCallTimeout
|
|
840
885
|
} = resolveApiParams(params);
|
|
841
886
|
if (signal?.aborted) {
|
|
842
887
|
throw new RequestAbortedError(meta.name, meta.level, signal.reason);
|
|
@@ -880,7 +925,7 @@ function createRouteForge(options) {
|
|
|
880
925
|
},
|
|
881
926
|
(err) => {
|
|
882
927
|
if (err instanceof ForgeError) throw err;
|
|
883
|
-
if (isAbortError2(err,
|
|
928
|
+
if (isAbortError2(err, signal)) {
|
|
884
929
|
throw new RequestAbortedError(meta.name, meta.level, err);
|
|
885
930
|
}
|
|
886
931
|
throw new NetworkError(
|
|
@@ -948,6 +993,7 @@ function createRouteForge(options) {
|
|
|
948
993
|
return result;
|
|
949
994
|
}
|
|
950
995
|
void autoDiscoveryPromise.then(() => {
|
|
996
|
+
autoDiscoveryCompleted = true;
|
|
951
997
|
options.onSummaryReady?.();
|
|
952
998
|
if (effectiveEager.length > 0) {
|
|
953
999
|
return Promise.all(effectiveEager.map((lvl) => load(lvl))).catch((e) => {
|
|
@@ -955,11 +1001,74 @@ function createRouteForge(options) {
|
|
|
955
1001
|
});
|
|
956
1002
|
}
|
|
957
1003
|
}).then(() => {
|
|
958
|
-
|
|
959
|
-
resolveReady();
|
|
1004
|
+
resolveReady(forgeInstance);
|
|
960
1005
|
}).catch(() => {
|
|
961
1006
|
});
|
|
962
|
-
|
|
1007
|
+
function ready(onFulfilled, onRejected) {
|
|
1008
|
+
if (onFulfilled) {
|
|
1009
|
+
const p = readyPromise.then(onFulfilled, onRejected);
|
|
1010
|
+
return p.then(() => forgeInstance);
|
|
1011
|
+
}
|
|
1012
|
+
return readyPromise;
|
|
1013
|
+
}
|
|
1014
|
+
const forgeResolver = { load, hasRoute };
|
|
1015
|
+
function createBoundForge(level, prefix) {
|
|
1016
|
+
const levelLoadedPromise = load(level).catch(() => {
|
|
1017
|
+
});
|
|
1018
|
+
const apiFn = prefix ? (name, params) => resolveRouteName(forgeResolver, level, prefix, name).then(
|
|
1019
|
+
(resolved) => api(level, resolved, params)
|
|
1020
|
+
) : (name, params) => api(level, name, params);
|
|
1021
|
+
const callable = apiFn;
|
|
1022
|
+
defineImmutableProps(callable, {
|
|
1023
|
+
level,
|
|
1024
|
+
...prefix !== void 0 ? { prefix } : {},
|
|
1025
|
+
api: apiFn,
|
|
1026
|
+
route: prefix ? (name, params) => route(level, resolveRouteNameSync(forgeResolver, level, prefix, name), params) : (name, params) => route(level, name, params),
|
|
1027
|
+
url: prefix ? (name, params) => route(level, resolveRouteNameSync(forgeResolver, level, prefix, name), params) : (name, params) => route(level, name, params),
|
|
1028
|
+
hasRoute: (name) => hasRoute(level, name),
|
|
1029
|
+
getRoutes: () => getRoutes(level),
|
|
1030
|
+
load: () => load(level),
|
|
1031
|
+
invalidate: () => invalidate(level),
|
|
1032
|
+
isLoaded: () => isLoaded(level),
|
|
1033
|
+
isLoading: () => loadingTracker.isLoading(),
|
|
1034
|
+
onLoadingChange: (cb) => loadingTracker.subscribe(cb)
|
|
1035
|
+
});
|
|
1036
|
+
Object.defineProperty(callable, "levelLoaded", {
|
|
1037
|
+
value: levelLoadedPromise,
|
|
1038
|
+
writable: false,
|
|
1039
|
+
enumerable: true,
|
|
1040
|
+
configurable: true
|
|
1041
|
+
});
|
|
1042
|
+
return callable;
|
|
1043
|
+
}
|
|
1044
|
+
function boundOnLevelLoaded(bound, levelLoadedPromise, onFulfilled, onRejected) {
|
|
1045
|
+
const p = levelLoadedPromise.then(() => bound);
|
|
1046
|
+
if (onFulfilled) {
|
|
1047
|
+
return p.then(onFulfilled, onRejected).then(() => bound);
|
|
1048
|
+
}
|
|
1049
|
+
return p;
|
|
1050
|
+
}
|
|
1051
|
+
function attachBoundMethods(bound, levelLoadedPromise, level) {
|
|
1052
|
+
Object.defineProperty(bound, "onLevelLoaded", {
|
|
1053
|
+
value: (onFulfilled, onRejected) => boundOnLevelLoaded(bound, levelLoadedPromise, onFulfilled, onRejected),
|
|
1054
|
+
writable: false,
|
|
1055
|
+
enumerable: false,
|
|
1056
|
+
configurable: false
|
|
1057
|
+
});
|
|
1058
|
+
Object.defineProperty(bound, "useRoutePrefix", {
|
|
1059
|
+
value: (prefix) => createBoundForgeWithMethods(level, prefix),
|
|
1060
|
+
writable: false,
|
|
1061
|
+
enumerable: false,
|
|
1062
|
+
configurable: false
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
function createBoundForgeWithMethods(level, prefix) {
|
|
1066
|
+
const bound = createBoundForge(level, prefix);
|
|
1067
|
+
const levelLoadedPromise = bound.levelLoaded;
|
|
1068
|
+
attachBoundMethods(bound, levelLoadedPromise, level);
|
|
1069
|
+
return bound;
|
|
1070
|
+
}
|
|
1071
|
+
const forgeInstance = {
|
|
963
1072
|
api,
|
|
964
1073
|
load,
|
|
965
1074
|
route,
|
|
@@ -974,15 +1083,13 @@ function createRouteForge(options) {
|
|
|
974
1083
|
request: requestInterceptors,
|
|
975
1084
|
response: responseInterceptors
|
|
976
1085
|
},
|
|
977
|
-
ready
|
|
978
|
-
|
|
979
|
-
if (
|
|
980
|
-
|
|
981
|
-
}
|
|
982
|
-
levelLoadedListeners.get(level).add(cb);
|
|
983
|
-
return () => levelLoadedListeners.get(level)?.delete(cb);
|
|
1086
|
+
ready,
|
|
1087
|
+
use(level, prefix) {
|
|
1088
|
+
if (level === void 0) return forgeInstance;
|
|
1089
|
+
return createBoundForgeWithMethods(level, prefix);
|
|
984
1090
|
}
|
|
985
1091
|
};
|
|
1092
|
+
return forgeInstance;
|
|
986
1093
|
}
|
|
987
1094
|
function pickMethod(meta) {
|
|
988
1095
|
const m = meta.methods.find((x) => x.toUpperCase() !== "HEAD");
|
|
@@ -1006,7 +1113,6 @@ function resolveApiParams(input) {
|
|
|
1006
1113
|
body: rawBody,
|
|
1007
1114
|
headers: rawHeaders,
|
|
1008
1115
|
timeout: perCallTimeout,
|
|
1009
|
-
signal,
|
|
1010
1116
|
...flatRest
|
|
1011
1117
|
} = input;
|
|
1012
1118
|
const pathParams = explicitParams ? { ...explicitParams } : {};
|
|
@@ -1039,7 +1145,7 @@ function resolveApiParams(input) {
|
|
|
1039
1145
|
pathParams.headers = rawHeaders;
|
|
1040
1146
|
}
|
|
1041
1147
|
}
|
|
1042
|
-
return { pathParams, query, body, headers, timeout: perCallTimeout
|
|
1148
|
+
return { pathParams, query, body, headers, timeout: perCallTimeout };
|
|
1043
1149
|
}
|
|
1044
1150
|
function isAbortError2(err, signal) {
|
|
1045
1151
|
if (signal?.aborted) return true;
|
|
@@ -1050,25 +1156,6 @@ function isAbortError2(err, signal) {
|
|
|
1050
1156
|
return false;
|
|
1051
1157
|
}
|
|
1052
1158
|
|
|
1053
|
-
// src/resolveRouteName.ts
|
|
1054
|
-
async function resolveRouteName(forge, level, prefix, suffix, separator = ".") {
|
|
1055
|
-
if (!suffix) return prefix;
|
|
1056
|
-
const joined = `${prefix}${separator}${suffix}`;
|
|
1057
|
-
if (!suffix.startsWith(`${prefix}${separator}`)) return joined;
|
|
1058
|
-
await forge.load(level);
|
|
1059
|
-
if (forge.hasRoute(level, joined)) return joined;
|
|
1060
|
-
if (forge.hasRoute(level, suffix)) return suffix;
|
|
1061
|
-
throw new UnknownRouteError(joined, level);
|
|
1062
|
-
}
|
|
1063
|
-
function resolveRouteNameSync(forge, level, prefix, suffix, separator = ".") {
|
|
1064
|
-
if (!suffix) return prefix;
|
|
1065
|
-
const joined = `${prefix}${separator}${suffix}`;
|
|
1066
|
-
if (!suffix.startsWith(`${prefix}${separator}`)) return joined;
|
|
1067
|
-
if (forge.hasRoute(level, joined)) return joined;
|
|
1068
|
-
if (forge.hasRoute(level, suffix)) return suffix;
|
|
1069
|
-
throw new UnknownRouteError(joined, level);
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
1159
|
exports.AdapterNotFoundError = AdapterNotFoundError;
|
|
1073
1160
|
exports.ForgeError = ForgeError;
|
|
1074
1161
|
exports.HTTPError = HTTPError;
|