@route-forge/vue 1.3.0 → 2.0.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
@@ -1,7 +1,7 @@
1
1
  # @route-forge/vue
2
2
 
3
3
  Route Forge 的 Vue 3 集成:插件 + composable(`useForge` / `useForgeApi` /
4
- `useForgeRoute` / `useForgeByPrefix`)。
4
+ `useForgeRoute`)。
5
5
 
6
6
  ## 安装
7
7
 
@@ -19,16 +19,18 @@ import App from './App.vue'
19
19
 
20
20
  const plugin = createRouteForgePlugin({
21
21
  endpoint: '/_forge/routes',
22
- onSummaryReady: () => {
23
- // 推荐:在路由数据就绪后挂载应用
24
- app.mount('#app')
25
- },
26
22
  })
27
23
 
28
24
  const app = createApp(App)
29
25
  app.use(plugin)
30
- // 如果不使用 onSummaryReady 回调,也可以直接挂载
31
- // app.mount('#app')
26
+ // 推荐:ready()(摘要 + eager 层级全部完成)后再挂载应用
27
+ // (onSummaryReady 回调已移除,统一走 ready——完整成功/失败语义链)
28
+ plugin.ready()
29
+ .then(() => app.mount('#app'))
30
+ .catch((err) => {
31
+ // 失败兜底:摘要端点不可达(网络错误/非 2xx/超时)时接住 reject,避免静默白屏
32
+ console.error('[route-forge] init failed', err)
33
+ })
32
34
  ```
33
35
 
34
36
  ## useForge — 核心 composable
@@ -94,20 +96,25 @@ forge.api('search.show', {
94
96
  import {
95
97
  useForgeApi,
96
98
  useForgeRoute,
97
- useForgeByPrefix,
98
99
  } from '@route-forge/vue'
99
100
 
100
- // useForgeApi — 带 loading/error 状态的 API 调用
101
- const { call, pending, error } = useForgeApi()
102
- const { data } = await call('admin', 'users.show', { user: 1 })
101
+ // useForgeApi — 带 loading/error 状态的 API 调用(事件处理器场景)
102
+ // 不抛异常:错误作为 { data: undefined, error } 返回,同时写入 error 状态
103
+ // 三种调用形态(level 绑定与 useForge 一致):
104
+ const api = useForgeApi() // 未绑定:call(level, name, params)
105
+ const admin = useForgeApi('admin') // 绑定层级:call(name, params)
106
+ const users = useForgeApi('admin', 'users') // 绑定层级 + 前缀:call(suffix, params)
103
107
 
104
- // useForgeRoute 响应式 URL 生成器,内部处理 level 加载状态
105
- // level 未加载时返回 '',加载后自动更新
106
- const url = useForgeRoute('public', 'login.show')
108
+ const { data, error } = await admin.call('users.show', { user: 1 })
109
+ // pending: Ref<boolean>,并发多个 call 时全部完成才置 false(引用计数)
110
+ // error: Ref<unknown>,最近一次失败信息
107
111
 
108
- // useForgeByPrefix层级 + 名字前缀封装
109
- const { api, route } = useForgeByPrefix('admin', 'users')
110
- await api('show', { user: 1 }) // = forge.api('admin', 'users.show', { user: 1 })
112
+ // useForgeRoute响应式 URL 生成器(模板层场景)
113
+ // ComputedRef<string>,内部处理 level 加载状态:
114
+ // level 未加载时返回 '',加载后自动更新;参数(getter)变化自动重算
115
+ const url = useForgeRoute('public', 'login.show')
116
+ const profile = useForgeRoute('admin', 'users.show', () => ({ user: userId.value }))
117
+ // 路由名错误或必填参数缺失:降级为 '' 保证渲染不中断,控制台输出完整错误(含堆栈)
111
118
  ```
112
119
 
113
120
  ## 模板内使用
package/dist/index.cjs CHANGED
@@ -42,24 +42,36 @@ function useForge(level, prefix) {
42
42
  return bound;
43
43
  }
44
44
  function useForgeApi(level, prefix) {
45
- const forge = level !== void 0 ? useForge(level, prefix) : useForge();
45
+ const bound = level !== void 0 ? useForge(level, prefix) : void 0;
46
+ const unbound = bound === void 0 ? useForge() : void 0;
46
47
  const pending = vue.ref(false);
47
48
  const error = vue.ref(null);
49
+ let inflight = 0;
48
50
  async function call(...args) {
51
+ inflight++;
49
52
  pending.value = true;
50
53
  error.value = null;
51
54
  try {
52
- const data = await forge.api(...args);
55
+ const data = bound !== void 0 ? await bound.api(args[0], args[1]) : await unbound.api(args[0], args[1], args[2]);
53
56
  return { data, error: null };
54
57
  } catch (e) {
55
58
  error.value = e;
56
59
  return { data: void 0, error: e };
57
60
  } finally {
58
- pending.value = false;
61
+ inflight--;
62
+ pending.value = inflight > 0;
59
63
  }
60
64
  }
61
65
  return { pending, error, call };
62
66
  }
67
+ function warnRenderError(error) {
68
+ console.warn(
69
+ "%c[route-forge]%c useForgeRoute \u6E32\u67D3\u671F\u9519\u8BEF\uFF08\u5DF2\u964D\u7EA7\u4E3A\u7A7A\u5B57\u7B26\u4E32\uFF0C\u6E32\u67D3\u672A\u4E2D\u65AD\uFF09",
70
+ "color:#e67e22;font-weight:bold",
71
+ "color:inherit",
72
+ error
73
+ );
74
+ }
63
75
  function useForgeRoute(level, name, params) {
64
76
  const forge = vue.inject(FORGE_INJECTION_KEY);
65
77
  const levelLoaded = vue.ref(forge.isLoaded(typeof level === "function" ? level() : level));
@@ -77,27 +89,19 @@ function useForgeRoute(level, name, params) {
77
89
  const l = typeof level === "function" ? level() : level;
78
90
  const n = typeof name === "function" ? name() : name;
79
91
  const p = params ? params() : void 0;
80
- return forge.route(l, n, p);
81
- });
82
- }
83
- function useForgeByPrefix(level, prefix, separator = ".") {
84
- const forge = vue.inject(FORGE_INJECTION_KEY);
85
- return {
86
- api: (suffix, params) => core.resolveRouteName(forge, level, prefix, suffix, separator).then(
87
- (name) => forge.api(level, name, params)
88
- ),
89
- route: (suffix, params) => {
90
- if (!forge.isLoaded(level)) return "";
91
- return forge.route(level, core.resolveRouteNameSync(forge, level, prefix, suffix, separator), params);
92
+ try {
93
+ return forge.route(l, n, p);
94
+ } catch (e) {
95
+ warnRenderError(e);
96
+ return "";
92
97
  }
93
- };
98
+ });
94
99
  }
95
100
 
96
101
  exports.FORGE_INJECTION_KEY = FORGE_INJECTION_KEY;
97
102
  exports.createRouteForgePlugin = createRouteForgePlugin;
98
103
  exports.useForge = useForge;
99
104
  exports.useForgeApi = useForgeApi;
100
- exports.useForgeByPrefix = useForgeByPrefix;
101
105
  exports.useForgeRoute = useForgeRoute;
102
106
  //# sourceMappingURL=index.cjs.map
