@route-forge/core 1.2.1 → 1.3.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 CHANGED
@@ -210,26 +210,69 @@ const forge = createRouteForge({
210
210
  })
211
211
  ```
212
212
 
213
- ### `forge.ready` Promise
213
+ ### `forge.ready()` 方法
214
214
 
215
- auto-discovery + eager load 完成后 resolve,适合 async/await 风格:
215
+ auto-discovery + eager load 完成后 resolve,返回 `Promise<RouteForge>`(resolve 值为 forge 自身),适合 async/await 风格:
216
216
 
217
217
  ```ts
218
218
  const forge = createRouteForge({ endpoint: '/_forge/routes' })
219
- await forge.ready
219
+
220
+ // 无参模式:直接 await
221
+ await forge.ready()
220
222
  // 路由数据已就绪,可安全调用 route() / hasRoute()
223
+
224
+ // 回调模式:onFulfilled / onRejected
225
+ forge.ready(
226
+ (f) => { console.log('ready!', f) },
227
+ (err) => { console.error(err) }
228
+ )
229
+
230
+ // 链式调用:ready 返回 forge 自身
231
+ const bound = await forge.ready().then(f => f.use('admin'))
221
232
  ```
222
233
 
223
- ### `onLevelLoaded` 订阅
234
+ ### `forge.use(level?, prefix?)` 层级绑定
224
235
 
225
- 订阅指定 level 加载完成事件:
236
+ `forge.use()` 是 core 层唯一的 level 绑定入口,返回 `BoundForge` 对象:
226
237
 
227
238
  ```ts
