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