103
107
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/plugin.ts","../src/composables/useForgeApi.ts","../src/composables/useForgeRoute.ts","../src/composables/useForgeByPrefix.ts"],"names":["createRouteForge","inject","ref","onMounted","computed","resolveRouteName","resolveRouteNameSync"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Vue 3 插件入口\n * @see .docs/SPEC.md §4.1.7\n *\n * 提供:\n * - 全局属性 $forge(route() 工具)\n * - inject symbol 注入 RouteForge 实例供 composable 使用\n * - useForge() / useForge(level) / useForge(level, prefix) 返回统一方法的 forge 实例\n * • 不传 level:forge(level, name, params?) 直接调用\n * • 传 level:forge(name, params?) 直接调用,自动绑定层级\n * • 传 level + prefix:forge(suffix, params?) 自动拼接 prefix\n *\n * 类型推断:\n * 当 ForgeRouteMap 通过 codegen 或 module augmentation 定义时,\n * level / name / params 均自动推断,IDE 提供补全提示。\n */\n\nimport type { App, InjectionKey, Plugin, Ref } from 'vue';\nimport { inject, ref } from 'vue';\nimport {\n type ApiCallParams,\n type BoundForge,\n createRouteForge,\n type RouteForge,\n type RouteForgeOptions,\n} from '@route-forge/core';\n\nexport const FORGE_INJECTION_KEY: InjectionKey<RouteForge> = Symbol('route-forge');\n\nexport interface RouteForgePluginOptions extends RouteForgeOptions {}\n\n/** Vue 特化类型:levelLoaded 为 Ref<boolean> */\nexport type VueBoundForge = BoundForge<Ref<boolean>>;\n\nexport function createRouteForgePlugin(options: RouteForgePluginOptions): Plugin<[]> & {\n ready: RouteForge['ready']\n} {\n const forge = createRouteForge(options);\n\n return {\n ready: forge.ready.bind(forge),\n install(app: App) {\n app.provide(FORGE_INJECTION_KEY, forge);\n app.config.globalProperties.$forge = {\n route: (level: string, name: string, params?: Record<string, unknown>) =>\n forge.route(level, name, params),\n };\n },\n };\n}\n\n/**\n * 获取 forge 实例。内部委托 core 的 forge.use(),仅将 levelLoaded 替换为 Vue 响应式 Ref。\n *\n * @example\n * // 不绑定层级\n * const forge = useForge()\n * forge('admin', 'users.show', { user: 1 })\n * forge.api('admin', 'users.show')\n *\n * @example\n * // 绑定层级 — 直接调用和 api/route/url 均无需传 level\n * const forge = useForge('admin')\n * forge.level // → 'admin'\n * forge.levelLoaded // Ref<boolean>\n * forge('users.show', { user: 1 })\n * forge.route('users.show', { user: 1 })\n *\n * @example\n * // 绑定层级 + 前缀 — 路由名自动拼接\n * const forge = useForge('admin', 'users')\n * forge('show', { user: 1 }) // → forge.api('admin', 'users.show', ...)\n * forge.route('show', { user: 1 }) // → forge.route('admin', 'users.show', ...)\n */\nexport function useForge<L extends string>(level: L, prefix: string): VueBoundForge;\nexport function useForge<L extends string>(level: L): VueBoundForge;\nexport function useForge(): RouteForge;\nexport function useForge(level?: string, prefix?: string): RouteForge | VueBoundForge {\n const forge = inject(FORGE_INJECTION_KEY);\n if (!forge) {\n throw new Error(\n '[route-forge/vue] useForge() must be used inside an app with createRouteForgePlugin() installed',\n );\n }\n\n if (level === undefined) {\n return forge;\n }\n\n // 委托 core 的 use(),获取 BoundForge(levelLoaded 为 Promise<void>)\n const bound = forge.use(level, prefix);\n\n // 将 levelLoaded 从 Promise<void> 替换为 Ref<boolean>\n const levelLoadedRef = ref(false);\n bound.levelLoaded.then(() => {\n levelLoadedRef.value = true;\n }).catch(() => { /* 加载失败时 levelLoaded 保持 false */ });\n\n Object.defineProperty(bound, 'levelLoaded', {\n value: levelLoadedRef,\n writable: false,\n enumerable: true,\n configurable: false,\n });\n\n return bound as unknown as VueBoundForge;\n}\n","/**\n * useForgeApi:包装 forge.api(),自动管理 loading/error 状态\n * @see .docs/SPEC.md §4.1.7\n *\n * 支持:\n * - useForgeApi() — call(level, name, params)\n * - useForgeApi(level) — call(name, params),自动绑定层级\n * - useForgeApi(level, prefix) — call(suffix, params),自动绑定层级 + 拼接前缀\n *\n * 类型推断:\n * 当 ForgeRouteMap 通过 codegen 或 module augmentation 定义时,\n * level / name / params 均自动推断,IDE 提供补全提示。\n */\n\nimport type { Ref } from 'vue';\nimport { ref } from 'vue';\nimport { useForge } from '../plugin.js';\nimport type {\n UseForgeApiBoundCall,\n UseForgeApiBoundReturn,\n UseForgeApiCall,\n UseForgeApiReturn,\n} from '@route-forge/core';\n\n// Vue 特化类型:pending/error 为 Ref\nexport type { UseForgeApiCall, UseForgeApiBoundCall };\nexport type UseForgeApiReturnVue = UseForgeApiReturn<Ref<boolean>, Ref<unknown>>;\nexport type UseForgeApiBoundReturnVue<L extends string> = UseForgeApiBoundReturn<L, Ref<boolean>, Ref<unknown>>;\n\n/** 不绑定层级 — call 需要传 level */\nexport function useForgeApi(): UseForgeApiReturnVue;\n/** 绑定层级 — call 无需传 level */\nexport function useForgeApi<L extends string>(level: L): UseForgeApiBoundReturnVue<L>;\n/** 绑定层级 + 前缀 — call 无需传 level,路由名自动拼接 prefix */\nexport function useForgeApi<L extends string>(level: L, prefix: string): UseForgeApiBoundReturnVue<L>;\nexport function useForgeApi(level?: string, prefix?: string): UseForgeApiReturnVue | UseForgeApiBoundReturnVue<string> {\n const forge = level !== undefined ? useForge(level, prefix as string) : useForge();\n const pending = ref(false);\n const error = ref<unknown>(null);\n\n async function call(...args: unknown[]): Promise<{ data: unknown; error: unknown }> {\n pending.value = true;\n error.value = null;\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const data = await (forge as any).api(...args);\n return { data, error: null };\n } catch (e) {\n error.value = e;\n return { data: undefined, error: e };\n } finally {\n pending.value = false;\n }\n }\n\n return { pending, error, call: call as any };\n}\n","/**\n * useForgeRoute:响应式 URL 生成器,内部处理 level 加载状态\n * @see .docs/SPEC.md §4.1.7\n *\n * - level 未加载时返回空字符串 ''(不抛错,模板不崩)\n * - level 加载完成后自动重新计算并返回正确 URL\n * - 用户无需关心 levelLoaded 状态,直接用即可\n */\n\nimport { computed, type ComputedRef, inject, onMounted, ref } from 'vue';\nimport { FORGE_INJECTION_KEY } from '../plugin.js';\nimport type { RouteForge } from '@route-forge/core';\n\nexport function useForgeRoute(\n level: string | (() => string),\n name: string | (() => string),\n params?: () => Record<string, unknown>,\n): ComputedRef<string> {\n const forge = inject(FORGE_INJECTION_KEY) as RouteForge;\n\n // 响应式追踪 level 加载状态(computed 依赖此 ref 触发重新计算)\n const levelLoaded = ref(forge.isLoaded(typeof level === 'function' ? level() : level));\n\n const lvl = typeof level === 'function' ? level() : level;\n if (!levelLoaded.value) {\n onMounted(() => {\n forge.load(lvl).then(() => {\n levelLoaded.value = true;\n }).catch(() => { /* 加载失败时 computed 返回 '' */\n });\n });\n }\n\n return computed(() => {\n // level 未加载 → 返回空字符串,不抛错\n if (!levelLoaded.value) return '';\n const l = typeof level === 'function' ? level() : level;\n const n = typeof name === 'function' ? name() : name;\n const p = params ? params() : undefined;\n return forge.route(l, n, p);\n });\n}\n","/**\n * useForgeByPrefix:带层级 + 名字前缀的封装,方便后续减少名称传入\n * @see .docs/SPEC.md §4.1.7\n *\n * 智能前缀解析:\n * - 传入后缀不以前缀开头 → 自动拼接 prefix.suffix\n * - 传入后缀已以前缀开头(歧义) → 优先尝试完整拼接,\n * 若不存在则视为已含前缀直接使用,均不存在则报错\n *\n * 示例(prefix = 'test'):\n * api('1') → test.1\n * api('test1.1') → test.test1.1\n * api('test.1') → 歧义:优先 test.test.1,回退 test.1\n */\n\nimport { inject } from 'vue';\nimport { FORGE_INJECTION_KEY } from '../plugin.js';\nimport {\n resolveRouteName,\n resolveRouteNameSync,\n type RouteForge,\n type UseForgeByPrefixReturn,\n} from '@route-forge/core';\n\nexport type { UseForgeByPrefixReturn };\n\nexport function useForgeByPrefix(level: string, prefix: string, separator = '.'): UseForgeByPrefixReturn {\n const forge = inject(FORGE_INJECTION_KEY) as RouteForge;\n\n return {\n api: (suffix, params) =>\n resolveRouteName(forge, level, prefix, suffix, separator).then(\n (name) => forge.api(level, name, params),\n ),\n route: (suffix, params) => {\n // level 未加载时返回空字符串,避免 resolveRouteNameSync 内部 hasRoute() 抛错\n if (!forge.isLoaded(level)) return '';\n return forge.route(level, resolveRouteNameSync(forge, level, prefix, suffix, separator), params);\n },\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/plugin.ts","../src/composables/useForgeApi.ts","../src/composables/useForgeRoute.ts"],"names":["createRouteForge","inject","ref","onMounted","computed"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Vue 3 插件入口\n * @see .docs/SPEC.md §4.1.7\n *\n * 提供:\n * - 全局属性 $forge(route() 工具)\n * - inject symbol 注入 RouteForge 实例供 composable 使用\n * - useForge() / useForge(level) / useForge(level, prefix) 返回统一方法的 forge 实例\n * • 不传 level:forge(level, name, params?) 直接调用\n * • 传 level:forge(name, params?) 直接调用,自动绑定层级\n * • 传 level + prefix:forge(suffix, params?) 自动拼接 prefix\n *\n * 类型推断:\n * 当 ForgeRouteMap 通过 codegen 或 module augmentation 定义时,\n * level / name / params 均自动推断,IDE 提供补全提示。\n */\n\nimport type { App, InjectionKey, Plugin, Ref } from 'vue';\nimport { inject, ref } from 'vue';\nimport {\n type ApiCallParams,\n type BoundForge,\n createRouteForge,\n type RouteForge,\n type RouteForgeOptions,\n} from '@route-forge/core';\n\nexport const FORGE_INJECTION_KEY: InjectionKey<RouteForge> = Symbol('route-forge');\n\nexport interface RouteForgePluginOptions extends RouteForgeOptions {}\n\n/** Vue 特化类型:levelLoaded 为 Ref<boolean> */\nexport type VueBoundForge = BoundForge<Ref<boolean>>;\n\nexport function createRouteForgePlugin(options: RouteForgePluginOptions): Plugin<[]> & {\n ready: RouteForge['ready']\n} {\n const forge = createRouteForge(options);\n\n return {\n ready: forge.ready.bind(forge),\n install(app: App) {\n app.provide(FORGE_INJECTION_KEY, forge);\n app.config.globalProperties.$forge = {\n route: (level: string, name: string, params?: Record<string, unknown>) =>\n forge.route(level, name, params),\n };\n },\n };\n}\n\n/**\n * 获取 forge 实例。内部委托 core 的 forge.use(),仅将 levelLoaded 替换为 Vue 响应式 Ref。\n *\n * @example\n * // 不绑定层级\n * const forge = useForge()\n * forge('admin', 'users.show', { user: 1 })\n * forge.api('admin', 'users.show')\n *\n * @example\n * // 绑定层级 — 直接调用和 api/route/url 均无需传 level\n * const forge = useForge('admin')\n * forge.level // → 'admin'\n * forge.levelLoaded // Ref<boolean>\n * forge('users.show', { user: 1 })\n * forge.route('users.show', { user: 1 })\n *\n * @example\n * // 绑定层级 + 前缀 — 路由名自动拼接\n * const forge = useForge('admin', 'users')\n * forge('show', { user: 1 }) // → forge.api('admin', 'users.show', ...)\n * forge.route('show', { user: 1 }) // → forge.route('admin', 'users.show', ...)\n */\nexport function useForge<L extends string>(level: L, prefix: string): VueBoundForge;\nexport function useForge<L extends string>(level: L): VueBoundForge;\nexport function useForge(): RouteForge;\nexport function useForge(level?: string, prefix?: string): RouteForge | VueBoundForge {\n const forge = inject(FORGE_INJECTION_KEY);\n if (!forge) {\n throw new Error(\n '[route-forge/vue] useForge() must be used inside an app with createRouteForgePlugin() installed',\n );\n }\n\n if (level === undefined) {\n return forge;\n }\n\n // 委托 core 的 use(),获取 BoundForge(levelLoaded 为 Promise<void>)\n const bound = forge.use(level, prefix);\n\n // 将 levelLoaded 从 Promise<void> 替换为 Ref<boolean>\n const levelLoadedRef = ref(false);\n bound.levelLoaded.then(() => {\n levelLoadedRef.value = true;\n }).catch(() => { /* 加载失败时 levelLoaded 保持 false */ });\n\n Object.defineProperty(bound, 'levelLoaded', {\n value: levelLoadedRef,\n writable: false,\n enumerable: true,\n configurable: false,\n });\n\n return bound as unknown as VueBoundForge;\n}\n","/**\n * useForgeApi:包装 forge.api(),自动管理 loading/error 状态\n * @see .docs/SPEC.md §4.1.7\n *\n * 支持:\n * - useForgeApi() — call(level, name, params)\n * - useForgeApi(level) — call(name, params),自动绑定层级\n * - useForgeApi(level, prefix) — call(suffix, params),自动绑定层级 + 拼接前缀\n *\n * 类型推断:\n * 当 ForgeRouteMap 通过 codegen 或 module augmentation 定义时,\n * level / name / params 均自动推断,IDE 提供补全提示。\n *\n * pending 采用引用计数:并发多个 call 时,全部完成才置 false\n * (先完成的 call 不再把其他在途请求的 pending 提前清掉)。\n */\n\nimport type { Ref } from 'vue';\nimport { ref } from 'vue';\nimport { useForge } from '../plugin.js';\nimport type {\n BoundForge,\n RouteForge,\n UseForgeApiBoundCall,\n UseForgeApiBoundReturn,\n UseForgeApiCall,\n UseForgeApiReturn,\n} from '@route-forge/core';\n\n// Vue 特化类型:pending/error 为 Ref\nexport type { UseForgeApiCall, UseForgeApiBoundCall };\nexport type UseForgeApiReturnVue = UseForgeApiReturn<Ref<boolean>, Ref<unknown>>;\nexport type UseForgeApiBoundReturnVue<L extends string> = UseForgeApiBoundReturn<L, Ref<boolean>, Ref<unknown>>;\n\n/** 不绑定层级 — call 需要传 level */\nexport function useForgeApi(): UseForgeApiReturnVue;\n/** 绑定层级 — call 无需传 level */\nexport function useForgeApi<L extends string>(level: L): UseForgeApiBoundReturnVue<L>;\n/** 绑定层级 + 前缀 — call 无需传 level,路由名自动拼接 prefix */\nexport function useForgeApi<L extends string>(level: L, prefix: string): UseForgeApiBoundReturnVue<L>;\nexport function useForgeApi(level?: string, prefix?: string): UseForgeApiReturnVue | UseForgeApiBoundReturnVue<string> {\n const bound = level !== undefined ? useForge(level, prefix as string) : undefined;\n const unbound = bound === undefined ? useForge() : undefined;\n const pending = ref(false);\n const error = ref<unknown>(null);\n // 并发计数:setup 闭包变量(组件实例生命周期存活)\n let inflight = 0;\n\n async function call(...args: unknown[]): Promise<{ data: unknown; error: unknown }> {\n inflight++;\n pending.value = true;\n error.value = null;\n try {\n // 按绑定形态分发:bound 走 BoundForge.api(name, params),unbound 走 RouteForge.api(level, name, params)\n // (窄断言替代 as any:重载签名已保证参数形状,此处仅消除联合类型/框架特化类型的分支歧义)\n const data = bound !== undefined\n ? await (bound as unknown as BoundForge).api(args[0] as string, args[1] as never)\n : await (unbound as RouteForge).api(args[0] as string, args[1] as string, args[2] as never);\n return { data, error: null };\n } catch (e) {\n error.value = e;\n return { data: undefined, error: e };\n } finally {\n inflight--;\n pending.value = inflight > 0;\n }\n }\n\n return { pending, error, call: call as unknown as UseForgeApiCall };\n}\n","/**\n * useForgeRoute:响应式 URL 生成器,内部处理 level 加载状态\n * @see .docs/SPEC.md §4.1.7\n *\n * - level 未加载时返回空字符串 ''(不抛错,模板不崩)\n * - level 加载完成后自动重新计算并返回正确 URL\n * - 路由名不存在或必填参数缺失等渲染期错误:降级为 '' 保证渲染不中断,\n * 同时以醒目的样式化 warn 输出完整错误(含堆栈)——开发期可见,生产无副作用\n * - 用户无需关心 levelLoaded 状态,直接用即可\n */\n\nimport { computed, type ComputedRef, inject, onMounted, ref } from 'vue';\nimport { FORGE_INJECTION_KEY } from '../plugin.js';\nimport type { RouteForge } from '@route-forge/core';\n\n/** 渲染期错误降级输出:橙色加粗标签 + 完整错误对象,控制台一眼可见 */\nfunction warnRenderError(error: unknown): void {\n console.warn(\n '%c[route-forge]%c useForgeRoute 渲染期错误(已降级为空字符串,渲染未中断)',\n 'color:#e67e22;font-weight:bold',\n 'color:inherit',\n error,\n );\n}\n\nexport function useForgeRoute(\n level: string | (() => string),\n name: string | (() => string),\n params?: () => Record<string, unknown>,\n): ComputedRef<string> {\n const forge = inject(FORGE_INJECTION_KEY) as RouteForge;\n\n // 响应式追踪 level 加载状态(computed 依赖此 ref 触发重新计算)\n const levelLoaded = ref(forge.isLoaded(typeof level === 'function' ? level() : level));\n\n const lvl = typeof level === 'function' ? level() : level;\n if (!levelLoaded.value) {\n onMounted(() => {\n forge.load(lvl).then(() => {\n levelLoaded.value = true;\n }).catch(() => { /* 加载失败时 computed 返回 '' */\n });\n });\n }\n\n return computed(() => {\n // level 未加载 → 返回空字符串,不抛错\n if (!levelLoaded.value) return '';\n const l = typeof level === 'function' ? level() : level;\n const n = typeof name === 'function' ? name() : name;\n const p = params ? params() : undefined;\n try {\n return forge.route(l, n, p);\n } catch (e) {\n warnRenderError(e);\n return '';\n }\n });\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { InjectionKey, Ref, Plugin, ComputedRef } from 'vue';
2
- import { RouteForge, RouteForgeOptions, BoundForge, UseForgeApiBoundReturn, UseForgeApiReturn, UseForgeByPrefixReturn } from '@route-forge/core';
3
- export { UseForgeApiBoundCall, UseForgeApiCall, UseForgeByPrefixReturn } from '@route-forge/core';
2
+ import { RouteForge, RouteForgeOptions, BoundForge, UseForgeApiBoundReturn, UseForgeApiReturn } from '@route-forge/core';
3
+ export { UseForgeApiBoundCall, UseForgeApiCall } from '@route-forge/core';
4
4
 
5
5
  /**
6
6
  * Vue 3 插件入口
@@ -66,6 +66,9 @@ declare function useForge(): RouteForge;
66
66
  * 类型推断:
67
67
  * 当 ForgeRouteMap 通过 codegen 或 module augmentation 定义时,
68
68
  * level / name / params 均自动推断,IDE 提供补全提示。
69
+ *
70
+ * pending 采用引用计数:并发多个 call 时,全部完成才置 false
71
+ * (先完成的 call 不再把其他在途请求的 pending 提前清掉)。
69
72
  */
70
73
 
71
74
  type UseForgeApiReturnVue = UseForgeApiReturn<Ref<boolean>, Ref<unknown>>;
@@ -83,26 +86,11 @@ declare function useForgeApi<L extends string>(level: L, prefix: string): UseFor
83
86
  *
84
87
  * - level 未加载时返回空字符串 ''(不抛错,模板不崩)
85
88
  * - level 加载完成后自动重新计算并返回正确 URL
89
+ * - 路由名不存在或必填参数缺失等渲染期错误:降级为 '' 保证渲染不中断,
90
+ * 同时以醒目的样式化 warn 输出完整错误(含堆栈)——开发期可见,生产无副作用
86
91
  * - 用户无需关心 levelLoaded 状态,直接用即可
87
92
  */
88
93
 
89
94
  declare function useForgeRoute(level: string | (() => string), name: string | (() => string), params?: () => Record<string, unknown>): ComputedRef<string>;
90
95
 
91
- /**
92
- * useForgeByPrefix:带层级 + 名字前缀的封装,方便后续减少名称传入
93
- * @see .docs/SPEC.md §4.1.7
94
- *
95
- * 智能前缀解析:
96
- * - 传入后缀不以前缀开头 → 自动拼接 prefix.suffix
97
- * - 传入后缀已以前缀开头(歧义) → 优先尝试完整拼接,
98
- * 若不存在则视为已含前缀直接使用,均不存在则报错
99
- *
100
- * 示例(prefix = 'test'):
101
- * api('1') → test.1
102
- * api('test1.1') → test.test1.1
103
- * api('test.1') → 歧义:优先 test.test.1,回退 test.1
104
- */
105
-
106
- declare function useForgeByPrefix(level: string, prefix: string, separator?: string): UseForgeByPrefixReturn;
107
-
108
- export { FORGE_INJECTION_KEY, type RouteForgePluginOptions, type UseForgeApiBoundReturnVue, type UseForgeApiReturnVue, type VueBoundForge, createRouteForgePlugin, useForge, useForgeApi, useForgeByPrefix, useForgeRoute };
96
+ export { FORGE_INJECTION_KEY, type RouteForgePluginOptions, type UseForgeApiBoundReturnVue, type UseForgeApiReturnVue, type VueBoundForge, createRouteForgePlugin, useForge, useForgeApi, useForgeRoute };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { InjectionKey, Ref, Plugin, ComputedRef } from 'vue';
2
- import { RouteForge, RouteForgeOptions, BoundForge, UseForgeApiBoundReturn, UseForgeApiReturn, UseForgeByPrefixReturn } from '@route-forge/core';
3
- export { UseForgeApiBoundCall, UseForgeApiCall, UseForgeByPrefixReturn } from '@route-forge/core';
2
+ import { RouteForge, RouteForgeOptions, BoundForge, UseForgeApiBoundReturn, UseForgeApiReturn } from '@route-forge/core';
3
+ export { UseForgeApiBoundCall, UseForgeApiCall } from '@route-forge/core';
4
4
 
5
5
  /**
6
6
  * Vue 3 插件入口
@@ -66,6 +66,9 @@ declare function useForge(): RouteForge;
66
66
  * 类型推断:
67
67
  * 当 ForgeRouteMap 通过 codegen 或 module augmentation 定义时,
68
68
  * level / name / params 均自动推断,IDE 提供补全提示。
69
+ *
70
+ * pending 采用引用计数:并发多个 call 时,全部完成才置 false
71
+ * (先完成的 call 不再把其他在途请求的 pending 提前清掉)。
69
72
  */
70
73
 
71
74
  type UseForgeApiReturnVue = UseForgeApiReturn<Ref<boolean>, Ref<unknown>>;
@@ -83,26 +86,11 @@ declare function useForgeApi<L extends string>(level: L, prefix: string): UseFor
83
86
  *
84
87
  * - level 未加载时返回空字符串 ''(不抛错,模板不崩)
85
88
  * - level 加载完成后自动重新计算并返回正确 URL
89
+ * - 路由名不存在或必填参数缺失等渲染期错误:降级为 '' 保证渲染不中断,
90
+ * 同时以醒目的样式化 warn 输出完整错误(含堆栈)——开发期可见,生产无副作用
86
91
  * - 用户无需关心 levelLoaded 状态,直接用即可
87
92
  */
88
93
 
89
94
  declare function useForgeRoute(level: string | (() => string), name: string | (() => string), params?: () => Record<string, unknown>): ComputedRef<string>;
90
95
 
91
- /**
92
- * useForgeByPrefix:带层级 + 名字前缀的封装,方便后续减少名称传入
93
- * @see .docs/SPEC.md §4.1.7
94
- *
95
- * 智能前缀解析:
96
- * - 传入后缀不以前缀开头 → 自动拼接 prefix.suffix
97
- * - 传入后缀已以前缀开头(歧义) → 优先尝试完整拼接,
98
- * 若不存在则视为已含前缀直接使用,均不存在则报错
99
- *
100
- * 示例(prefix = 'test'):
101
- * api('1') → test.1
102
- * api('test1.1') → test.test1.1
103
- * api('test.1') → 歧义:优先 test.test.1,回退 test.1
104
- */
105
-
106
- declare function useForgeByPrefix(level: string, prefix: string, separator?: string): UseForgeByPrefixReturn;
107
-
108
- export { FORGE_INJECTION_KEY, type RouteForgePluginOptions, type UseForgeApiBoundReturnVue, type UseForgeApiReturnVue, type VueBoundForge, createRouteForgePlugin, useForge, useForgeApi, useForgeByPrefix, useForgeRoute };
96
+ export { FORGE_INJECTION_KEY, type RouteForgePluginOptions, type UseForgeApiBoundReturnVue, type UseForgeApiReturnVue, type VueBoundForge, createRouteForgePlugin, useForge, useForgeApi, useForgeRoute };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { inject, ref, onMounted, computed } from 'vue';
2
- import { createRouteForge, resolveRouteNameSync, resolveRouteName } from '@route-forge/core';
2
+ import { createRouteForge } from '@route-forge/core';
3
3
 
4
4
  // src/plugin.ts
5
5
  var FORGE_INJECTION_KEY = /* @__PURE__ */ Symbol("route-forge");
@@ -40,24 +40,36 @@ function useForge(level, prefix) {
40
40
  return bound;
41
41
  }
42
42
  function useForgeApi(level, prefix) {
43
- const forge = level !== void 0 ? useForge(level, prefix) : useForge();
43
+ const bound = level !== void 0 ? useForge(level, prefix) : void 0;
44
+ const unbound = bound === void 0 ? useForge() : void 0;
44
45
  const pending = ref(false);
45
46
  const error = ref(null);
47
+ let inflight = 0;
46
48
  async function call(...args) {
49
+ inflight++;
47
50
  pending.value = true;
48
51
  error.value = null;
49
52
  try {
50
- const data = await forge.api(...args);
53
+ const data = bound !== void 0 ? await bound.api(args[0], args[1]) : await unbound.api(args[0], args[1], args[2]);
51
54
  return { data, error: null };
52
55
  } catch (e) {
53
56
  error.value = e;
54
57
  return { data: void 0, error: e };
55
58
  } finally {
56
- pending.value = false;
59
+ inflight--;
60
+ pending.value = inflight > 0;
57
61
  }
58
62
  }
59
63
  return { pending, error, call };
60
64
  }
65
+ function warnRenderError(error) {
66
+ console.warn(
67
+ "%c[route-forge]%c useForgeRoute \u6E32\u67D3\u671F\u9519\u8BEF\uFF08\u5DF2\u964D\u7EA7\u4E3A\u7A7A\u5B57\u7B26\u4E32\uFF0C\u6E32\u67D3\u672A\u4E2D\u65AD\uFF09",
68
+ "color:#e67e22;font-weight:bold",
69
+ "color:inherit",
70
+ error
71
+ );
72
+ }
61
73
  function useForgeRoute(level, name, params) {
62
74
  const forge = inject(FORGE_INJECTION_KEY);
63
75
  const levelLoaded = ref(forge.isLoaded(typeof level === "function" ? level() : level));
@@ -75,22 +87,15 @@ function useForgeRoute(level, name, params) {
75
87
  const l = typeof level === "function" ? level() : level;
76
88
  const n = typeof name === "function" ? name() : name;
77
89
  const p = params ? params() : void 0;
78
- return forge.route(l, n, p);
79
- });
80
- }
81
- function useForgeByPrefix(level, prefix, separator = ".") {
82
- const forge = inject(FORGE_INJECTION_KEY);
83
- return {
84
- api: (suffix, params) => resolveRouteName(forge, level, prefix, suffix, separator).then(
85
- (name) => forge.api(level, name, params)
86
- ),
87
- route: (suffix, params) => {
88
- if (!forge.isLoaded(level)) return "";
89
- return forge.route(level, resolveRouteNameSync(forge, level, prefix, suffix, separator), params);
90
+ try {
91
+ return forge.route(l, n, p);
92
+ } catch (e) {
93
+ warnRenderError(e);
94
+ return "";
90
95
  }
91
- };
96
+ });
92
97
  }
