@route-forge/core 2.2.1 → 3.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.
@@ -144,30 +144,56 @@ var RouteForge = (function (exports) {
144
144
  if (opts.cause !== void 0) this.cause = opts.cause;
145
145
  }
146
146
  };
147
+ function formatCandidates(candidates) {
148
+ if (!candidates || candidates.length === 0) return "";
149
+ const MAX = 5;
150
+ const shown = candidates.slice(0, MAX).join(", ");
151
+ const more = candidates.length > MAX ? ` (+${candidates.length - MAX} more)` : "";
152
+ return ` Available: ${shown}${more}`;
153
+ }
147
154
  var UnknownRouteError = class extends ForgeError {
148
- constructor(route, level) {
149
- super(`Route "${route}" not found${level ? ` in level "${level}"` : ""}`, {
150
- code: "RF_FE_001",
151
- route,
152
- level
153
- });
155
+ constructor(route, level, candidates) {
156
+ super(
157
+ `Route "${route}" not found${level ? ` in level "${level}"` : ""}.${formatCandidates(candidates)}`,
158
+ {
159
+ code: "RF_FE_001",
160
+ route,
161
+ level,
162
+ context: candidates && candidates.length > 0 ? { candidates } : void 0
163
+ }
164
+ );
154
165
  }
155
166
  };
156
167
  var UnknownLevelError = class extends ForgeError {
157
- constructor(level) {
158
- super(`Level "${level}" not declared in options.levels`, {
159
- code: "RF_FE_002",
160
- level
161
- });
168
+ constructor(level, candidates) {
169
+ super(
170
+ `Level "${level}" not declared in options.levels.${formatCandidates(candidates)}`,
171
+ {
172
+ code: "RF_FE_002",
173
+ level,
174
+ context: candidates && candidates.length > 0 ? { candidates } : void 0
175
+ }
176
+ );
162
177
  }
163
178
  };
164
179
  var MissingRouteParamError = class extends ForgeError {
165
- constructor(route, missingParams) {
166
- super(`Missing path parameter(s) ${missingParams.join(", ")} for route "${route}"`, {
167
- code: "RF_FE_003",
168
- route,
169
- context: { missingParams }
170
- });
180
+ constructor(route, missingParams, uri) {
181
+ super(
182
+ `Missing path parameter(s) ${missingParams.join(", ")} for route "${route}"${uri ? ` (${uri})` : ""}`,
183
+ {
184
+ code: "RF_FE_003",
185
+ route,
186
+ context: { missingParams, ...uri !== void 0 ? { uri } : {} }
187
+ }
188
+ );
189
+ }
190
+ };
191
+ var InvalidPathParamError = class extends ForgeError {
192
+ constructor(route, param, value) {
193
+ super(
194
+ `Path parameter "${param}" must be a primitive value (string, number, boolean), got ${typeof value}`,
195
+ { code: "RF_FE_003", route, context: { param, value } }
196
+ );
171
197
  }
172
198
  };
173
199
  var AdapterNotFoundError = class extends ForgeError {
@@ -200,6 +226,7 @@ var RouteForge = (function (exports) {
200
226
  context: { status: opts.status, url: opts.url, method: opts.method },
201
227
  cause: opts.cause
202
228
  });
229
+ if (opts.response !== void 0) this.response = opts.response;
203
230
  }
204
231
  };
205
232
  var RequestAbortedError = class extends ForgeError {
@@ -212,6 +239,14 @@ var RouteForge = (function (exports) {
212
239
  });
213
240
  }
214
241
  };
242
+ var DiscoveryNotReadyError = class extends ForgeError {
243
+ constructor() {
244
+ super(
245
+ "Route data not available. Auto-discovery has not completed. Use forge.ready() or forge.use(level) first, or await ready() before calling route()/hasRoute().",
246
+ { code: "RF_FE_010" }
247
+ );
248
+ }
249
+ };
215
250
 
216
251
  // src/interceptors.ts
217
252
  var InterceptorManagerImpl = class {
@@ -278,6 +313,43 @@ var RouteForge = (function (exports) {
278
313
  function createInterceptorManager() {
279
314
  return new InterceptorManagerImpl();
280
315
  }
316
+ function normalizeInterceptorDeclaration(value) {
317
+ if (value === null || value === void 0) return {};
318
+ let rawResolve;
319
+ let rawReject;
320
+ if (typeof value === "function") {
321
+ rawResolve = value;
322
+ } else if (Array.isArray(value)) {
323
+ rawResolve = value[0];
324
+ rawReject = value[1];
325
+ } else if (typeof value === "object") {
326
+ const obj = value;
327
+ rawResolve = obj.resolve;
328
+ rawReject = obj.reject;
329
+ } else {
330
+ throw new TypeError(
331
+ `Interceptor declaration must be a function, a [resolve, reject] tuple, or a { resolve, reject } object; received ${typeof value}.`
332
+ );
333
+ }
334
+ const handler = {};
335
+ if (rawResolve !== void 0 && rawResolve !== null) {
336
+ if (typeof rawResolve !== "function") {
337
+ throw new TypeError(
338
+ `Interceptor "resolve" (onFulfilled) must be a function; received ${typeof rawResolve}.`
339
+ );
340
+ }
341
+ handler.onFulfilled = rawResolve;
342
+ }
343
+ if (rawReject !== void 0 && rawReject !== null) {
344
+ if (typeof rawReject !== "function") {
345
+ throw new TypeError(
346
+ `Interceptor "reject" (onRejected) must be a function; received ${typeof rawReject}.`
347
+ );
348
+ }
349
+ handler.onRejected = rawReject;
350
+ }
351
+ return handler;
352
+ }
281
353
 
282
354
  // src/adapters/fetch-core.ts
283
355
  function isAbortError(err, signal) {
@@ -378,7 +450,10 @@ var RouteForge = (function (exports) {
378
450
  level: config.level,
379
451
  status: res.status,
380
452
  url,
381
- method: config.method
453
+ method: config.method,
454
+ // 完整 ResponseData 随错误逐段传递(响应拦截器 onRejected 链 → 最终 catch),
455
+ // 供调用方检查响应体,如 Laravel 422 校验错误 err.response.data.errors
456
+ response: responseData
382
457
  }
383
458
  );
384
459
  }
@@ -590,7 +665,7 @@ var RouteForge = (function (exports) {
590
665
  }
591
666
  }
592
667
  if (missingRequired.length > 0) {
593
- throw new MissingRouteParamError(meta.name, missingRequired);
668
+ throw new MissingRouteParamError(meta.name, missingRequired, meta.uri);
594
669
  }
595
670
  let uri = meta.uri.replace(/\{([^{}]+)\}/g, (match, raw) => {
596
671
  const optional = raw.endsWith("?");
@@ -598,10 +673,7 @@ var RouteForge = (function (exports) {
598
673
  if (values[name] !== void 0) {
599
674
  const val = values[name];
600
675
  if (typeof val === "object") {
601
- throw new ForgeError(
602
- `Path parameter "${name}" must be a primitive value (string, number, boolean), got ${typeof val}`,
603
- { code: "RF_FE_003", route: meta.name, context: { param: name, value: val } }
604
- );
676
+ throw new InvalidPathParamError(meta.name, name, val);
605
677
  }
606
678
  return encodeURIComponent(String(val));
607
679
  }
@@ -674,24 +746,31 @@ var RouteForge = (function (exports) {
674
746
  }
675
747
 
676
748
  // src/auto-discovery.ts
749
+ var DEFAULT_ENDPOINT = "/_forge/routes";
677
750
  async function fetchSummary(inputs, baseURL, fetchMeta) {
678
- const { explicitLevels, explicitEndpoint } = inputs;
679
- if (!explicitEndpoint) {
680
- throw new UnknownLevelError("(auto-discovery)");
681
- }
751
+ const { explicitLevels, explicitEndpoint, warnings } = inputs;
752
+ const endpoint = explicitEndpoint ?? DEFAULT_ENDPOINT;
753
+ const base = baseURL.endsWith("/") ? baseURL.slice(0, -1) : baseURL;
754
+ const ep = endpoint.startsWith("/") ? endpoint : `/${endpoint}`;
755
+ const url = `${base}${ep}`;
682
756
  try {
683
- const base = baseURL.endsWith("/") ? baseURL.slice(0, -1) : baseURL;
684
- const ep = explicitEndpoint.startsWith("/") ? explicitEndpoint : `/${explicitEndpoint}`;
685
- const data = await fetchMeta("__forge__.summary", `${base}${ep}`);
757
+ const data = await fetchMeta("__forge__.summary", url);
686
758
  return data;
687
759
  } catch (e) {
688
760
  if (explicitLevels && explicitLevels.length > 0) {
689
- console.warn(
690
- `[route-forge] summary endpoint unreachable: ${e.message}; using explicit levels`
691
- );
761
+ if (warnings) {
762
+ console.warn(
763
+ `[route-forge] summary endpoint unreachable: ${e.message}; using explicit levels`
764
+ );
765
+ }
692
766
  return null;
693
767
  }
694
- throw new UnknownLevelError("(auto-discovery)");
768
+ throw new NetworkError(
769
+ `Failed to fetch route summary from "${url}": ${e?.message ?? String(e)}; check the backend manifest endpoint or options.endpoint`,
770
+ void 0,
771
+ void 0,
772
+ e
773
+ );
695
774
  }
696
775
  }
697
776
  function normalizeCacheTtl(raw) {
@@ -699,15 +778,15 @@ var RouteForge = (function (exports) {
699
778
  return raw;
700
779
  }
701
780
  function applySummaryToState(summary, state, inputs) {
702
- const { explicitLevels, explicitEager, explicitEndpoint } = inputs;
781
+ const { explicitLevels, explicitEager, explicitEndpoint, warnings } = inputs;
703
782
  const schemeVersion = summary.schemeVersion ?? 1;
704
- if (schemeVersion > 1) {
783
+ if (schemeVersion > 1 && warnings) {
705
784
  console.warn(
706
785
  `[route-forge] backend schemeVersion=${schemeVersion} > client supported 1; some features may be unavailable`
707
786
  );
708
787
  }
709
788
  if (summary.config.endpoint_prefix && summary.config.endpoint_prefix !== explicitEndpoint) {
710
- if (explicitEndpoint) {
789
+ if (explicitEndpoint && warnings) {
711
790
  console.warn(
712
791
  `[route-forge] backend endpoint_prefix "${summary.config.endpoint_prefix}" overrides frontend endpoint "${explicitEndpoint}"`
713
792
  );
@@ -730,7 +809,7 @@ var RouteForge = (function (exports) {
730
809
  if (explicitLevels && explicitLevels.length > 0) {
731
810
  const intersection = explicitLevels.filter((l) => backendLevels.includes(l));
732
811
  const removed = explicitLevels.filter((l) => !backendLevels.includes(l));
733
- if (removed.length > 0) {
812
+ if (removed.length > 0 && warnings) {
734
813
  console.warn(
735
814
  `[route-forge] levels not in backend summary and dropped: ${removed.join(", ")}`
736
815
  );
@@ -782,7 +861,7 @@ var RouteForge = (function (exports) {
782
861
  }
783
862
  assertLevelDeclared(level) {
784
863
  if (!this.state.levels.includes(level)) {
785
- throw new UnknownLevelError(level);
864
+ throw new UnknownLevelError(level, this.state.levels);
786
865
  }
787
866
  }
788
867
  async fetchLevel(level) {
@@ -856,6 +935,7 @@ var RouteForge = (function (exports) {
856
935
  }
857
936
  getRoutes(level) {
858
937
  if (level !== void 0) {
938
+ this.assertLevelDeclared(level);
859
939
  const entry = this.cache.get(level);
860
940
  const routes = entry?.routes ?? {};
861
941
  const result2 = {};
@@ -887,6 +967,7 @@ var RouteForge = (function (exports) {
887
967
  responseInterceptors,
888
968
  load,
889
969
  findRouteMeta,
970
+ getRouteNames,
890
971
  baseURL,
891
972
  state,
892
973
  timeout,
@@ -938,7 +1019,10 @@ var RouteForge = (function (exports) {
938
1019
  level: resp.level,
939
1020
  status: resp.status,
940
1021
  url: resp.url,
941
- method: resp.method
1022
+ method: resp.method,
1023
+ // 完整 ResponseData 随错误逐段传递(响应拦截器 onRejected 链 → 最终 catch),
1024
+ // 供调用方检查响应体,如 Laravel 422 校验错误 err.response.data.errors
1025
+ response: resp
942
1026
  }
943
1027
  );
944
1028
  }
@@ -976,7 +1060,7 @@ var RouteForge = (function (exports) {
976
1060
  await load(level);
977
1061
  const meta = findRouteMeta(level, name);
978
1062
  if (!meta) {
979
- throw new UnknownRouteError(name, level);
1063
+ throw new UnknownRouteError(name, level, getRouteNames(level));
980
1064
  }
981
1065
  return doApiCall(meta, params, ctrl.signal);
982
1066
  })();
@@ -993,22 +1077,31 @@ var RouteForge = (function (exports) {
993
1077
  }
994
1078
 
995
1079
  // src/resolveRouteName.ts
1080
+ function stripTrailingSeparator(prefix, separator) {
1081
+ let normalized = prefix;
1082
+ while (normalized.endsWith(separator)) {
1083
+ normalized = normalized.slice(0, -separator.length);
1084
+ }
1085
+ return normalized;
1086
+ }
996
1087
  async function resolveRouteName(forge, level, prefix, suffix, separator = ".") {
997
- if (!suffix) return prefix;
998
- const joined = `${prefix}${separator}${suffix}`;
999
- if (!suffix.startsWith(`${prefix}${separator}`)) return joined;
1088
+ const normalized = stripTrailingSeparator(prefix, separator);
1089
+ if (!suffix) return normalized;
1090
+ const joined = `${normalized}${separator}${suffix}`;
1091
+ if (!suffix.startsWith(`${normalized}${separator}`)) return joined;
1000
1092
  await forge.load(level);
1001
1093
  if (forge.hasRoute(level, joined)) return joined;
1002
1094
  if (forge.hasRoute(level, suffix)) return suffix;
1003
- throw new UnknownRouteError(joined, level);
1095
+ throw new UnknownRouteError(joined, level, forge.getRouteNames?.(level));
1004
1096
  }
1005
1097
  function resolveRouteNameSync(forge, level, prefix, suffix, separator = ".") {
1006
- if (!suffix) return prefix;
1007
- const joined = `${prefix}${separator}${suffix}`;
1008
- if (!suffix.startsWith(`${prefix}${separator}`)) return joined;
1098
+ const normalized = stripTrailingSeparator(prefix, separator);
1099
+ if (!suffix) return normalized;
1100
+ const joined = `${normalized}${separator}${suffix}`;
1101
+ if (!suffix.startsWith(`${normalized}${separator}`)) return joined;
1009
1102
  if (forge.hasRoute(level, joined)) return joined;
1010
1103
  if (forge.hasRoute(level, suffix)) return suffix;
1011
- throw new UnknownRouteError(joined, level);
1104
+ throw new UnknownRouteError(joined, level, forge.getRouteNames?.(level));
1012
1105
  }
1013
1106
 
1014
1107
  // src/defineImmutableProps.ts
@@ -1028,7 +1121,7 @@ var RouteForge = (function (exports) {
1028
1121
  // src/bound-forge.ts
1029
1122
  function createBoundForgeFactory(deps) {
1030
1123
  const { load, api, route, hasRoute, getRoutes, invalidate, isLoaded, loadingTracker } = deps;
1031
- const resolver = { load, hasRoute };
1124
+ const resolver = { load, hasRoute, getRouteNames: (lvl) => Object.keys(getRoutes(lvl)) };
1032
1125
  function createBoundForge(level, prefix) {
1033
1126
  const levelLoadedPromise = load(level);
1034
1127
  levelLoadedPromise.catch(() => {
@@ -1094,11 +1187,6 @@ var RouteForge = (function (exports) {
1094
1187
  var DEFAULT_CACHE_TTL = 3600;
1095
1188
  function createRouteForge(options = {}) {
1096
1189
  const bootstrapSummary = readEmbeddedSummary() ?? options.summary ?? null;
1097
- if (!bootstrapSummary && !options.endpoint) {
1098
- throw new TypeError(
1099
- "createRouteForge: \u9700\u8981 options.endpoint\uFF0C\u6216 options.summary\uFF0C\u6216\u9875\u9762\u5185\u5D4C window.__ROUTE_FORGE__"
1100
- );
1101
- }
1102
1190
  const {
1103
1191
  adapter = "auto",
1104
1192
  timeout = DEFAULT_TIMEOUT,
@@ -1106,6 +1194,7 @@ var RouteForge = (function (exports) {
1106
1194
  interceptors: declarativeInterceptors,
1107
1195
  cache: cacheOpts = {}
1108
1196
  } = options;
1197
+ const warnings = options.warnings ?? true;
1109
1198
  const loadingTracker = new LoadingTracker();
1110
1199
  const explicitLevels = options.levels;
1111
1200
  const explicitEager = options.eager;
@@ -1113,12 +1202,12 @@ var RouteForge = (function (exports) {
1113
1202
  const discoveryState = {
1114
1203
  levels: explicitLevels ?? [],
1115
1204
  eager: explicitEager ?? [],
1116
- endpoint: explicitEndpoint ?? bootstrapSummary?.config?.endpoint_prefix ?? "",
1205
+ endpoint: explicitEndpoint ?? bootstrapSummary?.config?.endpoint_prefix ?? DEFAULT_ENDPOINT,
1117
1206
  urlPrefix: "",
1118
1207
  cacheTtl: void 0,
1119
1208
  levelRoutes: {}
1120
1209
  };
1121
- const discoveryInputs = { explicitLevels, explicitEager, explicitEndpoint };
1210
+ const discoveryInputs = { explicitLevels, explicitEager, explicitEndpoint, warnings };
1122
1211
  let autoDiscoveryCompleted = false;
1123
1212
  let resolveReady;
1124
1213
  let rejectReady;
@@ -1128,30 +1217,30 @@ var RouteForge = (function (exports) {
1128
1217
  });
1129
1218
  readyPromise.catch(() => {
1130
1219
  });
1220
+ let readySettledOk = false;
1221
+ readyPromise.then(
1222
+ () => {
1223
+ readySettledOk = true;
1224
+ },
1225
+ () => {
1226
+ }
1227
+ );
1131
1228
  const cacheTtl = cacheOpts.ttl ?? DEFAULT_CACHE_TTL;
1132
1229
  const cacheStorage = cacheOpts.storage ?? "memory";
1133
1230
  const cache = new RouteCache({ storage: cacheStorage, ttl: cacheTtl });
1134
1231
  const requestInterceptors = new InterceptorManagerImpl();
1135
1232
  const responseInterceptors = new InterceptorManagerImpl();
1136
- if (declarativeInterceptors?.request) {
1137
- for (const entry of declarativeInterceptors.request) {
1138
- if (typeof entry === "function") {
1139
- requestInterceptors.use(entry);
1140
- } else {
1141
- const [onFulfilled, onRejected] = entry;
1142
- requestInterceptors.use(onFulfilled, onRejected);
1143
- }
1144
- }
1233
+ const reqDecl = normalizeInterceptorDeclaration(
1234
+ declarativeInterceptors?.request
1235
+ );
1236
+ if (reqDecl.onFulfilled || reqDecl.onRejected) {
1237
+ requestInterceptors.use(reqDecl.onFulfilled, reqDecl.onRejected);
1145
1238
  }
1146
- if (declarativeInterceptors?.response) {
1147
- for (const entry of declarativeInterceptors.response) {
1148
- if (typeof entry === "function") {
1149
- responseInterceptors.use(entry);
1150
- } else {
1151
- const [onFulfilled, onRejected] = entry;
1152
- responseInterceptors.use(onFulfilled, onRejected);
1153
- }
1154
- }
1239
+ const resDecl = normalizeInterceptorDeclaration(
1240
+ declarativeInterceptors?.response
1241
+ );
1242
+ if (resDecl.onFulfilled || resDecl.onRejected) {
1243
+ responseInterceptors.use(resDecl.onFulfilled, resDecl.onRejected);
1155
1244
  }
1156
1245
  const adapterPromise = resolveAdapter({
1157
1246
  adapter,
@@ -1163,6 +1252,11 @@ var RouteForge = (function (exports) {
1163
1252
  if (!adapterResolved) {
1164
1253
  adapterObj = await adapterPromise.catch((e) => {
1165
1254
  if (e instanceof AdapterNotFoundError) throw e;
1255
+ if (warnings) {
1256
+ console.warn(
1257
+ `[route-forge] adapter initialization failed (${e?.message ?? String(e)}); falling back to builtin`
1258
+ );
1259
+ }
1166
1260
  return resolveAdapter({
1167
1261
  adapter: "builtin",
1168
1262
  forgeInterceptors: { request: requestInterceptors, response: responseInterceptors }
@@ -1174,10 +1268,7 @@ var RouteForge = (function (exports) {
1174
1268
  }
1175
1269
  function assertDiscoveryReady() {
1176
1270
  if (!autoDiscoveryCompleted && !explicitLevels?.length) {
1177
- throw new ForgeError(
1178
- "Route data not available. Auto-discovery has not completed. Use forge.ready() or forge.use(level) first.",
1179
- { code: "RF_FE_010" }
1180
- );
1271
+ throw new DiscoveryNotReadyError();
1181
1272
  }
1182
1273
  }
1183
1274
  async function fetchMeta(routeTag, url, level = "") {
@@ -1202,8 +1293,8 @@ var RouteForge = (function (exports) {
1202
1293
  const resp = await doRawRequest(config);
1203
1294
  if (!resp || resp.status < 200 || resp.status >= 300) {
1204
1295
  throw new HTTPError(
1205
- `Failed to fetch "${routeTag}": HTTP ${resp?.status}`,
1206
- { level, status: resp?.status, url, method: "GET" }
1296
+ `Failed to fetch "${routeTag}" (${url}): HTTP ${resp?.status}`,
1297
+ { level, status: resp?.status, url, method: "GET", response: resp ?? void 0 }
1207
1298
  );
1208
1299
  }
1209
1300
  return resp.data;
@@ -1240,11 +1331,14 @@ var RouteForge = (function (exports) {
1240
1331
  function getRoutes(level) {
1241
1332
  return level === void 0 ? store.getRoutes() : store.getRoutes(level);
1242
1333
  }
1334
+ function getLevels() {
1335
+ return [...discoveryState.levels];
1336
+ }
1243
1337
  function route(level, name, params) {
1244
1338
  assertDiscoveryReady();
1245
1339
  const meta = findRouteMeta(level, name);
1246
1340
  if (!meta) {
1247
- throw new UnknownRouteError(name, level);
1341
+ throw new UnknownRouteError(name, level, Object.keys(store.getRoutes(level)));
1248
1342
  }
1249
1343
  return buildRequestUrl(meta, params ?? {}, { baseURL, urlPrefix: discoveryState.urlPrefix });
1250
1344
  }
@@ -1254,6 +1348,7 @@ var RouteForge = (function (exports) {
1254
1348
  responseInterceptors,
1255
1349
  load,
1256
1350
  findRouteMeta,
1351
+ getRouteNames: (lvl) => Object.keys(store.getRoutes(lvl)),
1257
1352
  baseURL,
1258
1353
  state: discoveryState,
1259
1354
  timeout,
@@ -1309,7 +1404,10 @@ var RouteForge = (function (exports) {
1309
1404
  isLoaded,
1310
1405
  hasRoute,
1311
1406
  getRoutes,
1407
+ getLevels,
1408
+ warnings,
1312
1409
  isLoading: () => loadingTracker.isLoading(),
1410
+ isReady: () => readySettledOk,
1313
1411
  onLoadingChange: (cb) => loadingTracker.subscribe(cb),
1314
1412
  interceptors: {
1315
1413
  request: requestInterceptors,
@@ -1325,10 +1423,12 @@ var RouteForge = (function (exports) {
1325
1423
  }
1326
1424
 
1327
1425
  exports.AdapterNotFoundError = AdapterNotFoundError;
1426
+ exports.DiscoveryNotReadyError = DiscoveryNotReadyError;
1328
1427
  exports.ForgeError = ForgeError;
1329
1428
  exports.HTTPError = HTTPError;
1330
1429
  exports.InterceptorManagerImpl = InterceptorManagerImpl;
1331
1430
  exports.InvalidInterceptorReturnError = InvalidInterceptorReturnError;
1431
+ exports.InvalidPathParamError = InvalidPathParamError;
1332
1432
  exports.LoadingTracker = LoadingTracker;
1333
1433
  exports.MissingRouteParamError = MissingRouteParamError;
1334
1434
  exports.NetworkError = NetworkError;