@route-forge/core 1.3.1 → 2.0.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 +50 -6
- package/dist/codegen.d.cts +1 -1
- package/dist/codegen.d.ts +1 -1
- package/dist/index.cjs +152 -123
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -5
- package/dist/index.d.ts +16 -5
- package/dist/index.js +152 -123
- package/dist/index.js.map +1 -1
- package/dist/route-forge.global.js +152 -123
- 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-BvLdl02f.d.cts} +13 -17
- package/dist/{types-Cd1c1OdJ.d.ts → types-BvLdl02f.d.ts} +13 -17
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -196,18 +196,22 @@ unsub()
|
|
|
196
196
|
| Level load | 拉取某层级路由元数据 | `forge.isLoaded(level)` |
|
|
197
197
|
| API request | 业务接口请求 | `forge.isLoading()` |
|
|
198
198
|
|
|
199
|
-
### `
|
|
199
|
+
### `ready()` 后挂载应用
|
|
200
200
|
|
|
201
|
-
|
|
201
|
+
推荐在 `ready()` resolve 后挂载应用(auto-discovery + eager 层级全部完成),
|
|
202
|
+
失败走 `catch`(onSummaryReady 回调已移除,统一走 ready——完整成功/失败语义链):
|
|
202
203
|
|
|
203
204
|
```ts
|
|
204
205
|
const forge = createRouteForge({
|
|
205
206
|
endpoint: '/_forge/routes',
|
|
206
|
-
onSummaryReady: () => {
|
|
207
|
-
// 摘要端点完成,路由数据已可用
|
|
208
|
-
app.mount('#app')
|
|
209
|
-
},
|
|
210
207
|
})
|
|
208
|
+
// 成功:摘要 + eager 完成后挂载;失败:接住 reject,避免静默白屏
|
|
209
|
+
forge.ready()
|
|
210
|
+
.then(() => app.mount('#app'))
|
|
211
|
+
.catch((err) => {
|
|
212
|
+
console.error('[route-forge] init failed', err)
|
|
213
|
+
// 按业务需要降级:渲染错误页 / 重试 / 上报
|
|
214
|
+
})
|
|
211
215
|
```
|
|
212
216
|
|
|
213
217
|
### `forge.ready()` 方法
|
|
@@ -281,6 +285,46 @@ const prefixed = bound.useRoutePrefix('posts') // 追加前缀
|
|
|
281
285
|
`ForgeError (RF_FE_010)`, 防止在路由数据未就绪时返回错误结果。`api()` 不受影响(内部自动 await
|
|
282
286
|
discovery)。
|
|
283
287
|
|
|
288
|
+
## 工具导出
|
|
289
|
+
|
|
290
|
+
除 `createRouteForge` 外,core 包还导出以下工具件,供高级场景按需使用:
|
|
291
|
+
|
|
292
|
+
| 导出 | 说明 |
|
|
293
|
+
|-----------------------------|----------------------------------------------------------------------------------------|
|
|
294
|
+
| `createInterceptorManager` | 创建拦截器管理器(`use`/`eject`/`clear`),供自定义 Fetcher 复用统一拦截器实现 |
|
|
295
|
+
| `RouteCache` | 按层级隔离的路由缓存类(memory / sessionStorage / localStorage,TTL 过期),可独立使用 |
|
|
296
|
+
| `LoadingTracker` | 加载状态跟踪器(引用计数 + 订阅),框架适配层可基于它实现全局加载指示 |
|
|
297
|
+
| `resolveRouteName` | 前缀歧义异步消解(`prefix.suffix` 优先,回退后缀本身),`api()` 调用路径使用 |
|
|
298
|
+
| `resolveRouteNameSync` | 前缀歧义同步消解(基于已加载缓存),`route()` / `url()` 调用路径使用 |
|
|
299
|
+
|
|
300
|
+
## 错误参考
|
|
301
|
+
|
|
302
|
+
所有错误均为 `ForgeError` 子类,携带稳定的 `code` 字段,可按 `code` 分支处理:
|
|
303
|
+
|
|
304
|
+
| 错误类 | code | 触发场景 |
|
|
305
|
+
|-------------------------------|-------------|------------------------------------------------------|
|
|
306
|
+
| `UnknownRouteError` | `RF_FE_001` | 路由名不存在于已加载层级中 |
|
|
307
|
+
| `UnknownLevelError` | `RF_FE_002` | 层级未在 levels 声明(前端校验始终开启) |
|
|
308
|
+
| `MissingRouteParamError` | `RF_FE_003` | 必填路径参数缺失(无后端默认值) |
|
|
309
|
+
| `AdapterNotFoundError` | `RF_FE_005` | `adapter: 'axios'` 但宿主未安装/无有效 axios |
|
|
310
|
+
| `InvalidInterceptorReturnError` | `RF_FE_006` | 请求拦截器未返回 RequestConfig 对象 |
|
|
311
|
+
| `NetworkError` | `RF_FE_007` | 网络层失败(DNS、连接被拒等),`cause` 保留原始错误 |
|
|
312
|
+
| `HTTPError` | `RF_FE_008` | HTTP 非 2xx,`context.status` 为状态码 |
|
|
313
|
+
| `RequestAbortedError` | `RF_FE_009` | 请求被 `abort()` / AbortSignal 取消 |
|
|
314
|
+
| `ForgeError`(守卫) | `RF_FE_010` | auto-discovery 未完成时调用 `route()`/`hasRoute()` |
|
|
315
|
+
|
|
316
|
+
错误对象结构:
|
|
317
|
+
|
|
318
|
+
```ts
|
|
319
|
+
{
|
|
320
|
+
code: 'RF_FE_008', // 稳定错误码
|
|
321
|
+
route?: string, // 关联路由名
|
|
322
|
+
level?: string, // 关联层级
|
|
323
|
+
context?: Record<string, unknown>, // 附加上下文(如 HTTP 状态码)
|
|
324
|
+
cause?: unknown, // 原始底层错误
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
284
328
|
## 文档
|
|
285
329
|
|
|
286
330
|
- 仓库主页: https://github.com/route-forge/route-forge
|
package/dist/codegen.d.cts
CHANGED
package/dist/codegen.d.ts
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -16,9 +16,18 @@ function pickStorage(storage) {
|
|
|
16
16
|
var RouteCache = class {
|
|
17
17
|
constructor(opts) {
|
|
18
18
|
this.memory = /* @__PURE__ */ new Map();
|
|
19
|
+
/** 其他 tab 修改 storage 时失效对应镜像(storage 事件:自己的写不触发,自己的写经 set/del 已同步) */
|
|
20
|
+
this.onStorageEvent = (e) => {
|
|
21
|
+
if (!e.key) return;
|
|
22
|
+
if (!e.key.startsWith(KEY_PREFIX)) return;
|
|
23
|
+
this.memory.delete(e.key.slice(KEY_PREFIX.length));
|
|
24
|
+
};
|
|
19
25
|
this.storage = opts.storage;
|
|
20
26
|
this.fallbackTtl = opts.ttl;
|
|
21
27
|
this.backend = pickStorage(opts.storage);
|
|
28
|
+
if (this.backend && typeof globalThis.addEventListener === "function") {
|
|
29
|
+
globalThis.addEventListener("storage", this.onStorageEvent);
|
|
30
|
+
}
|
|
22
31
|
}
|
|
23
32
|
key(level) {
|
|
24
33
|
return `${KEY_PREFIX}${level}`;
|
|
@@ -27,6 +36,8 @@ var RouteCache = class {
|
|
|
27
36
|
if (this.storage === "memory" || !this.backend) {
|
|
28
37
|
return this.getFromMemory(level);
|
|
29
38
|
}
|
|
39
|
+
const mirrored = this.getFromMemory(level);
|
|
40
|
+
if (mirrored) return mirrored;
|
|
30
41
|
const raw = this.backend.getItem(this.key(level));
|
|
31
42
|
if (raw) {
|
|
32
43
|
try {
|
|
@@ -35,12 +46,13 @@ var RouteCache = class {
|
|
|
35
46
|
this.del(level);
|
|
36
47
|
return void 0;
|
|
37
48
|
}
|
|
49
|
+
this.memory.set(level, entry);
|
|
38
50
|
return entry;
|
|
39
51
|
} catch {
|
|
40
52
|
return void 0;
|
|
41
53
|
}
|
|
42
54
|
}
|
|
43
|
-
return
|
|
55
|
+
return void 0;
|
|
44
56
|
}
|
|
45
57
|
getFromMemory(level) {
|
|
46
58
|
const entry = this.memory.get(level);
|
|
@@ -64,14 +76,13 @@ var RouteCache = class {
|
|
|
64
76
|
ttl,
|
|
65
77
|
cachedAt: Date.now()
|
|
66
78
|
};
|
|
79
|
+
this.memory.set(resp.level, entry);
|
|
67
80
|
if (this.storage === "memory" || !this.backend) {
|
|
68
|
-
this.memory.set(resp.level, entry);
|
|
69
81
|
return;
|
|
70
82
|
}
|
|
71
83
|
try {
|
|
72
84
|
this.backend.setItem(this.key(resp.level), JSON.stringify(entry));
|
|
73
85
|
} catch {
|
|
74
|
-
this.memory.set(resp.level, entry);
|
|
75
86
|
}
|
|
76
87
|
}
|
|
77
88
|
del(level) {
|
|
@@ -251,14 +262,14 @@ function createInterceptorManager() {
|
|
|
251
262
|
return new InterceptorManagerImpl();
|
|
252
263
|
}
|
|
253
264
|
|
|
254
|
-
// src/adapters/
|
|
255
|
-
function
|
|
256
|
-
if (
|
|
257
|
-
return typeof FormData !== "undefined" && body instanceof FormData || typeof URLSearchParams !== "undefined" && body instanceof URLSearchParams || typeof Blob !== "undefined" && body instanceof Blob || typeof ArrayBuffer !== "undefined" && (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) || typeof ReadableStream !== "undefined" && body instanceof ReadableStream;
|
|
258
|
-
}
|
|
259
|
-
function isAbortError(err) {
|
|
265
|
+
// src/adapters/fetch-core.ts
|
|
266
|
+
function isAbortError(err, signal) {
|
|
267
|
+
if (signal?.aborted) return true;
|
|
260
268
|
const e = err;
|
|
261
|
-
|
|
269
|
+
if (!e) return false;
|
|
270
|
+
if (e.name === "AbortError") return true;
|
|
271
|
+
if (e.code === "ERR_CANCELED") return true;
|
|
272
|
+
return false;
|
|
262
273
|
}
|
|
263
274
|
function combineSignals(...signals) {
|
|
264
275
|
const valid = signals.filter((s) => !!s);
|
|
@@ -277,81 +288,92 @@ function combineSignals(...signals) {
|
|
|
277
288
|
}
|
|
278
289
|
return controller.signal;
|
|
279
290
|
}
|
|
280
|
-
function
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
}
|
|
291
|
+
function isPassthroughBody(body) {
|
|
292
|
+
if (body === null) return false;
|
|
293
|
+
return typeof FormData !== "undefined" && body instanceof FormData || typeof URLSearchParams !== "undefined" && body instanceof URLSearchParams || typeof Blob !== "undefined" && body instanceof Blob || typeof ArrayBuffer !== "undefined" && (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) || typeof ReadableStream !== "undefined" && body instanceof ReadableStream;
|
|
294
|
+
}
|
|
295
|
+
async function rawFetch(config) {
|
|
296
|
+
const signal = combineSignals(
|
|
297
|
+
config.signal,
|
|
298
|
+
config.timeout && config.timeout > 0 ? AbortSignal.timeout(config.timeout) : void 0
|
|
299
|
+
);
|
|
300
|
+
let url = config.url;
|
|
301
|
+
if (config.paramsSerializer && config.params) {
|
|
302
|
+
const qs = config.paramsSerializer(config.params);
|
|
303
|
+
if (qs) {
|
|
304
|
+
url = url.includes("?") ? `${url}&${qs}` : `${url}?${qs}`;
|
|
295
305
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
306
|
+
}
|
|
307
|
+
const headers = new Headers(config.headers);
|
|
308
|
+
const fetchInit = {
|
|
309
|
+
method: config.method,
|
|
310
|
+
headers
|
|
311
|
+
};
|
|
312
|
+
if (signal) fetchInit.signal = signal;
|
|
313
|
+
if (config.body !== void 0 && !["GET", "HEAD"].includes(config.method.toUpperCase())) {
|
|
314
|
+
if (typeof config.body === "string" || isPassthroughBody(config.body)) {
|
|
315
|
+
fetchInit.body = config.body;
|
|
316
|
+
} else {
|
|
317
|
+
fetchInit.body = JSON.stringify(config.body);
|
|
318
|
+
if (!headers.has("Content-Type")) {
|
|
319
|
+
headers.set("Content-Type", "application/json");
|
|
310
320
|
}
|
|
311
321
|
}
|
|
312
|
-
|
|
322
|
+
}
|
|
323
|
+
let res;
|
|
324
|
+
try {
|
|
325
|
+
res = await fetch(url, fetchInit);
|
|
326
|
+
} catch (e) {
|
|
327
|
+
if (isAbortError(e)) throw e;
|
|
328
|
+
throw new NetworkError(
|
|
329
|
+
e instanceof Error ? e.message : String(e),
|
|
330
|
+
config.route,
|
|
331
|
+
config.level,
|
|
332
|
+
e
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
const text = await res.text();
|
|
336
|
+
let data = text;
|
|
337
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
338
|
+
if (contentType.includes("application/json")) {
|
|
313
339
|
try {
|
|
314
|
-
|
|
315
|
-
} catch
|
|
316
|
-
if (isAbortError(e)) throw e;
|
|
317
|
-
throw new NetworkError(
|
|
318
|
-
e instanceof Error ? e.message : String(e),
|
|
319
|
-
finalConfig.route,
|
|
320
|
-
finalConfig.level,
|
|
321
|
-
e
|
|
322
|
-
);
|
|
323
|
-
}
|
|
324
|
-
const text = await res.text();
|
|
325
|
-
let data = text;
|
|
326
|
-
const contentType = res.headers.get("content-type") ?? "";
|
|
327
|
-
if (contentType.includes("application/json")) {
|
|
328
|
-
try {
|
|
329
|
-
data = JSON.parse(text);
|
|
330
|
-
} catch {
|
|
331
|
-
}
|
|
340
|
+
data = JSON.parse(text);
|
|
341
|
+
} catch {
|
|
332
342
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
343
|
+
}
|
|
344
|
+
const responseData = {
|
|
345
|
+
route: config.route,
|
|
346
|
+
level: config.level,
|
|
347
|
+
method: config.method,
|
|
348
|
+
url,
|
|
349
|
+
status: res.status,
|
|
350
|
+
headers: res.headers,
|
|
351
|
+
data,
|
|
352
|
+
config
|
|
353
|
+
};
|
|
354
|
+
if (res.status >= 200 && res.status < 300) {
|
|
355
|
+
return responseData;
|
|
356
|
+
}
|
|
357
|
+
throw new HTTPError(
|
|
358
|
+
`HTTP ${res.status} for route "${config.route}" (${config.method} ${url})`,
|
|
359
|
+
{
|
|
360
|
+
route: config.route,
|
|
361
|
+
level: config.level,
|
|
338
362
|
status: res.status,
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
)
|
|
354
|
-
);
|
|
363
|
+
url,
|
|
364
|
+
method: config.method
|
|
365
|
+
}
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// src/adapters/builtin-http.ts
|
|
370
|
+
function createBuiltinHttp(forgeInterceptors) {
|
|
371
|
+
const requestMgr = forgeInterceptors?.request ?? createInterceptorManager();
|
|
372
|
+
const responseMgr = forgeInterceptors?.response ?? createInterceptorManager();
|
|
373
|
+
const requestRaw = rawFetch;
|
|
374
|
+
async function request(config) {
|
|
375
|
+
const finalConfig = await runRequestInterceptors(requestMgr, config);
|
|
376
|
+
const source = Promise.resolve(finalConfig).then(requestRaw);
|
|
355
377
|
return runResponseInterceptors(
|
|
356
378
|
responseMgr,
|
|
357
379
|
source
|
|
@@ -364,6 +386,7 @@ function createBuiltinHttp(forgeInterceptors) {
|
|
|
364
386
|
const del = (url, config) => request({ ...config, url, method: "DELETE" });
|
|
365
387
|
return {
|
|
366
388
|
request,
|
|
389
|
+
requestRaw,
|
|
367
390
|
interceptors: {
|
|
368
391
|
request: requestMgr,
|
|
369
392
|
response: responseMgr
|
|
@@ -575,7 +598,6 @@ function createRouteForge(options) {
|
|
|
575
598
|
const loadingTracker = new LoadingTracker();
|
|
576
599
|
const explicitLevels = options.levels;
|
|
577
600
|
const explicitEager = options.eager;
|
|
578
|
-
const explicitStrict = options.strict ?? false;
|
|
579
601
|
const explicitEndpoint = options.endpoint;
|
|
580
602
|
let effectiveLevels = explicitLevels ?? [];
|
|
581
603
|
let effectiveEager = explicitEager ?? [];
|
|
@@ -583,17 +605,12 @@ function createRouteForge(options) {
|
|
|
583
605
|
let effectiveUrlPrefix = "";
|
|
584
606
|
let summaryUnassigned;
|
|
585
607
|
let backendHasUnassignedLevel = false;
|
|
586
|
-
const
|
|
608
|
+
const fetchSummary = async () => {
|
|
587
609
|
try {
|
|
588
|
-
const
|
|
589
|
-
const
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
`[route-forge] summary endpoint ${summaryUrl} unreachable (HTTP ${resp.status}); falling back to explicit options`
|
|
593
|
-
);
|
|
594
|
-
return null;
|
|
595
|
-
}
|
|
596
|
-
return await resp.json();
|
|
610
|
+
const base = baseURL.endsWith("/") ? baseURL.slice(0, -1) : baseURL;
|
|
611
|
+
const ep = explicitEndpoint.startsWith("/") ? explicitEndpoint : `/${explicitEndpoint}`;
|
|
612
|
+
const data = await fetchMeta("__forge__.summary", `${base}${ep}`);
|
|
613
|
+
return data;
|
|
597
614
|
} catch (e) {
|
|
598
615
|
if (explicitLevels && explicitLevels.length > 0) {
|
|
599
616
|
console.warn(
|
|
@@ -603,8 +620,11 @@ function createRouteForge(options) {
|
|
|
603
620
|
}
|
|
604
621
|
throw new UnknownLevelError("(auto-discovery)");
|
|
605
622
|
}
|
|
606
|
-
}
|
|
607
|
-
|
|
623
|
+
};
|
|
624
|
+
let summaryPromise;
|
|
625
|
+
const summaryWaiters = [];
|
|
626
|
+
const whenSummary = () => summaryPromise ?? new Promise((resolve) => summaryWaiters.push(resolve));
|
|
627
|
+
const autoDiscoveryPromise = whenSummary().then((summary) => {
|
|
608
628
|
if (summary === null) {
|
|
609
629
|
return;
|
|
610
630
|
}
|
|
@@ -623,11 +643,6 @@ function createRouteForge(options) {
|
|
|
623
643
|
if (summary.config.url_prefix) {
|
|
624
644
|
effectiveUrlPrefix = summary.config.url_prefix.endsWith("/") ? summary.config.url_prefix.slice(0, -1) : summary.config.url_prefix;
|
|
625
645
|
}
|
|
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
646
|
const backendLevels = Object.keys(summary.levels);
|
|
632
647
|
backendHasUnassignedLevel = backendLevels.includes(UNASSIGNED_LEVEL);
|
|
633
648
|
if (Array.isArray(summary.unassigned) && summary.unassigned.length > 0 && !backendHasUnassignedLevel) {
|
|
@@ -663,8 +678,12 @@ function createRouteForge(options) {
|
|
|
663
678
|
});
|
|
664
679
|
let autoDiscoveryCompleted = false;
|
|
665
680
|
let resolveReady;
|
|
666
|
-
|
|
681
|
+
let rejectReady;
|
|
682
|
+
const readyPromise = new Promise((resolve, reject) => {
|
|
667
683
|
resolveReady = resolve;
|
|
684
|
+
rejectReady = reject;
|
|
685
|
+
});
|
|
686
|
+
readyPromise.catch(() => {
|
|
668
687
|
});
|
|
669
688
|
const cacheTtl = cacheOpts.ttl ?? DEFAULT_CACHE_TTL;
|
|
670
689
|
const cacheStorage = cacheOpts.storage ?? "memory";
|
|
@@ -695,13 +714,20 @@ function createRouteForge(options) {
|
|
|
695
714
|
adapter,
|
|
696
715
|
forgeInterceptors: { request: requestInterceptors, response: responseInterceptors }
|
|
697
716
|
});
|
|
717
|
+
Promise.resolve().then(() => {
|
|
718
|
+
summaryPromise = fetchSummary();
|
|
719
|
+
for (const resolve of summaryWaiters) resolve(summaryPromise);
|
|
720
|
+
});
|
|
698
721
|
let adapterResolved = false;
|
|
699
722
|
let adapterObj = null;
|
|
700
723
|
async function ensureAdapter() {
|
|
701
724
|
if (!adapterResolved) {
|
|
702
725
|
adapterObj = await adapterPromise.catch((e) => {
|
|
703
726
|
if (e instanceof AdapterNotFoundError) throw e;
|
|
704
|
-
return resolveAdapter({
|
|
727
|
+
return resolveAdapter({
|
|
728
|
+
adapter: "builtin",
|
|
729
|
+
forgeInterceptors: { request: requestInterceptors, response: responseInterceptors }
|
|
730
|
+
});
|
|
705
731
|
});
|
|
706
732
|
adapterResolved = true;
|
|
707
733
|
}
|
|
@@ -727,33 +753,36 @@ function createRouteForge(options) {
|
|
|
727
753
|
const ep = effectiveEndpoint.startsWith("/") ? effectiveEndpoint : `/${effectiveEndpoint}`;
|
|
728
754
|
return `${base}${ep}/${encodeURIComponent(level)}`;
|
|
729
755
|
}
|
|
730
|
-
async function
|
|
756
|
+
async function fetchMeta(routeTag, url, level = "") {
|
|
731
757
|
const adp = await ensureAdapter();
|
|
732
758
|
const config = {
|
|
733
|
-
route:
|
|
759
|
+
route: routeTag,
|
|
734
760
|
level,
|
|
735
761
|
method: "GET",
|
|
736
|
-
url
|
|
762
|
+
url,
|
|
737
763
|
headers: { Accept: "application/json" },
|
|
738
764
|
params: {},
|
|
739
765
|
timeout,
|
|
740
766
|
meta: {
|
|
741
|
-
name:
|
|
742
|
-
uri:
|
|
767
|
+
name: routeTag,
|
|
768
|
+
uri: url,
|
|
743
769
|
methods: ["GET"],
|
|
744
770
|
parameters: [],
|
|
745
771
|
level
|
|
746
772
|
}
|
|
747
773
|
};
|
|
748
|
-
const
|
|
774
|
+
const doRawRequest = adp.requestRaw ?? adp.request;
|
|
775
|
+
const resp = await doRawRequest(config);
|
|
749
776
|
if (!resp || resp.status < 200 || resp.status >= 300) {
|
|
750
777
|
throw new HTTPError(
|
|
751
|
-
`Failed to
|
|
752
|
-
{ level, status: resp?.status, url
|
|
778
|
+
`Failed to fetch "${routeTag}": HTTP ${resp?.status}`,
|
|
779
|
+
{ level, status: resp?.status, url, method: "GET" }
|
|
753
780
|
);
|
|
754
781
|
}
|
|
755
|
-
|
|
756
|
-
|
|
782
|
+
return resp.data;
|
|
783
|
+
}
|
|
784
|
+
async function fetchLevel(level) {
|
|
785
|
+
return await fetchMeta(`__forge__.load.${level}`, buildUrl(level), level);
|
|
757
786
|
}
|
|
758
787
|
async function loadOne(level) {
|
|
759
788
|
if (autoDiscoveryError) throw autoDiscoveryError;
|
|
@@ -925,7 +954,7 @@ function createRouteForge(options) {
|
|
|
925
954
|
},
|
|
926
955
|
(err) => {
|
|
927
956
|
if (err instanceof ForgeError) throw err;
|
|
928
|
-
if (
|
|
957
|
+
if (isAbortError(err, signal)) {
|
|
929
958
|
throw new RequestAbortedError(meta.name, meta.level, err);
|
|
930
959
|
}
|
|
931
960
|
throw new NetworkError(
|
|
@@ -994,15 +1023,22 @@ function createRouteForge(options) {
|
|
|
994
1023
|
}
|
|
995
1024
|
void autoDiscoveryPromise.then(() => {
|
|
996
1025
|
autoDiscoveryCompleted = true;
|
|
997
|
-
options.onSummaryReady?.();
|
|
998
1026
|
if (effectiveEager.length > 0) {
|
|
999
|
-
return Promise.
|
|
1000
|
-
|
|
1027
|
+
return Promise.allSettled(effectiveEager.map((lvl) => load(lvl))).then((results) => {
|
|
1028
|
+
results.forEach((r, i) => {
|
|
1029
|
+
if (r.status === "rejected") {
|
|
1030
|
+
console.error(
|
|
1031
|
+
`[route-forge] eager load failed for level "${effectiveEager[i]}":`,
|
|
1032
|
+
r.reason
|
|
1033
|
+
);
|
|
1034
|
+
}
|
|
1035
|
+
});
|
|
1001
1036
|
});
|
|
1002
1037
|
}
|
|
1003
1038
|
}).then(() => {
|
|
1004
1039
|
resolveReady(forgeInstance);
|
|
1005
|
-
}).catch(() => {
|
|
1040
|
+
}).catch((e) => {
|
|
1041
|
+
rejectReady(e);
|
|
1006
1042
|
});
|
|
1007
1043
|
function ready(onFulfilled, onRejected) {
|
|
1008
1044
|
if (onFulfilled) {
|
|
@@ -1013,7 +1049,8 @@ function createRouteForge(options) {
|
|
|
1013
1049
|
}
|
|
1014
1050
|
const forgeResolver = { load, hasRoute };
|
|
1015
1051
|
function createBoundForge(level, prefix) {
|
|
1016
|
-
const levelLoadedPromise = load(level)
|
|
1052
|
+
const levelLoadedPromise = load(level);
|
|
1053
|
+
levelLoadedPromise.catch(() => {
|
|
1017
1054
|
});
|
|
1018
1055
|
const apiFn = prefix ? (name, params) => resolveRouteName(forgeResolver, level, prefix, name).then(
|
|
1019
1056
|
(resolved) => api(level, resolved, params)
|
|
@@ -1147,14 +1184,6 @@ function resolveApiParams(input) {
|
|
|
1147
1184
|
}
|
|
1148
1185
|
return { pathParams, query, body, headers, timeout: perCallTimeout };
|
|
1149
1186
|
}
|
|
1150
|
-
function isAbortError2(err, signal) {
|
|
1151
|
-
if (signal?.aborted) return true;
|
|
1152
|
-
const e = err;
|
|
1153
|
-
if (!e) return false;
|
|
1154
|
-
if (e.name === "AbortError") return true;
|
|
1155
|
-
if (e.code === "ERR_CANCELED") return true;
|
|
1156
|
-
return false;
|
|
1157
|
-
}
|
|
1158
1187
|
|
|
1159
1188
|
exports.AdapterNotFoundError = AdapterNotFoundError;
|
|
1160
1189
|
exports.ForgeError = ForgeError;
|