@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.
@@ -17,9 +17,18 @@ var RouteForge = (function (exports) {
17
17
  var RouteCache = class {
18
18
  constructor(opts) {
19
19
  this.memory = /* @__PURE__ */ new Map();
20
+ /** 其他 tab 修改 storage 时失效对应镜像(storage 事件:自己的写不触发,自己的写经 set/del 已同步) */
21
+ this.onStorageEvent = (e) => {
22
+ if (!e.key) return;
23
+ if (!e.key.startsWith(KEY_PREFIX)) return;
24
+ this.memory.delete(e.key.slice(KEY_PREFIX.length));
25
+ };
20
26
  this.storage = opts.storage;
21
27
  this.fallbackTtl = opts.ttl;
22
28
  this.backend = pickStorage(opts.storage);
29
+ if (this.backend && typeof globalThis.addEventListener === "function") {
30
+ globalThis.addEventListener("storage", this.onStorageEvent);
31
+ }
23
32
  }
24
33
  key(level) {
25
34
  return `${KEY_PREFIX}${level}`;
@@ -28,6 +37,8 @@ var RouteForge = (function (exports) {
28
37
  if (this.storage === "memory" || !this.backend) {
29
38
  return this.getFromMemory(level);
30
39
  }
40
+ const mirrored = this.getFromMemory(level);
41
+ if (mirrored) return mirrored;
31
42
  const raw = this.backend.getItem(this.key(level));
32
43
  if (raw) {
33
44
  try {
@@ -36,12 +47,13 @@ var RouteForge = (function (exports) {
36
47
  this.del(level);
37
48
  return void 0;
38
49
  }
50
+ this.memory.set(level, entry);
39
51
  return entry;
40
52
  } catch {
41
53
  return void 0;
42
54
  }
43
55
  }
44
- return this.getFromMemory(level);
56
+ return void 0;
45
57
  }
46
58
  getFromMemory(level) {
47
59
  const entry = this.memory.get(level);
@@ -65,14 +77,13 @@ var RouteForge = (function (exports) {
65
77
  ttl,
66
78
  cachedAt: Date.now()
67
79
  };
80
+ this.memory.set(resp.level, entry);
68
81
  if (this.storage === "memory" || !this.backend) {
69
- this.memory.set(resp.level, entry);
70
82
  return;
71
83
  }
72
84
  try {
73
85
  this.backend.setItem(this.key(resp.level), JSON.stringify(entry));
74
86
  } catch {
75
- this.memory.set(resp.level, entry);
76
87
  }
77
88
  }
78
89
  del(level) {
@@ -252,14 +263,14 @@ var RouteForge = (function (exports) {
252
263
  return new InterceptorManagerImpl();
253
264
  }
254
265
 
255
- // src/adapters/builtin-http.ts
256
- function isPassthroughBody(body) {
257
- if (body === null) return false;
258
- 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;
259
- }
260
- function isAbortError(err) {
266
+ // src/adapters/fetch-core.ts
267
+ function isAbortError(err, signal) {
268
+ if (signal?.aborted) return true;
261
269
  const e = err;
262
- return !!e && e.name === "AbortError";
270
+ if (!e) return false;
271
+ if (e.name === "AbortError") return true;
272
+ if (e.code === "ERR_CANCELED") return true;
273
+ return false;
263
274
  }
264
275
  function combineSignals(...signals) {
265
276
  const valid = signals.filter((s) => !!s);
@@ -278,82 +289,89 @@ var RouteForge = (function (exports) {
278
289
  }
279
290
  return controller.signal;
280
291
  }
281
- function createBuiltinHttp(forgeInterceptors) {
282
- const requestMgr = forgeInterceptors?.request ?? createInterceptorManager();
283
- const responseMgr = forgeInterceptors?.response ?? createInterceptorManager();
284
- async function requestRaw(config) {
285
- const signal = combineSignals(
286
- config.signal,
287
- config.timeout && config.timeout > 0 ? AbortSignal.timeout(config.timeout) : void 0
288
- );
289
- let url = config.url;
290
- if (config.paramsSerializer && config.params) {
291
- const qs = config.paramsSerializer(config.params);
292
- if (qs) {
293
- url = url.includes("?") ? `${url}&${qs}` : `${url}?${qs}`;
294
- }
292
+ function isPassthroughBody(body) {
293
+ if (body === null) return false;
294
+ 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;
295
+ }
296
+ async function rawFetch(config) {
297
+ const signal = combineSignals(
298
+ config.signal,
299
+ config.timeout && config.timeout > 0 ? AbortSignal.timeout(config.timeout) : void 0
300
+ );
301
+ let url = config.url;
302
+ if (config.paramsSerializer && config.params) {
303
+ const qs = config.paramsSerializer(config.params);
304
+ if (qs) {
305
+ url = url.includes("?") ? `${url}&${qs}` : `${url}?${qs}`;
295
306
  }
296
- const headers = new Headers(config.headers);
297
- const fetchInit = {
298
- method: config.method,
299
- headers
300
- };
301
- if (signal) fetchInit.signal = signal;
302
- if (config.body !== void 0 && !["GET", "HEAD"].includes(config.method.toUpperCase())) {
303
- if (typeof config.body === "string" || isPassthroughBody(config.body)) {
304
- fetchInit.body = config.body;
305
- } else {
306
- fetchInit.body = JSON.stringify(config.body);
307
- if (!headers.has("Content-Type")) {
308
- headers.set("Content-Type", "application/json");
309
- }
307
+ }
308
+ const headers = new Headers(config.headers);
309
+ const fetchInit = {
310
+ method: config.method,
311
+ headers
312
+ };
313
+ if (signal) fetchInit.signal = signal;
314
+ if (config.body !== void 0 && !["GET", "HEAD"].includes(config.method.toUpperCase())) {
315
+ if (typeof config.body === "string" || isPassthroughBody(config.body)) {
316
+ fetchInit.body = config.body;
317
+ } else {
318
+ fetchInit.body = JSON.stringify(config.body);
319
+ if (!headers.has("Content-Type")) {
320
+ headers.set("Content-Type", "application/json");
310
321
  }
311
322
  }
312
- let res;
323
+ }
324
+ let res;
325
+ try {
326
+ res = await fetch(url, fetchInit);
327
+ } catch (e) {
328
+ if (isAbortError(e)) throw e;
329
+ throw new NetworkError(
330
+ e instanceof Error ? e.message : String(e),
331
+ config.route,
332
+ config.level,
333
+ e
334
+ );
335
+ }
336
+ const text = await res.text();
337
+ let data = text;
338
+ const contentType = res.headers.get("content-type") ?? "";
339
+ if (contentType.includes("application/json")) {
313
340
  try {
314
- res = await fetch(url, fetchInit);
315
- } catch (e) {
316
- if (isAbortError(e)) throw e;
317
- throw new NetworkError(
318
- e instanceof Error ? e.message : String(e),
319
- config.route,
320
- config.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
- }
341
+ data = JSON.parse(text);
342
+ } catch {
332
343
  }
333
- const responseData = {
344
+ }
345
+ const responseData = {
346
+ route: config.route,
347
+ level: config.level,
348
+ method: config.method,
349
+ url,
350
+ status: res.status,
351
+ headers: res.headers,
352
+ data,
353
+ config
354
+ };
355
+ if (res.status >= 200 && res.status < 300) {
356
+ return responseData;
357
+ }
358
+ throw new HTTPError(
359
+ `HTTP ${res.status} for route "${config.route}" (${config.method} ${url})`,
360
+ {
334
361
  route: config.route,
335
362
  level: config.level,
336
- method: config.method,
337
- url,
338
363
  status: res.status,
339
- headers: res.headers,
340
- data,
341
- config
342
- };
343
- if (res.status >= 200 && res.status < 300) {
344
- return responseData;
364
+ url,
365
+ method: config.method
345
366
  }
346
- throw new HTTPError(
347
- `HTTP ${res.status} for route "${config.route}" (${config.method} ${url})`,
348
- {
349
- route: config.route,
350
- level: config.level,
351
- status: res.status,
352
- url,
353
- method: config.method
354
- }
355
- );
356
- }
367
+ );
368
+ }
369
+
370
+ // src/adapters/builtin-http.ts
371
+ function createBuiltinHttp(forgeInterceptors) {
372
+ const requestMgr = forgeInterceptors?.request ?? createInterceptorManager();
373
+ const responseMgr = forgeInterceptors?.response ?? createInterceptorManager();
374
+ const requestRaw = rawFetch;
357
375
  async function request(config) {
358
376
  const finalConfig = await runRequestInterceptors(requestMgr, config);
359
377
  const source = Promise.resolve(finalConfig).then(requestRaw);
@@ -588,17 +606,12 @@ var RouteForge = (function (exports) {
588
606
  let effectiveUrlPrefix = "";
589
607
  let summaryUnassigned;
590
608
  let backendHasUnassignedLevel = false;
591
- const summaryPromise = (async () => {
609
+ const fetchSummary = async () => {
592
610
  try {
593
- const summaryUrl = explicitEndpoint;
594
- const resp = await fetch(summaryUrl, { method: "GET" });
595
- if (!resp.ok) {
596
- console.warn(
597
- `[route-forge] summary endpoint ${summaryUrl} unreachable (HTTP ${resp.status}); falling back to explicit options`
598
- );
599
- return null;
600
- }
601
- return await resp.json();
611
+ const base = baseURL.endsWith("/") ? baseURL.slice(0, -1) : baseURL;
612
+ const ep = explicitEndpoint.startsWith("/") ? explicitEndpoint : `/${explicitEndpoint}`;
613
+ const data = await fetchMeta("__forge__.summary", `${base}${ep}`);
614
+ return data;
602
615
  } catch (e) {
603
616
  if (explicitLevels && explicitLevels.length > 0) {
604
617
  console.warn(
@@ -608,8 +621,11 @@ var RouteForge = (function (exports) {
608
621
  }
609
622
  throw new UnknownLevelError("(auto-discovery)");
610
623
  }
611
- })();
612
- const autoDiscoveryPromise = summaryPromise.then((summary) => {
624
+ };
625
+ let summaryPromise;
626
+ const summaryWaiters = [];
627
+ const whenSummary = () => summaryPromise ?? new Promise((resolve) => summaryWaiters.push(resolve));
628
+ const autoDiscoveryPromise = whenSummary().then((summary) => {
613
629
  if (summary === null) {
614
630
  return;
615
631
  }
@@ -663,8 +679,12 @@ var RouteForge = (function (exports) {
663
679
  });
664
680
  let autoDiscoveryCompleted = false;
665
681
  let resolveReady;
666
- const readyPromise = new Promise((resolve) => {
682
+ let rejectReady;
683
+ const readyPromise = new Promise((resolve, reject) => {
667
684
  resolveReady = resolve;
685
+ rejectReady = reject;
686
+ });
687
+ readyPromise.catch(() => {
668
688
  });
669
689
  const cacheTtl = cacheOpts.ttl ?? DEFAULT_CACHE_TTL;
670
690
  const cacheStorage = cacheOpts.storage ?? "memory";
@@ -695,6 +715,10 @@ var RouteForge = (function (exports) {
695
715
  adapter,
696
716
  forgeInterceptors: { request: requestInterceptors, response: responseInterceptors }
697
717
  });
718
+ Promise.resolve().then(() => {
719
+ summaryPromise = fetchSummary();
720
+ for (const resolve of summaryWaiters) resolve(summaryPromise);
721
+ });
698
722
  let adapterResolved = false;
699
723
  let adapterObj = null;
700
724
  async function ensureAdapter() {
@@ -730,19 +754,19 @@ var RouteForge = (function (exports) {
730
754
  const ep = effectiveEndpoint.startsWith("/") ? effectiveEndpoint : `/${effectiveEndpoint}`;
731
755
  return `${base}${ep}/${encodeURIComponent(level)}`;
732
756
  }
733
- async function fetchLevel(level) {
757
+ async function fetchMeta(routeTag, url, level = "") {
734
758
  const adp = await ensureAdapter();
735
759
  const config = {
736
- route: `__forge__.load.${level}`,
760
+ route: routeTag,
737
761
  level,
738
762
  method: "GET",
739
- url: buildUrl(level),
763
+ url,
740
764
  headers: { Accept: "application/json" },
741
765
  params: {},
742
766
  timeout,
743
767
  meta: {
744
- name: `__forge__.load.${level}`,
745
- uri: buildUrl(level),
768
+ name: routeTag,
769
+ uri: url,
746
770
  methods: ["GET"],
747
771
  parameters: [],
748
772
  level
@@ -752,12 +776,14 @@ var RouteForge = (function (exports) {
752
776
  const resp = await doRawRequest(config);
753
777
  if (!resp || resp.status < 200 || resp.status >= 300) {
754
778
  throw new HTTPError(
755
- `Failed to load level "${level}": HTTP ${resp?.status}`,
756
- { level, status: resp?.status, url: buildUrl(level), method: "GET" }
779
+ `Failed to fetch "${routeTag}": HTTP ${resp?.status}`,
780
+ { level, status: resp?.status, url, method: "GET" }
757
781
  );
758
782
  }
759
- const data = resp.data;
760
- return data;
783
+ return resp.data;
784
+ }
785
+ async function fetchLevel(level) {
786
+ return await fetchMeta(`__forge__.load.${level}`, buildUrl(level), level);
761
787
  }
762
788
  async function loadOne(level) {
763
789
  if (autoDiscoveryError) throw autoDiscoveryError;
@@ -929,7 +955,7 @@ var RouteForge = (function (exports) {
929
955
  },
930
956
  (err) => {
931
957
  if (err instanceof ForgeError) throw err;
932
- if (isAbortError2(err, signal)) {
958
+ if (isAbortError(err, signal)) {
933
959
  throw new RequestAbortedError(meta.name, meta.level, err);
934
960
  }
935
961
  throw new NetworkError(
@@ -998,15 +1024,22 @@ var RouteForge = (function (exports) {
998
1024
  }
999
1025
  void autoDiscoveryPromise.then(() => {
1000
1026
  autoDiscoveryCompleted = true;
1001
- options.onSummaryReady?.();
1002
1027
  if (effectiveEager.length > 0) {
1003
- return Promise.all(effectiveEager.map((lvl) => load(lvl))).catch((e) => {
1004
- console.warn(`[route-forge] eager load failed: ${e.message}`);
1028
+ return Promise.allSettled(effectiveEager.map((lvl) => load(lvl))).then((results) => {
1029
+ results.forEach((r, i) => {
1030
+ if (r.status === "rejected") {
1031
+ console.error(
1032
+ `[route-forge] eager load failed for level "${effectiveEager[i]}":`,
1033
+ r.reason
1034
+ );
1035
+ }
1036
+ });
1005
1037
  });
1006
1038
  }
1007
1039
  }).then(() => {
1008
1040
  resolveReady(forgeInstance);
1009
- }).catch(() => {
1041
+ }).catch((e) => {
1042
+ rejectReady(e);
1010
1043
  });
1011
1044
  function ready(onFulfilled, onRejected) {
1012
1045
  if (onFulfilled) {
@@ -1152,14 +1185,6 @@ var RouteForge = (function (exports) {
1152
1185
  }
1153
1186
  return { pathParams, query, body, headers, timeout: perCallTimeout };
1154
1187
  }
1155
- function isAbortError2(err, signal) {
1156
- if (signal?.aborted) return true;
1157
- const e = err;
1158
- if (!e) return false;
1159
- if (e.name === "AbortError") return true;
1160
- if (e.code === "ERR_CANCELED") return true;
1161
- return false;
1162
- }
1163
1188
 
1164
1189
  exports.AdapterNotFoundError = AdapterNotFoundError;
1165
1190
  exports.ForgeError = ForgeError;