@route-forge/core 1.3.0 → 1.4.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
@@ -278,30 +278,29 @@ function combineSignals(...signals) {
278
278
  function createBuiltinHttp(forgeInterceptors) {
279
279
  const requestMgr = forgeInterceptors?.request ?? createInterceptorManager();
280
280
  const responseMgr = forgeInterceptors?.response ?? createInterceptorManager();
281
- async function request(config) {
282
- const finalConfig = await runRequestInterceptors(requestMgr, config);
281
+ async function requestRaw(config) {
283
282
  const signal = combineSignals(
284
- finalConfig.signal,
285
- finalConfig.timeout && finalConfig.timeout > 0 ? AbortSignal.timeout(finalConfig.timeout) : void 0
283
+ config.signal,
284
+ config.timeout && config.timeout > 0 ? AbortSignal.timeout(config.timeout) : void 0
286
285
  );
287
- let url = finalConfig.url;
288
- if (finalConfig.paramsSerializer && finalConfig.params) {
289
- const qs = finalConfig.paramsSerializer(finalConfig.params);
286
+ let url = config.url;
287
+ if (config.paramsSerializer && config.params) {
288
+ const qs = config.paramsSerializer(config.params);
290
289
  if (qs) {
291
290
  url = url.includes("?") ? `${url}&${qs}` : `${url}?${qs}`;
292
291
  }
293
292
  }
294
- const headers = new Headers(finalConfig.headers);
293
+ const headers = new Headers(config.headers);
295
294
  const fetchInit = {
296
- method: finalConfig.method,
295
+ method: config.method,
297
296
  headers
298
297
  };
299
298
  if (signal) fetchInit.signal = signal;
300
- if (finalConfig.body !== void 0 && !["GET", "HEAD"].includes(finalConfig.method.toUpperCase())) {
301
- if (typeof finalConfig.body === "string" || isPassthroughBody(finalConfig.body)) {
302
- fetchInit.body = finalConfig.body;
299
+ if (config.body !== void 0 && !["GET", "HEAD"].includes(config.method.toUpperCase())) {
300
+ if (typeof config.body === "string" || isPassthroughBody(config.body)) {
301
+ fetchInit.body = config.body;
303
302
  } else {
304
- fetchInit.body = JSON.stringify(finalConfig.body);
303
+ fetchInit.body = JSON.stringify(config.body);
305
304
  if (!headers.has("Content-Type")) {
306
305
  headers.set("Content-Type", "application/json");
307
306
  }
@@ -314,8 +313,8 @@ function createBuiltinHttp(forgeInterceptors) {
314
313
  if (isAbortError(e)) throw e;
315
314
  throw new NetworkError(
316
315
  e instanceof Error ? e.message : String(e),
317
- finalConfig.route,
318
- finalConfig.level,
316
+ config.route,
317
+ config.level,
319
318
  e
320
319
  );
321
320
  }
@@ -329,27 +328,32 @@ function createBuiltinHttp(forgeInterceptors) {
329
328
  }
330
329
  }
331
330
  const responseData = {
332
- route: finalConfig.route,
333
- level: finalConfig.level,
334
- method: finalConfig.method,
331
+ route: config.route,
332
+ level: config.level,
333
+ method: config.method,
335
334
  url,
336
335
  status: res.status,
337
336
  headers: res.headers,
338
337
  data,
339
- config: finalConfig
338
+ config
340
339
  };
341
- const source = res.status >= 200 && res.status < 300 ? Promise.resolve(responseData) : Promise.reject(
342
- new HTTPError(
343
- `HTTP ${res.status} for route "${finalConfig.route}" (${finalConfig.method} ${url})`,
344
- {
345
- route: finalConfig.route,
346
- level: finalConfig.level,
347
- status: res.status,
348
- url,
349
- method: finalConfig.method
350
- }
351
- )
340
+ if (res.status >= 200 && res.status < 300) {
341
+ return responseData;
342
+ }
343
+ throw new HTTPError(
344
+ `HTTP ${res.status} for route "${config.route}" (${config.method} ${url})`,
345
+ {
346
+ route: config.route,
347
+ level: config.level,
348
+ status: res.status,
349
+ url,
350
+ method: config.method
351
+ }
352
352
  );
353
+ }
354
+ async function request(config) {
355
+ const finalConfig = await runRequestInterceptors(requestMgr, config);
356
+ const source = Promise.resolve(finalConfig).then(requestRaw);
353
357
  return runResponseInterceptors(
354
358
  responseMgr,
355
359
  source
@@ -362,6 +366,7 @@ function createBuiltinHttp(forgeInterceptors) {
362
366
  const del = (url, config) => request({ ...config, url, method: "DELETE" });
363
367
  return {
364
368
  request,
369
+ requestRaw,
365
370
  interceptors: {
366
371
  request: requestMgr,
367
372
  response: responseMgr
@@ -573,7 +578,6 @@ function createRouteForge(options) {
573
578
  const loadingTracker = new LoadingTracker();
574
579
  const explicitLevels = options.levels;
575
580
  const explicitEager = options.eager;
576
- const explicitStrict = options.strict ?? false;
577
581
  const explicitEndpoint = options.endpoint;
578
582
  let effectiveLevels = explicitLevels ?? [];
579
583
  let effectiveEager = explicitEager ?? [];
@@ -621,11 +625,6 @@ function createRouteForge(options) {
621
625
  if (summary.config.url_prefix) {
622
626
  effectiveUrlPrefix = summary.config.url_prefix.endsWith("/") ? summary.config.url_prefix.slice(0, -1) : summary.config.url_prefix;
623
627
  }
624
- if (summary.config.strict_mode && !explicitStrict) {
625
- console.warn(
626
- "[route-forge] backend strict_mode=true overrides frontend strict=false; forcing strict=true"
627
- );
628
- }
629
628
  const backendLevels = Object.keys(summary.levels);
630
629
  backendHasUnassignedLevel = backendLevels.includes(UNASSIGNED_LEVEL);
631
630
  if (Array.isArray(summary.unassigned) && summary.unassigned.length > 0 && !backendHasUnassignedLevel) {
@@ -699,7 +698,10 @@ function createRouteForge(options) {
699
698
  if (!adapterResolved) {
700
699
  adapterObj = await adapterPromise.catch((e) => {
701
700
  if (e instanceof AdapterNotFoundError) throw e;
702
- return resolveAdapter({ adapter: "builtin" });
701
+ return resolveAdapter({
702
+ adapter: "builtin",
703
+ forgeInterceptors: { request: requestInterceptors, response: responseInterceptors }
704
+ });
703
705
  });
704
706
  adapterResolved = true;
705
707
  }
@@ -743,7 +745,8 @@ function createRouteForge(options) {
743
745
  level
744
746
  }
745
747
  };
746
- const resp = await adp.request(config);
748
+ const doRawRequest = adp.requestRaw ?? adp.request;
749
+ const resp = await doRawRequest(config);
747
750
  if (!resp || resp.status < 200 || resp.status >= 300) {
748
751
  throw new HTTPError(
749
752
  `Failed to load level "${level}": HTTP ${resp?.status}`,
@@ -846,23 +849,40 @@ function createRouteForge(options) {
846
849
  }
847
850
  return void 0;
848
851
  }
849
- async function api(level, name, params = {}) {
850
- await autoDiscoveryPromise;
851
- await load(level);
852
- const meta = findRouteMeta(level, name);
853
- if (!meta) {
854
- throw new UnknownRouteError(name, level);
855
- }
856
- return doApiCall(meta, params);
852
+ function api(level, name, params = {}) {
853
+ let ctrl;
854
+ let abortedBeforeInit = false;
855
+ let abortReason;
856
+ const work = (async () => {
857
+ ctrl = new AbortController();
858
+ if (abortedBeforeInit) {
859
+ ctrl.abort(abortReason);
860
+ }
861
+ await autoDiscoveryPromise;
862
+ await load(level);
863
+ const meta = findRouteMeta(level, name);
864
+ if (!meta) {
865
+ throw new UnknownRouteError(name, level);
866
+ }
867
+ return doApiCall(meta, params, ctrl.signal);
868
+ })();
869
+ const request = work;
870
+ request.abort = () => {
871
+ if (ctrl) {
872
+ ctrl.abort();
873
+ } else {
874
+ abortedBeforeInit = true;
875
+ }
876
+ };
877
+ return request;
857
878
  }
858
- async function doApiCall(meta, params) {
879
+ async function doApiCall(meta, params, signal) {
859
880
  const {
860
881
  pathParams,
861
882
  query,
862
883
  body,
863
884
  headers,
864
- timeout: perCallTimeout,
865
- signal
885
+ timeout: perCallTimeout
866
886
  } = resolveApiParams(params);
867
887
  if (signal?.aborted) {
868
888
  throw new RequestAbortedError(meta.name, meta.level, signal.reason);
@@ -906,7 +926,7 @@ function createRouteForge(options) {
906
926
  },
907
927
  (err) => {
908
928
  if (err instanceof ForgeError) throw err;
909
- if (isAbortError2(err, finalConfig.signal)) {
929
+ if (isAbortError2(err, signal)) {
910
930
  throw new RequestAbortedError(meta.name, meta.level, err);
911
931
  }
912
932
  throw new NetworkError(
@@ -994,7 +1014,8 @@ function createRouteForge(options) {
994
1014
  }
995
1015
  const forgeResolver = { load, hasRoute };
996
1016
  function createBoundForge(level, prefix) {
997
- const levelLoadedPromise = load(level).catch(() => {
1017
+ const levelLoadedPromise = load(level);
1018
+ levelLoadedPromise.catch(() => {
998
1019
  });
999
1020
  const apiFn = prefix ? (name, params) => resolveRouteName(forgeResolver, level, prefix, name).then(
1000
1021
  (resolved) => api(level, resolved, params)
@@ -1094,7 +1115,6 @@ function resolveApiParams(input) {
1094
1115
  body: rawBody,
1095
1116
  headers: rawHeaders,
1096
1117
  timeout: perCallTimeout,
1097
- signal,
1098
1118
  ...flatRest
1099
1119
  } = input;
1100
1120
  const pathParams = explicitParams ? { ...explicitParams } : {};
@@ -1127,7 +1147,7 @@ function resolveApiParams(input) {
1127
1147
  pathParams.headers = rawHeaders;
1128
1148
  }
1129
1149
  }
1130
- return { pathParams, query, body, headers, timeout: perCallTimeout, signal };
1150
+ return { pathParams, query, body, headers, timeout: perCallTimeout };
1131
1151
  }
1132
1152
  function isAbortError2(err, signal) {
1133
1153
  if (signal?.aborted) return true;