@route-forge/core 1.1.0 → 1.2.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/dist/index.js CHANGED
@@ -387,10 +387,11 @@ function toHeaders(raw) {
387
387
  }
388
388
  async function wrapAxiosAdapter() {
389
389
  let axios;
390
+ const axiosModule = "axios";
390
391
  try {
391
392
  const mod = await import(
392
393
  /* @vite-ignore */
393
- 'axios'
394
+ axiosModule
394
395
  );
395
396
  axios = mod.default ?? mod;
396
397
  } catch {
@@ -621,8 +622,16 @@ function createRouteForge(options) {
621
622
  effectiveEager = [...union];
622
623
  }
623
624
  });
624
- autoDiscoveryPromise.catch(() => {
625
+ let autoDiscoveryError = null;
626
+ autoDiscoveryPromise.catch((e) => {
627
+ autoDiscoveryError = e;
625
628
  });
629
+ let autoDiscoveryCompleted = false;
630
+ let resolveReady;
631
+ const readyPromise = new Promise((resolve) => {
632
+ resolveReady = resolve;
633
+ });
634
+ const levelLoadedListeners = /* @__PURE__ */ new Map();
626
635
  const cacheTtl = cacheOpts.ttl ?? DEFAULT_CACHE_TTL;
627
636
  const cacheStorage = cacheOpts.storage ?? "memory";
628
637
  const cache = new RouteCache({ storage: cacheStorage, ttl: cacheTtl });
@@ -665,11 +674,20 @@ function createRouteForge(options) {
665
674
  return adapterObj;
666
675
  }
667
676
  const inflight = /* @__PURE__ */ new Map();
677
+ const invalidationGens = /* @__PURE__ */ new Map();
668
678
  function assertLevelDeclared(level) {
669
679
  if (!effectiveLevels.includes(level)) {
670
680
  throw new UnknownLevelError(level);
671
681
  }
672
682
  }
683
+ function assertDiscoveryReady() {
684
+ if (!autoDiscoveryCompleted && !explicitLevels?.length) {
685
+ throw new ForgeError(
686
+ "Route data not available. Auto-discovery has not completed. Use onSummaryReady callback to mount app, or await forge.ready / forge.load(level) first.",
687
+ { code: "RF_FE_010" }
688
+ );
689
+ }
690
+ }
673
691
  function buildUrl(level) {
674
692
  const base = baseURL.endsWith("/") ? baseURL.slice(0, -1) : baseURL;
675
693
  const ep = effectiveEndpoint.startsWith("/") ? effectiveEndpoint : `/${effectiveEndpoint}`;
@@ -704,10 +722,12 @@ function createRouteForge(options) {
704
722
  return data;
705
723
  }
706
724
  async function loadOne(level) {
725
+ if (autoDiscoveryError) throw autoDiscoveryError;
707
726
  assertLevelDeclared(level);
708
727
  if (cache.get(level)) return;
709
728
  const existing = inflight.get(level);
710
729
  if (existing) return existing;
730
+ const gen = invalidationGens.get(level) ?? 0;
711
731
  const p = (async () => {
712
732
  try {
713
733
  if (level === UNASSIGNED_LEVEL && !backendHasUnassignedLevel && summaryUnassigned) {
@@ -716,10 +736,16 @@ function createRouteForge(options) {
716
736
  routes[r.name] = { ...r, level: UNASSIGNED_LEVEL };
717
737
  }
718
738
  cache.set({ level: UNASSIGNED_LEVEL, routes, cache: null });
739
+ const listeners = levelLoadedListeners.get(level);
740
+ if (listeners) listeners.forEach((cb) => cb());
719
741
  return;
720
742
  }
721
743
  const resp = await fetchLevel(level);
722
- cache.set(resp);
744
+ if ((invalidationGens.get(level) ?? 0) === gen) {
745
+ cache.set(resp);
746
+ const listeners = levelLoadedListeners.get(level);
747
+ if (listeners) listeners.forEach((cb) => cb());
748
+ }
723
749
  } finally {
724
750
  inflight.delete(level);
725
751
  }
@@ -733,6 +759,7 @@ function createRouteForge(options) {
733
759
  await Promise.all(list.map(loadOne));
734
760
  }
735
761
  function route(level, name, params) {
762
+ assertDiscoveryReady();
736
763
  const meta = findRouteMeta(level, name);
737
764
  if (!meta) {
738
765
  throw new UnknownRouteError(name, level);
@@ -763,12 +790,19 @@ function createRouteForge(options) {
763
790
  const optional = raw.endsWith("?");
764
791
  const name = optional ? raw.slice(0, -1) : raw;
765
792
  if (values[name] !== void 0) {
766
- return encodeURIComponent(String(values[name]));
793
+ const val = values[name];
794
+ if (typeof val === "object") {
795
+ throw new ForgeError(
796
+ `Path parameter "${name}" must be a primitive value (string, number, boolean), got ${typeof val}`,
797
+ { code: "RF_FE_003", route: meta.name, context: { param: name, value: val } }
798
+ );
799
+ }
800
+ return encodeURIComponent(String(val));
767
801
  }
768
802
  return optional ? "" : match;
769
803
  });
770
804
  uri = uri.replace(/\/+/g, "/").replace(/\/$/, "");
771
- if (effectiveUrlPrefix.includes("://")) {
805
+ if (/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(effectiveUrlPrefix)) {
772
806
  const prefix2 = effectiveUrlPrefix.endsWith("/") ? effectiveUrlPrefix.slice(0, -1) : effectiveUrlPrefix;
773
807
  return uri.startsWith("/") ? `${prefix2}${uri}` : `${prefix2}/${uri}`;
774
808
  }
@@ -864,10 +898,20 @@ function createRouteForge(options) {
864
898
  function invalidate(level) {
865
899
  if (level === void 0) {
866
900
  cache.clear();
901
+ inflight.clear();
902
+ for (const lvl of effectiveLevels) {
903
+ invalidationGens.set(lvl, (invalidationGens.get(lvl) ?? 0) + 1);
904
+ }
867
905
  } else if (Array.isArray(level)) {
868
- for (const lvl of level) cache.del(lvl);
906
+ for (const lvl of level) {
907
+ cache.del(lvl);
908
+ inflight.delete(lvl);
909
+ invalidationGens.set(lvl, (invalidationGens.get(lvl) ?? 0) + 1);
910
+ }
869
911
  } else {
870
912
  cache.del(level);
913
+ inflight.delete(level);
914
+ invalidationGens.set(level, (invalidationGens.get(level) ?? 0) + 1);
871
915
  }
872
916
  }
873
917
  function isLoaded(level) {
@@ -875,6 +919,7 @@ function createRouteForge(options) {
875
919
  return effectiveLevels.length > 0 && effectiveLevels.every((lvl) => cache.get(lvl) !== void 0);
876
920
  }
877
921
  function hasRoute(level, name) {
922
+ assertDiscoveryReady();
878
923
  return findRouteMeta(level, name) !== void 0;
879
924
  }
880
925
  function getRoutes(level) {
@@ -883,7 +928,7 @@ function createRouteForge(options) {
883
928
  const routes = entry?.routes ?? {};
884
929
  const result2 = {};
885
930
  for (const [k, v] of Object.entries(routes)) {
886
- result2[k] = { ...v };
931
+ result2[k] = JSON.parse(JSON.stringify(v));
887
932
  }
888
933
  return result2;
889
934
  }
@@ -893,7 +938,7 @@ function createRouteForge(options) {
893
938
  if (entry) {
894
939
  const levelRoutes = {};
895
940
  for (const [k, v] of Object.entries(entry.routes)) {
896
- levelRoutes[k] = { ...v };
941
+ levelRoutes[k] = JSON.parse(JSON.stringify(v));
897
942
  }
898
943
  result[lvl] = levelRoutes;
899
944
  }
@@ -901,11 +946,15 @@ function createRouteForge(options) {
901
946
  return result;
902
947
  }
903
948
  void autoDiscoveryPromise.then(() => {
949
+ options.onSummaryReady?.();
904
950
  if (effectiveEager.length > 0) {
905
- void Promise.all(effectiveEager.map((lvl) => load(lvl))).catch((e) => {
951
+ return Promise.all(effectiveEager.map((lvl) => load(lvl))).catch((e) => {
906
952
  console.warn(`[route-forge] eager load failed: ${e.message}`);
907
953
  });
908
954
  }
955
+ }).then(() => {
956
+ autoDiscoveryCompleted = true;
957
+ resolveReady();
909
958
  }).catch(() => {
910
959
  });
911
960
  return {
@@ -922,6 +971,14 @@ function createRouteForge(options) {
922
971
  interceptors: {
923
972
  request: requestInterceptors,
924
973
  response: responseInterceptors
974
+ },
975
+ ready: readyPromise,
976
+ onLevelLoaded(level, cb) {
977
+ if (!levelLoadedListeners.has(level)) {
978
+ levelLoadedListeners.set(level, /* @__PURE__ */ new Set());
979
+ }
980
+ levelLoadedListeners.get(level).add(cb);
981
+ return () => levelLoadedListeners.get(level)?.delete(cb);
925
982
  }
926
983
  };
927
984
  }
@@ -991,6 +1048,25 @@ function isAbortError2(err, signal) {
991
1048
  return false;
992
1049
  }
993
1050
 
994
- export { AdapterNotFoundError, ForgeError, HTTPError, InterceptorManagerImpl, InvalidInterceptorReturnError, LoadingTracker, MissingRouteParamError, NetworkError, RequestAbortedError, RouteCache, UnknownLevelError, UnknownRouteError, createInterceptorManager, createRouteForge };
1051
+ // src/resolveRouteName.ts
1052
+ async function resolveRouteName(forge, level, prefix, suffix, separator = ".") {
1053
+ if (!suffix) return prefix;
1054
+ const joined = `${prefix}${separator}${suffix}`;
1055
+ if (!suffix.startsWith(`${prefix}${separator}`)) return joined;
1056
+ await forge.load(level);
1057
+ if (forge.hasRoute(level, joined)) return joined;
1058
+ if (forge.hasRoute(level, suffix)) return suffix;
1059
+ throw new UnknownRouteError(joined, level);
1060
+ }
1061
+ function resolveRouteNameSync(forge, level, prefix, suffix, separator = ".") {
1062
+ if (!suffix) return prefix;
1063
+ const joined = `${prefix}${separator}${suffix}`;
1064
+ if (!suffix.startsWith(`${prefix}${separator}`)) return joined;
1065
+ if (forge.hasRoute(level, joined)) return joined;
1066
+ if (forge.hasRoute(level, suffix)) return suffix;
1067
+ throw new UnknownRouteError(joined, level);
1068
+ }
1069
+
1070
+ export { AdapterNotFoundError, ForgeError, HTTPError, InterceptorManagerImpl, InvalidInterceptorReturnError, LoadingTracker, MissingRouteParamError, NetworkError, RequestAbortedError, RouteCache, UnknownLevelError, UnknownRouteError, createInterceptorManager, createRouteForge, resolveRouteName, resolveRouteNameSync };
995
1071
  //# sourceMappingURL=index.js.map
996
1072
  //# sourceMappingURL=index.js.map