@route-forge/core 1.4.0 → 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 +10 -6
- package/dist/codegen.d.cts +1 -1
- package/dist/codegen.d.ts +1 -1
- package/dist/index.cjs +137 -112
- 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 +137 -112
- package/dist/index.js.map +1 -1
- package/dist/route-forge.global.js +137 -112
- package/dist/route-forge.global.js.map +1 -1
- package/dist/route-forge.global.min.js +2 -2
- package/dist/{types-BFlrOTrN.d.cts → types-BvLdl02f.d.cts} +6 -17
- package/dist/{types-BFlrOTrN.d.ts → types-BvLdl02f.d.ts} +6 -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()` 方法
|
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,82 +288,89 @@ 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
|
-
}
|
|
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}`;
|
|
294
305
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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");
|
|
309
320
|
}
|
|
310
321
|
}
|
|
311
|
-
|
|
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")) {
|
|
312
339
|
try {
|
|
313
|
-
|
|
314
|
-
} catch
|
|
315
|
-
if (isAbortError(e)) throw e;
|
|
316
|
-
throw new NetworkError(
|
|
317
|
-
e instanceof Error ? e.message : String(e),
|
|
318
|
-
config.route,
|
|
319
|
-
config.level,
|
|
320
|
-
e
|
|
321
|
-
);
|
|
322
|
-
}
|
|
323
|
-
const text = await res.text();
|
|
324
|
-
let data = text;
|
|
325
|
-
const contentType = res.headers.get("content-type") ?? "";
|
|
326
|
-
if (contentType.includes("application/json")) {
|
|
327
|
-
try {
|
|
328
|
-
data = JSON.parse(text);
|
|
329
|
-
} catch {
|
|
330
|
-
}
|
|
340
|
+
data = JSON.parse(text);
|
|
341
|
+
} catch {
|
|
331
342
|
}
|
|
332
|
-
|
|
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
|
+
{
|
|
333
360
|
route: config.route,
|
|
334
361
|
level: config.level,
|
|
335
|
-
method: config.method,
|
|
336
|
-
url,
|
|
337
362
|
status: res.status,
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
config
|
|
341
|
-
};
|
|
342
|
-
if (res.status >= 200 && res.status < 300) {
|
|
343
|
-
return responseData;
|
|
363
|
+
url,
|
|
364
|
+
method: config.method
|
|
344
365
|
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
}
|
|
354
|
-
);
|
|
355
|
-
}
|
|
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;
|
|
356
374
|
async function request(config) {
|
|
357
375
|
const finalConfig = await runRequestInterceptors(requestMgr, config);
|
|
358
376
|
const source = Promise.resolve(finalConfig).then(requestRaw);
|
|
@@ -587,17 +605,12 @@ function createRouteForge(options) {
|
|
|
587
605
|
let effectiveUrlPrefix = "";
|
|
588
606
|
let summaryUnassigned;
|
|
589
607
|
let backendHasUnassignedLevel = false;
|
|
590
|
-
const
|
|
608
|
+
const fetchSummary = async () => {
|
|
591
609
|
try {
|
|
592
|
-
const
|
|
593
|
-
const
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
`[route-forge] summary endpoint ${summaryUrl} unreachable (HTTP ${resp.status}); falling back to explicit options`
|
|
597
|
-
);
|
|
598
|
-
return null;
|
|
599
|
-
}
|
|
600
|
-
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;
|
|
601
614
|
} catch (e) {
|
|
602
615
|
if (explicitLevels && explicitLevels.length > 0) {
|
|
603
616
|
console.warn(
|
|
@@ -607,8 +620,11 @@ function createRouteForge(options) {
|
|
|
607
620
|
}
|
|
608
621
|
throw new UnknownLevelError("(auto-discovery)");
|
|
609
622
|
}
|
|
610
|
-
}
|
|
611
|
-
|
|
623
|
+
};
|
|
624
|
+
let summaryPromise;
|
|
625
|
+
const summaryWaiters = [];
|
|
626
|
+
const whenSummary = () => summaryPromise ?? new Promise((resolve) => summaryWaiters.push(resolve));
|
|
627
|
+
const autoDiscoveryPromise = whenSummary().then((summary) => {
|
|
612
628
|
if (summary === null) {
|
|
613
629
|
return;
|
|
614
630
|
}
|
|
@@ -662,8 +678,12 @@ function createRouteForge(options) {
|
|
|
662
678
|
});
|
|
663
679
|
let autoDiscoveryCompleted = false;
|
|
664
680
|
let resolveReady;
|
|
665
|
-
|
|
681
|
+
let rejectReady;
|
|
682
|
+
const readyPromise = new Promise((resolve, reject) => {
|
|
666
683
|
resolveReady = resolve;
|
|
684
|
+
rejectReady = reject;
|
|
685
|
+
});
|
|
686
|
+
readyPromise.catch(() => {
|
|
667
687
|
});
|
|
668
688
|
const cacheTtl = cacheOpts.ttl ?? DEFAULT_CACHE_TTL;
|
|
669
689
|
const cacheStorage = cacheOpts.storage ?? "memory";
|
|
@@ -694,6 +714,10 @@ function createRouteForge(options) {
|
|
|
694
714
|
adapter,
|
|
695
715
|
forgeInterceptors: { request: requestInterceptors, response: responseInterceptors }
|
|
696
716
|
});
|
|
717
|
+
Promise.resolve().then(() => {
|
|
718
|
+
summaryPromise = fetchSummary();
|
|
719
|
+
for (const resolve of summaryWaiters) resolve(summaryPromise);
|
|
720
|
+
});
|
|
697
721
|
let adapterResolved = false;
|
|
698
722
|
let adapterObj = null;
|
|
699
723
|
async function ensureAdapter() {
|
|
@@ -729,19 +753,19 @@ function createRouteForge(options) {
|
|
|
729
753
|
const ep = effectiveEndpoint.startsWith("/") ? effectiveEndpoint : `/${effectiveEndpoint}`;
|
|
730
754
|
return `${base}${ep}/${encodeURIComponent(level)}`;
|
|
731
755
|
}
|
|
732
|
-
async function
|
|
756
|
+
async function fetchMeta(routeTag, url, level = "") {
|
|
733
757
|
const adp = await ensureAdapter();
|
|
734
758
|
const config = {
|
|
735
|
-
route:
|
|
759
|
+
route: routeTag,
|
|
736
760
|
level,
|
|
737
761
|
method: "GET",
|
|
738
|
-
url
|
|
762
|
+
url,
|
|
739
763
|
headers: { Accept: "application/json" },
|
|
740
764
|
params: {},
|
|
741
765
|
timeout,
|
|
742
766
|
meta: {
|
|
743
|
-
name:
|
|
744
|
-
uri:
|
|
767
|
+
name: routeTag,
|
|
768
|
+
uri: url,
|
|
745
769
|
methods: ["GET"],
|
|
746
770
|
parameters: [],
|
|
747
771
|
level
|
|
@@ -751,12 +775,14 @@ function createRouteForge(options) {
|
|
|
751
775
|
const resp = await doRawRequest(config);
|
|
752
776
|
if (!resp || resp.status < 200 || resp.status >= 300) {
|
|
753
777
|
throw new HTTPError(
|
|
754
|
-
`Failed to
|
|
755
|
-
{ level, status: resp?.status, url
|
|
778
|
+
`Failed to fetch "${routeTag}": HTTP ${resp?.status}`,
|
|
779
|
+
{ level, status: resp?.status, url, method: "GET" }
|
|
756
780
|
);
|
|
757
781
|
}
|
|
758
|
-
|
|
759
|
-
|
|
782
|
+
return resp.data;
|
|
783
|
+
}
|
|
784
|
+
async function fetchLevel(level) {
|
|
785
|
+
return await fetchMeta(`__forge__.load.${level}`, buildUrl(level), level);
|
|
760
786
|
}
|
|
761
787
|
async function loadOne(level) {
|
|
762
788
|
if (autoDiscoveryError) throw autoDiscoveryError;
|
|
@@ -928,7 +954,7 @@ function createRouteForge(options) {
|
|
|
928
954
|
},
|
|
929
955
|
(err) => {
|
|
930
956
|
if (err instanceof ForgeError) throw err;
|
|
931
|
-
if (
|
|
957
|
+
if (isAbortError(err, signal)) {
|
|
932
958
|
throw new RequestAbortedError(meta.name, meta.level, err);
|
|
933
959
|
}
|
|
934
960
|
throw new NetworkError(
|
|
@@ -997,15 +1023,22 @@ function createRouteForge(options) {
|
|
|
997
1023
|
}
|
|
998
1024
|
void autoDiscoveryPromise.then(() => {
|
|
999
1025
|
autoDiscoveryCompleted = true;
|
|
1000
|
-
options.onSummaryReady?.();
|
|
1001
1026
|
if (effectiveEager.length > 0) {
|
|
1002
|
-
return Promise.
|
|
1003
|
-
|
|
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
|
+
});
|
|
1004
1036
|
});
|
|
1005
1037
|
}
|
|
1006
1038
|
}).then(() => {
|
|
1007
1039
|
resolveReady(forgeInstance);
|
|
1008
|
-
}).catch(() => {
|
|
1040
|
+
}).catch((e) => {
|
|
1041
|
+
rejectReady(e);
|
|
1009
1042
|
});
|
|
1010
1043
|
function ready(onFulfilled, onRejected) {
|
|
1011
1044
|
if (onFulfilled) {
|
|
@@ -1151,14 +1184,6 @@ function resolveApiParams(input) {
|
|
|
1151
1184
|
}
|
|
1152
1185
|
return { pathParams, query, body, headers, timeout: perCallTimeout };
|
|
1153
1186
|
}
|
|
1154
|
-
function isAbortError2(err, signal) {
|
|
1155
|
-
if (signal?.aborted) return true;
|
|
1156
|
-
const e = err;
|
|
1157
|
-
if (!e) return false;
|
|
1158
|
-
if (e.name === "AbortError") return true;
|
|
1159
|
-
if (e.code === "ERR_CANCELED") return true;
|
|
1160
|
-
return false;
|
|
1161
|
-
}
|
|
1162
1187
|
|
|
1163
1188
|
exports.AdapterNotFoundError = AdapterNotFoundError;
|
|
1164
1189
|
exports.ForgeError = ForgeError;
|