@route-forge/core 1.4.0 → 2.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 +10 -6
- package/dist/codegen.d.cts +1 -1
- package/dist/codegen.d.ts +1 -1
- package/dist/index.cjs +618 -508
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +61 -50
- package/dist/index.d.ts +61 -50
- package/dist/index.js +618 -508
- package/dist/index.js.map +1 -1
- package/dist/route-forge.global.js +618 -508
- package/dist/route-forge.global.js.map +1 -1
- package/dist/route-forge.global.min.js +2 -2
- package/dist/{types-BFlrOTrN.d.cts → types-C51rXaiY.d.cts} +6 -17
- package/dist/{types-BFlrOTrN.d.ts → types-C51rXaiY.d.ts} +6 -17
- package/package.json +1 -1
|
@@ -17,9 +17,18 @@ var RouteForge = (function (exports) {
|
|
|
17
17
|
var RouteCache = class {
|
|
18
18
|
constructor(opts) {
|
|
19
19
|
this.memory = /* @__PURE__ */ new Map();
|
|
20
|
+
/** 其他 tab 修改 storage 时失效对应镜像(storage 事件:自己的写不触发,自己的写经 set/del 已同步) */
|
|
21
|
+
this.onStorageEvent = (e) => {
|
|
22
|
+
if (!e.key) return;
|
|
23
|
+
if (!e.key.startsWith(KEY_PREFIX)) return;
|
|
24
|
+
this.memory.delete(e.key.slice(KEY_PREFIX.length));
|
|
25
|
+
};
|
|
20
26
|
this.storage = opts.storage;
|
|
21
27
|
this.fallbackTtl = opts.ttl;
|
|
22
28
|
this.backend = pickStorage(opts.storage);
|
|
29
|
+
if (this.backend && typeof globalThis.addEventListener === "function") {
|
|
30
|
+
globalThis.addEventListener("storage", this.onStorageEvent);
|
|
31
|
+
}
|
|
23
32
|
}
|
|
24
33
|
key(level) {
|
|
25
34
|
return `${KEY_PREFIX}${level}`;
|
|
@@ -28,6 +37,8 @@ var RouteForge = (function (exports) {
|
|
|
28
37
|
if (this.storage === "memory" || !this.backend) {
|
|
29
38
|
return this.getFromMemory(level);
|
|
30
39
|
}
|
|
40
|
+
const mirrored = this.getFromMemory(level);
|
|
41
|
+
if (mirrored) return mirrored;
|
|
31
42
|
const raw = this.backend.getItem(this.key(level));
|
|
32
43
|
if (raw) {
|
|
33
44
|
try {
|
|
@@ -36,12 +47,13 @@ var RouteForge = (function (exports) {
|
|
|
36
47
|
this.del(level);
|
|
37
48
|
return void 0;
|
|
38
49
|
}
|
|
50
|
+
this.memory.set(level, entry);
|
|
39
51
|
return entry;
|
|
40
52
|
} catch {
|
|
41
53
|
return void 0;
|
|
42
54
|
}
|
|
43
55
|
}
|
|
44
|
-
return
|
|
56
|
+
return void 0;
|
|
45
57
|
}
|
|
46
58
|
getFromMemory(level) {
|
|
47
59
|
const entry = this.memory.get(level);
|
|
@@ -65,14 +77,13 @@ var RouteForge = (function (exports) {
|
|
|
65
77
|
ttl,
|
|
66
78
|
cachedAt: Date.now()
|
|
67
79
|
};
|
|
80
|
+
this.memory.set(resp.level, entry);
|
|
68
81
|
if (this.storage === "memory" || !this.backend) {
|
|
69
|
-
this.memory.set(resp.level, entry);
|
|
70
82
|
return;
|
|
71
83
|
}
|
|
72
84
|
try {
|
|
73
85
|
this.backend.setItem(this.key(resp.level), JSON.stringify(entry));
|
|
74
86
|
} catch {
|
|
75
|
-
this.memory.set(resp.level, entry);
|
|
76
87
|
}
|
|
77
88
|
}
|
|
78
89
|
del(level) {
|
|
@@ -252,14 +263,14 @@ var RouteForge = (function (exports) {
|
|
|
252
263
|
return new InterceptorManagerImpl();
|
|
253
264
|
}
|
|
254
265
|
|
|
255
|
-
// src/adapters/
|
|
256
|
-
function
|
|
257
|
-
if (
|
|
258
|
-
return typeof FormData !== "undefined" && body instanceof FormData || typeof URLSearchParams !== "undefined" && body instanceof URLSearchParams || typeof Blob !== "undefined" && body instanceof Blob || typeof ArrayBuffer !== "undefined" && (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) || typeof ReadableStream !== "undefined" && body instanceof ReadableStream;
|
|
259
|
-
}
|
|
260
|
-
function isAbortError(err) {
|
|
266
|
+
// src/adapters/fetch-core.ts
|
|
267
|
+
function isAbortError(err, signal) {
|
|
268
|
+
if (signal?.aborted) return true;
|
|
261
269
|
const e = err;
|
|
262
|
-
|
|
270
|
+
if (!e) return false;
|
|
271
|
+
if (e.name === "AbortError") return true;
|
|
272
|
+
if (e.code === "ERR_CANCELED") return true;
|
|
273
|
+
return false;
|
|
263
274
|
}
|
|
264
275
|
function combineSignals(...signals) {
|
|
265
276
|
const valid = signals.filter((s) => !!s);
|
|
@@ -278,82 +289,89 @@ var RouteForge = (function (exports) {
|
|
|
278
289
|
}
|
|
279
290
|
return controller.signal;
|
|
280
291
|
}
|
|
281
|
-
function
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
)
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
}
|
|
292
|
+
function isPassthroughBody(body) {
|
|
293
|
+
if (body === null) return false;
|
|
294
|
+
return typeof FormData !== "undefined" && body instanceof FormData || typeof URLSearchParams !== "undefined" && body instanceof URLSearchParams || typeof Blob !== "undefined" && body instanceof Blob || typeof ArrayBuffer !== "undefined" && (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) || typeof ReadableStream !== "undefined" && body instanceof ReadableStream;
|
|
295
|
+
}
|
|
296
|
+
async function rawFetch(config) {
|
|
297
|
+
const signal = combineSignals(
|
|
298
|
+
config.signal,
|
|
299
|
+
config.timeout && config.timeout > 0 ? AbortSignal.timeout(config.timeout) : void 0
|
|
300
|
+
);
|
|
301
|
+
let url = config.url;
|
|
302
|
+
if (config.paramsSerializer && config.params) {
|
|
303
|
+
const qs = config.paramsSerializer(config.params);
|
|
304
|
+
if (qs) {
|
|
305
|
+
url = url.includes("?") ? `${url}&${qs}` : `${url}?${qs}`;
|
|
295
306
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
307
|
+
}
|
|
308
|
+
const headers = new Headers(config.headers);
|
|
309
|
+
const fetchInit = {
|
|
310
|
+
method: config.method,
|
|
311
|
+
headers
|
|
312
|
+
};
|
|
313
|
+
if (signal) fetchInit.signal = signal;
|
|
314
|
+
if (config.body !== void 0 && !["GET", "HEAD"].includes(config.method.toUpperCase())) {
|
|
315
|
+
if (typeof config.body === "string" || isPassthroughBody(config.body)) {
|
|
316
|
+
fetchInit.body = config.body;
|
|
317
|
+
} else {
|
|
318
|
+
fetchInit.body = JSON.stringify(config.body);
|
|
319
|
+
if (!headers.has("Content-Type")) {
|
|
320
|
+
headers.set("Content-Type", "application/json");
|
|
310
321
|
}
|
|
311
322
|
}
|
|
312
|
-
|
|
323
|
+
}
|
|
324
|
+
let res;
|
|
325
|
+
try {
|
|
326
|
+
res = await fetch(url, fetchInit);
|
|
327
|
+
} catch (e) {
|
|
328
|
+
if (isAbortError(e)) throw e;
|
|
329
|
+
throw new NetworkError(
|
|
330
|
+
e instanceof Error ? e.message : String(e),
|
|
331
|
+
config.route,
|
|
332
|
+
config.level,
|
|
333
|
+
e
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
const text = await res.text();
|
|
337
|
+
let data = text;
|
|
338
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
339
|
+
if (contentType.includes("application/json")) {
|
|
313
340
|
try {
|
|
314
|
-
|
|
315
|
-
} catch
|
|
316
|
-
if (isAbortError(e)) throw e;
|
|
317
|
-
throw new NetworkError(
|
|
318
|
-
e instanceof Error ? e.message : String(e),
|
|
319
|
-
config.route,
|
|
320
|
-
config.level,
|
|
321
|
-
e
|
|
322
|
-
);
|
|
323
|
-
}
|
|
324
|
-
const text = await res.text();
|
|
325
|
-
let data = text;
|
|
326
|
-
const contentType = res.headers.get("content-type") ?? "";
|
|
327
|
-
if (contentType.includes("application/json")) {
|
|
328
|
-
try {
|
|
329
|
-
data = JSON.parse(text);
|
|
330
|
-
} catch {
|
|
331
|
-
}
|
|
341
|
+
data = JSON.parse(text);
|
|
342
|
+
} catch {
|
|
332
343
|
}
|
|
333
|
-
|
|
344
|
+
}
|
|
345
|
+
const responseData = {
|
|
346
|
+
route: config.route,
|
|
347
|
+
level: config.level,
|
|
348
|
+
method: config.method,
|
|
349
|
+
url,
|
|
350
|
+
status: res.status,
|
|
351
|
+
headers: res.headers,
|
|
352
|
+
data,
|
|
353
|
+
config
|
|
354
|
+
};
|
|
355
|
+
if (res.status >= 200 && res.status < 300) {
|
|
356
|
+
return responseData;
|
|
357
|
+
}
|
|
358
|
+
throw new HTTPError(
|
|
359
|
+
`HTTP ${res.status} for route "${config.route}" (${config.method} ${url})`,
|
|
360
|
+
{
|
|
334
361
|
route: config.route,
|
|
335
362
|
level: config.level,
|
|
336
|
-
method: config.method,
|
|
337
|
-
url,
|
|
338
363
|
status: res.status,
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
config
|
|
342
|
-
};
|
|
343
|
-
if (res.status >= 200 && res.status < 300) {
|
|
344
|
-
return responseData;
|
|
364
|
+
url,
|
|
365
|
+
method: config.method
|
|
345
366
|
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
}
|
|
355
|
-
);
|
|
356
|
-
}
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// src/adapters/builtin-http.ts
|
|
371
|
+
function createBuiltinHttp(forgeInterceptors) {
|
|
372
|
+
const requestMgr = forgeInterceptors?.request ?? createInterceptorManager();
|
|
373
|
+
const responseMgr = forgeInterceptors?.response ?? createInterceptorManager();
|
|
374
|
+
const requestRaw = rawFetch;
|
|
357
375
|
async function request(config) {
|
|
358
376
|
const finalConfig = await runRequestInterceptors(requestMgr, config);
|
|
359
377
|
const source = Promise.resolve(finalConfig).then(requestRaw);
|
|
@@ -473,39 +491,6 @@ var RouteForge = (function (exports) {
|
|
|
473
491
|
return createBuiltinHttp(opts.forgeInterceptors);
|
|
474
492
|
}
|
|
475
493
|
|
|
476
|
-
// src/resolveRouteName.ts
|
|
477
|
-
async function resolveRouteName(forge, level, prefix, suffix, separator = ".") {
|
|
478
|
-
if (!suffix) return prefix;
|
|
479
|
-
const joined = `${prefix}${separator}${suffix}`;
|
|
480
|
-
if (!suffix.startsWith(`${prefix}${separator}`)) return joined;
|
|
481
|
-
await forge.load(level);
|
|
482
|
-
if (forge.hasRoute(level, joined)) return joined;
|
|
483
|
-
if (forge.hasRoute(level, suffix)) return suffix;
|
|
484
|
-
throw new UnknownRouteError(joined, level);
|
|
485
|
-
}
|
|
486
|
-
function resolveRouteNameSync(forge, level, prefix, suffix, separator = ".") {
|
|
487
|
-
if (!suffix) return prefix;
|
|
488
|
-
const joined = `${prefix}${separator}${suffix}`;
|
|
489
|
-
if (!suffix.startsWith(`${prefix}${separator}`)) return joined;
|
|
490
|
-
if (forge.hasRoute(level, joined)) return joined;
|
|
491
|
-
if (forge.hasRoute(level, suffix)) return suffix;
|
|
492
|
-
throw new UnknownRouteError(joined, level);
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
// src/defineImmutableProps.ts
|
|
496
|
-
function defineImmutableProps(target, props) {
|
|
497
|
-
for (const key of Object.keys(props)) {
|
|
498
|
-
const val = props[key];
|
|
499
|
-
Object.defineProperty(target, key, {
|
|
500
|
-
value: val !== null && typeof val === "object" ? Object.freeze(val) : val,
|
|
501
|
-
writable: false,
|
|
502
|
-
enumerable: false,
|
|
503
|
-
configurable: false
|
|
504
|
-
});
|
|
505
|
-
}
|
|
506
|
-
return target;
|
|
507
|
-
}
|
|
508
|
-
|
|
509
494
|
// src/loading.ts
|
|
510
495
|
var LoadingTracker = class {
|
|
511
496
|
constructor() {
|
|
@@ -565,320 +550,308 @@ var RouteForge = (function (exports) {
|
|
|
565
550
|
}
|
|
566
551
|
};
|
|
567
552
|
|
|
568
|
-
// src/
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
const explicitLevels = options.levels;
|
|
583
|
-
const explicitEager = options.eager;
|
|
584
|
-
const explicitEndpoint = options.endpoint;
|
|
585
|
-
let effectiveLevels = explicitLevels ?? [];
|
|
586
|
-
let effectiveEager = explicitEager ?? [];
|
|
587
|
-
let effectiveEndpoint = explicitEndpoint;
|
|
588
|
-
let effectiveUrlPrefix = "";
|
|
589
|
-
let summaryUnassigned;
|
|
590
|
-
let backendHasUnassignedLevel = false;
|
|
591
|
-
const summaryPromise = (async () => {
|
|
592
|
-
try {
|
|
593
|
-
const summaryUrl = explicitEndpoint;
|
|
594
|
-
const resp = await fetch(summaryUrl, { method: "GET" });
|
|
595
|
-
if (!resp.ok) {
|
|
596
|
-
console.warn(
|
|
597
|
-
`[route-forge] summary endpoint ${summaryUrl} unreachable (HTTP ${resp.status}); falling back to explicit options`
|
|
598
|
-
);
|
|
599
|
-
return null;
|
|
600
|
-
}
|
|
601
|
-
return await resp.json();
|
|
602
|
-
} catch (e) {
|
|
603
|
-
if (explicitLevels && explicitLevels.length > 0) {
|
|
604
|
-
console.warn(
|
|
605
|
-
`[route-forge] summary endpoint unreachable: ${e.message}; using explicit levels`
|
|
606
|
-
);
|
|
607
|
-
return null;
|
|
608
|
-
}
|
|
609
|
-
throw new UnknownLevelError("(auto-discovery)");
|
|
610
|
-
}
|
|
611
|
-
})();
|
|
612
|
-
const autoDiscoveryPromise = summaryPromise.then((summary) => {
|
|
613
|
-
if (summary === null) {
|
|
614
|
-
return;
|
|
615
|
-
}
|
|
616
|
-
const schemaVersion = summary.schemaVersion ?? 1;
|
|
617
|
-
if (schemaVersion > 1) {
|
|
618
|
-
console.warn(
|
|
619
|
-
`[route-forge] backend schemaVersion=${schemaVersion} > client supported 1; some features may be unavailable`
|
|
620
|
-
);
|
|
621
|
-
}
|
|
622
|
-
if (summary.config.endpoint_prefix && summary.config.endpoint_prefix !== explicitEndpoint) {
|
|
623
|
-
console.warn(
|
|
624
|
-
`[route-forge] backend endpoint_prefix "${summary.config.endpoint_prefix}" overrides frontend endpoint "${explicitEndpoint}"`
|
|
625
|
-
);
|
|
626
|
-
effectiveEndpoint = summary.config.endpoint_prefix;
|
|
627
|
-
}
|
|
628
|
-
if (summary.config.url_prefix) {
|
|
629
|
-
effectiveUrlPrefix = summary.config.url_prefix.endsWith("/") ? summary.config.url_prefix.slice(0, -1) : summary.config.url_prefix;
|
|
630
|
-
}
|
|
631
|
-
const backendLevels = Object.keys(summary.levels);
|
|
632
|
-
backendHasUnassignedLevel = backendLevels.includes(UNASSIGNED_LEVEL);
|
|
633
|
-
if (Array.isArray(summary.unassigned) && summary.unassigned.length > 0 && !backendHasUnassignedLevel) {
|
|
634
|
-
summaryUnassigned = summary.unassigned;
|
|
635
|
-
}
|
|
636
|
-
const availableLevels = backendLevels.slice();
|
|
637
|
-
if (summaryUnassigned && !backendHasUnassignedLevel) {
|
|
638
|
-
availableLevels.push(UNASSIGNED_LEVEL);
|
|
553
|
+
// src/url-builder.ts
|
|
554
|
+
function buildUrl(level, ctx) {
|
|
555
|
+
const base = ctx.baseURL.endsWith("/") ? ctx.baseURL.slice(0, -1) : ctx.baseURL;
|
|
556
|
+
const ep = ctx.endpoint.startsWith("/") ? ctx.endpoint : `/${ctx.endpoint}`;
|
|
557
|
+
return `${base}${ep}/${encodeURIComponent(level)}`;
|
|
558
|
+
}
|
|
559
|
+
function buildRequestUrl(meta, params, ctx) {
|
|
560
|
+
const defaults = meta.parameter_defaults ?? {};
|
|
561
|
+
const missingRequired = [];
|
|
562
|
+
const values = {};
|
|
563
|
+
for (const p of meta.parameters) {
|
|
564
|
+
let v = params[p];
|
|
565
|
+
if ((v === void 0 || v === null) && p in defaults) {
|
|
566
|
+
v = defaults[p];
|
|
639
567
|
}
|
|
640
|
-
if (
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
if (removed.length > 0) {
|
|
644
|
-
console.warn(
|
|
645
|
-
`[route-forge] levels not in backend summary and dropped: ${removed.join(", ")}`
|
|
646
|
-
);
|
|
568
|
+
if (v === void 0 || v === null) {
|
|
569
|
+
if (!meta.uri.includes(`{${p}?}`)) {
|
|
570
|
+
missingRequired.push(p);
|
|
647
571
|
}
|
|
648
|
-
effectiveLevels = intersection;
|
|
649
572
|
} else {
|
|
650
|
-
|
|
573
|
+
values[p] = v;
|
|
651
574
|
}
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
575
|
+
}
|
|
576
|
+
if (missingRequired.length > 0) {
|
|
577
|
+
throw new MissingRouteParamError(meta.name, missingRequired);
|
|
578
|
+
}
|
|
579
|
+
let uri = meta.uri.replace(/\{([^{}]+)\}/g, (match, raw) => {
|
|
580
|
+
const optional = raw.endsWith("?");
|
|
581
|
+
const name = optional ? raw.slice(0, -1) : raw;
|
|
582
|
+
if (values[name] !== void 0) {
|
|
583
|
+
const val = values[name];
|
|
584
|
+
if (typeof val === "object") {
|
|
585
|
+
throw new ForgeError(
|
|
586
|
+
`Path parameter "${name}" must be a primitive value (string, number, boolean), got ${typeof val}`,
|
|
587
|
+
{ code: "RF_FE_003", route: meta.name, context: { param: name, value: val } }
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
return encodeURIComponent(String(val));
|
|
658
591
|
}
|
|
592
|
+
return optional ? "" : match;
|
|
659
593
|
});
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
const
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
const
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
if (
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
594
|
+
uri = uri.replace(/\/+/g, "/").replace(/\/$/, "");
|
|
595
|
+
if (/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(ctx.urlPrefix)) {
|
|
596
|
+
const prefix2 = ctx.urlPrefix.endsWith("/") ? ctx.urlPrefix.slice(0, -1) : ctx.urlPrefix;
|
|
597
|
+
return uri.startsWith("/") ? `${prefix2}${uri}` : `${prefix2}/${uri}`;
|
|
598
|
+
}
|
|
599
|
+
const base = ctx.baseURL.endsWith("/") ? ctx.baseURL.slice(0, -1) : ctx.baseURL;
|
|
600
|
+
const prefix = ctx.urlPrefix;
|
|
601
|
+
return uri.startsWith("/") ? `${base}${prefix}${uri}` : `${base}${prefix}/${uri}`;
|
|
602
|
+
}
|
|
603
|
+
function pickMethod(meta) {
|
|
604
|
+
const m = meta.methods.find((x) => x.toUpperCase() !== "HEAD");
|
|
605
|
+
return (m ?? meta.methods[0] ?? "GET").toUpperCase();
|
|
606
|
+
}
|
|
607
|
+
function appendQuery(url, query) {
|
|
608
|
+
if (!query) return url;
|
|
609
|
+
const usp = new URLSearchParams();
|
|
610
|
+
for (const [k, v] of Object.entries(query)) {
|
|
611
|
+
if (v === void 0 || v === null) continue;
|
|
612
|
+
usp.append(k, String(v));
|
|
613
|
+
}
|
|
614
|
+
const qs = usp.toString();
|
|
615
|
+
if (!qs) return url;
|
|
616
|
+
return url.includes("?") ? `${url}&${qs}` : `${url}?${qs}`;
|
|
617
|
+
}
|
|
618
|
+
function resolveApiParams(input) {
|
|
619
|
+
const {
|
|
620
|
+
params: explicitParams,
|
|
621
|
+
query: rawQuery,
|
|
622
|
+
body: rawBody,
|
|
623
|
+
headers: rawHeaders,
|
|
624
|
+
timeout: perCallTimeout,
|
|
625
|
+
...flatRest
|
|
626
|
+
} = input;
|
|
627
|
+
const pathParams = explicitParams ? { ...explicitParams } : {};
|
|
628
|
+
for (const [k, v] of Object.entries(flatRest)) {
|
|
629
|
+
if (!(k in pathParams)) {
|
|
630
|
+
pathParams[k] = v;
|
|
682
631
|
}
|
|
683
632
|
}
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
633
|
+
let query;
|
|
634
|
+
let body;
|
|
635
|
+
let headers;
|
|
636
|
+
if (rawQuery !== void 0) {
|
|
637
|
+
if (typeof rawQuery === "object" && rawQuery !== null) {
|
|
638
|
+
query = rawQuery;
|
|
639
|
+
} else if (!("query" in pathParams)) {
|
|
640
|
+
pathParams.query = rawQuery;
|
|
692
641
|
}
|
|
693
642
|
}
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
let adapterObj = null;
|
|
700
|
-
async function ensureAdapter() {
|
|
701
|
-
if (!adapterResolved) {
|
|
702
|
-
adapterObj = await adapterPromise.catch((e) => {
|
|
703
|
-
if (e instanceof AdapterNotFoundError) throw e;
|
|
704
|
-
return resolveAdapter({
|
|
705
|
-
adapter: "builtin",
|
|
706
|
-
forgeInterceptors: { request: requestInterceptors, response: responseInterceptors }
|
|
707
|
-
});
|
|
708
|
-
});
|
|
709
|
-
adapterResolved = true;
|
|
643
|
+
if (rawBody !== void 0) {
|
|
644
|
+
if (typeof rawBody !== "string" && typeof rawBody !== "number") {
|
|
645
|
+
body = rawBody;
|
|
646
|
+
} else if (!("body" in pathParams)) {
|
|
647
|
+
pathParams.body = rawBody;
|
|
710
648
|
}
|
|
711
|
-
return adapterObj;
|
|
712
649
|
}
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
if (!
|
|
717
|
-
|
|
650
|
+
if (rawHeaders !== void 0) {
|
|
651
|
+
if (typeof rawHeaders === "object" && rawHeaders !== null) {
|
|
652
|
+
headers = rawHeaders;
|
|
653
|
+
} else if (!("headers" in pathParams)) {
|
|
654
|
+
pathParams.headers = rawHeaders;
|
|
718
655
|
}
|
|
719
656
|
}
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
657
|
+
return { pathParams, query, body, headers, timeout: perCallTimeout };
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
// src/auto-discovery.ts
|
|
661
|
+
var UNASSIGNED_LEVEL = "unassigned";
|
|
662
|
+
async function fetchSummary(inputs, baseURL, fetchMeta) {
|
|
663
|
+
const { explicitLevels, explicitEndpoint } = inputs;
|
|
664
|
+
try {
|
|
665
|
+
const base = baseURL.endsWith("/") ? baseURL.slice(0, -1) : baseURL;
|
|
666
|
+
const ep = explicitEndpoint.startsWith("/") ? explicitEndpoint : `/${explicitEndpoint}`;
|
|
667
|
+
const data = await fetchMeta("__forge__.summary", `${base}${ep}`);
|
|
668
|
+
return data;
|
|
669
|
+
} catch (e) {
|
|
670
|
+
if (explicitLevels && explicitLevels.length > 0) {
|
|
671
|
+
console.warn(
|
|
672
|
+
`[route-forge] summary endpoint unreachable: ${e.message}; using explicit levels`
|
|
725
673
|
);
|
|
674
|
+
return null;
|
|
726
675
|
}
|
|
676
|
+
throw new UnknownLevelError("(auto-discovery)");
|
|
727
677
|
}
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
678
|
+
}
|
|
679
|
+
function applySummaryToState(summary, state, inputs) {
|
|
680
|
+
const { explicitLevels, explicitEager, explicitEndpoint } = inputs;
|
|
681
|
+
const schemaVersion = summary.schemaVersion ?? 1;
|
|
682
|
+
if (schemaVersion > 1) {
|
|
683
|
+
console.warn(
|
|
684
|
+
`[route-forge] backend schemaVersion=${schemaVersion} > client supported 1; some features may be unavailable`
|
|
685
|
+
);
|
|
732
686
|
}
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
const
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
687
|
+
if (summary.config.endpoint_prefix && summary.config.endpoint_prefix !== explicitEndpoint) {
|
|
688
|
+
console.warn(
|
|
689
|
+
`[route-forge] backend endpoint_prefix "${summary.config.endpoint_prefix}" overrides frontend endpoint "${explicitEndpoint}"`
|
|
690
|
+
);
|
|
691
|
+
state.endpoint = summary.config.endpoint_prefix;
|
|
692
|
+
}
|
|
693
|
+
if (summary.config.url_prefix) {
|
|
694
|
+
state.urlPrefix = summary.config.url_prefix.endsWith("/") ? summary.config.url_prefix.slice(0, -1) : summary.config.url_prefix;
|
|
695
|
+
}
|
|
696
|
+
const backendLevels = Object.keys(summary.levels);
|
|
697
|
+
state.backendHasUnassignedLevel = backendLevels.includes(UNASSIGNED_LEVEL);
|
|
698
|
+
if (Array.isArray(summary.unassigned) && summary.unassigned.length > 0 && !state.backendHasUnassignedLevel) {
|
|
699
|
+
state.summaryUnassigned = summary.unassigned;
|
|
700
|
+
}
|
|
701
|
+
const availableLevels = backendLevels.slice();
|
|
702
|
+
if (state.summaryUnassigned && !state.backendHasUnassignedLevel) {
|
|
703
|
+
availableLevels.push(UNASSIGNED_LEVEL);
|
|
704
|
+
}
|
|
705
|
+
if (explicitLevels && explicitLevels.length > 0) {
|
|
706
|
+
const intersection = explicitLevels.filter((l) => availableLevels.includes(l));
|
|
707
|
+
const removed = explicitLevels.filter((l) => !availableLevels.includes(l));
|
|
708
|
+
if (removed.length > 0) {
|
|
709
|
+
console.warn(
|
|
710
|
+
`[route-forge] levels not in backend summary and dropped: ${removed.join(", ")}`
|
|
757
711
|
);
|
|
758
712
|
}
|
|
759
|
-
|
|
760
|
-
|
|
713
|
+
state.levels = intersection;
|
|
714
|
+
} else {
|
|
715
|
+
state.levels = availableLevels;
|
|
716
|
+
}
|
|
717
|
+
const backendEager = backendLevels.filter((lvl) => summary.levels[lvl]?.load === "eager");
|
|
718
|
+
if (!explicitEager) {
|
|
719
|
+
state.eager = backendEager;
|
|
720
|
+
} else {
|
|
721
|
+
const union = /* @__PURE__ */ new Set([...backendEager, ...explicitEager]);
|
|
722
|
+
state.eager = [...union];
|
|
761
723
|
}
|
|
762
|
-
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// src/route-store.ts
|
|
727
|
+
var RouteStore = class {
|
|
728
|
+
constructor(deps) {
|
|
729
|
+
this.inflight = /* @__PURE__ */ new Map();
|
|
730
|
+
/** 每个层级的失效代数,用于检测 loadOne 期间是否发生了 invalidate */
|
|
731
|
+
this.invalidationGens = /* @__PURE__ */ new Map();
|
|
732
|
+
this.cache = deps.cache;
|
|
733
|
+
this.state = deps.state;
|
|
734
|
+
this.baseURL = deps.baseURL;
|
|
735
|
+
this.fetchMeta = deps.fetchMeta;
|
|
736
|
+
this.autoDiscoveryPromise = deps.autoDiscoveryPromise;
|
|
737
|
+
this.getAutoDiscoveryError = deps.getAutoDiscoveryError;
|
|
738
|
+
}
|
|
739
|
+
assertLevelDeclared(level) {
|
|
740
|
+
if (!this.state.levels.includes(level)) {
|
|
741
|
+
throw new UnknownLevelError(level);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
async fetchLevel(level) {
|
|
745
|
+
return await this.fetchMeta(
|
|
746
|
+
`__forge__.load.${level}`,
|
|
747
|
+
buildUrl(level, { baseURL: this.baseURL, endpoint: this.state.endpoint }),
|
|
748
|
+
level
|
|
749
|
+
);
|
|
750
|
+
}
|
|
751
|
+
async loadOne(level) {
|
|
752
|
+
const autoDiscoveryError = this.getAutoDiscoveryError();
|
|
763
753
|
if (autoDiscoveryError) throw autoDiscoveryError;
|
|
764
|
-
assertLevelDeclared(level);
|
|
765
|
-
if (cache.get(level)) return;
|
|
766
|
-
const existing = inflight.get(level);
|
|
754
|
+
this.assertLevelDeclared(level);
|
|
755
|
+
if (this.cache.get(level)) return;
|
|
756
|
+
const existing = this.inflight.get(level);
|
|
767
757
|
if (existing) return existing;
|
|
768
|
-
const gen = invalidationGens.get(level) ?? 0;
|
|
758
|
+
const gen = this.invalidationGens.get(level) ?? 0;
|
|
769
759
|
const p = (async () => {
|
|
770
760
|
try {
|
|
771
|
-
if (level === UNASSIGNED_LEVEL && !backendHasUnassignedLevel && summaryUnassigned) {
|
|
761
|
+
if (level === UNASSIGNED_LEVEL && !this.state.backendHasUnassignedLevel && this.state.summaryUnassigned) {
|
|
772
762
|
const routes = {};
|
|
773
|
-
for (const r of summaryUnassigned) {
|
|
763
|
+
for (const r of this.state.summaryUnassigned) {
|
|
774
764
|
routes[r.name] = { ...r, level: UNASSIGNED_LEVEL };
|
|
775
765
|
}
|
|
776
|
-
cache.set({ level: UNASSIGNED_LEVEL, routes, cache: null });
|
|
766
|
+
this.cache.set({ level: UNASSIGNED_LEVEL, routes, cache: null });
|
|
777
767
|
return;
|
|
778
768
|
}
|
|
779
|
-
const resp = await fetchLevel(level);
|
|
780
|
-
if ((invalidationGens.get(level) ?? 0) === gen) {
|
|
781
|
-
cache.set(resp);
|
|
769
|
+
const resp = await this.fetchLevel(level);
|
|
770
|
+
if ((this.invalidationGens.get(level) ?? 0) === gen) {
|
|
771
|
+
this.cache.set(resp);
|
|
782
772
|
}
|
|
783
773
|
} finally {
|
|
784
|
-
inflight.delete(level);
|
|
774
|
+
this.inflight.delete(level);
|
|
785
775
|
}
|
|
786
776
|
})();
|
|
787
|
-
inflight.set(level, p);
|
|
777
|
+
this.inflight.set(level, p);
|
|
788
778
|
return p;
|
|
789
779
|
}
|
|
790
|
-
async
|
|
791
|
-
await autoDiscoveryPromise;
|
|
780
|
+
async load(level) {
|
|
781
|
+
await this.autoDiscoveryPromise;
|
|
792
782
|
const list = Array.isArray(level) ? level : [level];
|
|
793
|
-
await Promise.all(list.map(loadOne));
|
|
783
|
+
await Promise.all(list.map((l) => this.loadOne(l)));
|
|
794
784
|
}
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
return buildRequestUrl(meta, params ?? {});
|
|
802
|
-
}
|
|
803
|
-
function buildRequestUrl(meta, params) {
|
|
804
|
-
const defaults = meta.parameter_defaults ?? {};
|
|
805
|
-
const missingRequired = [];
|
|
806
|
-
const values = {};
|
|
807
|
-
for (const p of meta.parameters) {
|
|
808
|
-
let v = params[p];
|
|
809
|
-
if ((v === void 0 || v === null) && p in defaults) {
|
|
810
|
-
v = defaults[p];
|
|
811
|
-
}
|
|
812
|
-
if (v === void 0 || v === null) {
|
|
813
|
-
if (!meta.uri.includes(`{${p}?}`)) {
|
|
814
|
-
missingRequired.push(p);
|
|
815
|
-
}
|
|
816
|
-
} else {
|
|
817
|
-
values[p] = v;
|
|
785
|
+
invalidate(level) {
|
|
786
|
+
if (level === void 0) {
|
|
787
|
+
this.cache.clear();
|
|
788
|
+
this.inflight.clear();
|
|
789
|
+
for (const lvl of this.state.levels) {
|
|
790
|
+
this.invalidationGens.set(lvl, (this.invalidationGens.get(lvl) ?? 0) + 1);
|
|
818
791
|
}
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
const optional = raw.endsWith("?");
|
|
825
|
-
const name = optional ? raw.slice(0, -1) : raw;
|
|
826
|
-
if (values[name] !== void 0) {
|
|
827
|
-
const val = values[name];
|
|
828
|
-
if (typeof val === "object") {
|
|
829
|
-
throw new ForgeError(
|
|
830
|
-
`Path parameter "${name}" must be a primitive value (string, number, boolean), got ${typeof val}`,
|
|
831
|
-
{ code: "RF_FE_003", route: meta.name, context: { param: name, value: val } }
|
|
832
|
-
);
|
|
833
|
-
}
|
|
834
|
-
return encodeURIComponent(String(val));
|
|
792
|
+
} else if (Array.isArray(level)) {
|
|
793
|
+
for (const lvl of level) {
|
|
794
|
+
this.cache.del(lvl);
|
|
795
|
+
this.inflight.delete(lvl);
|
|
796
|
+
this.invalidationGens.set(lvl, (this.invalidationGens.get(lvl) ?? 0) + 1);
|
|
835
797
|
}
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
const prefix2 = effectiveUrlPrefix.endsWith("/") ? effectiveUrlPrefix.slice(0, -1) : effectiveUrlPrefix;
|
|
841
|
-
return uri.startsWith("/") ? `${prefix2}${uri}` : `${prefix2}/${uri}`;
|
|
798
|
+
} else {
|
|
799
|
+
this.cache.del(level);
|
|
800
|
+
this.inflight.delete(level);
|
|
801
|
+
this.invalidationGens.set(level, (this.invalidationGens.get(level) ?? 0) + 1);
|
|
842
802
|
}
|
|
843
|
-
const base = baseURL.endsWith("/") ? baseURL.slice(0, -1) : baseURL;
|
|
844
|
-
const prefix = effectiveUrlPrefix;
|
|
845
|
-
return uri.startsWith("/") ? `${base}${prefix}${uri}` : `${base}${prefix}/${uri}`;
|
|
846
803
|
}
|
|
847
|
-
|
|
848
|
-
|
|
804
|
+
isLoaded(level) {
|
|
805
|
+
if (level) return this.cache.get(level) !== void 0;
|
|
806
|
+
return this.state.levels.length > 0 && this.state.levels.every((lvl) => this.cache.get(lvl) !== void 0);
|
|
807
|
+
}
|
|
808
|
+
findRouteMeta(level, name) {
|
|
809
|
+
const entry = this.cache.get(level);
|
|
849
810
|
const meta = entry?.routes[name];
|
|
850
811
|
if (meta) {
|
|
851
812
|
return { ...meta, level };
|
|
852
813
|
}
|
|
853
814
|
return void 0;
|
|
854
815
|
}
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
ctrl.abort(abortReason);
|
|
863
|
-
}
|
|
864
|
-
await autoDiscoveryPromise;
|
|
865
|
-
await load(level);
|
|
866
|
-
const meta = findRouteMeta(level, name);
|
|
867
|
-
if (!meta) {
|
|
868
|
-
throw new UnknownRouteError(name, level);
|
|
816
|
+
getRoutes(level) {
|
|
817
|
+
if (level !== void 0) {
|
|
818
|
+
const entry = this.cache.get(level);
|
|
819
|
+
const routes = entry?.routes ?? {};
|
|
820
|
+
const result2 = {};
|
|
821
|
+
for (const [k, v] of Object.entries(routes)) {
|
|
822
|
+
result2[k] = JSON.parse(JSON.stringify(v));
|
|
869
823
|
}
|
|
870
|
-
return
|
|
871
|
-
}
|
|
872
|
-
const
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
824
|
+
return result2;
|
|
825
|
+
}
|
|
826
|
+
const result = {};
|
|
827
|
+
for (const lvl of this.state.levels) {
|
|
828
|
+
const entry = this.cache.get(lvl);
|
|
829
|
+
if (entry) {
|
|
830
|
+
const levelRoutes = {};
|
|
831
|
+
for (const [k, v] of Object.entries(entry.routes)) {
|
|
832
|
+
levelRoutes[k] = JSON.parse(JSON.stringify(v));
|
|
833
|
+
}
|
|
834
|
+
result[lvl] = levelRoutes;
|
|
878
835
|
}
|
|
879
|
-
}
|
|
880
|
-
return
|
|
836
|
+
}
|
|
837
|
+
return result;
|
|
881
838
|
}
|
|
839
|
+
};
|
|
840
|
+
|
|
841
|
+
// src/http-runner.ts
|
|
842
|
+
function createHttpRunner(deps) {
|
|
843
|
+
const {
|
|
844
|
+
ensureAdapter,
|
|
845
|
+
requestInterceptors,
|
|
846
|
+
responseInterceptors,
|
|
847
|
+
load,
|
|
848
|
+
findRouteMeta,
|
|
849
|
+
baseURL,
|
|
850
|
+
state,
|
|
851
|
+
timeout,
|
|
852
|
+
loadingTracker,
|
|
853
|
+
autoDiscoveryPromise
|
|
854
|
+
} = deps;
|
|
882
855
|
async function doApiCall(meta, params, signal) {
|
|
883
856
|
const {
|
|
884
857
|
pathParams,
|
|
@@ -891,7 +864,10 @@ var RouteForge = (function (exports) {
|
|
|
891
864
|
throw new RequestAbortedError(meta.name, meta.level, signal.reason);
|
|
892
865
|
}
|
|
893
866
|
const method = pickMethod(meta);
|
|
894
|
-
const urlWithQuery = appendQuery(
|
|
867
|
+
const urlWithQuery = appendQuery(
|
|
868
|
+
buildRequestUrl(meta, pathParams, { baseURL, urlPrefix: state.urlPrefix }),
|
|
869
|
+
query
|
|
870
|
+
);
|
|
895
871
|
const config = {
|
|
896
872
|
route: meta.name,
|
|
897
873
|
level: meta.level ?? "",
|
|
@@ -929,7 +905,7 @@ var RouteForge = (function (exports) {
|
|
|
929
905
|
},
|
|
930
906
|
(err) => {
|
|
931
907
|
if (err instanceof ForgeError) throw err;
|
|
932
|
-
if (
|
|
908
|
+
if (isAbortError(err, signal)) {
|
|
933
909
|
throw new RequestAbortedError(meta.name, meta.level, err);
|
|
934
910
|
}
|
|
935
911
|
throw new NetworkError(
|
|
@@ -946,81 +922,77 @@ var RouteForge = (function (exports) {
|
|
|
946
922
|
loadingTracker.stop();
|
|
947
923
|
}
|
|
948
924
|
}
|
|
949
|
-
function
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
for (const lvl of level) {
|
|
958
|
-
cache.del(lvl);
|
|
959
|
-
inflight.delete(lvl);
|
|
960
|
-
invalidationGens.set(lvl, (invalidationGens.get(lvl) ?? 0) + 1);
|
|
925
|
+
return function api(level, name, params = {}) {
|
|
926
|
+
let ctrl;
|
|
927
|
+
let abortedBeforeInit = false;
|
|
928
|
+
let abortReason;
|
|
929
|
+
const work = (async () => {
|
|
930
|
+
ctrl = new AbortController();
|
|
931
|
+
if (abortedBeforeInit) {
|
|
932
|
+
ctrl.abort(abortReason);
|
|
961
933
|
}
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
}
|
|
968
|
-
function isLoaded(level) {
|
|
969
|
-
if (level) return cache.get(level) !== void 0;
|
|
970
|
-
return effectiveLevels.length > 0 && effectiveLevels.every((lvl) => cache.get(lvl) !== void 0);
|
|
971
|
-
}
|
|
972
|
-
function hasRoute(level, name) {
|
|
973
|
-
assertDiscoveryReady();
|
|
974
|
-
return findRouteMeta(level, name) !== void 0;
|
|
975
|
-
}
|
|
976
|
-
function getRoutes(level) {
|
|
977
|
-
if (level !== void 0) {
|
|
978
|
-
const entry = cache.get(level);
|
|
979
|
-
const routes = entry?.routes ?? {};
|
|
980
|
-
const result2 = {};
|
|
981
|
-
for (const [k, v] of Object.entries(routes)) {
|
|
982
|
-
result2[k] = JSON.parse(JSON.stringify(v));
|
|
934
|
+
await autoDiscoveryPromise;
|
|
935
|
+
await load(level);
|
|
936
|
+
const meta = findRouteMeta(level, name);
|
|
937
|
+
if (!meta) {
|
|
938
|
+
throw new UnknownRouteError(name, level);
|
|
983
939
|
}
|
|
984
|
-
return
|
|
985
|
-
}
|
|
986
|
-
const
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
levelRoutes[k] = JSON.parse(JSON.stringify(v));
|
|
993
|
-
}
|
|
994
|
-
result[lvl] = levelRoutes;
|
|
940
|
+
return doApiCall(meta, params, ctrl.signal);
|
|
941
|
+
})();
|
|
942
|
+
const request = work;
|
|
943
|
+
request.abort = () => {
|
|
944
|
+
if (ctrl) {
|
|
945
|
+
ctrl.abort();
|
|
946
|
+
} else {
|
|
947
|
+
abortedBeforeInit = true;
|
|
995
948
|
}
|
|
996
|
-
}
|
|
997
|
-
return
|
|
998
|
-
}
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
949
|
+
};
|
|
950
|
+
return request;
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
// src/resolveRouteName.ts
|
|
955
|
+
async function resolveRouteName(forge, level, prefix, suffix, separator = ".") {
|
|
956
|
+
if (!suffix) return prefix;
|
|
957
|
+
const joined = `${prefix}${separator}${suffix}`;
|
|
958
|
+
if (!suffix.startsWith(`${prefix}${separator}`)) return joined;
|
|
959
|
+
await forge.load(level);
|
|
960
|
+
if (forge.hasRoute(level, joined)) return joined;
|
|
961
|
+
if (forge.hasRoute(level, suffix)) return suffix;
|
|
962
|
+
throw new UnknownRouteError(joined, level);
|
|
963
|
+
}
|
|
964
|
+
function resolveRouteNameSync(forge, level, prefix, suffix, separator = ".") {
|
|
965
|
+
if (!suffix) return prefix;
|
|
966
|
+
const joined = `${prefix}${separator}${suffix}`;
|
|
967
|
+
if (!suffix.startsWith(`${prefix}${separator}`)) return joined;
|
|
968
|
+
if (forge.hasRoute(level, joined)) return joined;
|
|
969
|
+
if (forge.hasRoute(level, suffix)) return suffix;
|
|
970
|
+
throw new UnknownRouteError(joined, level);
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
// src/defineImmutableProps.ts
|
|
974
|
+
function defineImmutableProps(target, props) {
|
|
975
|
+
for (const key of Object.keys(props)) {
|
|
976
|
+
const val = props[key];
|
|
977
|
+
Object.defineProperty(target, key, {
|
|
978
|
+
value: val !== null && typeof val === "object" ? Object.freeze(val) : val,
|
|
979
|
+
writable: false,
|
|
980
|
+
enumerable: false,
|
|
981
|
+
configurable: false
|
|
982
|
+
});
|
|
1017
983
|
}
|
|
1018
|
-
|
|
984
|
+
return target;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// src/bound-forge.ts
|
|
988
|
+
function createBoundForgeFactory(deps) {
|
|
989
|
+
const { load, api, route, hasRoute, getRoutes, invalidate, isLoaded, loadingTracker } = deps;
|
|
990
|
+
const resolver = { load, hasRoute };
|
|
1019
991
|
function createBoundForge(level, prefix) {
|
|
1020
992
|
const levelLoadedPromise = load(level);
|
|
1021
993
|
levelLoadedPromise.catch(() => {
|
|
1022
994
|
});
|
|
1023
|
-
const apiFn = prefix ? (name, params) => resolveRouteName(
|
|
995
|
+
const apiFn = prefix ? (name, params) => resolveRouteName(resolver, level, prefix, name).then(
|
|
1024
996
|
(resolved) => api(level, resolved, params)
|
|
1025
997
|
) : (name, params) => api(level, name, params);
|
|
1026
998
|
const callable = apiFn;
|
|
@@ -1028,8 +1000,8 @@ var RouteForge = (function (exports) {
|
|
|
1028
1000
|
level,
|
|
1029
1001
|
...prefix !== void 0 ? { prefix } : {},
|
|
1030
1002
|
api: apiFn,
|
|
1031
|
-
route: prefix ? (name, params) => route(level, resolveRouteNameSync(
|
|
1032
|
-
url: prefix ? (name, params) => route(level, resolveRouteNameSync(
|
|
1003
|
+
route: prefix ? (name, params) => route(level, resolveRouteNameSync(resolver, level, prefix, name), params) : (name, params) => route(level, name, params),
|
|
1004
|
+
url: prefix ? (name, params) => route(level, resolveRouteNameSync(resolver, level, prefix, name), params) : (name, params) => route(level, name, params),
|
|
1033
1005
|
hasRoute: (name) => hasRoute(level, name),
|
|
1034
1006
|
getRoutes: () => getRoutes(level),
|
|
1035
1007
|
load: () => load(level),
|
|
@@ -1073,6 +1045,208 @@ var RouteForge = (function (exports) {
|
|
|
1073
1045
|
attachBoundMethods(bound, levelLoadedPromise, level);
|
|
1074
1046
|
return bound;
|
|
1075
1047
|
}
|
|
1048
|
+
return createBoundForgeWithMethods;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
// src/forge.ts
|
|
1052
|
+
var DEFAULT_TIMEOUT = 3e4;
|
|
1053
|
+
var DEFAULT_CACHE_TTL = 3600;
|
|
1054
|
+
function createRouteForge(options) {
|
|
1055
|
+
if (!options.endpoint) throw new TypeError("options.endpoint is required");
|
|
1056
|
+
const {
|
|
1057
|
+
adapter = "auto",
|
|
1058
|
+
timeout = DEFAULT_TIMEOUT,
|
|
1059
|
+
baseURL = "",
|
|
1060
|
+
interceptors: declarativeInterceptors,
|
|
1061
|
+
cache: cacheOpts = {}
|
|
1062
|
+
} = options;
|
|
1063
|
+
const loadingTracker = new LoadingTracker();
|
|
1064
|
+
const explicitLevels = options.levels;
|
|
1065
|
+
const explicitEager = options.eager;
|
|
1066
|
+
const explicitEndpoint = options.endpoint;
|
|
1067
|
+
const discoveryState = {
|
|
1068
|
+
levels: explicitLevels ?? [],
|
|
1069
|
+
eager: explicitEager ?? [],
|
|
1070
|
+
endpoint: explicitEndpoint,
|
|
1071
|
+
urlPrefix: "",
|
|
1072
|
+
summaryUnassigned: void 0,
|
|
1073
|
+
backendHasUnassignedLevel: false
|
|
1074
|
+
};
|
|
1075
|
+
const discoveryInputs = { explicitLevels, explicitEager, explicitEndpoint };
|
|
1076
|
+
let autoDiscoveryCompleted = false;
|
|
1077
|
+
let resolveReady;
|
|
1078
|
+
let rejectReady;
|
|
1079
|
+
const readyPromise = new Promise((resolve, reject) => {
|
|
1080
|
+
resolveReady = resolve;
|
|
1081
|
+
rejectReady = reject;
|
|
1082
|
+
});
|
|
1083
|
+
readyPromise.catch(() => {
|
|
1084
|
+
});
|
|
1085
|
+
const cacheTtl = cacheOpts.ttl ?? DEFAULT_CACHE_TTL;
|
|
1086
|
+
const cacheStorage = cacheOpts.storage ?? "memory";
|
|
1087
|
+
const cache = new RouteCache({ storage: cacheStorage, ttl: cacheTtl });
|
|
1088
|
+
const requestInterceptors = new InterceptorManagerImpl();
|
|
1089
|
+
const responseInterceptors = new InterceptorManagerImpl();
|
|
1090
|
+
if (declarativeInterceptors?.request) {
|
|
1091
|
+
for (const entry of declarativeInterceptors.request) {
|
|
1092
|
+
if (typeof entry === "function") {
|
|
1093
|
+
requestInterceptors.use(entry);
|
|
1094
|
+
} else {
|
|
1095
|
+
const [onFulfilled, onRejected] = entry;
|
|
1096
|
+
requestInterceptors.use(onFulfilled, onRejected);
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
if (declarativeInterceptors?.response) {
|
|
1101
|
+
for (const entry of declarativeInterceptors.response) {
|
|
1102
|
+
if (typeof entry === "function") {
|
|
1103
|
+
responseInterceptors.use(entry);
|
|
1104
|
+
} else {
|
|
1105
|
+
const [onFulfilled, onRejected] = entry;
|
|
1106
|
+
responseInterceptors.use(onFulfilled, onRejected);
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
const adapterPromise = resolveAdapter({
|
|
1111
|
+
adapter,
|
|
1112
|
+
forgeInterceptors: { request: requestInterceptors, response: responseInterceptors }
|
|
1113
|
+
});
|
|
1114
|
+
let adapterResolved = false;
|
|
1115
|
+
let adapterObj = null;
|
|
1116
|
+
async function ensureAdapter() {
|
|
1117
|
+
if (!adapterResolved) {
|
|
1118
|
+
adapterObj = await adapterPromise.catch((e) => {
|
|
1119
|
+
if (e instanceof AdapterNotFoundError) throw e;
|
|
1120
|
+
return resolveAdapter({
|
|
1121
|
+
adapter: "builtin",
|
|
1122
|
+
forgeInterceptors: { request: requestInterceptors, response: responseInterceptors }
|
|
1123
|
+
});
|
|
1124
|
+
});
|
|
1125
|
+
adapterResolved = true;
|
|
1126
|
+
}
|
|
1127
|
+
return adapterObj;
|
|
1128
|
+
}
|
|
1129
|
+
function assertDiscoveryReady() {
|
|
1130
|
+
if (!autoDiscoveryCompleted && !explicitLevels?.length) {
|
|
1131
|
+
throw new ForgeError(
|
|
1132
|
+
"Route data not available. Auto-discovery has not completed. Use forge.ready() or forge.use(level) first.",
|
|
1133
|
+
{ code: "RF_FE_010" }
|
|
1134
|
+
);
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
async function fetchMeta(routeTag, url, level = "") {
|
|
1138
|
+
const adp = await ensureAdapter();
|
|
1139
|
+
const config = {
|
|
1140
|
+
route: routeTag,
|
|
1141
|
+
level,
|
|
1142
|
+
method: "GET",
|
|
1143
|
+
url,
|
|
1144
|
+
headers: { Accept: "application/json" },
|
|
1145
|
+
params: {},
|
|
1146
|
+
timeout,
|
|
1147
|
+
meta: {
|
|
1148
|
+
name: routeTag,
|
|
1149
|
+
uri: url,
|
|
1150
|
+
methods: ["GET"],
|
|
1151
|
+
parameters: [],
|
|
1152
|
+
level
|
|
1153
|
+
}
|
|
1154
|
+
};
|
|
1155
|
+
const doRawRequest = adp.requestRaw ?? adp.request;
|
|
1156
|
+
const resp = await doRawRequest(config);
|
|
1157
|
+
if (!resp || resp.status < 200 || resp.status >= 300) {
|
|
1158
|
+
throw new HTTPError(
|
|
1159
|
+
`Failed to fetch "${routeTag}": HTTP ${resp?.status}`,
|
|
1160
|
+
{ level, status: resp?.status, url, method: "GET" }
|
|
1161
|
+
);
|
|
1162
|
+
}
|
|
1163
|
+
return resp.data;
|
|
1164
|
+
}
|
|
1165
|
+
const autoDiscoveryPromise = fetchSummary(discoveryInputs, baseURL, fetchMeta).then((summary) => {
|
|
1166
|
+
if (summary === null) {
|
|
1167
|
+
return;
|
|
1168
|
+
}
|
|
1169
|
+
applySummaryToState(summary, discoveryState, discoveryInputs);
|
|
1170
|
+
});
|
|
1171
|
+
let autoDiscoveryError = null;
|
|
1172
|
+
autoDiscoveryPromise.catch((e) => {
|
|
1173
|
+
autoDiscoveryError = e;
|
|
1174
|
+
});
|
|
1175
|
+
const store = new RouteStore({
|
|
1176
|
+
cache,
|
|
1177
|
+
state: discoveryState,
|
|
1178
|
+
baseURL,
|
|
1179
|
+
fetchMeta,
|
|
1180
|
+
autoDiscoveryPromise,
|
|
1181
|
+
getAutoDiscoveryError: () => autoDiscoveryError
|
|
1182
|
+
});
|
|
1183
|
+
const load = (level) => store.load(level);
|
|
1184
|
+
const findRouteMeta = (level, name) => store.findRouteMeta(level, name);
|
|
1185
|
+
const invalidate = (level) => store.invalidate(level);
|
|
1186
|
+
const isLoaded = (level) => store.isLoaded(level);
|
|
1187
|
+
function getRoutes(level) {
|
|
1188
|
+
return level === void 0 ? store.getRoutes() : store.getRoutes(level);
|
|
1189
|
+
}
|
|
1190
|
+
function route(level, name, params) {
|
|
1191
|
+
assertDiscoveryReady();
|
|
1192
|
+
const meta = findRouteMeta(level, name);
|
|
1193
|
+
if (!meta) {
|
|
1194
|
+
throw new UnknownRouteError(name, level);
|
|
1195
|
+
}
|
|
1196
|
+
return buildRequestUrl(meta, params ?? {}, { baseURL, urlPrefix: discoveryState.urlPrefix });
|
|
1197
|
+
}
|
|
1198
|
+
const api = createHttpRunner({
|
|
1199
|
+
ensureAdapter,
|
|
1200
|
+
requestInterceptors,
|
|
1201
|
+
responseInterceptors,
|
|
1202
|
+
load,
|
|
1203
|
+
findRouteMeta,
|
|
1204
|
+
baseURL,
|
|
1205
|
+
state: discoveryState,
|
|
1206
|
+
timeout,
|
|
1207
|
+
loadingTracker,
|
|
1208
|
+
autoDiscoveryPromise
|
|
1209
|
+
});
|
|
1210
|
+
function hasRoute(level, name) {
|
|
1211
|
+
assertDiscoveryReady();
|
|
1212
|
+
return findRouteMeta(level, name) !== void 0;
|
|
1213
|
+
}
|
|
1214
|
+
void autoDiscoveryPromise.then(() => {
|
|
1215
|
+
autoDiscoveryCompleted = true;
|
|
1216
|
+
if (discoveryState.eager.length > 0) {
|
|
1217
|
+
return Promise.allSettled(discoveryState.eager.map((lvl) => load(lvl))).then((results) => {
|
|
1218
|
+
results.forEach((r, i) => {
|
|
1219
|
+
if (r.status === "rejected") {
|
|
1220
|
+
console.error(
|
|
1221
|
+
`[route-forge] eager load failed for level "${discoveryState.eager[i]}":`,
|
|
1222
|
+
r.reason
|
|
1223
|
+
);
|
|
1224
|
+
}
|
|
1225
|
+
});
|
|
1226
|
+
});
|
|
1227
|
+
}
|
|
1228
|
+
}).then(() => {
|
|
1229
|
+
resolveReady(forgeInstance);
|
|
1230
|
+
}).catch((e) => {
|
|
1231
|
+
rejectReady(e);
|
|
1232
|
+
});
|
|
1233
|
+
function ready(onFulfilled, onRejected) {
|
|
1234
|
+
if (onFulfilled) {
|
|
1235
|
+
const p = readyPromise.then(onFulfilled, onRejected);
|
|
1236
|
+
return p.then(() => forgeInstance);
|
|
1237
|
+
}
|
|
1238
|
+
return readyPromise;
|
|
1239
|
+
}
|
|
1240
|
+
const createBoundForgeWithMethods = createBoundForgeFactory({
|
|
1241
|
+
load,
|
|
1242
|
+
api,
|
|
1243
|
+
route,
|
|
1244
|
+
hasRoute,
|
|
1245
|
+
getRoutes,
|
|
1246
|
+
invalidate,
|
|
1247
|
+
isLoaded,
|
|
1248
|
+
loadingTracker
|
|
1249
|
+
});
|
|
1076
1250
|
const forgeInstance = {
|
|
1077
1251
|
api,
|
|
1078
1252
|
load,
|
|
@@ -1096,70 +1270,6 @@ var RouteForge = (function (exports) {
|
|
|
1096
1270
|
};
|
|
1097
1271
|
return forgeInstance;
|
|
1098
1272
|
}
|
|
1099
|
-
function pickMethod(meta) {
|
|
1100
|
-
const m = meta.methods.find((x) => x.toUpperCase() !== "HEAD");
|
|
1101
|
-
return (m ?? meta.methods[0] ?? "GET").toUpperCase();
|
|
1102
|
-
}
|
|
1103
|
-
function appendQuery(url, query) {
|
|
1104
|
-
if (!query) return url;
|
|
1105
|
-
const usp = new URLSearchParams();
|
|
1106
|
-
for (const [k, v] of Object.entries(query)) {
|
|
1107
|
-
if (v === void 0 || v === null) continue;
|
|
1108
|
-
usp.append(k, String(v));
|
|
1109
|
-
}
|
|
1110
|
-
const qs = usp.toString();
|
|
1111
|
-
if (!qs) return url;
|
|
1112
|
-
return url.includes("?") ? `${url}&${qs}` : `${url}?${qs}`;
|
|
1113
|
-
}
|
|
1114
|
-
function resolveApiParams(input) {
|
|
1115
|
-
const {
|
|
1116
|
-
params: explicitParams,
|
|
1117
|
-
query: rawQuery,
|
|
1118
|
-
body: rawBody,
|
|
1119
|
-
headers: rawHeaders,
|
|
1120
|
-
timeout: perCallTimeout,
|
|
1121
|
-
...flatRest
|
|
1122
|
-
} = input;
|
|
1123
|
-
const pathParams = explicitParams ? { ...explicitParams } : {};
|
|
1124
|
-
for (const [k, v] of Object.entries(flatRest)) {
|
|
1125
|
-
if (!(k in pathParams)) {
|
|
1126
|
-
pathParams[k] = v;
|
|
1127
|
-
}
|
|
1128
|
-
}
|
|
1129
|
-
let query;
|
|
1130
|
-
let body;
|
|
1131
|
-
let headers;
|
|
1132
|
-
if (rawQuery !== void 0) {
|
|
1133
|
-
if (typeof rawQuery === "object" && rawQuery !== null) {
|
|
1134
|
-
query = rawQuery;
|
|
1135
|
-
} else if (!("query" in pathParams)) {
|
|
1136
|
-
pathParams.query = rawQuery;
|
|
1137
|
-
}
|
|
1138
|
-
}
|
|
1139
|
-
if (rawBody !== void 0) {
|
|
1140
|
-
if (typeof rawBody !== "string" && typeof rawBody !== "number") {
|
|
1141
|
-
body = rawBody;
|
|
1142
|
-
} else if (!("body" in pathParams)) {
|
|
1143
|
-
pathParams.body = rawBody;
|
|
1144
|
-
}
|
|
1145
|
-
}
|
|
1146
|
-
if (rawHeaders !== void 0) {
|
|
1147
|
-
if (typeof rawHeaders === "object" && rawHeaders !== null) {
|
|
1148
|
-
headers = rawHeaders;
|
|
1149
|
-
} else if (!("headers" in pathParams)) {
|
|
1150
|
-
pathParams.headers = rawHeaders;
|
|
1151
|
-
}
|
|
1152
|
-
}
|
|
1153
|
-
return { pathParams, query, body, headers, timeout: perCallTimeout };
|
|
1154
|
-
}
|
|
1155
|
-
function isAbortError2(err, signal) {
|
|
1156
|
-
if (signal?.aborted) return true;
|
|
1157
|
-
const e = err;
|
|
1158
|
-
if (!e) return false;
|
|
1159
|
-
if (e.name === "AbortError") return true;
|
|
1160
|
-
if (e.code === "ERR_CANCELED") return true;
|
|
1161
|
-
return false;
|
|
1162
|
-
}
|
|
1163
1273
|
|
|
1164
1274
|
exports.AdapterNotFoundError = AdapterNotFoundError;
|
|
1165
1275
|
exports.ForgeError = ForgeError;
|