@route-forge/core 2.2.1 → 3.1.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 +31 -23
- package/README_zh.md +30 -23
- package/dist/codegen.cjs +96 -69
- package/dist/codegen.cjs.map +1 -1
- package/dist/codegen.d.cts +23 -15
- package/dist/codegen.d.ts +23 -15
- package/dist/codegen.js +96 -69
- package/dist/codegen.js.map +1 -1
- package/dist/index.cjs +465 -210
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +522 -24
- package/dist/index.d.ts +522 -24
- package/dist/index.js +463 -211
- package/dist/index.js.map +1 -1
- package/dist/manifest-bjy4P_B4.d.cts +76 -0
- package/dist/manifest-bjy4P_B4.d.ts +76 -0
- package/dist/route-forge.global.js +465 -210
- package/dist/route-forge.global.js.map +1 -1
- package/dist/route-forge.global.min.js +2 -2
- package/package.json +4 -1
- package/dist/types-CDKE8rw-.d.cts +0 -462
- package/dist/types-CDKE8rw-.d.ts +0 -462
package/dist/index.cjs
CHANGED
|
@@ -131,6 +131,36 @@ var RouteCache = class {
|
|
|
131
131
|
}
|
|
132
132
|
};
|
|
133
133
|
|
|
134
|
+
// src/interceptors/manager.ts
|
|
135
|
+
var InterceptorManagerImpl = class {
|
|
136
|
+
constructor() {
|
|
137
|
+
this.handlers = [];
|
|
138
|
+
this.nextId = 0;
|
|
139
|
+
}
|
|
140
|
+
use(onFulfilled, onRejected) {
|
|
141
|
+
const id = this.nextId++;
|
|
142
|
+
this.handlers.push({ id, onFulfilled, onRejected });
|
|
143
|
+
return id;
|
|
144
|
+
}
|
|
145
|
+
eject(id) {
|
|
146
|
+
const idx = this.handlers.findIndex((h) => h.id === id);
|
|
147
|
+
if (idx >= 0) this.handlers.splice(idx, 1);
|
|
148
|
+
}
|
|
149
|
+
clear() {
|
|
150
|
+
this.handlers = [];
|
|
151
|
+
}
|
|
152
|
+
forEach(fn) {
|
|
153
|
+
for (const h of this.handlers) fn(h);
|
|
154
|
+
}
|
|
155
|
+
/** 测试用:当前注册数量 */
|
|
156
|
+
get size() {
|
|
157
|
+
return this.handlers.length;
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
function createInterceptorManager() {
|
|
161
|
+
return new InterceptorManagerImpl();
|
|
162
|
+
}
|
|
163
|
+
|
|
134
164
|
// src/errors.ts
|
|
135
165
|
var ForgeError = class extends Error {
|
|
136
166
|
constructor(message, opts) {
|
|
@@ -143,30 +173,56 @@ var ForgeError = class extends Error {
|
|
|
143
173
|
if (opts.cause !== void 0) this.cause = opts.cause;
|
|
144
174
|
}
|
|
145
175
|
};
|
|
176
|
+
function formatCandidates(candidates) {
|
|
177
|
+
if (!candidates || candidates.length === 0) return "";
|
|
178
|
+
const MAX = 5;
|
|
179
|
+
const shown = candidates.slice(0, MAX).join(", ");
|
|
180
|
+
const more = candidates.length > MAX ? ` (+${candidates.length - MAX} more)` : "";
|
|
181
|
+
return ` Available: ${shown}${more}`;
|
|
182
|
+
}
|
|
146
183
|
var UnknownRouteError = class extends ForgeError {
|
|
147
|
-
constructor(route, level) {
|
|
148
|
-
super(
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
184
|
+
constructor(route, level, candidates) {
|
|
185
|
+
super(
|
|
186
|
+
`Route "${route}" not found${level ? ` in level "${level}"` : ""}.${formatCandidates(candidates)}`,
|
|
187
|
+
{
|
|
188
|
+
code: "RF_FE_001",
|
|
189
|
+
route,
|
|
190
|
+
level,
|
|
191
|
+
context: candidates && candidates.length > 0 ? { candidates } : void 0
|
|
192
|
+
}
|
|
193
|
+
);
|
|
153
194
|
}
|
|
154
195
|
};
|
|
155
196
|
var UnknownLevelError = class extends ForgeError {
|
|
156
|
-
constructor(level) {
|
|
157
|
-
super(
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
197
|
+
constructor(level, candidates) {
|
|
198
|
+
super(
|
|
199
|
+
`Level "${level}" not declared in options.levels.${formatCandidates(candidates)}`,
|
|
200
|
+
{
|
|
201
|
+
code: "RF_FE_002",
|
|
202
|
+
level,
|
|
203
|
+
context: candidates && candidates.length > 0 ? { candidates } : void 0
|
|
204
|
+
}
|
|
205
|
+
);
|
|
161
206
|
}
|
|
162
207
|
};
|
|
163
208
|
var MissingRouteParamError = class extends ForgeError {
|
|
164
|
-
constructor(route, missingParams) {
|
|
165
|
-
super(
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
209
|
+
constructor(route, missingParams, uri) {
|
|
210
|
+
super(
|
|
211
|
+
`Missing path parameter(s) ${missingParams.join(", ")} for route "${route}"${uri ? ` (${uri})` : ""}`,
|
|
212
|
+
{
|
|
213
|
+
code: "RF_FE_003",
|
|
214
|
+
route,
|
|
215
|
+
context: { missingParams, ...uri !== void 0 ? { uri } : {} }
|
|
216
|
+
}
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
var InvalidPathParamError = class extends ForgeError {
|
|
221
|
+
constructor(route, param, value) {
|
|
222
|
+
super(
|
|
223
|
+
`Path parameter "${param}" must be a primitive value (string, number, boolean), got ${typeof value}`,
|
|
224
|
+
{ code: "RF_FE_003", route, context: { param, value } }
|
|
225
|
+
);
|
|
170
226
|
}
|
|
171
227
|
};
|
|
172
228
|
var AdapterNotFoundError = class extends ForgeError {
|
|
@@ -199,6 +255,7 @@ var HTTPError = class extends ForgeError {
|
|
|
199
255
|
context: { status: opts.status, url: opts.url, method: opts.method },
|
|
200
256
|
cause: opts.cause
|
|
201
257
|
});
|
|
258
|
+
if (opts.response !== void 0) this.response = opts.response;
|
|
202
259
|
}
|
|
203
260
|
};
|
|
204
261
|
var RequestAbortedError = class extends ForgeError {
|
|
@@ -211,33 +268,16 @@ var RequestAbortedError = class extends ForgeError {
|
|
|
211
268
|
});
|
|
212
269
|
}
|
|
213
270
|
};
|
|
214
|
-
|
|
215
|
-
// src/interceptors.ts
|
|
216
|
-
var InterceptorManagerImpl = class {
|
|
271
|
+
var DiscoveryNotReadyError = class extends ForgeError {
|
|
217
272
|
constructor() {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
const id = this.nextId++;
|
|
223
|
-
this.handlers.push({ id, onFulfilled, onRejected });
|
|
224
|
-
return id;
|
|
225
|
-
}
|
|
226
|
-
eject(id) {
|
|
227
|
-
const idx = this.handlers.findIndex((h) => h.id === id);
|
|
228
|
-
if (idx >= 0) this.handlers.splice(idx, 1);
|
|
229
|
-
}
|
|
230
|
-
clear() {
|
|
231
|
-
this.handlers = [];
|
|
232
|
-
}
|
|
233
|
-
forEach(fn) {
|
|
234
|
-
for (const h of this.handlers) fn(h);
|
|
235
|
-
}
|
|
236
|
-
/** 测试用:当前注册数量 */
|
|
237
|
-
get size() {
|
|
238
|
-
return this.handlers.length;
|
|
273
|
+
super(
|
|
274
|
+
"Route data not available. Auto-discovery has not completed. Use forge.ready() or forge.use(level) first, or await ready() before calling route()/hasRoute().",
|
|
275
|
+
{ code: "RF_FE_010" }
|
|
276
|
+
);
|
|
239
277
|
}
|
|
240
278
|
};
|
|
279
|
+
|
|
280
|
+
// src/interceptors/runner.ts
|
|
241
281
|
async function runRequestInterceptors(manager, initial) {
|
|
242
282
|
const handlers = [];
|
|
243
283
|
manager.forEach((h) => handlers.push(h));
|
|
@@ -274,8 +314,44 @@ async function runResponseInterceptors(manager, source) {
|
|
|
274
314
|
}
|
|
275
315
|
return p;
|
|
276
316
|
}
|
|
277
|
-
|
|
278
|
-
|
|
317
|
+
|
|
318
|
+
// src/interceptors/normalize.ts
|
|
319
|
+
function normalizeInterceptorDeclaration(value) {
|
|
320
|
+
if (value === null || value === void 0) return {};
|
|
321
|
+
let rawResolve;
|
|
322
|
+
let rawReject;
|
|
323
|
+
if (typeof value === "function") {
|
|
324
|
+
rawResolve = value;
|
|
325
|
+
} else if (Array.isArray(value)) {
|
|
326
|
+
rawResolve = value[0];
|
|
327
|
+
rawReject = value[1];
|
|
328
|
+
} else if (typeof value === "object") {
|
|
329
|
+
const obj = value;
|
|
330
|
+
rawResolve = obj.resolve;
|
|
331
|
+
rawReject = obj.reject;
|
|
332
|
+
} else {
|
|
333
|
+
throw new TypeError(
|
|
334
|
+
`Interceptor declaration must be a function, a [resolve, reject] tuple, or a { resolve, reject } object; received ${typeof value}.`
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
const handler = {};
|
|
338
|
+
if (rawResolve !== void 0 && rawResolve !== null) {
|
|
339
|
+
if (typeof rawResolve !== "function") {
|
|
340
|
+
throw new TypeError(
|
|
341
|
+
`Interceptor "resolve" (onFulfilled) must be a function; received ${typeof rawResolve}.`
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
handler.onFulfilled = rawResolve;
|
|
345
|
+
}
|
|
346
|
+
if (rawReject !== void 0 && rawReject !== null) {
|
|
347
|
+
if (typeof rawReject !== "function") {
|
|
348
|
+
throw new TypeError(
|
|
349
|
+
`Interceptor "reject" (onRejected) must be a function; received ${typeof rawReject}.`
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
handler.onRejected = rawReject;
|
|
353
|
+
}
|
|
354
|
+
return handler;
|
|
279
355
|
}
|
|
280
356
|
|
|
281
357
|
// src/adapters/fetch-core.ts
|
|
@@ -377,7 +453,10 @@ async function rawFetch(config) {
|
|
|
377
453
|
level: config.level,
|
|
378
454
|
status: res.status,
|
|
379
455
|
url,
|
|
380
|
-
method: config.method
|
|
456
|
+
method: config.method,
|
|
457
|
+
// 完整 ResponseData 随错误逐段传递(响应拦截器 onRejected 链 → 最终 catch),
|
|
458
|
+
// 供调用方检查响应体,如 Laravel 422 校验错误 err.response.data.errors
|
|
459
|
+
response: responseData
|
|
381
460
|
}
|
|
382
461
|
);
|
|
383
462
|
}
|
|
@@ -506,6 +585,69 @@ async function resolveAdapter(opts) {
|
|
|
506
585
|
return createBuiltinHttp(opts.forgeInterceptors);
|
|
507
586
|
}
|
|
508
587
|
|
|
588
|
+
// src/adapter-bootstrap.ts
|
|
589
|
+
function createAdapterBootstrap(deps) {
|
|
590
|
+
const { adapter, requestInterceptors, responseInterceptors, warnings } = deps;
|
|
591
|
+
const adapterPromise = resolveAdapter({
|
|
592
|
+
adapter,
|
|
593
|
+
forgeInterceptors: { request: requestInterceptors, response: responseInterceptors }
|
|
594
|
+
});
|
|
595
|
+
let adapterResolved = false;
|
|
596
|
+
let adapterObj = null;
|
|
597
|
+
return async function ensureAdapter() {
|
|
598
|
+
if (!adapterResolved) {
|
|
599
|
+
adapterObj = await adapterPromise.catch((e) => {
|
|
600
|
+
if (e instanceof AdapterNotFoundError) throw e;
|
|
601
|
+
if (warnings) {
|
|
602
|
+
console.warn(
|
|
603
|
+
`[route-forge] adapter initialization failed (${e?.message ?? String(e)}); falling back to builtin`
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
return resolveAdapter({
|
|
607
|
+
adapter: "builtin",
|
|
608
|
+
forgeInterceptors: { request: requestInterceptors, response: responseInterceptors }
|
|
609
|
+
});
|
|
610
|
+
});
|
|
611
|
+
adapterResolved = true;
|
|
612
|
+
}
|
|
613
|
+
return adapterObj;
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// src/ready-latch.ts
|
|
618
|
+
function createReadyLatch() {
|
|
619
|
+
let resolveReady;
|
|
620
|
+
let rejectReady;
|
|
621
|
+
const readyPromise = new Promise((resolve, reject) => {
|
|
622
|
+
resolveReady = resolve;
|
|
623
|
+
rejectReady = reject;
|
|
624
|
+
});
|
|
625
|
+
let settledValue;
|
|
626
|
+
readyPromise.catch(() => {
|
|
627
|
+
});
|
|
628
|
+
let readySettledOk = false;
|
|
629
|
+
readyPromise.then(
|
|
630
|
+
(value) => {
|
|
631
|
+
settledValue = value;
|
|
632
|
+
readySettledOk = true;
|
|
633
|
+
},
|
|
634
|
+
() => {
|
|
635
|
+
}
|
|
636
|
+
);
|
|
637
|
+
function ready(onFulfilled, onRejected) {
|
|
638
|
+
if (onFulfilled) {
|
|
639
|
+
return readyPromise.then(onFulfilled, onRejected).then(() => settledValue);
|
|
640
|
+
}
|
|
641
|
+
return readyPromise;
|
|
642
|
+
}
|
|
643
|
+
return {
|
|
644
|
+
resolve: (value) => resolveReady(value),
|
|
645
|
+
reject: (reason) => rejectReady(reason),
|
|
646
|
+
isReady: () => readySettledOk,
|
|
647
|
+
ready
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
|
|
509
651
|
// src/loading.ts
|
|
510
652
|
var LoadingTracker = class {
|
|
511
653
|
constructor() {
|
|
@@ -565,12 +707,107 @@ var LoadingTracker = class {
|
|
|
565
707
|
}
|
|
566
708
|
};
|
|
567
709
|
|
|
568
|
-
// src/
|
|
710
|
+
// src/route-change.ts
|
|
711
|
+
var RouteChangeTracker = class {
|
|
712
|
+
constructor() {
|
|
713
|
+
this.subscribers = /* @__PURE__ */ new Set();
|
|
714
|
+
/** 本微任务周期内累积的变更层级(去重) */
|
|
715
|
+
this.pending = /* @__PURE__ */ new Set();
|
|
716
|
+
/** 是否已排定一次微任务 flush */
|
|
717
|
+
this.scheduled = false;
|
|
718
|
+
}
|
|
719
|
+
/** 订阅路由表数据变更;返回取消订阅函数 */
|
|
720
|
+
subscribe(cb) {
|
|
721
|
+
this.subscribers.add(cb);
|
|
722
|
+
return () => {
|
|
723
|
+
this.subscribers.delete(cb);
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
/** 记录某层级数据已变更;同一 tick 多次调用合并为一次投递(每层级各投一次) */
|
|
727
|
+
notify(level) {
|
|
728
|
+
if (this.subscribers.size === 0) return;
|
|
729
|
+
this.pending.add(level);
|
|
730
|
+
if (this.scheduled) return;
|
|
731
|
+
this.scheduled = true;
|
|
732
|
+
queueMicrotask(() => this.flush());
|
|
733
|
+
}
|
|
734
|
+
/** 一次性投递本周期累积的所有变更层级;单订阅者抛错不影响其它 */
|
|
735
|
+
flush() {
|
|
736
|
+
this.scheduled = false;
|
|
737
|
+
const levels = [...this.pending];
|
|
738
|
+
this.pending.clear();
|
|
739
|
+
for (const level of levels) {
|
|
740
|
+
for (const cb of this.subscribers) {
|
|
741
|
+
try {
|
|
742
|
+
cb(level);
|
|
743
|
+
} catch {
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
};
|
|
749
|
+
|
|
750
|
+
// src/url/utils.ts
|
|
751
|
+
function trimTrailingSlash(s) {
|
|
752
|
+
return s.endsWith("/") ? s.slice(0, -1) : s;
|
|
753
|
+
}
|
|
754
|
+
function withLeadingSlash(s) {
|
|
755
|
+
return s.startsWith("/") ? s : `/${s}`;
|
|
756
|
+
}
|
|
757
|
+
function joinBaseAndPath(base, path) {
|
|
758
|
+
return `${trimTrailingSlash(base)}${withLeadingSlash(path)}`;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// src/url/endpoint.ts
|
|
569
762
|
function buildUrl(level, ctx) {
|
|
570
|
-
|
|
571
|
-
const ep = ctx.endpoint.startsWith("/") ? ctx.endpoint : `/${ctx.endpoint}`;
|
|
572
|
-
return `${base}${ep}/${encodeURIComponent(level)}`;
|
|
763
|
+
return `${joinBaseAndPath(ctx.baseURL, withLeadingSlash(ctx.endpoint))}/${encodeURIComponent(level)}`;
|
|
573
764
|
}
|
|
765
|
+
|
|
766
|
+
// src/url/params.ts
|
|
767
|
+
function resolveApiParams(input) {
|
|
768
|
+
const {
|
|
769
|
+
params: explicitParams,
|
|
770
|
+
query: rawQuery,
|
|
771
|
+
body: rawBody,
|
|
772
|
+
headers: rawHeaders,
|
|
773
|
+
timeout: perCallTimeout,
|
|
774
|
+
signal: rawSignal,
|
|
775
|
+
...flatRest
|
|
776
|
+
} = input;
|
|
777
|
+
const pathParams = explicitParams ? { ...explicitParams } : {};
|
|
778
|
+
for (const [k, v] of Object.entries(flatRest)) {
|
|
779
|
+
if (!(k in pathParams)) {
|
|
780
|
+
pathParams[k] = v;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
let query;
|
|
784
|
+
let body;
|
|
785
|
+
let headers;
|
|
786
|
+
if (rawQuery !== void 0) {
|
|
787
|
+
if (typeof rawQuery === "object" && rawQuery !== null) {
|
|
788
|
+
query = rawQuery;
|
|
789
|
+
} else if (!("query" in pathParams)) {
|
|
790
|
+
pathParams.query = rawQuery;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
if (rawBody !== void 0) {
|
|
794
|
+
if (typeof rawBody !== "string" && typeof rawBody !== "number") {
|
|
795
|
+
body = rawBody;
|
|
796
|
+
} else if (!("body" in pathParams)) {
|
|
797
|
+
pathParams.body = rawBody;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
if (rawHeaders !== void 0) {
|
|
801
|
+
if (typeof rawHeaders === "object" && rawHeaders !== null) {
|
|
802
|
+
headers = rawHeaders;
|
|
803
|
+
} else if (!("headers" in pathParams)) {
|
|
804
|
+
pathParams.headers = rawHeaders;
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
return { pathParams, query, body, headers, timeout: perCallTimeout, signal: rawSignal };
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// src/url/request.ts
|
|
574
811
|
function buildRequestUrl(meta, params, ctx) {
|
|
575
812
|
const defaults = meta.parameter_defaults ?? {};
|
|
576
813
|
const missingRequired = [];
|
|
@@ -589,7 +826,7 @@ function buildRequestUrl(meta, params, ctx) {
|
|
|
589
826
|
}
|
|
590
827
|
}
|
|
591
828
|
if (missingRequired.length > 0) {
|
|
592
|
-
throw new MissingRouteParamError(meta.name, missingRequired);
|
|
829
|
+
throw new MissingRouteParamError(meta.name, missingRequired, meta.uri);
|
|
593
830
|
}
|
|
594
831
|
let uri = meta.uri.replace(/\{([^{}]+)\}/g, (match, raw) => {
|
|
595
832
|
const optional = raw.endsWith("?");
|
|
@@ -597,10 +834,7 @@ function buildRequestUrl(meta, params, ctx) {
|
|
|
597
834
|
if (values[name] !== void 0) {
|
|
598
835
|
const val = values[name];
|
|
599
836
|
if (typeof val === "object") {
|
|
600
|
-
throw new
|
|
601
|
-
`Path parameter "${name}" must be a primitive value (string, number, boolean), got ${typeof val}`,
|
|
602
|
-
{ code: "RF_FE_003", route: meta.name, context: { param: name, value: val } }
|
|
603
|
-
);
|
|
837
|
+
throw new InvalidPathParamError(meta.name, name, val);
|
|
604
838
|
}
|
|
605
839
|
return encodeURIComponent(String(val));
|
|
606
840
|
}
|
|
@@ -608,10 +842,10 @@ function buildRequestUrl(meta, params, ctx) {
|
|
|
608
842
|
});
|
|
609
843
|
uri = uri.replace(/\/+/g, "/").replace(/\/$/, "");
|
|
610
844
|
if (/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(ctx.urlPrefix)) {
|
|
611
|
-
const prefix2 =
|
|
845
|
+
const prefix2 = trimTrailingSlash(ctx.urlPrefix);
|
|
612
846
|
return uri.startsWith("/") ? `${prefix2}${uri}` : `${prefix2}/${uri}`;
|
|
613
847
|
}
|
|
614
|
-
const base =
|
|
848
|
+
const base = trimTrailingSlash(ctx.baseURL);
|
|
615
849
|
const prefix = ctx.urlPrefix;
|
|
616
850
|
return uri.startsWith("/") ? `${base}${prefix}${uri}` : `${base}${prefix}/${uri}`;
|
|
617
851
|
}
|
|
@@ -630,67 +864,44 @@ function appendQuery(url, query) {
|
|
|
630
864
|
if (!qs) return url;
|
|
631
865
|
return url.includes("?") ? `${url}&${qs}` : `${url}?${qs}`;
|
|
632
866
|
}
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
timeout: perCallTimeout,
|
|
640
|
-
...flatRest
|
|
641
|
-
} = input;
|
|
642
|
-
const pathParams = explicitParams ? { ...explicitParams } : {};
|
|
643
|
-
for (const [k, v] of Object.entries(flatRest)) {
|
|
644
|
-
if (!(k in pathParams)) {
|
|
645
|
-
pathParams[k] = v;
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
let query;
|
|
649
|
-
let body;
|
|
650
|
-
let headers;
|
|
651
|
-
if (rawQuery !== void 0) {
|
|
652
|
-
if (typeof rawQuery === "object" && rawQuery !== null) {
|
|
653
|
-
query = rawQuery;
|
|
654
|
-
} else if (!("query" in pathParams)) {
|
|
655
|
-
pathParams.query = rawQuery;
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
if (rawBody !== void 0) {
|
|
659
|
-
if (typeof rawBody !== "string" && typeof rawBody !== "number") {
|
|
660
|
-
body = rawBody;
|
|
661
|
-
} else if (!("body" in pathParams)) {
|
|
662
|
-
pathParams.body = rawBody;
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
if (rawHeaders !== void 0) {
|
|
666
|
-
if (typeof rawHeaders === "object" && rawHeaders !== null) {
|
|
667
|
-
headers = rawHeaders;
|
|
668
|
-
} else if (!("headers" in pathParams)) {
|
|
669
|
-
pathParams.headers = rawHeaders;
|
|
867
|
+
var RESERVED_API_KEYS = ["params", "query", "body", "headers", "timeout", "signal"];
|
|
868
|
+
function buildRouteUrl(meta, params, ctx) {
|
|
869
|
+
if (params) {
|
|
870
|
+
const hasReservedKey = RESERVED_API_KEYS.some((k) => k in params);
|
|
871
|
+
if (!hasReservedKey) {
|
|
872
|
+
return buildRequestUrl(meta, params, ctx);
|
|
670
873
|
}
|
|
874
|
+
} else {
|
|
875
|
+
return buildRequestUrl(meta, {}, ctx);
|
|
671
876
|
}
|
|
672
|
-
|
|
877
|
+
const { pathParams, query } = resolveApiParams(params);
|
|
878
|
+
return appendQuery(buildRequestUrl(meta, pathParams, ctx), query);
|
|
673
879
|
}
|
|
674
880
|
|
|
675
881
|
// src/auto-discovery.ts
|
|
882
|
+
var DEFAULT_ENDPOINT = "/_forge/routes";
|
|
676
883
|
async function fetchSummary(inputs, baseURL, fetchMeta) {
|
|
677
|
-
const { explicitLevels, explicitEndpoint } = inputs;
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
}
|
|
884
|
+
const { explicitLevels, explicitEndpoint, warnings } = inputs;
|
|
885
|
+
const endpoint = explicitEndpoint ?? DEFAULT_ENDPOINT;
|
|
886
|
+
const url = joinBaseAndPath(baseURL, endpoint);
|
|
681
887
|
try {
|
|
682
|
-
const
|
|
683
|
-
const ep = explicitEndpoint.startsWith("/") ? explicitEndpoint : `/${explicitEndpoint}`;
|
|
684
|
-
const data = await fetchMeta("__forge__.summary", `${base}${ep}`);
|
|
888
|
+
const data = await fetchMeta("__forge__.summary", url);
|
|
685
889
|
return data;
|
|
686
890
|
} catch (e) {
|
|
687
891
|
if (explicitLevels && explicitLevels.length > 0) {
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
892
|
+
if (warnings) {
|
|
893
|
+
console.warn(
|
|
894
|
+
`[route-forge] summary endpoint unreachable: ${e.message}; using explicit levels`
|
|
895
|
+
);
|
|
896
|
+
}
|
|
691
897
|
return null;
|
|
692
898
|
}
|
|
693
|
-
throw new
|
|
899
|
+
throw new NetworkError(
|
|
900
|
+
`Failed to fetch route summary from "${url}": ${e?.message ?? String(e)}; check the backend manifest endpoint or options.endpoint`,
|
|
901
|
+
void 0,
|
|
902
|
+
void 0,
|
|
903
|
+
e
|
|
904
|
+
);
|
|
694
905
|
}
|
|
695
906
|
}
|
|
696
907
|
function normalizeCacheTtl(raw) {
|
|
@@ -698,15 +909,15 @@ function normalizeCacheTtl(raw) {
|
|
|
698
909
|
return raw;
|
|
699
910
|
}
|
|
700
911
|
function applySummaryToState(summary, state, inputs) {
|
|
701
|
-
const { explicitLevels, explicitEager, explicitEndpoint } = inputs;
|
|
912
|
+
const { explicitLevels, explicitEager, explicitEndpoint, warnings } = inputs;
|
|
702
913
|
const schemeVersion = summary.schemeVersion ?? 1;
|
|
703
|
-
if (schemeVersion > 1) {
|
|
914
|
+
if (schemeVersion > 1 && warnings) {
|
|
704
915
|
console.warn(
|
|
705
916
|
`[route-forge] backend schemeVersion=${schemeVersion} > client supported 1; some features may be unavailable`
|
|
706
917
|
);
|
|
707
918
|
}
|
|
708
919
|
if (summary.config.endpoint_prefix && summary.config.endpoint_prefix !== explicitEndpoint) {
|
|
709
|
-
if (explicitEndpoint) {
|
|
920
|
+
if (explicitEndpoint && warnings) {
|
|
710
921
|
console.warn(
|
|
711
922
|
`[route-forge] backend endpoint_prefix "${summary.config.endpoint_prefix}" overrides frontend endpoint "${explicitEndpoint}"`
|
|
712
923
|
);
|
|
@@ -729,7 +940,7 @@ function applySummaryToState(summary, state, inputs) {
|
|
|
729
940
|
if (explicitLevels && explicitLevels.length > 0) {
|
|
730
941
|
const intersection = explicitLevels.filter((l) => backendLevels.includes(l));
|
|
731
942
|
const removed = explicitLevels.filter((l) => !backendLevels.includes(l));
|
|
732
|
-
if (removed.length > 0) {
|
|
943
|
+
if (removed.length > 0 && warnings) {
|
|
733
944
|
console.warn(
|
|
734
945
|
`[route-forge] levels not in backend summary and dropped: ${removed.join(", ")}`
|
|
735
946
|
);
|
|
@@ -778,23 +989,18 @@ var RouteStore = class {
|
|
|
778
989
|
this.fetchMeta = deps.fetchMeta;
|
|
779
990
|
this.autoDiscoveryPromise = deps.autoDiscoveryPromise;
|
|
780
991
|
this.getAutoDiscoveryError = deps.getAutoDiscoveryError;
|
|
992
|
+
this.onChange = deps.onChange;
|
|
781
993
|
}
|
|
782
994
|
assertLevelDeclared(level) {
|
|
783
995
|
if (!this.state.levels.includes(level)) {
|
|
784
|
-
throw new UnknownLevelError(level);
|
|
996
|
+
throw new UnknownLevelError(level, this.state.levels);
|
|
785
997
|
}
|
|
786
998
|
}
|
|
787
999
|
async fetchLevel(level) {
|
|
788
1000
|
const uri = this.state.levelRoutes[level]?.uri;
|
|
789
|
-
const url = uri ?
|
|
1001
|
+
const url = uri ? joinBaseAndPath(this.baseURL, uri) : buildUrl(level, { baseURL: this.baseURL, endpoint: this.state.endpoint });
|
|
790
1002
|
return await this.fetchMeta(`route-forge.${level}`, url, level);
|
|
791
1003
|
}
|
|
792
|
-
/** baseURL 与后端下发的绝对 path 拼接(规范化斜杠),与 buildUrl 的 base 处理一致 */
|
|
793
|
-
joinBaseAndPath(baseURL, path) {
|
|
794
|
-
const base = baseURL.endsWith("/") ? baseURL.slice(0, -1) : baseURL;
|
|
795
|
-
const p = path.startsWith("/") ? path : `/${path}`;
|
|
796
|
-
return `${base}${p}`;
|
|
797
|
-
}
|
|
798
1004
|
async loadOne(level) {
|
|
799
1005
|
const autoDiscoveryError = this.getAutoDiscoveryError();
|
|
800
1006
|
if (autoDiscoveryError) throw autoDiscoveryError;
|
|
@@ -809,6 +1015,7 @@ var RouteStore = class {
|
|
|
809
1015
|
const resp = await this.fetchLevel(level);
|
|
810
1016
|
if ((this.invalidationGens.get(level) ?? 0) === gen) {
|
|
811
1017
|
this.cache.set(resp, this.state.cacheTtl);
|
|
1018
|
+
this.onChange?.(level);
|
|
812
1019
|
}
|
|
813
1020
|
} finally {
|
|
814
1021
|
this.inflight.delete(level);
|
|
@@ -822,6 +1029,36 @@ var RouteStore = class {
|
|
|
822
1029
|
const list = Array.isArray(level) ? level : [level];
|
|
823
1030
|
await Promise.all(list.map((l) => this.loadOne(l)));
|
|
824
1031
|
}
|
|
1032
|
+
/**
|
|
1033
|
+
* 强制刷新:跳过缓存命中短路重新拉取指定层级,成功后覆盖缓存、失败保留旧值。
|
|
1034
|
+
* 与 invalidate→load 的区别——全程不清空缓存,故读取旧数据不受影响、无空窗。
|
|
1035
|
+
*/
|
|
1036
|
+
async revalidate(level) {
|
|
1037
|
+
await this.autoDiscoveryPromise;
|
|
1038
|
+
const list = Array.isArray(level) ? level : [level];
|
|
1039
|
+
await Promise.all(list.map((l) => this.revalidateOne(l)));
|
|
1040
|
+
}
|
|
1041
|
+
async revalidateOne(level) {
|
|
1042
|
+
const autoDiscoveryError = this.getAutoDiscoveryError();
|
|
1043
|
+
if (autoDiscoveryError) throw autoDiscoveryError;
|
|
1044
|
+
this.assertLevelDeclared(level);
|
|
1045
|
+
const existing = this.inflight.get(level);
|
|
1046
|
+
if (existing) return existing;
|
|
1047
|
+
const gen = this.invalidationGens.get(level) ?? 0;
|
|
1048
|
+
const p = (async () => {
|
|
1049
|
+
try {
|
|
1050
|
+
const resp = await this.fetchLevel(level);
|
|
1051
|
+
if ((this.invalidationGens.get(level) ?? 0) === gen) {
|
|
1052
|
+
this.cache.set(resp, this.state.cacheTtl);
|
|
1053
|
+
this.onChange?.(level);
|
|
1054
|
+
}
|
|
1055
|
+
} finally {
|
|
1056
|
+
this.inflight.delete(level);
|
|
1057
|
+
}
|
|
1058
|
+
})();
|
|
1059
|
+
this.inflight.set(level, p);
|
|
1060
|
+
return p;
|
|
1061
|
+
}
|
|
825
1062
|
invalidate(level) {
|
|
826
1063
|
if (level === void 0) {
|
|
827
1064
|
this.cache.clear();
|
|
@@ -829,16 +1066,19 @@ var RouteStore = class {
|
|
|
829
1066
|
for (const lvl of this.state.levels) {
|
|
830
1067
|
this.invalidationGens.set(lvl, (this.invalidationGens.get(lvl) ?? 0) + 1);
|
|
831
1068
|
}
|
|
1069
|
+
for (const lvl of this.state.levels) this.onChange?.(lvl);
|
|
832
1070
|
} else if (Array.isArray(level)) {
|
|
833
1071
|
for (const lvl of level) {
|
|
834
1072
|
this.cache.del(lvl);
|
|
835
1073
|
this.inflight.delete(lvl);
|
|
836
1074
|
this.invalidationGens.set(lvl, (this.invalidationGens.get(lvl) ?? 0) + 1);
|
|
837
1075
|
}
|
|
1076
|
+
for (const lvl of level) this.onChange?.(lvl);
|
|
838
1077
|
} else {
|
|
839
1078
|
this.cache.del(level);
|
|
840
1079
|
this.inflight.delete(level);
|
|
841
1080
|
this.invalidationGens.set(level, (this.invalidationGens.get(level) ?? 0) + 1);
|
|
1081
|
+
this.onChange?.(level);
|
|
842
1082
|
}
|
|
843
1083
|
}
|
|
844
1084
|
isLoaded(level) {
|
|
@@ -853,13 +1093,24 @@ var RouteStore = class {
|
|
|
853
1093
|
}
|
|
854
1094
|
return void 0;
|
|
855
1095
|
}
|
|
1096
|
+
/**
|
|
1097
|
+
* 该层级当前已加载的路由名列表(不深拷贝,仅读键名)。
|
|
1098
|
+
* 供错误候选与 prefix 解析使用——避免为取名字而 getRoutes 深拷贝整表。
|
|
1099
|
+
* 层级未声明抛 UnknownLevelError(与 getRoutes/route 一致)。
|
|
1100
|
+
*/
|
|
1101
|
+
routeNames(level) {
|
|
1102
|
+
this.assertLevelDeclared(level);
|
|
1103
|
+
const entry = this.cache.get(level);
|
|
1104
|
+
return entry ? Object.keys(entry.routes) : [];
|
|
1105
|
+
}
|
|
856
1106
|
getRoutes(level) {
|
|
857
1107
|
if (level !== void 0) {
|
|
1108
|
+
this.assertLevelDeclared(level);
|
|
858
1109
|
const entry = this.cache.get(level);
|
|
859
1110
|
const routes = entry?.routes ?? {};
|
|
860
1111
|
const result2 = {};
|
|
861
1112
|
for (const [k, v] of Object.entries(routes)) {
|
|
862
|
-
result2[k] =
|
|
1113
|
+
result2[k] = structuredClone(v);
|
|
863
1114
|
}
|
|
864
1115
|
return result2;
|
|
865
1116
|
}
|
|
@@ -869,7 +1120,7 @@ var RouteStore = class {
|
|
|
869
1120
|
if (entry) {
|
|
870
1121
|
const levelRoutes = {};
|
|
871
1122
|
for (const [k, v] of Object.entries(entry.routes)) {
|
|
872
|
-
levelRoutes[k] =
|
|
1123
|
+
levelRoutes[k] = structuredClone(v);
|
|
873
1124
|
}
|
|
874
1125
|
result[lvl] = levelRoutes;
|
|
875
1126
|
}
|
|
@@ -886,6 +1137,7 @@ function createHttpRunner(deps) {
|
|
|
886
1137
|
responseInterceptors,
|
|
887
1138
|
load,
|
|
888
1139
|
findRouteMeta,
|
|
1140
|
+
getRouteNames,
|
|
889
1141
|
baseURL,
|
|
890
1142
|
state,
|
|
891
1143
|
timeout,
|
|
@@ -937,7 +1189,10 @@ function createHttpRunner(deps) {
|
|
|
937
1189
|
level: resp.level,
|
|
938
1190
|
status: resp.status,
|
|
939
1191
|
url: resp.url,
|
|
940
|
-
method: resp.method
|
|
1192
|
+
method: resp.method,
|
|
1193
|
+
// 完整 ResponseData 随错误逐段传递(响应拦截器 onRejected 链 → 最终 catch),
|
|
1194
|
+
// 供调用方检查响应体,如 Laravel 422 校验错误 err.response.data.errors
|
|
1195
|
+
response: resp
|
|
941
1196
|
}
|
|
942
1197
|
);
|
|
943
1198
|
}
|
|
@@ -966,18 +1221,37 @@ function createHttpRunner(deps) {
|
|
|
966
1221
|
let ctrl;
|
|
967
1222
|
let abortedBeforeInit = false;
|
|
968
1223
|
let abortReason;
|
|
969
|
-
const
|
|
970
|
-
|
|
971
|
-
if (
|
|
972
|
-
ctrl.abort(
|
|
1224
|
+
const externalSignal = params.signal;
|
|
1225
|
+
const onExternalAbort = () => {
|
|
1226
|
+
if (ctrl) {
|
|
1227
|
+
ctrl.abort(externalSignal?.reason);
|
|
1228
|
+
} else {
|
|
1229
|
+
abortedBeforeInit = true;
|
|
1230
|
+
abortReason = externalSignal?.reason;
|
|
973
1231
|
}
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
1232
|
+
};
|
|
1233
|
+
if (externalSignal?.aborted) {
|
|
1234
|
+
abortedBeforeInit = true;
|
|
1235
|
+
abortReason = externalSignal.reason;
|
|
1236
|
+
} else {
|
|
1237
|
+
externalSignal?.addEventListener("abort", onExternalAbort, { once: true });
|
|
1238
|
+
}
|
|
1239
|
+
const work = (async () => {
|
|
1240
|
+
try {
|
|
1241
|
+
ctrl = new AbortController();
|
|
1242
|
+
if (abortedBeforeInit) {
|
|
1243
|
+
ctrl.abort(abortReason);
|
|
1244
|
+
}
|
|
1245
|
+
await autoDiscoveryPromise;
|
|
1246
|
+
await load(level);
|
|
1247
|
+
const meta = findRouteMeta(level, name);
|
|
1248
|
+
if (!meta) {
|
|
1249
|
+
throw new UnknownRouteError(name, level, getRouteNames(level));
|
|
1250
|
+
}
|
|
1251
|
+
return await doApiCall(meta, params, ctrl.signal);
|
|
1252
|
+
} finally {
|
|
1253
|
+
externalSignal?.removeEventListener("abort", onExternalAbort);
|
|
979
1254
|
}
|
|
980
|
-
return doApiCall(meta, params, ctrl.signal);
|
|
981
1255
|
})();
|
|
982
1256
|
const request = work;
|
|
983
1257
|
request.abort = () => {
|
|
@@ -992,22 +1266,31 @@ function createHttpRunner(deps) {
|
|
|
992
1266
|
}
|
|
993
1267
|
|
|
994
1268
|
// src/resolveRouteName.ts
|
|
1269
|
+
function stripTrailingSeparator(prefix, separator) {
|
|
1270
|
+
let normalized = prefix;
|
|
1271
|
+
while (normalized.endsWith(separator)) {
|
|
1272
|
+
normalized = normalized.slice(0, -separator.length);
|
|
1273
|
+
}
|
|
1274
|
+
return normalized;
|
|
1275
|
+
}
|
|
995
1276
|
async function resolveRouteName(forge, level, prefix, suffix, separator = ".") {
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
1277
|
+
const normalized = stripTrailingSeparator(prefix, separator);
|
|
1278
|
+
if (!suffix) return normalized;
|
|
1279
|
+
const joined = `${normalized}${separator}${suffix}`;
|
|
1280
|
+
if (!suffix.startsWith(`${normalized}${separator}`)) return joined;
|
|
999
1281
|
await forge.load(level);
|
|
1000
1282
|
if (forge.hasRoute(level, joined)) return joined;
|
|
1001
1283
|
if (forge.hasRoute(level, suffix)) return suffix;
|
|
1002
|
-
throw new UnknownRouteError(joined, level);
|
|
1284
|
+
throw new UnknownRouteError(joined, level, forge.getRouteNames?.(level));
|
|
1003
1285
|
}
|
|
1004
1286
|
function resolveRouteNameSync(forge, level, prefix, suffix, separator = ".") {
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1287
|
+
const normalized = stripTrailingSeparator(prefix, separator);
|
|
1288
|
+
if (!suffix) return normalized;
|
|
1289
|
+
const joined = `${normalized}${separator}${suffix}`;
|
|
1290
|
+
if (!suffix.startsWith(`${normalized}${separator}`)) return joined;
|
|
1008
1291
|
if (forge.hasRoute(level, joined)) return joined;
|
|
1009
1292
|
if (forge.hasRoute(level, suffix)) return suffix;
|
|
1010
|
-
throw new UnknownRouteError(joined, level);
|
|
1293
|
+
throw new UnknownRouteError(joined, level, forge.getRouteNames?.(level));
|
|
1011
1294
|
}
|
|
1012
1295
|
|
|
1013
1296
|
// src/defineImmutableProps.ts
|
|
@@ -1026,8 +1309,8 @@ function defineImmutableProps(target, props) {
|
|
|
1026
1309
|
|
|
1027
1310
|
// src/bound-forge.ts
|
|
1028
1311
|
function createBoundForgeFactory(deps) {
|
|
1029
|
-
const { load, api, route, hasRoute, getRoutes, invalidate, isLoaded, loadingTracker } = deps;
|
|
1030
|
-
const resolver = { load, hasRoute };
|
|
1312
|
+
const { load, api, route, hasRoute, getRoutes, getRouteNames, invalidate, isLoaded, loadingTracker } = deps;
|
|
1313
|
+
const resolver = { load, hasRoute, getRouteNames };
|
|
1031
1314
|
function createBoundForge(level, prefix) {
|
|
1032
1315
|
const levelLoadedPromise = load(level);
|
|
1033
1316
|
levelLoadedPromise.catch(() => {
|
|
@@ -1093,11 +1376,6 @@ var DEFAULT_TIMEOUT = 3e4;
|
|
|
1093
1376
|
var DEFAULT_CACHE_TTL = 3600;
|
|
1094
1377
|
function createRouteForge(options = {}) {
|
|
1095
1378
|
const bootstrapSummary = readEmbeddedSummary() ?? options.summary ?? null;
|
|
1096
|
-
if (!bootstrapSummary && !options.endpoint) {
|
|
1097
|
-
throw new TypeError(
|
|
1098
|
-
"createRouteForge: \u9700\u8981 options.endpoint\uFF0C\u6216 options.summary\uFF0C\u6216\u9875\u9762\u5185\u5D4C window.__ROUTE_FORGE__"
|
|
1099
|
-
);
|
|
1100
|
-
}
|
|
1101
1379
|
const {
|
|
1102
1380
|
adapter = "auto",
|
|
1103
1381
|
timeout = DEFAULT_TIMEOUT,
|
|
@@ -1105,78 +1383,49 @@ function createRouteForge(options = {}) {
|
|
|
1105
1383
|
interceptors: declarativeInterceptors,
|
|
1106
1384
|
cache: cacheOpts = {}
|
|
1107
1385
|
} = options;
|
|
1386
|
+
const warnings = options.warnings ?? true;
|
|
1108
1387
|
const loadingTracker = new LoadingTracker();
|
|
1388
|
+
const routesTracker = new RouteChangeTracker();
|
|
1109
1389
|
const explicitLevels = options.levels;
|
|
1110
1390
|
const explicitEager = options.eager;
|
|
1111
1391
|
const explicitEndpoint = options.endpoint;
|
|
1112
1392
|
const discoveryState = {
|
|
1113
1393
|
levels: explicitLevels ?? [],
|
|
1114
1394
|
eager: explicitEager ?? [],
|
|
1115
|
-
endpoint: explicitEndpoint ?? bootstrapSummary?.config?.endpoint_prefix ??
|
|
1395
|
+
endpoint: explicitEndpoint ?? bootstrapSummary?.config?.endpoint_prefix ?? DEFAULT_ENDPOINT,
|
|
1116
1396
|
urlPrefix: "",
|
|
1117
1397
|
cacheTtl: void 0,
|
|
1118
1398
|
levelRoutes: {}
|
|
1119
1399
|
};
|
|
1120
|
-
const discoveryInputs = { explicitLevels, explicitEager, explicitEndpoint };
|
|
1400
|
+
const discoveryInputs = { explicitLevels, explicitEager, explicitEndpoint, warnings };
|
|
1121
1401
|
let autoDiscoveryCompleted = false;
|
|
1122
|
-
|
|
1123
|
-
let rejectReady;
|
|
1124
|
-
const readyPromise = new Promise((resolve, reject) => {
|
|
1125
|
-
resolveReady = resolve;
|
|
1126
|
-
rejectReady = reject;
|
|
1127
|
-
});
|
|
1128
|
-
readyPromise.catch(() => {
|
|
1129
|
-
});
|
|
1402
|
+
const latch = createReadyLatch();
|
|
1130
1403
|
const cacheTtl = cacheOpts.ttl ?? DEFAULT_CACHE_TTL;
|
|
1131
1404
|
const cacheStorage = cacheOpts.storage ?? "memory";
|
|
1132
1405
|
const cache = new RouteCache({ storage: cacheStorage, ttl: cacheTtl });
|
|
1133
1406
|
const requestInterceptors = new InterceptorManagerImpl();
|
|
1134
1407
|
const responseInterceptors = new InterceptorManagerImpl();
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
const [onFulfilled, onRejected] = entry;
|
|
1141
|
-
requestInterceptors.use(onFulfilled, onRejected);
|
|
1142
|
-
}
|
|
1143
|
-
}
|
|
1408
|
+
const reqDecl = normalizeInterceptorDeclaration(
|
|
1409
|
+
declarativeInterceptors?.request
|
|
1410
|
+
);
|
|
1411
|
+
if (reqDecl.onFulfilled || reqDecl.onRejected) {
|
|
1412
|
+
requestInterceptors.use(reqDecl.onFulfilled, reqDecl.onRejected);
|
|
1144
1413
|
}
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
const [onFulfilled, onRejected] = entry;
|
|
1151
|
-
responseInterceptors.use(onFulfilled, onRejected);
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1414
|
+
const resDecl = normalizeInterceptorDeclaration(
|
|
1415
|
+
declarativeInterceptors?.response
|
|
1416
|
+
);
|
|
1417
|
+
if (resDecl.onFulfilled || resDecl.onRejected) {
|
|
1418
|
+
responseInterceptors.use(resDecl.onFulfilled, resDecl.onRejected);
|
|
1154
1419
|
}
|
|
1155
|
-
const
|
|
1420
|
+
const ensureAdapter = createAdapterBootstrap({
|
|
1156
1421
|
adapter,
|
|
1157
|
-
|
|
1422
|
+
requestInterceptors,
|
|
1423
|
+
responseInterceptors,
|
|
1424
|
+
warnings
|
|
1158
1425
|
});
|
|
1159
|
-
let adapterResolved = false;
|
|
1160
|
-
let adapterObj = null;
|
|
1161
|
-
async function ensureAdapter() {
|
|
1162
|
-
if (!adapterResolved) {
|
|
1163
|
-
adapterObj = await adapterPromise.catch((e) => {
|
|
1164
|
-
if (e instanceof AdapterNotFoundError) throw e;
|
|
1165
|
-
return resolveAdapter({
|
|
1166
|
-
adapter: "builtin",
|
|
1167
|
-
forgeInterceptors: { request: requestInterceptors, response: responseInterceptors }
|
|
1168
|
-
});
|
|
1169
|
-
});
|
|
1170
|
-
adapterResolved = true;
|
|
1171
|
-
}
|
|
1172
|
-
return adapterObj;
|
|
1173
|
-
}
|
|
1174
1426
|
function assertDiscoveryReady() {
|
|
1175
1427
|
if (!autoDiscoveryCompleted && !explicitLevels?.length) {
|
|
1176
|
-
throw new
|
|
1177
|
-
"Route data not available. Auto-discovery has not completed. Use forge.ready() or forge.use(level) first.",
|
|
1178
|
-
{ code: "RF_FE_010" }
|
|
1179
|
-
);
|
|
1428
|
+
throw new DiscoveryNotReadyError();
|
|
1180
1429
|
}
|
|
1181
1430
|
}
|
|
1182
1431
|
async function fetchMeta(routeTag, url, level = "") {
|
|
@@ -1201,8 +1450,8 @@ function createRouteForge(options = {}) {
|
|
|
1201
1450
|
const resp = await doRawRequest(config);
|
|
1202
1451
|
if (!resp || resp.status < 200 || resp.status >= 300) {
|
|
1203
1452
|
throw new HTTPError(
|
|
1204
|
-
`Failed to fetch "${routeTag}": HTTP ${resp?.status}`,
|
|
1205
|
-
{ level, status: resp?.status, url, method: "GET" }
|
|
1453
|
+
`Failed to fetch "${routeTag}" (${url}): HTTP ${resp?.status}`,
|
|
1454
|
+
{ level, status: resp?.status, url, method: "GET", response: resp ?? void 0 }
|
|
1206
1455
|
);
|
|
1207
1456
|
}
|
|
1208
1457
|
return resp.data;
|
|
@@ -1230,22 +1479,25 @@ function createRouteForge(options = {}) {
|
|
|
1230
1479
|
baseURL,
|
|
1231
1480
|
fetchMeta,
|
|
1232
1481
|
autoDiscoveryPromise,
|
|
1233
|
-
getAutoDiscoveryError: () => autoDiscoveryError
|
|
1482
|
+
getAutoDiscoveryError: () => autoDiscoveryError,
|
|
1483
|
+
onChange: (level) => routesTracker.notify(level)
|
|
1234
1484
|
});
|
|
1235
1485
|
const load = (level) => store.load(level);
|
|
1486
|
+
const revalidate = (level) => store.revalidate(level);
|
|
1236
1487
|
const findRouteMeta = (level, name) => store.findRouteMeta(level, name);
|
|
1237
1488
|
const invalidate = (level) => store.invalidate(level);
|
|
1238
1489
|
const isLoaded = (level) => store.isLoaded(level);
|
|
1239
|
-
|
|
1240
|
-
|
|
1490
|
+
const getRoutes = store.getRoutes.bind(store);
|
|
1491
|
+
function getLevels() {
|
|
1492
|
+
return [...discoveryState.levels];
|
|
1241
1493
|
}
|
|
1242
1494
|
function route(level, name, params) {
|
|
1243
1495
|
assertDiscoveryReady();
|
|
1244
1496
|
const meta = findRouteMeta(level, name);
|
|
1245
1497
|
if (!meta) {
|
|
1246
|
-
throw new UnknownRouteError(name, level);
|
|
1498
|
+
throw new UnknownRouteError(name, level, store.routeNames(level));
|
|
1247
1499
|
}
|
|
1248
|
-
return
|
|
1500
|
+
return buildRouteUrl(meta, params ?? {}, { baseURL, urlPrefix: discoveryState.urlPrefix });
|
|
1249
1501
|
}
|
|
1250
1502
|
const api = createHttpRunner({
|
|
1251
1503
|
ensureAdapter,
|
|
@@ -1253,6 +1505,7 @@ function createRouteForge(options = {}) {
|
|
|
1253
1505
|
responseInterceptors,
|
|
1254
1506
|
load,
|
|
1255
1507
|
findRouteMeta,
|
|
1508
|
+
getRouteNames: (lvl) => store.routeNames(lvl),
|
|
1256
1509
|
baseURL,
|
|
1257
1510
|
state: discoveryState,
|
|
1258
1511
|
timeout,
|
|
@@ -1278,23 +1531,17 @@ function createRouteForge(options = {}) {
|
|
|
1278
1531
|
});
|
|
1279
1532
|
}
|
|
1280
1533
|
}).then(() => {
|
|
1281
|
-
|
|
1534
|
+
latch.resolve(forgeInstance);
|
|
1282
1535
|
}).catch((e) => {
|
|
1283
|
-
|
|
1536
|
+
latch.reject(e);
|
|
1284
1537
|
});
|
|
1285
|
-
function ready(onFulfilled, onRejected) {
|
|
1286
|
-
if (onFulfilled) {
|
|
1287
|
-
const p = readyPromise.then(onFulfilled, onRejected);
|
|
1288
|
-
return p.then(() => forgeInstance);
|
|
1289
|
-
}
|
|
1290
|
-
return readyPromise;
|
|
1291
|
-
}
|
|
1292
1538
|
const createBoundForgeWithMethods = createBoundForgeFactory({
|
|
1293
1539
|
load,
|
|
1294
1540
|
api,
|
|
1295
1541
|
route,
|
|
1296
1542
|
hasRoute,
|
|
1297
1543
|
getRoutes,
|
|
1544
|
+
getRouteNames: (lvl) => store.routeNames(lvl),
|
|
1298
1545
|
invalidate,
|
|
1299
1546
|
isLoaded,
|
|
1300
1547
|
loadingTracker
|
|
@@ -1302,19 +1549,24 @@ function createRouteForge(options = {}) {
|
|
|
1302
1549
|
const forgeInstance = {
|
|
1303
1550
|
api,
|
|
1304
1551
|
load,
|
|
1552
|
+
revalidate,
|
|
1305
1553
|
route,
|
|
1306
1554
|
url: route,
|
|
1307
1555
|
invalidate,
|
|
1308
1556
|
isLoaded,
|
|
1309
1557
|
hasRoute,
|
|
1310
1558
|
getRoutes,
|
|
1559
|
+
getLevels,
|
|
1560
|
+
warnings,
|
|
1311
1561
|
isLoading: () => loadingTracker.isLoading(),
|
|
1562
|
+
isReady: () => latch.isReady(),
|
|
1312
1563
|
onLoadingChange: (cb) => loadingTracker.subscribe(cb),
|
|
1564
|
+
onRoutesChange: (cb) => routesTracker.subscribe(cb),
|
|
1313
1565
|
interceptors: {
|
|
1314
1566
|
request: requestInterceptors,
|
|
1315
1567
|
response: responseInterceptors
|
|
1316
1568
|
},
|
|
1317
|
-
ready,
|
|
1569
|
+
ready: latch.ready,
|
|
1318
1570
|
use(level, prefix) {
|
|
1319
1571
|
if (level === void 0) return forgeInstance;
|
|
1320
1572
|
return createBoundForgeWithMethods(level, prefix);
|
|
@@ -1324,15 +1576,18 @@ function createRouteForge(options = {}) {
|
|
|
1324
1576
|
}
|
|
1325
1577
|
|
|
1326
1578
|
exports.AdapterNotFoundError = AdapterNotFoundError;
|
|
1579
|
+
exports.DiscoveryNotReadyError = DiscoveryNotReadyError;
|
|
1327
1580
|
exports.ForgeError = ForgeError;
|
|
1328
1581
|
exports.HTTPError = HTTPError;
|
|
1329
1582
|
exports.InterceptorManagerImpl = InterceptorManagerImpl;
|
|
1330
1583
|
exports.InvalidInterceptorReturnError = InvalidInterceptorReturnError;
|
|
1584
|
+
exports.InvalidPathParamError = InvalidPathParamError;
|
|
1331
1585
|
exports.LoadingTracker = LoadingTracker;
|
|
1332
1586
|
exports.MissingRouteParamError = MissingRouteParamError;
|
|
1333
1587
|
exports.NetworkError = NetworkError;
|
|
1334
1588
|
exports.RequestAbortedError = RequestAbortedError;
|
|
1335
1589
|
exports.RouteCache = RouteCache;
|
|
1590
|
+
exports.RouteChangeTracker = RouteChangeTracker;
|
|
1336
1591
|
exports.UnknownLevelError = UnknownLevelError;
|
|
1337
1592
|
exports.UnknownRouteError = UnknownRouteError;
|
|
1338
1593
|
exports.createInterceptorManager = createInterceptorManager;
|