@route-forge/core 1.3.1 → 1.4.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/README.md +40 -0
- package/dist/codegen.d.cts +1 -1
- package/dist/codegen.d.ts +1 -1
- package/dist/index.cjs +43 -39
- 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 +43 -39
- package/dist/index.js.map +1 -1
- package/dist/route-forge.global.js +43 -39
- package/dist/route-forge.global.js.map +1 -1
- package/dist/route-forge.global.min.js +2 -2
- package/dist/{types-Cd1c1OdJ.d.cts → types-BFlrOTrN.d.cts} +7 -0
- package/dist/{types-Cd1c1OdJ.d.ts → types-BFlrOTrN.d.ts} +7 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -281,6 +281,46 @@ const prefixed = bound.useRoutePrefix('posts') // 追加前缀
|
|
|
281
281
|
`ForgeError (RF_FE_010)`, 防止在路由数据未就绪时返回错误结果。`api()` 不受影响(内部自动 await
|
|
282
282
|
discovery)。
|
|
283
283
|
|
|
284
|
+
## 工具导出
|
|
285
|
+
|
|
286
|
+
除 `createRouteForge` 外,core 包还导出以下工具件,供高级场景按需使用:
|
|
287
|
+
|
|
288
|
+
| 导出 | 说明 |
|
|
289
|
+
|-----------------------------|----------------------------------------------------------------------------------------|
|
|
290
|
+
| `createInterceptorManager` | 创建拦截器管理器(`use`/`eject`/`clear`),供自定义 Fetcher 复用统一拦截器实现 |
|
|
291
|
+
| `RouteCache` | 按层级隔离的路由缓存类(memory / sessionStorage / localStorage,TTL 过期),可独立使用 |
|
|
292
|
+
| `LoadingTracker` | 加载状态跟踪器(引用计数 + 订阅),框架适配层可基于它实现全局加载指示 |
|
|
293
|
+
| `resolveRouteName` | 前缀歧义异步消解(`prefix.suffix` 优先,回退后缀本身),`api()` 调用路径使用 |
|
|
294
|
+
| `resolveRouteNameSync` | 前缀歧义同步消解(基于已加载缓存),`route()` / `url()` 调用路径使用 |
|
|
295
|
+
|
|
296
|
+
## 错误参考
|
|
297
|
+
|
|
298
|
+
所有错误均为 `ForgeError` 子类,携带稳定的 `code` 字段,可按 `code` 分支处理:
|
|
299
|
+
|
|
300
|
+
| 错误类 | code | 触发场景 |
|
|
301
|
+
|-------------------------------|-------------|------------------------------------------------------|
|
|
302
|
+
| `UnknownRouteError` | `RF_FE_001` | 路由名不存在于已加载层级中 |
|
|
303
|
+
| `UnknownLevelError` | `RF_FE_002` | 层级未在 levels 声明(前端校验始终开启) |
|
|
304
|
+
| `MissingRouteParamError` | `RF_FE_003` | 必填路径参数缺失(无后端默认值) |
|
|
305
|
+
| `AdapterNotFoundError` | `RF_FE_005` | `adapter: 'axios'` 但宿主未安装/无有效 axios |
|
|
306
|
+
| `InvalidInterceptorReturnError` | `RF_FE_006` | 请求拦截器未返回 RequestConfig 对象 |
|
|
307
|
+
| `NetworkError` | `RF_FE_007` | 网络层失败(DNS、连接被拒等),`cause` 保留原始错误 |
|
|
308
|
+
| `HTTPError` | `RF_FE_008` | HTTP 非 2xx,`context.status` 为状态码 |
|
|
309
|
+
| `RequestAbortedError` | `RF_FE_009` | 请求被 `abort()` / AbortSignal 取消 |
|
|
310
|
+
| `ForgeError`(守卫) | `RF_FE_010` | auto-discovery 未完成时调用 `route()`/`hasRoute()` |
|
|
311
|
+
|
|
312
|
+
错误对象结构:
|
|
313
|
+
|
|
314
|
+
```ts
|
|
315
|
+
{
|
|
316
|
+
code: 'RF_FE_008', // 稳定错误码
|
|
317
|
+
route?: string, // 关联路由名
|
|
318
|
+
level?: string, // 关联层级
|
|
319
|
+
context?: Record<string, unknown>, // 附加上下文(如 HTTP 状态码)
|
|
320
|
+
cause?: unknown, // 原始底层错误
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
284
324
|
## 文档
|
|
285
325
|
|
|
286
326
|
- 仓库主页: https://github.com/route-forge/route-forge
|
package/dist/codegen.d.cts
CHANGED
package/dist/codegen.d.ts
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -280,30 +280,29 @@ function combineSignals(...signals) {
|
|
|
280
280
|
function createBuiltinHttp(forgeInterceptors) {
|
|
281
281
|
const requestMgr = forgeInterceptors?.request ?? createInterceptorManager();
|
|
282
282
|
const responseMgr = forgeInterceptors?.response ?? createInterceptorManager();
|
|
283
|
-
async function
|
|
284
|
-
const finalConfig = await runRequestInterceptors(requestMgr, config);
|
|
283
|
+
async function requestRaw(config) {
|
|
285
284
|
const signal = combineSignals(
|
|
286
|
-
|
|
287
|
-
|
|
285
|
+
config.signal,
|
|
286
|
+
config.timeout && config.timeout > 0 ? AbortSignal.timeout(config.timeout) : void 0
|
|
288
287
|
);
|
|
289
|
-
let url =
|
|
290
|
-
if (
|
|
291
|
-
const qs =
|
|
288
|
+
let url = config.url;
|
|
289
|
+
if (config.paramsSerializer && config.params) {
|
|
290
|
+
const qs = config.paramsSerializer(config.params);
|
|
292
291
|
if (qs) {
|
|
293
292
|
url = url.includes("?") ? `${url}&${qs}` : `${url}?${qs}`;
|
|
294
293
|
}
|
|
295
294
|
}
|
|
296
|
-
const headers = new Headers(
|
|
295
|
+
const headers = new Headers(config.headers);
|
|
297
296
|
const fetchInit = {
|
|
298
|
-
method:
|
|
297
|
+
method: config.method,
|
|
299
298
|
headers
|
|
300
299
|
};
|
|
301
300
|
if (signal) fetchInit.signal = signal;
|
|
302
|
-
if (
|
|
303
|
-
if (typeof
|
|
304
|
-
fetchInit.body =
|
|
301
|
+
if (config.body !== void 0 && !["GET", "HEAD"].includes(config.method.toUpperCase())) {
|
|
302
|
+
if (typeof config.body === "string" || isPassthroughBody(config.body)) {
|
|
303
|
+
fetchInit.body = config.body;
|
|
305
304
|
} else {
|
|
306
|
-
fetchInit.body = JSON.stringify(
|
|
305
|
+
fetchInit.body = JSON.stringify(config.body);
|
|
307
306
|
if (!headers.has("Content-Type")) {
|
|
308
307
|
headers.set("Content-Type", "application/json");
|
|
309
308
|
}
|
|
@@ -316,8 +315,8 @@ function createBuiltinHttp(forgeInterceptors) {
|
|
|
316
315
|
if (isAbortError(e)) throw e;
|
|
317
316
|
throw new NetworkError(
|
|
318
317
|
e instanceof Error ? e.message : String(e),
|
|
319
|
-
|
|
320
|
-
|
|
318
|
+
config.route,
|
|
319
|
+
config.level,
|
|
321
320
|
e
|
|
322
321
|
);
|
|
323
322
|
}
|
|
@@ -331,27 +330,32 @@ function createBuiltinHttp(forgeInterceptors) {
|
|
|
331
330
|
}
|
|
332
331
|
}
|
|
333
332
|
const responseData = {
|
|
334
|
-
route:
|
|
335
|
-
level:
|
|
336
|
-
method:
|
|
333
|
+
route: config.route,
|
|
334
|
+
level: config.level,
|
|
335
|
+
method: config.method,
|
|
337
336
|
url,
|
|
338
337
|
status: res.status,
|
|
339
338
|
headers: res.headers,
|
|
340
339
|
data,
|
|
341
|
-
config
|
|
340
|
+
config
|
|
342
341
|
};
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
342
|
+
if (res.status >= 200 && res.status < 300) {
|
|
343
|
+
return responseData;
|
|
344
|
+
}
|
|
345
|
+
throw new HTTPError(
|
|
346
|
+
`HTTP ${res.status} for route "${config.route}" (${config.method} ${url})`,
|
|
347
|
+
{
|
|
348
|
+
route: config.route,
|
|
349
|
+
level: config.level,
|
|
350
|
+
status: res.status,
|
|
351
|
+
url,
|
|
352
|
+
method: config.method
|
|
353
|
+
}
|
|
354
354
|
);
|
|
355
|
+
}
|
|
356
|
+
async function request(config) {
|
|
357
|
+
const finalConfig = await runRequestInterceptors(requestMgr, config);
|
|
358
|
+
const source = Promise.resolve(finalConfig).then(requestRaw);
|
|
355
359
|
return runResponseInterceptors(
|
|
356
360
|
responseMgr,
|
|
357
361
|
source
|
|
@@ -364,6 +368,7 @@ function createBuiltinHttp(forgeInterceptors) {
|
|
|
364
368
|
const del = (url, config) => request({ ...config, url, method: "DELETE" });
|
|
365
369
|
return {
|
|
366
370
|
request,
|
|
371
|
+
requestRaw,
|
|
367
372
|
interceptors: {
|
|
368
373
|
request: requestMgr,
|
|
369
374
|
response: responseMgr
|
|
@@ -575,7 +580,6 @@ function createRouteForge(options) {
|
|
|
575
580
|
const loadingTracker = new LoadingTracker();
|
|
576
581
|
const explicitLevels = options.levels;
|
|
577
582
|
const explicitEager = options.eager;
|
|
578
|
-
const explicitStrict = options.strict ?? false;
|
|
579
583
|
const explicitEndpoint = options.endpoint;
|
|
580
584
|
let effectiveLevels = explicitLevels ?? [];
|
|
581
585
|
let effectiveEager = explicitEager ?? [];
|
|
@@ -623,11 +627,6 @@ function createRouteForge(options) {
|
|
|
623
627
|
if (summary.config.url_prefix) {
|
|
624
628
|
effectiveUrlPrefix = summary.config.url_prefix.endsWith("/") ? summary.config.url_prefix.slice(0, -1) : summary.config.url_prefix;
|
|
625
629
|
}
|
|
626
|
-
if (summary.config.strict_mode && !explicitStrict) {
|
|
627
|
-
console.warn(
|
|
628
|
-
"[route-forge] backend strict_mode=true overrides frontend strict=false; forcing strict=true"
|
|
629
|
-
);
|
|
630
|
-
}
|
|
631
630
|
const backendLevels = Object.keys(summary.levels);
|
|
632
631
|
backendHasUnassignedLevel = backendLevels.includes(UNASSIGNED_LEVEL);
|
|
633
632
|
if (Array.isArray(summary.unassigned) && summary.unassigned.length > 0 && !backendHasUnassignedLevel) {
|
|
@@ -701,7 +700,10 @@ function createRouteForge(options) {
|
|
|
701
700
|
if (!adapterResolved) {
|
|
702
701
|
adapterObj = await adapterPromise.catch((e) => {
|
|
703
702
|
if (e instanceof AdapterNotFoundError) throw e;
|
|
704
|
-
return resolveAdapter({
|
|
703
|
+
return resolveAdapter({
|
|
704
|
+
adapter: "builtin",
|
|
705
|
+
forgeInterceptors: { request: requestInterceptors, response: responseInterceptors }
|
|
706
|
+
});
|
|
705
707
|
});
|
|
706
708
|
adapterResolved = true;
|
|
707
709
|
}
|
|
@@ -745,7 +747,8 @@ function createRouteForge(options) {
|
|
|
745
747
|
level
|
|
746
748
|
}
|
|
747
749
|
};
|
|
748
|
-
const
|
|
750
|
+
const doRawRequest = adp.requestRaw ?? adp.request;
|
|
751
|
+
const resp = await doRawRequest(config);
|
|
749
752
|
if (!resp || resp.status < 200 || resp.status >= 300) {
|
|
750
753
|
throw new HTTPError(
|
|
751
754
|
`Failed to load level "${level}": HTTP ${resp?.status}`,
|
|
@@ -1013,7 +1016,8 @@ function createRouteForge(options) {
|
|
|
1013
1016
|
}
|
|
1014
1017
|
const forgeResolver = { load, hasRoute };
|
|
1015
1018
|
function createBoundForge(level, prefix) {
|
|
1016
|
-
const levelLoadedPromise = load(level)
|
|
1019
|
+
const levelLoadedPromise = load(level);
|
|
1020
|
+
levelLoadedPromise.catch(() => {
|
|
1017
1021
|
});
|
|
1018
1022
|
const apiFn = prefix ? (name, params) => resolveRouteName(forgeResolver, level, prefix, name).then(
|
|
1019
1023
|
(resolved) => api(level, resolved, params)
|