93
98
 
94
- export { FORGE_INJECTION_KEY, createRouteForgePlugin, useForge, useForgeApi, useForgeByPrefix, useForgeRoute };
99
+ export { FORGE_INJECTION_KEY, createRouteForgePlugin, useForge, useForgeApi, useForgeRoute };
95
100
  //# sourceMappingURL=index.js.map
96
101
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/plugin.ts","../src/composables/useForgeApi.ts","../src/composables/useForgeRoute.ts","../src/composables/useForgeByPrefix.ts"],"names":["ref","inject"],"mappings":";;;;AA2BO,IAAM,mBAAA,0BAAuD,aAAa;AAO1E,SAAS,uBAAuB,OAAA,EAErC;AACA,EAAA,MAAM,KAAA,GAAQ,iBAAiB,OAAO,CAAA;AAEtC,EAAA,OAAO;AAAA,IACL,KAAA,EAAO,KAAA,CAAM,KAAA,CAAM,IAAA,CAAK,KAAK,CAAA;AAAA,IAC7B,QAAQ,GAAA,EAAU;AAChB,MAAA,GAAA,CAAI,OAAA,CAAQ,qBAAqB,KAAK,CAAA;AACtC,MAAA,GAAA,CAAI,MAAA,CAAO,iBAAiB,MAAA,GAAS;AAAA,QACnC,KAAA,EAAO,CAAC,KAAA,EAAe,IAAA,EAAc,WACnC,KAAA,CAAM,KAAA,CAAM,KAAA,EAAO,IAAA,EAAM,MAAM;AAAA,OACnC;AAAA,IACF;AAAA,GACF;AACF;AA4BO,SAAS,QAAA,CAAS,OAAgB,MAAA,EAA6C;AACpF,EAAA,MAAM,KAAA,GAAQ,OAAO,mBAAmB,CAAA;AACxC,EAAA,IAAI,CAAC,KAAA,EAAO;AACV,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,KACF;AAAA,EACF;AAEA,EAAA,IAAI,UAAU,MAAA,EAAW;AACvB,IAAA,OAAO,KAAA;AAAA,EACT;AAGA,EAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,GAAA,CAAI,KAAA,EAAO,MAAM,CAAA;AAGrC,EAAA,MAAM,cAAA,GAAiB,IAAI,KAAK,CAAA;AAChC,EAAA,KAAA,CAAM,WAAA,CAAY,KAAK,MAAM;AAC3B,IAAA,cAAA,CAAe,KAAA,GAAQ,IAAA;AAAA,EACzB,CAAC,CAAA,CAAE,KAAA,CAAM,MAAM;AAAA,EAAmC,CAAC,CAAA;AAEnD,EAAA,MAAA,CAAO,cAAA,CAAe,OAAO,aAAA,EAAe;AAAA,IAC1C,KAAA,EAAO,cAAA;AAAA,IACP,QAAA,EAAU,KAAA;AAAA,IACV,UAAA,EAAY,IAAA;AAAA,IACZ,YAAA,EAAc;AAAA,GACf,CAAA;AAED,EAAA,OAAO,KAAA;AACT;ACvEO,SAAS,WAAA,CAAY,OAAgB,MAAA,EAA2E;AACrH,EAAA,MAAM,QAAQ,KAAA,KAAU,MAAA,GAAY,SAAS,KAAA,EAAO,MAAgB,IAAI,QAAA,EAAS;AACjF,EAAA,MAAM,OAAA,GAAUA,IAAI,KAAK,CAAA;AACzB,EAAA,MAAM,KAAA,GAAQA,IAAa,IAAI,CAAA;AAE/B,EAAA,eAAe,QAAQ,IAAA,EAA6D;AAClF,IAAA,OAAA,CAAQ,KAAA,GAAQ,IAAA;AAChB,IAAA,KAAA,CAAM,KAAA,GAAQ,IAAA;AACd,IAAA,IAAI;AAEF,MAAA,MAAM,IAAA,GAAO,MAAO,KAAA,CAAc,GAAA,CAAI,GAAG,IAAI,CAAA;AAC7C,MAAA,OAAO,EAAE,IAAA,EAAM,KAAA,EAAO,IAAA,EAAK;AAAA,IAC7B,SAAS,CAAA,EAAG;AACV,MAAA,KAAA,CAAM,KAAA,GAAQ,CAAA;AACd,MAAA,OAAO,EAAE,IAAA,EAAM,MAAA,EAAW,KAAA,EAAO,CAAA,EAAE;AAAA,IACrC,CAAA,SAAE;AACA,MAAA,OAAA,CAAQ,KAAA,GAAQ,KAAA;AAAA,IAClB;AAAA,EACF;AAEA,EAAA,OAAO,EAAE,OAAA,EAAS,KAAA,EAAO,IAAA,EAAkB;AAC7C;AC3CO,SAAS,aAAA,CACd,KAAA,EACA,IAAA,EACA,MAAA,EACqB;AACrB,EAAA,MAAM,KAAA,GAAQC,OAAO,mBAAmB,CAAA;AAGxC,EAAA,MAAM,WAAA,GAAcD,GAAAA,CAAI,KAAA,CAAM,QAAA,CAAS,OAAO,UAAU,UAAA,GAAa,KAAA,EAAM,GAAI,KAAK,CAAC,CAAA;AAErF,EAAA,MAAM,GAAA,GAAM,OAAO,KAAA,KAAU,UAAA,GAAa,OAAM,GAAI,KAAA;AACpD,EAAA,IAAI,CAAC,YAAY,KAAA,EAAO;AACtB,IAAA,SAAA,CAAU,MAAM;AACd,MAAA,KAAA,CAAM,IAAA,CAAK,GAAG,CAAA,CAAE,IAAA,CAAK,MAAM;AACzB,QAAA,WAAA,CAAY,KAAA,GAAQ,IAAA;AAAA,MACtB,CAAC,CAAA,CAAE,KAAA,CAAM,MAAM;AAAA,MACf,CAAC,CAAA;AAAA,IACH,CAAC,CAAA;AAAA,EACH;AAEA,EAAA,OAAO,SAAS,MAAM;AAEpB,IAAA,IAAI,CAAC,WAAA,CAAY,KAAA,EAAO,OAAO,EAAA;AAC/B,IAAA,MAAM,CAAA,GAAI,OAAO,KAAA,KAAU,UAAA,GAAa,OAAM,GAAI,KAAA;AAClD,IAAA,MAAM,CAAA,GAAI,OAAO,IAAA,KAAS,UAAA,GAAa,MAAK,GAAI,IAAA;AAChD,IAAA,MAAM,CAAA,GAAI,MAAA,GAAS,MAAA,EAAO,GAAI,MAAA;AAC9B,IAAA,OAAO,KAAA,CAAM,KAAA,CAAM,CAAA,EAAG,CAAA,EAAG,CAAC,CAAA;AAAA,EAC5B,CAAC,CAAA;AACH;ACfO,SAAS,gBAAA,CAAiB,KAAA,EAAe,MAAA,EAAgB,SAAA,GAAY,GAAA,EAA6B;AACvG,EAAA,MAAM,KAAA,GAAQC,OAAO,mBAAmB,CAAA;AAExC,EAAA,OAAO;AAAA,IACL,GAAA,EAAK,CAAC,MAAA,EAAQ,MAAA,KACZ,gBAAA,CAAiB,OAAO,KAAA,EAAO,MAAA,EAAQ,MAAA,EAAQ,SAAS,CAAA,CAAE,IAAA;AAAA,MACxD,CAAC,IAAA,KAAS,KAAA,CAAM,GAAA,CAAI,KAAA,EAAO,MAAM,MAAM;AAAA,KACzC;AAAA,IACF,KAAA,EAAO,CAAC,MAAA,EAAQ,MAAA,KAAW;AAEzB,MAAA,IAAI,CAAC,KAAA,CAAM,QAAA,CAAS,KAAK,GAAG,OAAO,EAAA;AACnC,MAAA,OAAO,KAAA,CAAM,KAAA,CAAM,KAAA,EAAO,oBAAA,CAAqB,KAAA,EAAO,OAAO,MAAA,EAAQ,MAAA,EAAQ,SAAS,CAAA,EAAG,MAAM,CAAA;AAAA,IACjG;AAAA,GACF;AACF","file":"index.js","sourcesContent":["/**\n * Vue 3 插件入口\n * @see .docs/SPEC.md §4.1.7\n *\n * 提供:\n * - 全局属性 $forge(route() 工具)\n * - inject symbol 注入 RouteForge 实例供 composable 使用\n * - useForge() / useForge(level) / useForge(level, prefix) 返回统一方法的 forge 实例\n * • 不传 level:forge(level, name, params?) 直接调用\n * • 传 level:forge(name, params?) 直接调用,自动绑定层级\n * • 传 level + prefix:forge(suffix, params?) 自动拼接 prefix\n *\n * 类型推断:\n * 当 ForgeRouteMap 通过 codegen 或 module augmentation 定义时,\n * level / name / params 均自动推断,IDE 提供补全提示。\n */\n\nimport type { App, InjectionKey, Plugin, Ref } from 'vue';\nimport { inject, ref } from 'vue';\nimport {\n type ApiCallParams,\n type BoundForge,\n createRouteForge,\n type RouteForge,\n type RouteForgeOptions,\n} from '@route-forge/core';\n\nexport const FORGE_INJECTION_KEY: InjectionKey<RouteForge> = Symbol('route-forge');\n\nexport interface RouteForgePluginOptions extends RouteForgeOptions {}\n\n/** Vue 特化类型:levelLoaded 为 Ref<boolean> */\nexport type VueBoundForge = BoundForge<Ref<boolean>>;\n\nexport function createRouteForgePlugin(options: RouteForgePluginOptions): Plugin<[]> & {\n ready: RouteForge['ready']\n} {\n const forge = createRouteForge(options);\n\n return {\n ready: forge.ready.bind(forge),\n install(app: App) {\n app.provide(FORGE_INJECTION_KEY, forge);\n app.config.globalProperties.$forge = {\n route: (level: string, name: string, params?: Record<string, unknown>) =>\n forge.route(level, name, params),\n };\n },\n };\n}\n\n/**\n * 获取 forge 实例。内部委托 core 的 forge.use(),仅将 levelLoaded 替换为 Vue 响应式 Ref。\n *\n * @example\n * // 不绑定层级\n * const forge = useForge()\n * forge('admin', 'users.show', { user: 1 })\n * forge.api('admin', 'users.show')\n *\n * @example\n * // 绑定层级 — 直接调用和 api/route/url 均无需传 level\n * const forge = useForge('admin')\n * forge.level // → 'admin'\n * forge.levelLoaded // Ref<boolean>\n * forge('users.show', { user: 1 })\n * forge.route('users.show', { user: 1 })\n *\n * @example\n * // 绑定层级 + 前缀 — 路由名自动拼接\n * const forge = useForge('admin', 'users')\n * forge('show', { user: 1 }) // → forge.api('admin', 'users.show', ...)\n * forge.route('show', { user: 1 }) // → forge.route('admin', 'users.show', ...)\n */\nexport function useForge<L extends string>(level: L, prefix: string): VueBoundForge;\nexport function useForge<L extends string>(level: L): VueBoundForge;\nexport function useForge(): RouteForge;\nexport function useForge(level?: string, prefix?: string): RouteForge | VueBoundForge {\n const forge = inject(FORGE_INJECTION_KEY);\n if (!forge) {\n throw new Error(\n '[route-forge/vue] useForge() must be used inside an app with createRouteForgePlugin() installed',\n );\n }\n\n if (level === undefined) {\n return forge;\n }\n\n // 委托 core 的 use(),获取 BoundForge(levelLoaded 为 Promise<void>)\n const bound = forge.use(level, prefix);\n\n // 将 levelLoaded 从 Promise<void> 替换为 Ref<boolean>\n const levelLoadedRef = ref(false);\n bound.levelLoaded.then(() => {\n levelLoadedRef.value = true;\n }).catch(() => { /* 加载失败时 levelLoaded 保持 false */ });\n\n Object.defineProperty(bound, 'levelLoaded', {\n value: levelLoadedRef,\n writable: false,\n enumerable: true,\n configurable: false,\n });\n\n return bound as unknown as VueBoundForge;\n}\n","/**\n * useForgeApi:包装 forge.api(),自动管理 loading/error 状态\n * @see .docs/SPEC.md §4.1.7\n *\n * 支持:\n * - useForgeApi() — call(level, name, params)\n * - useForgeApi(level) — call(name, params),自动绑定层级\n * - useForgeApi(level, prefix) — call(suffix, params),自动绑定层级 + 拼接前缀\n *\n * 类型推断:\n * 当 ForgeRouteMap 通过 codegen 或 module augmentation 定义时,\n * level / name / params 均自动推断,IDE 提供补全提示。\n */\n\nimport type { Ref } from 'vue';\nimport { ref } from 'vue';\nimport { useForge } from '../plugin.js';\nimport type {\n UseForgeApiBoundCall,\n UseForgeApiBoundReturn,\n UseForgeApiCall,\n UseForgeApiReturn,\n} from '@route-forge/core';\n\n// Vue 特化类型:pending/error 为 Ref\nexport type { UseForgeApiCall, UseForgeApiBoundCall };\nexport type UseForgeApiReturnVue = UseForgeApiReturn<Ref<boolean>, Ref<unknown>>;\nexport type UseForgeApiBoundReturnVue<L extends string> = UseForgeApiBoundReturn<L, Ref<boolean>, Ref<unknown>>;\n\n/** 不绑定层级 — call 需要传 level */\nexport function useForgeApi(): UseForgeApiReturnVue;\n/** 绑定层级 — call 无需传 level */\nexport function useForgeApi<L extends string>(level: L): UseForgeApiBoundReturnVue<L>;\n/** 绑定层级 + 前缀 — call 无需传 level,路由名自动拼接 prefix */\nexport function useForgeApi<L extends string>(level: L, prefix: string): UseForgeApiBoundReturnVue<L>;\nexport function useForgeApi(level?: string, prefix?: string): UseForgeApiReturnVue | UseForgeApiBoundReturnVue<string> {\n const forge = level !== undefined ? useForge(level, prefix as string) : useForge();\n const pending = ref(false);\n const error = ref<unknown>(null);\n\n async function call(...args: unknown[]): Promise<{ data: unknown; error: unknown }> {\n pending.value = true;\n error.value = null;\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const data = await (forge as any).api(...args);\n return { data, error: null };\n } catch (e) {\n error.value = e;\n return { data: undefined, error: e };\n } finally {\n pending.value = false;\n }\n }\n\n return { pending, error, call: call as any };\n}\n","/**\n * useForgeRoute:响应式 URL 生成器,内部处理 level 加载状态\n * @see .docs/SPEC.md §4.1.7\n *\n * - level 未加载时返回空字符串 ''(不抛错,模板不崩)\n * - level 加载完成后自动重新计算并返回正确 URL\n * - 用户无需关心 levelLoaded 状态,直接用即可\n */\n\nimport { computed, type ComputedRef, inject, onMounted, ref } from 'vue';\nimport { FORGE_INJECTION_KEY } from '../plugin.js';\nimport type { RouteForge } from '@route-forge/core';\n\nexport function useForgeRoute(\n level: string | (() => string),\n name: string | (() => string),\n params?: () => Record<string, unknown>,\n): ComputedRef<string> {\n const forge = inject(FORGE_INJECTION_KEY) as RouteForge;\n\n // 响应式追踪 level 加载状态(computed 依赖此 ref 触发重新计算)\n const levelLoaded = ref(forge.isLoaded(typeof level === 'function' ? level() : level));\n\n const lvl = typeof level === 'function' ? level() : level;\n if (!levelLoaded.value) {\n onMounted(() => {\n forge.load(lvl).then(() => {\n levelLoaded.value = true;\n }).catch(() => { /* 加载失败时 computed 返回 '' */\n });\n });\n }\n\n return computed(() => {\n // level 未加载 → 返回空字符串,不抛错\n if (!levelLoaded.value) return '';\n const l = typeof level === 'function' ? level() : level;\n const n = typeof name === 'function' ? name() : name;\n const p = params ? params() : undefined;\n return forge.route(l, n, p);\n });\n}\n","/**\n * useForgeByPrefix:带层级 + 名字前缀的封装,方便后续减少名称传入\n * @see .docs/SPEC.md §4.1.7\n *\n * 智能前缀解析:\n * - 传入后缀不以前缀开头 → 自动拼接 prefix.suffix\n * - 传入后缀已以前缀开头(歧义) → 优先尝试完整拼接,\n * 若不存在则视为已含前缀直接使用,均不存在则报错\n *\n * 示例(prefix = 'test'):\n * api('1') → test.1\n * api('test1.1') → test.test1.1\n * api('test.1') → 歧义:优先 test.test.1,回退 test.1\n */\n\nimport { inject } from 'vue';\nimport { FORGE_INJECTION_KEY } from '../plugin.js';\nimport {\n resolveRouteName,\n resolveRouteNameSync,\n type RouteForge,\n type UseForgeByPrefixReturn,\n} from '@route-forge/core';\n\nexport type { UseForgeByPrefixReturn };\n\nexport function useForgeByPrefix(level: string, prefix: string, separator = '.'): UseForgeByPrefixReturn {\n const forge = inject(FORGE_INJECTION_KEY) as RouteForge;\n\n return {\n api: (suffix, params) =>\n resolveRouteName(forge, level, prefix, suffix, separator).then(\n (name) => forge.api(level, name, params),\n ),\n route: (suffix, params) => {\n // level 未加载时返回空字符串,避免 resolveRouteNameSync 内部 hasRoute() 抛错\n if (!forge.isLoaded(level)) return '';\n return forge.route(level, resolveRouteNameSync(forge, level, prefix, suffix, separator), params);\n },\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/plugin.ts","../src/composables/useForgeApi.ts","../src/composables/useForgeRoute.ts"],"names":["ref","inject"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Vue 3 插件入口\n * @see .docs/SPEC.md §4.1.7\n *\n * 提供:\n * - 全局属性 $forge(route() 工具)\n * - inject symbol 注入 RouteForge 实例供 composable 使用\n * - useForge() / useForge(level) / useForge(level, prefix) 返回统一方法的 forge 实例\n * • 不传 level:forge(level, name, params?) 直接调用\n * • 传 level:forge(name, params?) 直接调用,自动绑定层级\n * • 传 level + prefix:forge(suffix, params?) 自动拼接 prefix\n *\n * 类型推断:\n * 当 ForgeRouteMap 通过 codegen 或 module augmentation 定义时,\n * level / name / params 均自动推断,IDE 提供补全提示。\n */\n\nimport type { App, InjectionKey, Plugin, Ref } from 'vue';\nimport { inject, ref } from 'vue';\nimport {\n type ApiCallParams,\n type BoundForge,\n createRouteForge,\n type RouteForge,\n type RouteForgeOptions,\n} from '@route-forge/core';\n\nexport const FORGE_INJECTION_KEY: InjectionKey<RouteForge> = Symbol('route-forge');\n\nexport interface RouteForgePluginOptions extends RouteForgeOptions {}\n\n/** Vue 特化类型:levelLoaded 为 Ref<boolean> */\nexport type VueBoundForge = BoundForge<Ref<boolean>>;\n\nexport function createRouteForgePlugin(options: RouteForgePluginOptions): Plugin<[]> & {\n ready: RouteForge['ready']\n} {\n const forge = createRouteForge(options);\n\n return {\n ready: forge.ready.bind(forge),\n install(app: App) {\n app.provide(FORGE_INJECTION_KEY, forge);\n app.config.globalProperties.$forge = {\n route: (level: string, name: string, params?: Record<string, unknown>) =>\n forge.route(level, name, params),\n };\n },\n };\n}\n\n/**\n * 获取 forge 实例。内部委托 core 的 forge.use(),仅将 levelLoaded 替换为 Vue 响应式 Ref。\n *\n * @example\n * // 不绑定层级\n * const forge = useForge()\n * forge('admin', 'users.show', { user: 1 })\n * forge.api('admin', 'users.show')\n *\n * @example\n * // 绑定层级 — 直接调用和 api/route/url 均无需传 level\n * const forge = useForge('admin')\n * forge.level // → 'admin'\n * forge.levelLoaded // Ref<boolean>\n * forge('users.show', { user: 1 })\n * forge.route('users.show', { user: 1 })\n *\n * @example\n * // 绑定层级 + 前缀 — 路由名自动拼接\n * const forge = useForge('admin', 'users')\n * forge('show', { user: 1 }) // → forge.api('admin', 'users.show', ...)\n * forge.route('show', { user: 1 }) // → forge.route('admin', 'users.show', ...)\n */\nexport function useForge<L extends string>(level: L, prefix: string): VueBoundForge;\nexport function useForge<L extends string>(level: L): VueBoundForge;\nexport function useForge(): RouteForge;\nexport function useForge(level?: string, prefix?: string): RouteForge | VueBoundForge {\n const forge = inject(FORGE_INJECTION_KEY);\n if (!forge) {\n throw new Error(\n '[route-forge/vue] useForge() must be used inside an app with createRouteForgePlugin() installed',\n );\n }\n\n if (level === undefined) {\n return forge;\n }\n\n // 委托 core 的 use(),获取 BoundForge(levelLoaded 为 Promise<void>)\n const bound = forge.use(level, prefix);\n\n // 将 levelLoaded 从 Promise<void> 替换为 Ref<boolean>\n const levelLoadedRef = ref(false);\n bound.levelLoaded.then(() => {\n levelLoadedRef.value = true;\n }).catch(() => { /* 加载失败时 levelLoaded 保持 false */ });\n\n Object.defineProperty(bound, 'levelLoaded', {\n value: levelLoadedRef,\n writable: false,\n enumerable: true,\n configurable: false,\n });\n\n return bound as unknown as VueBoundForge;\n}\n","/**\n * useForgeApi:包装 forge.api(),自动管理 loading/error 状态\n * @see .docs/SPEC.md §4.1.7\n *\n * 支持:\n * - useForgeApi() — call(level, name, params)\n * - useForgeApi(level) — call(name, params),自动绑定层级\n * - useForgeApi(level, prefix) — call(suffix, params),自动绑定层级 + 拼接前缀\n *\n * 类型推断:\n * 当 ForgeRouteMap 通过 codegen 或 module augmentation 定义时,\n * level / name / params 均自动推断,IDE 提供补全提示。\n *\n * pending 采用引用计数:并发多个 call 时,全部完成才置 false\n * (先完成的 call 不再把其他在途请求的 pending 提前清掉)。\n */\n\nimport type { Ref } from 'vue';\nimport { ref } from 'vue';\nimport { useForge } from '../plugin.js';\nimport type {\n BoundForge,\n RouteForge,\n UseForgeApiBoundCall,\n UseForgeApiBoundReturn,\n UseForgeApiCall,\n UseForgeApiReturn,\n} from '@route-forge/core';\n\n// Vue 特化类型:pending/error 为 Ref\nexport type { UseForgeApiCall, UseForgeApiBoundCall };\nexport type UseForgeApiReturnVue = UseForgeApiReturn<Ref<boolean>, Ref<unknown>>;\nexport type UseForgeApiBoundReturnVue<L extends string> = UseForgeApiBoundReturn<L, Ref<boolean>, Ref<unknown>>;\n\n/** 不绑定层级 — call 需要传 level */\nexport function useForgeApi(): UseForgeApiReturnVue;\n/** 绑定层级 — call 无需传 level */\nexport function useForgeApi<L extends string>(level: L): UseForgeApiBoundReturnVue<L>;\n/** 绑定层级 + 前缀 — call 无需传 level,路由名自动拼接 prefix */\nexport function useForgeApi<L extends string>(level: L, prefix: string): UseForgeApiBoundReturnVue<L>;\nexport function useForgeApi(level?: string, prefix?: string): UseForgeApiReturnVue | UseForgeApiBoundReturnVue<string> {\n const bound = level !== undefined ? useForge(level, prefix as string) : undefined;\n const unbound = bound === undefined ? useForge() : undefined;\n const pending = ref(false);\n const error = ref<unknown>(null);\n // 并发计数:setup 闭包变量(组件实例生命周期存活)\n let inflight = 0;\n\n async function call(...args: unknown[]): Promise<{ data: unknown; error: unknown }> {\n inflight++;\n pending.value = true;\n error.value = null;\n try {\n // 按绑定形态分发:bound 走 BoundForge.api(name, params),unbound 走 RouteForge.api(level, name, params)\n // (窄断言替代 as any:重载签名已保证参数形状,此处仅消除联合类型/框架特化类型的分支歧义)\n const data = bound !== undefined\n ? await (bound as unknown as BoundForge).api(args[0] as string, args[1] as never)\n : await (unbound as RouteForge).api(args[0] as string, args[1] as string, args[2] as never);\n return { data, error: null };\n } catch (e) {\n error.value = e;\n return { data: undefined, error: e };\n } finally {\n inflight--;\n pending.value = inflight > 0;\n }\n }\n\n return { pending, error, call: call as unknown as UseForgeApiCall };\n}\n","/**\n * useForgeRoute:响应式 URL 生成器,内部处理 level 加载状态\n * @see .docs/SPEC.md §4.1.7\n *\n * - level 未加载时返回空字符串 ''(不抛错,模板不崩)\n * - level 加载完成后自动重新计算并返回正确 URL\n * - 路由名不存在或必填参数缺失等渲染期错误:降级为 '' 保证渲染不中断,\n * 同时以醒目的样式化 warn 输出完整错误(含堆栈)——开发期可见,生产无副作用\n * - 用户无需关心 levelLoaded 状态,直接用即可\n */\n\nimport { computed, type ComputedRef, inject, onMounted, ref } from 'vue';\nimport { FORGE_INJECTION_KEY } from '../plugin.js';\nimport type { RouteForge } from '@route-forge/core';\n\n/** 渲染期错误降级输出:橙色加粗标签 + 完整错误对象,控制台一眼可见 */\nfunction warnRenderError(error: unknown): void {\n console.warn(\n '%c[route-forge]%c useForgeRoute 渲染期错误(已降级为空字符串,渲染未中断)',\n 'color:#e67e22;font-weight:bold',\n 'color:inherit',\n error,\n );\n}\n\nexport function useForgeRoute(\n level: string | (() => string),\n name: string | (() => string),\n params?: () => Record<string, unknown>,\n): ComputedRef<string> {\n const forge = inject(FORGE_INJECTION_KEY) as RouteForge;\n\n // 响应式追踪 level 加载状态(computed 依赖此 ref 触发重新计算)\n const levelLoaded = ref(forge.isLoaded(typeof level === 'function' ? level() : level));\n\n const lvl = typeof level === 'function' ? level() : level;\n if (!levelLoaded.value) {\n onMounted(() => {\n forge.load(lvl).then(() => {\n levelLoaded.value = true;\n }).catch(() => { /* 加载失败时 computed 返回 '' */\n });\n });\n }\n\n return computed(() => {\n // level 未加载 → 返回空字符串,不抛错\n if (!levelLoaded.value) return '';\n const l = typeof level === 'function' ? level() : level;\n const n = typeof name === 'function' ? name() : name;\n const p = params ? params() : undefined;\n try {\n return forge.route(l, n, p);\n } catch (e) {\n warnRenderError(e);\n return '';\n }\n });\n}\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@route-forge/vue",
3
- "version": "1.3.0",
4
- "description": "Route Forge 的 Vue 3 集成:插件 + composable(useForge/useForgeApi/useForgeLevel/useForgeRoute/useForgeByPrefix)",
3
+ "version": "2.0.0",
4
+ "description": "Route Forge 的 Vue 3 集成:插件 + composable(useForge/useForgeApi/useForgeRoute)",
5
5
  "license": "MIT",
6
6
  "author": "阿杰很厉害 <506907958@qq.com>",
7
7
  "homepage": "https://github.com/route-forge/route-forge",
@@ -40,7 +40,7 @@
40
40
  "vue": "^3.4.0"
41
41
  },
42
42
  "dependencies": {
43
- "@route-forge/core": "1.3.0"
43
+ "@route-forge/core": "^2.0.0"
44
44
  },
45
45
  "publishConfig": {
46
46
  "access": "public"