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