228
- const unsub = forge.onLevelLoaded('admin', () => {
229
- console.log('admin level loaded')
230
- })
231
- // 取消订阅
232
- unsub()
239
+ // 绑定层级 自动触发 load,提供快捷方法
240
+ const bound = forge.use('admin')
241
+ bound('users.show', { user: 1 }) // 可直接调用(= bound.api())
242
+ bound.route('users.show') // URL 生成
243
+ bound.level // → 'admin'
244
+ bound.levelLoaded // Promise<void>
245
+
246
+ // 绑定层级 + 前缀 — 路由名自动拼接
247
+ const bound = forge.use('admin', 'users')
248
+ bound('show', { user: 1 }) // → forge.api('admin', 'users.show', ...)
249
+
250
+ // BoundForge 独有方法
251
+ await bound.onLevelLoaded() // 等待 level 加载完成
252
+ const prefixed = bound.useRoutePrefix('posts') // 追加前缀
253
+ ```
254
+
255
+ ### IIFE 浏览器用法
256
+
257
+ 通过 `<script>` 标签引入后,`RouteForge` 全局可用:
258
+
259
+ ```html
260
+ <script src="https://unpkg.com/@route-forge/core"></script>
261
+ <script>
262
+ const forge = RouteForge.createRouteForge({
263
+ endpoint: '/_forge/routes',
264
+ })
265
+
266
+ // 等待就绪后绑定层级
267
+ forge.ready().then(function(f) {
268
+ const admin = f.use('admin')
269
+ return admin.onLevelLoaded()
270
+ }).then(function(bound) {
271
+ return bound('users.show', { user: 1 })
272
+ }).then(function(data) {
273
+ console.log(data)
274
+ })
275
+ </script>
233
276
  ```
234
277
 
235
278
  ### Auto-discovery 守卫
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { R as RouteMeta } from './types-BcaNLA_l.cjs';
2
+ import { R as RouteMeta } from './types-D4PMbR4-.cjs';
3
3
 
4
4
  /**
5
5
  * @route-forge/core codegen CLI
package/dist/codegen.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { R as RouteMeta } from './types-BcaNLA_l.js';
2
+ import { R as RouteMeta } from './types-D4PMbR4-.js';
3
3
 
4
4
  /**
5
5
  * @route-forge/core codegen CLI
package/dist/index.cjs CHANGED
@@ -467,6 +467,39 @@ async function resolveAdapter(opts) {
467
467
  return createBuiltinHttp(opts.forgeInterceptors);
468
468
  }
469
469
 
470
+ // src/resolveRouteName.ts
471
+ async function resolveRouteName(forge, level, prefix, suffix, separator = ".") {
472
+ if (!suffix) return prefix;
473
+ const joined = `${prefix}${separator}${suffix}`;
474
+ if (!suffix.startsWith(`${prefix}${separator}`)) return joined;
475
+ await forge.load(level);
476
+ if (forge.hasRoute(level, joined)) return joined;
477
+ if (forge.hasRoute(level, suffix)) return suffix;
478
+ throw new UnknownRouteError(joined, level);
479
+ }
480
+ function resolveRouteNameSync(forge, level, prefix, suffix, separator = ".") {
481
+ if (!suffix) return prefix;
482
+ const joined = `${prefix}${separator}${suffix}`;
483
+ if (!suffix.startsWith(`${prefix}${separator}`)) return joined;
484
+ if (forge.hasRoute(level, joined)) return joined;
485
+ if (forge.hasRoute(level, suffix)) return suffix;
486
+ throw new UnknownRouteError(joined, level);
487
+ }
488
+
489
+ // src/defineImmutableProps.ts
490
+ function defineImmutableProps(target, props) {
491
+ for (const key of Object.keys(props)) {
492
+ const val = props[key];
493
+ Object.defineProperty(target, key, {
494
+ value: val !== null && typeof val === "object" ? Object.freeze(val) : val,
495
+ writable: false,
496
+ enumerable: false,
497
+ configurable: false
498
+ });
499
+ }
500
+ return target;
501
+ }
502
+
470
503
  // src/loading.ts
471
504
  var LoadingTracker = class {
472
505
  constructor() {
@@ -633,7 +666,6 @@ function createRouteForge(options) {
633
666
  const readyPromise = new Promise((resolve) => {
634
667
  resolveReady = resolve;
635
668
  });
636
- const levelLoadedListeners = /* @__PURE__ */ new Map();
637
669
  const cacheTtl = cacheOpts.ttl ?? DEFAULT_CACHE_TTL;
638
670
  const cacheStorage = cacheOpts.storage ?? "memory";
639
671
  const cache = new RouteCache({ storage: cacheStorage, ttl: cacheTtl });
@@ -685,7 +717,7 @@ function createRouteForge(options) {
685
717
  function assertDiscoveryReady() {
686
718
  if (!autoDiscoveryCompleted && !explicitLevels?.length) {
687
719
  throw new ForgeError(
688
- "Route data not available. Auto-discovery has not completed. Use onSummaryReady callback to mount app, or await forge.ready / forge.load(level) first.",
720
+ "Route data not available. Auto-discovery has not completed. Use forge.ready() or forge.use(level) first.",
689
721
  { code: "RF_FE_010" }
690
722
  );
691
723
  }
@@ -738,15 +770,11 @@ function createRouteForge(options) {
738
770
  routes[r.name] = { ...r, level: UNASSIGNED_LEVEL };
739
771
  }
740
772
  cache.set({ level: UNASSIGNED_LEVEL, routes, cache: null });
741
- const listeners = levelLoadedListeners.get(level);
742
- if (listeners) listeners.forEach((cb) => cb());
743
773
  return;
744
774
  }
745
775
  const resp = await fetchLevel(level);
746
776
  if ((invalidationGens.get(level) ?? 0) === gen) {
747
777
  cache.set(resp);
748
- const listeners = levelLoadedListeners.get(level);
749
- if (listeners) listeners.forEach((cb) => cb());
750
778
  }
751
779
  } finally {
752
780
  inflight.delete(level);
@@ -948,6 +976,7 @@ function createRouteForge(options) {
948
976
  return result;
949
977
  }
950
978
  void autoDiscoveryPromise.then(() => {
979
+ autoDiscoveryCompleted = true;
951
980
  options.onSummaryReady?.();
952
981
  if (effectiveEager.length > 0) {
953
982
  return Promise.all(effectiveEager.map((lvl) => load(lvl))).catch((e) => {
@@ -955,11 +984,74 @@ function createRouteForge(options) {
955
984
  });
956
985
  }
957
986
  }).then(() => {
958
- autoDiscoveryCompleted = true;
959
- resolveReady();
987
+ resolveReady(forgeInstance);
960
988
  }).catch(() => {
961
989
  });
962
- return {
990
+ function ready(onFulfilled, onRejected) {
991
+ if (onFulfilled) {
992
+ const p = readyPromise.then(onFulfilled, onRejected);
993
+ return p.then(() => forgeInstance);
994
+ }
995
+ return readyPromise;
996
+ }
997
+ const forgeResolver = { load, hasRoute };
998
+ function createBoundForge(level, prefix) {
999
+ const levelLoadedPromise = load(level).catch(() => {
1000
+ });
1001
+ const apiFn = prefix ? (name, params) => resolveRouteName(forgeResolver, level, prefix, name).then(
1002
+ (resolved) => api(level, resolved, params)
1003
+ ) : (name, params) => api(level, name, params);
1004
+ const callable = apiFn;
1005
+ defineImmutableProps(callable, {
1006
+ level,
1007
+ ...prefix !== void 0 ? { prefix } : {},
1008
+ api: apiFn,
1009
+ route: prefix ? (name, params) => route(level, resolveRouteNameSync(forgeResolver, level, prefix, name), params) : (name, params) => route(level, name, params),
1010
+ url: prefix ? (name, params) => route(level, resolveRouteNameSync(forgeResolver, level, prefix, name), params) : (name, params) => route(level, name, params),
1011
+ hasRoute: (name) => hasRoute(level, name),
1012
+ getRoutes: () => getRoutes(level),
1013
+ load: () => load(level),
1014
+ invalidate: () => invalidate(level),
1015
+ isLoaded: () => isLoaded(level),
1016
+ isLoading: () => loadingTracker.isLoading(),
1017
+ onLoadingChange: (cb) => loadingTracker.subscribe(cb)
1018
+ });
1019
+ Object.defineProperty(callable, "levelLoaded", {
1020
+ value: levelLoadedPromise,
1021
+ writable: false,
1022
+ enumerable: true,
1023
+ configurable: true
1024
+ });
1025
+ return callable;
1026
+ }
1027
+ function boundOnLevelLoaded(bound, levelLoadedPromise, onFulfilled, onRejected) {
1028
+ const p = levelLoadedPromise.then(() => bound);
1029
+ if (onFulfilled) {
1030
+ return p.then(onFulfilled, onRejected).then(() => bound);
1031
+ }
1032
+ return p;
1033
+ }
1034
+ function attachBoundMethods(bound, levelLoadedPromise, level) {
1035
+ Object.defineProperty(bound, "onLevelLoaded", {
1036
+ value: (onFulfilled, onRejected) => boundOnLevelLoaded(bound, levelLoadedPromise, onFulfilled, onRejected),
1037
+ writable: false,
1038
+ enumerable: false,
1039
+ configurable: false
1040
+ });
1041
+ Object.defineProperty(bound, "useRoutePrefix", {
1042
+ value: (prefix) => createBoundForgeWithMethods(level, prefix),
1043
+ writable: false,
1044
+ enumerable: false,
1045
+ configurable: false
1046
+ });
1047
+ }
1048
+ function createBoundForgeWithMethods(level, prefix) {
1049
+ const bound = createBoundForge(level, prefix);
1050
+ const levelLoadedPromise = bound.levelLoaded;
1051
+ attachBoundMethods(bound, levelLoadedPromise, level);
1052
+ return bound;
1053
+ }
1054
+ const forgeInstance = {
963
1055
  api,
964
1056
  load,
965
1057
  route,
@@ -974,15 +1066,13 @@ function createRouteForge(options) {
974
1066
  request: requestInterceptors,
975
1067
  response: responseInterceptors
976
1068
  },
977
- ready: readyPromise,
978
- onLevelLoaded(level, cb) {
979
- if (!levelLoadedListeners.has(level)) {
980
- levelLoadedListeners.set(level, /* @__PURE__ */ new Set());
981
- }
982
- levelLoadedListeners.get(level).add(cb);
983
- return () => levelLoadedListeners.get(level)?.delete(cb);
1069
+ ready,
1070
+ use(level, prefix) {
1071
+ if (level === void 0) return forgeInstance;
1072
+ return createBoundForgeWithMethods(level, prefix);
984
1073
  }
985
1074
  };
1075
+ return forgeInstance;
986
1076
  }
987
1077
  function pickMethod(meta) {
988
1078
  const m = meta.methods.find((x) => x.toUpperCase() !== "HEAD");
@@ -1050,25 +1140,6 @@ function isAbortError2(err, signal) {
1050
1140
  return false;
1051
1141
  }
1052
1142
 
1053
- // src/resolveRouteName.ts
1054
- async function resolveRouteName(forge, level, prefix, suffix, separator = ".") {
1055
- if (!suffix) return prefix;
1056
- const joined = `${prefix}${separator}${suffix}`;
1057
- if (!suffix.startsWith(`${prefix}${separator}`)) return joined;
1058
- await forge.load(level);
1059
- if (forge.hasRoute(level, joined)) return joined;
1060
- if (forge.hasRoute(level, suffix)) return suffix;
1061
- throw new UnknownRouteError(joined, level);
1062
- }
1063
- function resolveRouteNameSync(forge, level, prefix, suffix, separator = ".") {
1064
- if (!suffix) return prefix;
1065
- const joined = `${prefix}${separator}${suffix}`;
1066
- if (!suffix.startsWith(`${prefix}${separator}`)) return joined;
1067
- if (forge.hasRoute(level, joined)) return joined;
1068
- if (forge.hasRoute(level, suffix)) return suffix;
1069
- throw new UnknownRouteError(joined, level);
1070
- }
1071
-
1072
1143
  exports.AdapterNotFoundError = AdapterNotFoundError;
1073
1144
  exports.ForgeError = ForgeError;
1074
1145
  exports.HTTPError = HTTPError;