@route-forge/core 1.0.0 → 1.0.2
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 +13 -2
- package/dist/codegen.cjs +5 -1
- package/dist/codegen.cjs.map +1 -1
- package/dist/codegen.d.cts +1 -1
- package/dist/codegen.d.ts +1 -1
- package/dist/codegen.js +5 -1
- package/dist/codegen.js.map +1 -1
- package/dist/index.cjs +126 -42
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +126 -42
- package/dist/index.js.map +1 -1
- package/dist/{types-yzY9-FWd.d.cts → types-DExMMUu1.d.cts} +13 -2
- package/dist/{types-yzY9-FWd.d.ts → types-DExMMUu1.d.ts} +13 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -42,8 +42,9 @@ if (!forge.isLoaded('admin')) {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
// 失效缓存
|
|
45
|
-
forge.invalidate('admin')
|
|
46
|
-
forge.invalidate()
|
|
45
|
+
forge.invalidate('admin') // 失效指定层级
|
|
46
|
+
forge.invalidate(['admin', 'manage']) // 批量失效多个层级
|
|
47
|
+
forge.invalidate() // 失效全部
|
|
47
48
|
```
|
|
48
49
|
|
|
49
50
|
## URL 前缀(`url_prefix`)
|
|
@@ -93,6 +94,16 @@ forge.api('admin', 'search.show', {
|
|
|
93
94
|
|
|
94
95
|
规则:`params` 优先 > 平铺 `string|number` → 路径参数 > 对象类型按原定义(`query`/`body`/`headers`)。
|
|
95
96
|
|
|
97
|
+
## 单次超时覆盖
|
|
98
|
+
|
|
99
|
+
`forge.api()` 支持通过 `timeout` 参数覆盖单次请求的超时时间(毫秒),不传时使用
|
|
100
|
+
`createRouteForge({ timeout })` 的全局值(默认 30s):
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
// 全局超时 30s,单次导出请求覆盖为 120s
|
|
104
|
+
forge.api('admin', 'reports.export', { timeout: 120_000 })
|
|
105
|
+
```
|
|
106
|
+
|
|
96
107
|
## 认证(Authentication)
|
|
97
108
|
|
|
98
109
|
Route Forge 不内置登录态管理,认证逻辑通过拦截器实现,灵活且完全可控。
|
package/dist/codegen.cjs
CHANGED
|
@@ -87,7 +87,11 @@ async function fetchLevel(endpoint, level) {
|
|
|
87
87
|
function generateRouteTypes(routesByLevel) {
|
|
88
88
|
const levelEntries = Object.entries(routesByLevel).map(([level, routes]) => {
|
|
89
89
|
const routeEntries = Object.entries(routes).map(([name, meta]) => {
|
|
90
|
-
const
|
|
90
|
+
const defaults = meta.parameter_defaults ?? {};
|
|
91
|
+
const params = (meta.parameters ?? []).map((p) => {
|
|
92
|
+
const optional = meta.uri?.includes(`{${p}?}`) || p in defaults;
|
|
93
|
+
return `${p}${optional ? "?" : ""}: string | number;`;
|
|
94
|
+
}).join(" ");
|
|
91
95
|
const method = meta.methods?.find((m) => m.toUpperCase() !== "HEAD") ?? "GET";
|
|
92
96
|
const methodUpper = method.toUpperCase();
|
|
93
97
|
const bodyField = BODY_METHODS.has(methodUpper) ? "\n body: unknown;" : "";
|
package/dist/codegen.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/codegen/index.ts"],"names":[],"mappings":"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","file":"codegen.cjs","sourcesContent":["#!/usr/bin/env node\n/**\n * @route-forge/core codegen CLI\n * @see .docs/SPEC.md §4.2\n *\n * 用法:\n * npx @route-forge/core codegen \\\n * --endpoint http://localhost/_forge/routes \\\n * --levels public,client,manage,admin \\\n * --out src/types/forge-routes.d.ts\n */\n\nimport type { RouteMeta } from '../types.js';\n\nexport interface CodegenOptions {\n endpoint: string;\n levels: string[];\n out: string;\n responseTypes?: string;\n}\n\nconst BODY_METHODS = new Set(['POST', 'PUT', 'PATCH']);\n\n/**\n * 解析 argv(最小手写实现,不引入 commander/yargs)\n * 支持:--endpoint VALUE / --endpoint=VALUE / --levels a,b,c / --out PATH / --responseTypes PATH\n */\nexport function parseArgs(argv: string[]): CodegenOptions {\n const opts: Partial<CodegenOptions> = {};\n\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n const next = (): string => {\n const v = argv[++i];\n if (v === undefined) {\n throw new Error(`[route-forge/codegen] missing value for ${arg}`);\n }\n return v;\n };\n\n if (arg === '--endpoint') {\n opts.endpoint = next();\n } else if (arg?.startsWith('--endpoint=')) {\n opts.endpoint = arg.slice('--endpoint='.length);\n } else if (arg === '--levels') {\n opts.levels = next().split(',').map((s) => s.trim()).filter(Boolean);\n } else if (arg?.startsWith('--levels=')) {\n opts.levels = arg.slice('--levels='.length).split(',').map((s) => s.trim()).filter(Boolean);\n } else if (arg === '--out') {\n opts.out = next();\n } else if (arg?.startsWith('--out=')) {\n opts.out = arg.slice('--out='.length);\n } else if (arg === '--responseTypes') {\n opts.responseTypes = next();\n } else if (arg?.startsWith('--responseTypes=')) {\n opts.responseTypes = arg.slice('--responseTypes='.length);\n } else if (arg === '--help' || arg === '-h') {\n printHelp();\n process.exit(0);\n }\n }\n\n if (!opts.endpoint) {\n console.error('[route-forge/codegen] --endpoint is required');\n printHelp();\n process.exit(1);\n }\n if (!opts.out) {\n console.error('[route-forge/codegen] --out is required');\n printHelp();\n process.exit(1);\n }\n\n return {\n endpoint: opts.endpoint,\n levels: opts.levels ?? [],\n out: opts.out,\n responseTypes: opts.responseTypes,\n };\n}\n\nfunction printHelp(): void {\n console.log(`\nroute-forge codegen - generate TS route types from backend summary endpoint\n\nUsage:\n npx @route-forge/core codegen --endpoint URL --out PATH [--levels a,b,c] [--responseTypes PATH]\n\nOptions:\n --endpoint URL Backend summary endpoint (e.g. http://localhost/_forge/routes)\n --levels a,b,c Optional: explicit level list (skip auto-discovery)\n --out PATH Output .d.ts file path\n --responseTypes PATH Optional: JSON file mapping route names to response types\n -h, --help Show this help\n`);\n}\n\n/**\n * 拉取摘要端点\n */\nexport async function fetchSummary(endpoint: string): Promise<{ levels: Record<string, unknown> }> {\n const resp = await fetch(endpoint, { method: 'GET' });\n if (!resp.ok) {\n throw new Error(`summary endpoint ${endpoint} returned ${resp.status}`);\n }\n return (await resp.json()) as { levels: Record<string, unknown> };\n}\n\n/**\n * 拉取单个层级的路由元信息\n */\nexport async function fetchLevel(endpoint: string, level: string): Promise<{ routes: Record<string, RouteMeta> }> {\n const base = endpoint.replace(/\\/$/, '');\n const url = `${base}/${encodeURIComponent(level)}`;\n const resp = await fetch(url, { method: 'GET' });\n if (!resp.ok) {\n throw new Error(`level endpoint ${url} returned ${resp.status}`);\n }\n const data = (await resp.json()) as { routes?: Record<string, RouteMeta> };\n return { routes: data.routes ?? {} };\n}\n\n/**\n * 生成 d.ts 内容\n * @param routesByLevel 按层级分组的路由元信息:{ level: { routeName: meta } }\n */\nexport function generateRouteTypes(routesByLevel: Record<string, Record<string, RouteMeta>>): string {\n const levelEntries = Object.entries(routesByLevel).map(([level, routes]) => {\n const routeEntries = Object.entries(routes).map(([name, meta]) => {\n const params = (meta.parameters ?? []).map((p) => `${p}: string | number;`).join(' ');\n const method = meta.methods?.find((m) => m.toUpperCase() !== 'HEAD') ?? 'GET';\n const methodUpper = method.toUpperCase();\n const bodyField = BODY_METHODS.has(methodUpper) ? '\\n body: unknown;' : '';\n return ` ${JSON.stringify(name)}: {\\n method: ${JSON.stringify(methodUpper)};\\n params: { ${params} };${bodyField}\\n response: unknown;\\n };`;\n });\n return ` ${JSON.stringify(level)}: {\\n${routeEntries.join('\\n')}\\n };`;\n });\n return `// AUTO-GENERATED by @route-forge/core codegen. Do not edit.\n\n/**\n * 二级路由类型映射:level → routeName → routeMeta\n * 可通过 module augmentation 增强:\n * declare module '@route-forge/core' {\n * interface ForgeRouteMap { admin: { 'users.show': { method: 'GET'; params: { user: string | number }; response: User } } }\n * }\n */\nexport interface ForgeRouteMap {\n${levelEntries.join('\\n')}\n}\n`;\n}\n\n/**\n * CLI 主入口\n */\nexport async function main(argv: string[] = process.argv.slice(2)): Promise<void> {\n const opts = parseArgs(argv);\n\n let levels = opts.levels;\n if (levels.length === 0) {\n try {\n const summary = await fetchSummary(opts.endpoint);\n levels = Object.keys(summary.levels);\n } catch (e) {\n console.error(`[route-forge/codegen] failed to auto-discover levels from summary endpoint: ${(e as Error).message}`);\n console.error('hint: pass --levels explicitly to skip auto-discovery');\n process.exit(1);\n }\n }\n\n if (levels.length === 0) {\n console.error('[route-forge/codegen] no levels found; pass --levels explicitly');\n process.exit(1);\n }\n\n const levelFetches = await Promise.allSettled(\n levels.map((lvl) => fetchLevel(opts.endpoint, lvl)),\n );\n\n const routesByLevel: Record<string, Record<string, RouteMeta>> = {};\n let failedLevels = 0;\n levelFetches.forEach((res, idx) => {\n if (res.status === 'fulfilled') {\n routesByLevel[levels[idx]!] = res.value.routes;\n } else {\n failedLevels++;\n console.warn(`[route-forge/codegen] failed to fetch level \"${levels[idx]}\": ${(res.reason as Error).message}`);\n }\n });\n\n const totalRoutes = Object.values(routesByLevel).reduce((sum, r) => sum + Object.keys(r).length, 0);\n if (totalRoutes === 0) {\n console.error('[route-forge/codegen] no routes collected from any level');\n process.exit(1);\n }\n\n const dts = generateRouteTypes(routesByLevel);\n\n const fs = await import('node:fs/promises');\n const path = await import('node:path');\n const outPath = path.resolve(opts.out);\n const dir = path.dirname(outPath);\n await fs.mkdir(dir, { recursive: true });\n await fs.writeFile(outPath, dts, 'utf8');\n\n console.log(`[route-forge/codegen] written ${totalRoutes} routes across ${Object.keys(routesByLevel).length} level(s) to ${outPath}`);\n if (failedLevels > 0) {\n console.warn(`[route-forge/codegen] ${failedLevels} level(s) failed; output may be incomplete`);\n }\n}\n\nconst invokedFromCli = (() => {\n try {\n if (!process.argv[1]) return false;\n const argv1 = process.argv[1].replace(/\\\\/g, '/');\n return argv1.endsWith('/codegen.js') || argv1.endsWith('/codegen.cjs') || argv1.endsWith('/codegen');\n } catch {\n return false;\n }\n})();\n\nif (invokedFromCli) {\n main().catch((err) => {\n console.error(err);\n process.exit(1);\n });\n}\n\nexport { main as runCodegen };\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/codegen/index.ts"],"names":[],"mappings":"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","file":"codegen.cjs","sourcesContent":["#!/usr/bin/env node\n/**\n * @route-forge/core codegen CLI\n * @see .docs/SPEC.md §4.2\n *\n * 用法:\n * npx @route-forge/core codegen \\\n * --endpoint http://localhost/_forge/routes \\\n * --levels public,client,manage,admin \\\n * --out src/types/forge-routes.d.ts\n */\n\nimport type { RouteMeta } from '../types.js';\n\nexport interface CodegenOptions {\n endpoint: string;\n levels: string[];\n out: string;\n responseTypes?: string;\n}\n\nconst BODY_METHODS = new Set(['POST', 'PUT', 'PATCH']);\n\n/**\n * 解析 argv(最小手写实现,不引入 commander/yargs)\n * 支持:--endpoint VALUE / --endpoint=VALUE / --levels a,b,c / --out PATH / --responseTypes PATH\n */\nexport function parseArgs(argv: string[]): CodegenOptions {\n const opts: Partial<CodegenOptions> = {};\n\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n const next = (): string => {\n const v = argv[++i];\n if (v === undefined) {\n throw new Error(`[route-forge/codegen] missing value for ${arg}`);\n }\n return v;\n };\n\n if (arg === '--endpoint') {\n opts.endpoint = next();\n } else if (arg?.startsWith('--endpoint=')) {\n opts.endpoint = arg.slice('--endpoint='.length);\n } else if (arg === '--levels') {\n opts.levels = next().split(',').map((s) => s.trim()).filter(Boolean);\n } else if (arg?.startsWith('--levels=')) {\n opts.levels = arg.slice('--levels='.length).split(',').map((s) => s.trim()).filter(Boolean);\n } else if (arg === '--out') {\n opts.out = next();\n } else if (arg?.startsWith('--out=')) {\n opts.out = arg.slice('--out='.length);\n } else if (arg === '--responseTypes') {\n opts.responseTypes = next();\n } else if (arg?.startsWith('--responseTypes=')) {\n opts.responseTypes = arg.slice('--responseTypes='.length);\n } else if (arg === '--help' || arg === '-h') {\n printHelp();\n process.exit(0);\n }\n }\n\n if (!opts.endpoint) {\n console.error('[route-forge/codegen] --endpoint is required');\n printHelp();\n process.exit(1);\n }\n if (!opts.out) {\n console.error('[route-forge/codegen] --out is required');\n printHelp();\n process.exit(1);\n }\n\n return {\n endpoint: opts.endpoint,\n levels: opts.levels ?? [],\n out: opts.out,\n responseTypes: opts.responseTypes,\n };\n}\n\nfunction printHelp(): void {\n console.log(`\nroute-forge codegen - generate TS route types from backend summary endpoint\n\nUsage:\n npx @route-forge/core codegen --endpoint URL --out PATH [--levels a,b,c] [--responseTypes PATH]\n\nOptions:\n --endpoint URL Backend summary endpoint (e.g. http://localhost/_forge/routes)\n --levels a,b,c Optional: explicit level list (skip auto-discovery)\n --out PATH Output .d.ts file path\n --responseTypes PATH Optional: JSON file mapping route names to response types\n -h, --help Show this help\n`);\n}\n\n/**\n * 拉取摘要端点\n */\nexport async function fetchSummary(endpoint: string): Promise<{ levels: Record<string, unknown> }> {\n const resp = await fetch(endpoint, { method: 'GET' });\n if (!resp.ok) {\n throw new Error(`summary endpoint ${endpoint} returned ${resp.status}`);\n }\n return (await resp.json()) as { levels: Record<string, unknown> };\n}\n\n/**\n * 拉取单个层级的路由元信息\n */\nexport async function fetchLevel(endpoint: string, level: string): Promise<{ routes: Record<string, RouteMeta> }> {\n const base = endpoint.replace(/\\/$/, '');\n const url = `${base}/${encodeURIComponent(level)}`;\n const resp = await fetch(url, { method: 'GET' });\n if (!resp.ok) {\n throw new Error(`level endpoint ${url} returned ${resp.status}`);\n }\n const data = (await resp.json()) as { routes?: Record<string, RouteMeta> };\n return { routes: data.routes ?? {} };\n}\n\n/**\n * 生成 d.ts 内容\n * @param routesByLevel 按层级分组的路由元信息:{ level: { routeName: meta } }\n */\nexport function generateRouteTypes(routesByLevel: Record<string, Record<string, RouteMeta>>): string {\n const levelEntries = Object.entries(routesByLevel).map(([level, routes]) => {\n const routeEntries = Object.entries(routes).map(([name, meta]) => {\n const defaults = meta.parameter_defaults ?? {};\n const params = (meta.parameters ?? []).map((p) => {\n // URI 中的 {p?} 或有后端默认值的参数 → 生成可选字段,避免调用方被迫传参\n const optional = meta.uri?.includes(`{${p}?}`) || p in defaults;\n return `${p}${optional ? '?' : ''}: string | number;`;\n }).join(' ');\n const method = meta.methods?.find((m) => m.toUpperCase() !== 'HEAD') ?? 'GET';\n const methodUpper = method.toUpperCase();\n const bodyField = BODY_METHODS.has(methodUpper) ? '\\n body: unknown;' : '';\n return ` ${JSON.stringify(name)}: {\\n method: ${JSON.stringify(methodUpper)};\\n params: { ${params} };${bodyField}\\n response: unknown;\\n };`;\n });\n return ` ${JSON.stringify(level)}: {\\n${routeEntries.join('\\n')}\\n };`;\n });\n return `// AUTO-GENERATED by @route-forge/core codegen. Do not edit.\n\n/**\n * 二级路由类型映射:level → routeName → routeMeta\n * 可通过 module augmentation 增强:\n * declare module '@route-forge/core' {\n * interface ForgeRouteMap { admin: { 'users.show': { method: 'GET'; params: { user: string | number }; response: User } } }\n * }\n */\nexport interface ForgeRouteMap {\n${levelEntries.join('\\n')}\n}\n`;\n}\n\n/**\n * CLI 主入口\n */\nexport async function main(argv: string[] = process.argv.slice(2)): Promise<void> {\n const opts = parseArgs(argv);\n\n let levels = opts.levels;\n if (levels.length === 0) {\n try {\n const summary = await fetchSummary(opts.endpoint);\n levels = Object.keys(summary.levels);\n } catch (e) {\n console.error(`[route-forge/codegen] failed to auto-discover levels from summary endpoint: ${(e as Error).message}`);\n console.error('hint: pass --levels explicitly to skip auto-discovery');\n process.exit(1);\n }\n }\n\n if (levels.length === 0) {\n console.error('[route-forge/codegen] no levels found; pass --levels explicitly');\n process.exit(1);\n }\n\n const levelFetches = await Promise.allSettled(\n levels.map((lvl) => fetchLevel(opts.endpoint, lvl)),\n );\n\n const routesByLevel: Record<string, Record<string, RouteMeta>> = {};\n let failedLevels = 0;\n levelFetches.forEach((res, idx) => {\n if (res.status === 'fulfilled') {\n routesByLevel[levels[idx]!] = res.value.routes;\n } else {\n failedLevels++;\n console.warn(`[route-forge/codegen] failed to fetch level \"${levels[idx]}\": ${(res.reason as Error).message}`);\n }\n });\n\n const totalRoutes = Object.values(routesByLevel).reduce((sum, r) => sum + Object.keys(r).length, 0);\n if (totalRoutes === 0) {\n console.error('[route-forge/codegen] no routes collected from any level');\n process.exit(1);\n }\n\n const dts = generateRouteTypes(routesByLevel);\n\n const fs = await import('node:fs/promises');\n const path = await import('node:path');\n const outPath = path.resolve(opts.out);\n const dir = path.dirname(outPath);\n await fs.mkdir(dir, { recursive: true });\n await fs.writeFile(outPath, dts, 'utf8');\n\n console.log(`[route-forge/codegen] written ${totalRoutes} routes across ${Object.keys(routesByLevel).length} level(s) to ${outPath}`);\n if (failedLevels > 0) {\n console.warn(`[route-forge/codegen] ${failedLevels} level(s) failed; output may be incomplete`);\n }\n}\n\nconst invokedFromCli = (() => {\n try {\n if (!process.argv[1]) return false;\n const argv1 = process.argv[1].replace(/\\\\/g, '/');\n return argv1.endsWith('/codegen.js') || argv1.endsWith('/codegen.cjs') || argv1.endsWith('/codegen');\n } catch {\n return false;\n }\n})();\n\nif (invokedFromCli) {\n main().catch((err) => {\n console.error(err);\n process.exit(1);\n });\n}\n\nexport { main as runCodegen };\n"]}
|
package/dist/codegen.d.cts
CHANGED
package/dist/codegen.d.ts
CHANGED
package/dist/codegen.js
CHANGED
|
@@ -85,7 +85,11 @@ async function fetchLevel(endpoint, level) {
|
|
|
85
85
|
function generateRouteTypes(routesByLevel) {
|
|
86
86
|
const levelEntries = Object.entries(routesByLevel).map(([level, routes]) => {
|
|
87
87
|
const routeEntries = Object.entries(routes).map(([name, meta]) => {
|
|
88
|
-
const
|
|
88
|
+
const defaults = meta.parameter_defaults ?? {};
|
|
89
|
+
const params = (meta.parameters ?? []).map((p) => {
|
|
90
|
+
const optional = meta.uri?.includes(`{${p}?}`) || p in defaults;
|
|
91
|
+
return `${p}${optional ? "?" : ""}: string | number;`;
|
|
92
|
+
}).join(" ");
|
|
89
93
|
const method = meta.methods?.find((m) => m.toUpperCase() !== "HEAD") ?? "GET";
|
|
90
94
|
const methodUpper = method.toUpperCase();
|
|
91
95
|
const bodyField = BODY_METHODS.has(methodUpper) ? "\n body: unknown;" : "";
|
package/dist/codegen.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/codegen/index.ts"],"names":[],"mappings":"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","file":"codegen.js","sourcesContent":["#!/usr/bin/env node\n/**\n * @route-forge/core codegen CLI\n * @see .docs/SPEC.md §4.2\n *\n * 用法:\n * npx @route-forge/core codegen \\\n * --endpoint http://localhost/_forge/routes \\\n * --levels public,client,manage,admin \\\n * --out src/types/forge-routes.d.ts\n */\n\nimport type { RouteMeta } from '../types.js';\n\nexport interface CodegenOptions {\n endpoint: string;\n levels: string[];\n out: string;\n responseTypes?: string;\n}\n\nconst BODY_METHODS = new Set(['POST', 'PUT', 'PATCH']);\n\n/**\n * 解析 argv(最小手写实现,不引入 commander/yargs)\n * 支持:--endpoint VALUE / --endpoint=VALUE / --levels a,b,c / --out PATH / --responseTypes PATH\n */\nexport function parseArgs(argv: string[]): CodegenOptions {\n const opts: Partial<CodegenOptions> = {};\n\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n const next = (): string => {\n const v = argv[++i];\n if (v === undefined) {\n throw new Error(`[route-forge/codegen] missing value for ${arg}`);\n }\n return v;\n };\n\n if (arg === '--endpoint') {\n opts.endpoint = next();\n } else if (arg?.startsWith('--endpoint=')) {\n opts.endpoint = arg.slice('--endpoint='.length);\n } else if (arg === '--levels') {\n opts.levels = next().split(',').map((s) => s.trim()).filter(Boolean);\n } else if (arg?.startsWith('--levels=')) {\n opts.levels = arg.slice('--levels='.length).split(',').map((s) => s.trim()).filter(Boolean);\n } else if (arg === '--out') {\n opts.out = next();\n } else if (arg?.startsWith('--out=')) {\n opts.out = arg.slice('--out='.length);\n } else if (arg === '--responseTypes') {\n opts.responseTypes = next();\n } else if (arg?.startsWith('--responseTypes=')) {\n opts.responseTypes = arg.slice('--responseTypes='.length);\n } else if (arg === '--help' || arg === '-h') {\n printHelp();\n process.exit(0);\n }\n }\n\n if (!opts.endpoint) {\n console.error('[route-forge/codegen] --endpoint is required');\n printHelp();\n process.exit(1);\n }\n if (!opts.out) {\n console.error('[route-forge/codegen] --out is required');\n printHelp();\n process.exit(1);\n }\n\n return {\n endpoint: opts.endpoint,\n levels: opts.levels ?? [],\n out: opts.out,\n responseTypes: opts.responseTypes,\n };\n}\n\nfunction printHelp(): void {\n console.log(`\nroute-forge codegen - generate TS route types from backend summary endpoint\n\nUsage:\n npx @route-forge/core codegen --endpoint URL --out PATH [--levels a,b,c] [--responseTypes PATH]\n\nOptions:\n --endpoint URL Backend summary endpoint (e.g. http://localhost/_forge/routes)\n --levels a,b,c Optional: explicit level list (skip auto-discovery)\n --out PATH Output .d.ts file path\n --responseTypes PATH Optional: JSON file mapping route names to response types\n -h, --help Show this help\n`);\n}\n\n/**\n * 拉取摘要端点\n */\nexport async function fetchSummary(endpoint: string): Promise<{ levels: Record<string, unknown> }> {\n const resp = await fetch(endpoint, { method: 'GET' });\n if (!resp.ok) {\n throw new Error(`summary endpoint ${endpoint} returned ${resp.status}`);\n }\n return (await resp.json()) as { levels: Record<string, unknown> };\n}\n\n/**\n * 拉取单个层级的路由元信息\n */\nexport async function fetchLevel(endpoint: string, level: string): Promise<{ routes: Record<string, RouteMeta> }> {\n const base = endpoint.replace(/\\/$/, '');\n const url = `${base}/${encodeURIComponent(level)}`;\n const resp = await fetch(url, { method: 'GET' });\n if (!resp.ok) {\n throw new Error(`level endpoint ${url} returned ${resp.status}`);\n }\n const data = (await resp.json()) as { routes?: Record<string, RouteMeta> };\n return { routes: data.routes ?? {} };\n}\n\n/**\n * 生成 d.ts 内容\n * @param routesByLevel 按层级分组的路由元信息:{ level: { routeName: meta } }\n */\nexport function generateRouteTypes(routesByLevel: Record<string, Record<string, RouteMeta>>): string {\n const levelEntries = Object.entries(routesByLevel).map(([level, routes]) => {\n const routeEntries = Object.entries(routes).map(([name, meta]) => {\n const params = (meta.parameters ?? []).map((p) => `${p}: string | number;`).join(' ');\n const method = meta.methods?.find((m) => m.toUpperCase() !== 'HEAD') ?? 'GET';\n const methodUpper = method.toUpperCase();\n const bodyField = BODY_METHODS.has(methodUpper) ? '\\n body: unknown;' : '';\n return ` ${JSON.stringify(name)}: {\\n method: ${JSON.stringify(methodUpper)};\\n params: { ${params} };${bodyField}\\n response: unknown;\\n };`;\n });\n return ` ${JSON.stringify(level)}: {\\n${routeEntries.join('\\n')}\\n };`;\n });\n return `// AUTO-GENERATED by @route-forge/core codegen. Do not edit.\n\n/**\n * 二级路由类型映射:level → routeName → routeMeta\n * 可通过 module augmentation 增强:\n * declare module '@route-forge/core' {\n * interface ForgeRouteMap { admin: { 'users.show': { method: 'GET'; params: { user: string | number }; response: User } } }\n * }\n */\nexport interface ForgeRouteMap {\n${levelEntries.join('\\n')}\n}\n`;\n}\n\n/**\n * CLI 主入口\n */\nexport async function main(argv: string[] = process.argv.slice(2)): Promise<void> {\n const opts = parseArgs(argv);\n\n let levels = opts.levels;\n if (levels.length === 0) {\n try {\n const summary = await fetchSummary(opts.endpoint);\n levels = Object.keys(summary.levels);\n } catch (e) {\n console.error(`[route-forge/codegen] failed to auto-discover levels from summary endpoint: ${(e as Error).message}`);\n console.error('hint: pass --levels explicitly to skip auto-discovery');\n process.exit(1);\n }\n }\n\n if (levels.length === 0) {\n console.error('[route-forge/codegen] no levels found; pass --levels explicitly');\n process.exit(1);\n }\n\n const levelFetches = await Promise.allSettled(\n levels.map((lvl) => fetchLevel(opts.endpoint, lvl)),\n );\n\n const routesByLevel: Record<string, Record<string, RouteMeta>> = {};\n let failedLevels = 0;\n levelFetches.forEach((res, idx) => {\n if (res.status === 'fulfilled') {\n routesByLevel[levels[idx]!] = res.value.routes;\n } else {\n failedLevels++;\n console.warn(`[route-forge/codegen] failed to fetch level \"${levels[idx]}\": ${(res.reason as Error).message}`);\n }\n });\n\n const totalRoutes = Object.values(routesByLevel).reduce((sum, r) => sum + Object.keys(r).length, 0);\n if (totalRoutes === 0) {\n console.error('[route-forge/codegen] no routes collected from any level');\n process.exit(1);\n }\n\n const dts = generateRouteTypes(routesByLevel);\n\n const fs = await import('node:fs/promises');\n const path = await import('node:path');\n const outPath = path.resolve(opts.out);\n const dir = path.dirname(outPath);\n await fs.mkdir(dir, { recursive: true });\n await fs.writeFile(outPath, dts, 'utf8');\n\n console.log(`[route-forge/codegen] written ${totalRoutes} routes across ${Object.keys(routesByLevel).length} level(s) to ${outPath}`);\n if (failedLevels > 0) {\n console.warn(`[route-forge/codegen] ${failedLevels} level(s) failed; output may be incomplete`);\n }\n}\n\nconst invokedFromCli = (() => {\n try {\n if (!process.argv[1]) return false;\n const argv1 = process.argv[1].replace(/\\\\/g, '/');\n return argv1.endsWith('/codegen.js') || argv1.endsWith('/codegen.cjs') || argv1.endsWith('/codegen');\n } catch {\n return false;\n }\n})();\n\nif (invokedFromCli) {\n main().catch((err) => {\n console.error(err);\n process.exit(1);\n });\n}\n\nexport { main as runCodegen };\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/codegen/index.ts"],"names":[],"mappings":"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","file":"codegen.js","sourcesContent":["#!/usr/bin/env node\n/**\n * @route-forge/core codegen CLI\n * @see .docs/SPEC.md §4.2\n *\n * 用法:\n * npx @route-forge/core codegen \\\n * --endpoint http://localhost/_forge/routes \\\n * --levels public,client,manage,admin \\\n * --out src/types/forge-routes.d.ts\n */\n\nimport type { RouteMeta } from '../types.js';\n\nexport interface CodegenOptions {\n endpoint: string;\n levels: string[];\n out: string;\n responseTypes?: string;\n}\n\nconst BODY_METHODS = new Set(['POST', 'PUT', 'PATCH']);\n\n/**\n * 解析 argv(最小手写实现,不引入 commander/yargs)\n * 支持:--endpoint VALUE / --endpoint=VALUE / --levels a,b,c / --out PATH / --responseTypes PATH\n */\nexport function parseArgs(argv: string[]): CodegenOptions {\n const opts: Partial<CodegenOptions> = {};\n\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n const next = (): string => {\n const v = argv[++i];\n if (v === undefined) {\n throw new Error(`[route-forge/codegen] missing value for ${arg}`);\n }\n return v;\n };\n\n if (arg === '--endpoint') {\n opts.endpoint = next();\n } else if (arg?.startsWith('--endpoint=')) {\n opts.endpoint = arg.slice('--endpoint='.length);\n } else if (arg === '--levels') {\n opts.levels = next().split(',').map((s) => s.trim()).filter(Boolean);\n } else if (arg?.startsWith('--levels=')) {\n opts.levels = arg.slice('--levels='.length).split(',').map((s) => s.trim()).filter(Boolean);\n } else if (arg === '--out') {\n opts.out = next();\n } else if (arg?.startsWith('--out=')) {\n opts.out = arg.slice('--out='.length);\n } else if (arg === '--responseTypes') {\n opts.responseTypes = next();\n } else if (arg?.startsWith('--responseTypes=')) {\n opts.responseTypes = arg.slice('--responseTypes='.length);\n } else if (arg === '--help' || arg === '-h') {\n printHelp();\n process.exit(0);\n }\n }\n\n if (!opts.endpoint) {\n console.error('[route-forge/codegen] --endpoint is required');\n printHelp();\n process.exit(1);\n }\n if (!opts.out) {\n console.error('[route-forge/codegen] --out is required');\n printHelp();\n process.exit(1);\n }\n\n return {\n endpoint: opts.endpoint,\n levels: opts.levels ?? [],\n out: opts.out,\n responseTypes: opts.responseTypes,\n };\n}\n\nfunction printHelp(): void {\n console.log(`\nroute-forge codegen - generate TS route types from backend summary endpoint\n\nUsage:\n npx @route-forge/core codegen --endpoint URL --out PATH [--levels a,b,c] [--responseTypes PATH]\n\nOptions:\n --endpoint URL Backend summary endpoint (e.g. http://localhost/_forge/routes)\n --levels a,b,c Optional: explicit level list (skip auto-discovery)\n --out PATH Output .d.ts file path\n --responseTypes PATH Optional: JSON file mapping route names to response types\n -h, --help Show this help\n`);\n}\n\n/**\n * 拉取摘要端点\n */\nexport async function fetchSummary(endpoint: string): Promise<{ levels: Record<string, unknown> }> {\n const resp = await fetch(endpoint, { method: 'GET' });\n if (!resp.ok) {\n throw new Error(`summary endpoint ${endpoint} returned ${resp.status}`);\n }\n return (await resp.json()) as { levels: Record<string, unknown> };\n}\n\n/**\n * 拉取单个层级的路由元信息\n */\nexport async function fetchLevel(endpoint: string, level: string): Promise<{ routes: Record<string, RouteMeta> }> {\n const base = endpoint.replace(/\\/$/, '');\n const url = `${base}/${encodeURIComponent(level)}`;\n const resp = await fetch(url, { method: 'GET' });\n if (!resp.ok) {\n throw new Error(`level endpoint ${url} returned ${resp.status}`);\n }\n const data = (await resp.json()) as { routes?: Record<string, RouteMeta> };\n return { routes: data.routes ?? {} };\n}\n\n/**\n * 生成 d.ts 内容\n * @param routesByLevel 按层级分组的路由元信息:{ level: { routeName: meta } }\n */\nexport function generateRouteTypes(routesByLevel: Record<string, Record<string, RouteMeta>>): string {\n const levelEntries = Object.entries(routesByLevel).map(([level, routes]) => {\n const routeEntries = Object.entries(routes).map(([name, meta]) => {\n const defaults = meta.parameter_defaults ?? {};\n const params = (meta.parameters ?? []).map((p) => {\n // URI 中的 {p?} 或有后端默认值的参数 → 生成可选字段,避免调用方被迫传参\n const optional = meta.uri?.includes(`{${p}?}`) || p in defaults;\n return `${p}${optional ? '?' : ''}: string | number;`;\n }).join(' ');\n const method = meta.methods?.find((m) => m.toUpperCase() !== 'HEAD') ?? 'GET';\n const methodUpper = method.toUpperCase();\n const bodyField = BODY_METHODS.has(methodUpper) ? '\\n body: unknown;' : '';\n return ` ${JSON.stringify(name)}: {\\n method: ${JSON.stringify(methodUpper)};\\n params: { ${params} };${bodyField}\\n response: unknown;\\n };`;\n });\n return ` ${JSON.stringify(level)}: {\\n${routeEntries.join('\\n')}\\n };`;\n });\n return `// AUTO-GENERATED by @route-forge/core codegen. Do not edit.\n\n/**\n * 二级路由类型映射:level → routeName → routeMeta\n * 可通过 module augmentation 增强:\n * declare module '@route-forge/core' {\n * interface ForgeRouteMap { admin: { 'users.show': { method: 'GET'; params: { user: string | number }; response: User } } }\n * }\n */\nexport interface ForgeRouteMap {\n${levelEntries.join('\\n')}\n}\n`;\n}\n\n/**\n * CLI 主入口\n */\nexport async function main(argv: string[] = process.argv.slice(2)): Promise<void> {\n const opts = parseArgs(argv);\n\n let levels = opts.levels;\n if (levels.length === 0) {\n try {\n const summary = await fetchSummary(opts.endpoint);\n levels = Object.keys(summary.levels);\n } catch (e) {\n console.error(`[route-forge/codegen] failed to auto-discover levels from summary endpoint: ${(e as Error).message}`);\n console.error('hint: pass --levels explicitly to skip auto-discovery');\n process.exit(1);\n }\n }\n\n if (levels.length === 0) {\n console.error('[route-forge/codegen] no levels found; pass --levels explicitly');\n process.exit(1);\n }\n\n const levelFetches = await Promise.allSettled(\n levels.map((lvl) => fetchLevel(opts.endpoint, lvl)),\n );\n\n const routesByLevel: Record<string, Record<string, RouteMeta>> = {};\n let failedLevels = 0;\n levelFetches.forEach((res, idx) => {\n if (res.status === 'fulfilled') {\n routesByLevel[levels[idx]!] = res.value.routes;\n } else {\n failedLevels++;\n console.warn(`[route-forge/codegen] failed to fetch level \"${levels[idx]}\": ${(res.reason as Error).message}`);\n }\n });\n\n const totalRoutes = Object.values(routesByLevel).reduce((sum, r) => sum + Object.keys(r).length, 0);\n if (totalRoutes === 0) {\n console.error('[route-forge/codegen] no routes collected from any level');\n process.exit(1);\n }\n\n const dts = generateRouteTypes(routesByLevel);\n\n const fs = await import('node:fs/promises');\n const path = await import('node:path');\n const outPath = path.resolve(opts.out);\n const dir = path.dirname(outPath);\n await fs.mkdir(dir, { recursive: true });\n await fs.writeFile(outPath, dts, 'utf8');\n\n console.log(`[route-forge/codegen] written ${totalRoutes} routes across ${Object.keys(routesByLevel).length} level(s) to ${outPath}`);\n if (failedLevels > 0) {\n console.warn(`[route-forge/codegen] ${failedLevels} level(s) failed; output may be incomplete`);\n }\n}\n\nconst invokedFromCli = (() => {\n try {\n if (!process.argv[1]) return false;\n const argv1 = process.argv[1].replace(/\\\\/g, '/');\n return argv1.endsWith('/codegen.js') || argv1.endsWith('/codegen.cjs') || argv1.endsWith('/codegen');\n } catch {\n return false;\n }\n})();\n\nif (invokedFromCli) {\n main().catch((err) => {\n console.error(err);\n process.exit(1);\n });\n}\n\nexport { main as runCodegen };\n"]}
|
package/dist/index.cjs
CHANGED
|
@@ -28,17 +28,19 @@ var RouteCache = class {
|
|
|
28
28
|
return this.getFromMemory(level);
|
|
29
29
|
}
|
|
30
30
|
const raw = this.backend.getItem(this.key(level));
|
|
31
|
-
if (
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
if (raw) {
|
|
32
|
+
try {
|
|
33
|
+
const entry = JSON.parse(raw);
|
|
34
|
+
if (this.isExpired(entry)) {
|
|
35
|
+
this.del(level);
|
|
36
|
+
return void 0;
|
|
37
|
+
}
|
|
38
|
+
return entry;
|
|
39
|
+
} catch {
|
|
36
40
|
return void 0;
|
|
37
41
|
}
|
|
38
|
-
return entry;
|
|
39
|
-
} catch {
|
|
40
|
-
return void 0;
|
|
41
42
|
}
|
|
43
|
+
return this.getFromMemory(level);
|
|
42
44
|
}
|
|
43
45
|
getFromMemory(level) {
|
|
44
46
|
const entry = this.memory.get(level);
|
|
@@ -240,6 +242,10 @@ function createInterceptorManager() {
|
|
|
240
242
|
}
|
|
241
243
|
|
|
242
244
|
// src/adapters/builtin-http.ts
|
|
245
|
+
function isPassthroughBody(body) {
|
|
246
|
+
if (body === null) return false;
|
|
247
|
+
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;
|
|
248
|
+
}
|
|
243
249
|
function createBuiltinHttp(forgeInterceptors) {
|
|
244
250
|
const requestMgr = forgeInterceptors?.request ?? createInterceptorManager();
|
|
245
251
|
const responseMgr = forgeInterceptors?.response ?? createInterceptorManager();
|
|
@@ -253,15 +259,33 @@ function createBuiltinHttp(forgeInterceptors) {
|
|
|
253
259
|
url = url.includes("?") ? `${url}&${qs}` : `${url}?${qs}`;
|
|
254
260
|
}
|
|
255
261
|
}
|
|
262
|
+
const headers = new Headers(finalConfig.headers);
|
|
256
263
|
const fetchInit = {
|
|
257
264
|
method: finalConfig.method,
|
|
258
|
-
headers
|
|
265
|
+
headers
|
|
259
266
|
};
|
|
260
267
|
if (signal) fetchInit.signal = signal;
|
|
261
268
|
if (finalConfig.body !== void 0 && !["GET", "HEAD"].includes(finalConfig.method.toUpperCase())) {
|
|
262
|
-
|
|
269
|
+
if (typeof finalConfig.body === "string" || isPassthroughBody(finalConfig.body)) {
|
|
270
|
+
fetchInit.body = finalConfig.body;
|
|
271
|
+
} else {
|
|
272
|
+
fetchInit.body = JSON.stringify(finalConfig.body);
|
|
273
|
+
if (!headers.has("Content-Type")) {
|
|
274
|
+
headers.set("Content-Type", "application/json");
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
let res;
|
|
279
|
+
try {
|
|
280
|
+
res = await fetch(url, fetchInit);
|
|
281
|
+
} catch (e) {
|
|
282
|
+
throw new NetworkError(
|
|
283
|
+
e instanceof Error ? e.message : String(e),
|
|
284
|
+
finalConfig.route,
|
|
285
|
+
finalConfig.level,
|
|
286
|
+
e
|
|
287
|
+
);
|
|
263
288
|
}
|
|
264
|
-
const res = await fetch(url, fetchInit);
|
|
265
289
|
const text = await res.text();
|
|
266
290
|
let data = text;
|
|
267
291
|
const contentType = res.headers.get("content-type") ?? "";
|
|
@@ -281,9 +305,21 @@ function createBuiltinHttp(forgeInterceptors) {
|
|
|
281
305
|
data,
|
|
282
306
|
config: finalConfig
|
|
283
307
|
};
|
|
308
|
+
const source = res.status >= 200 && res.status < 300 ? Promise.resolve(responseData) : Promise.reject(
|
|
309
|
+
new HTTPError(
|
|
310
|
+
`HTTP ${res.status} for route "${finalConfig.route}" (${finalConfig.method} ${url})`,
|
|
311
|
+
{
|
|
312
|
+
route: finalConfig.route,
|
|
313
|
+
level: finalConfig.level,
|
|
314
|
+
status: res.status,
|
|
315
|
+
url,
|
|
316
|
+
method: finalConfig.method
|
|
317
|
+
}
|
|
318
|
+
)
|
|
319
|
+
);
|
|
284
320
|
return runResponseInterceptors(
|
|
285
321
|
responseMgr,
|
|
286
|
-
|
|
322
|
+
source
|
|
287
323
|
);
|
|
288
324
|
}
|
|
289
325
|
const get = (url, config) => request({ ...config, url, method: "GET" });
|
|
@@ -307,6 +343,15 @@ function createBuiltinHttp(forgeInterceptors) {
|
|
|
307
343
|
}
|
|
308
344
|
|
|
309
345
|
// src/adapters/axios.ts
|
|
346
|
+
function toHeaders(raw) {
|
|
347
|
+
if (!raw) return new Headers();
|
|
348
|
+
const init = typeof raw.toJSON === "function" ? raw.toJSON() : raw;
|
|
349
|
+
try {
|
|
350
|
+
return new Headers(init);
|
|
351
|
+
} catch {
|
|
352
|
+
return new Headers();
|
|
353
|
+
}
|
|
354
|
+
}
|
|
310
355
|
async function wrapAxiosAdapter() {
|
|
311
356
|
let axios;
|
|
312
357
|
try {
|
|
@@ -323,23 +368,41 @@ async function wrapAxiosAdapter() {
|
|
|
323
368
|
if (!axios || typeof axios.request !== "function") return null;
|
|
324
369
|
async function request(config) {
|
|
325
370
|
const headers = { ...config.headers };
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
371
|
+
try {
|
|
372
|
+
const res = await axios.request({
|
|
373
|
+
url: config.url,
|
|
374
|
+
method: config.method,
|
|
375
|
+
headers,
|
|
376
|
+
data: config.body,
|
|
377
|
+
// axios 会处理 baseURL/transformRequest 等 defaults;timeout 透传保证超时语义与 builtin 一致
|
|
378
|
+
timeout: config.timeout
|
|
379
|
+
});
|
|
380
|
+
return {
|
|
381
|
+
route: config.route,
|
|
382
|
+
level: config.level,
|
|
383
|
+
method: config.method,
|
|
384
|
+
url: config.url,
|
|
385
|
+
status: res.status,
|
|
386
|
+
headers: toHeaders(res.headers),
|
|
387
|
+
data: res.data,
|
|
388
|
+
config
|
|
389
|
+
};
|
|
390
|
+
} catch (e) {
|
|
391
|
+
if (e && e.response) {
|
|
392
|
+
const resp = e.response;
|
|
393
|
+
return {
|
|
394
|
+
route: config.route,
|
|
395
|
+
level: config.level,
|
|
396
|
+
method: config.method,
|
|
397
|
+
url: config.url,
|
|
398
|
+
status: resp.status,
|
|
399
|
+
headers: toHeaders(resp.headers),
|
|
400
|
+
data: resp.data,
|
|
401
|
+
config
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
throw e;
|
|
405
|
+
}
|
|
343
406
|
}
|
|
344
407
|
return { request, interceptors: void 0 };
|
|
345
408
|
}
|
|
@@ -471,6 +534,12 @@ function createRouteForge(options) {
|
|
|
471
534
|
if (summary === null) {
|
|
472
535
|
return;
|
|
473
536
|
}
|
|
537
|
+
const schemaVersion = summary.schemaVersion ?? 1;
|
|
538
|
+
if (schemaVersion > 1) {
|
|
539
|
+
console.warn(
|
|
540
|
+
`[route-forge] backend schemaVersion=${schemaVersion} > client supported 1; some features may be unavailable`
|
|
541
|
+
);
|
|
542
|
+
}
|
|
474
543
|
if (summary.config.endpoint_prefix && summary.config.endpoint_prefix !== explicitEndpoint) {
|
|
475
544
|
console.warn(
|
|
476
545
|
`[route-forge] backend endpoint_prefix "${summary.config.endpoint_prefix}" overrides frontend endpoint "${explicitEndpoint}"`
|
|
@@ -580,7 +649,10 @@ function createRouteForge(options) {
|
|
|
580
649
|
};
|
|
581
650
|
const resp = await adp.request(config);
|
|
582
651
|
if (!resp || resp.status < 200 || resp.status >= 300) {
|
|
583
|
-
throw new
|
|
652
|
+
throw new HTTPError(
|
|
653
|
+
`Failed to load level "${level}": HTTP ${resp?.status}`,
|
|
654
|
+
{ level, status: resp?.status, url: buildUrl(level), method: "GET" }
|
|
655
|
+
);
|
|
584
656
|
}
|
|
585
657
|
const data = resp.data;
|
|
586
658
|
return data;
|
|
@@ -614,27 +686,33 @@ function createRouteForge(options) {
|
|
|
614
686
|
return buildRequestUrl(meta, params ?? {});
|
|
615
687
|
}
|
|
616
688
|
function buildRequestUrl(meta, params) {
|
|
617
|
-
let uri = meta.uri;
|
|
618
689
|
const defaults = meta.parameter_defaults ?? {};
|
|
619
690
|
const missingRequired = [];
|
|
691
|
+
const values = {};
|
|
620
692
|
for (const p of meta.parameters) {
|
|
621
693
|
let v = params[p];
|
|
622
694
|
if ((v === void 0 || v === null) && p in defaults) {
|
|
623
695
|
v = defaults[p];
|
|
624
696
|
}
|
|
625
697
|
if (v === void 0 || v === null) {
|
|
626
|
-
if (uri.includes(`{${p}?}`)) {
|
|
627
|
-
|
|
628
|
-
continue;
|
|
698
|
+
if (!meta.uri.includes(`{${p}?}`)) {
|
|
699
|
+
missingRequired.push(p);
|
|
629
700
|
}
|
|
630
|
-
missingRequired.push(p);
|
|
631
701
|
} else {
|
|
632
|
-
|
|
702
|
+
values[p] = v;
|
|
633
703
|
}
|
|
634
704
|
}
|
|
635
705
|
if (missingRequired.length > 0) {
|
|
636
706
|
throw new MissingRouteParamError(meta.name, missingRequired);
|
|
637
707
|
}
|
|
708
|
+
let uri = meta.uri.replace(/\{([^{}]+)\}/g, (match, raw) => {
|
|
709
|
+
const optional = raw.endsWith("?");
|
|
710
|
+
const name = optional ? raw.slice(0, -1) : raw;
|
|
711
|
+
if (values[name] !== void 0) {
|
|
712
|
+
return encodeURIComponent(String(values[name]));
|
|
713
|
+
}
|
|
714
|
+
return optional ? "" : match;
|
|
715
|
+
});
|
|
638
716
|
uri = uri.replace(/\/+/g, "/").replace(/\/$/, "");
|
|
639
717
|
if (effectiveUrlPrefix.includes("://")) {
|
|
640
718
|
const prefix2 = effectiveUrlPrefix.endsWith("/") ? effectiveUrlPrefix.slice(0, -1) : effectiveUrlPrefix;
|
|
@@ -662,7 +740,7 @@ function createRouteForge(options) {
|
|
|
662
740
|
return doApiCall(meta, params);
|
|
663
741
|
}
|
|
664
742
|
async function doApiCall(meta, params) {
|
|
665
|
-
const { pathParams, query, body, headers } = resolveApiParams(params);
|
|
743
|
+
const { pathParams, query, body, headers, timeout: perCallTimeout } = resolveApiParams(params);
|
|
666
744
|
const method = pickMethod(meta);
|
|
667
745
|
const urlWithQuery = appendQuery(buildRequestUrl(meta, pathParams), query);
|
|
668
746
|
const config = {
|
|
@@ -673,7 +751,7 @@ function createRouteForge(options) {
|
|
|
673
751
|
headers: { Accept: "application/json", ...headers ?? {} },
|
|
674
752
|
body,
|
|
675
753
|
params: pathParams,
|
|
676
|
-
timeout,
|
|
754
|
+
timeout: perCallTimeout ?? timeout,
|
|
677
755
|
meta
|
|
678
756
|
};
|
|
679
757
|
const adp = await ensureAdapter();
|
|
@@ -713,12 +791,17 @@ function createRouteForge(options) {
|
|
|
713
791
|
}
|
|
714
792
|
}
|
|
715
793
|
function invalidate(level) {
|
|
716
|
-
if (level)
|
|
717
|
-
|
|
794
|
+
if (level === void 0) {
|
|
795
|
+
cache.clear();
|
|
796
|
+
} else if (Array.isArray(level)) {
|
|
797
|
+
for (const lvl of level) cache.del(lvl);
|
|
798
|
+
} else {
|
|
799
|
+
cache.del(level);
|
|
800
|
+
}
|
|
718
801
|
}
|
|
719
802
|
function isLoaded(level) {
|
|
720
803
|
if (level) return cache.get(level) !== void 0;
|
|
721
|
-
return effectiveLevels.every((lvl) => cache.get(lvl) !== void 0);
|
|
804
|
+
return effectiveLevels.length > 0 && effectiveLevels.every((lvl) => cache.get(lvl) !== void 0);
|
|
722
805
|
}
|
|
723
806
|
function hasRoute(level, name) {
|
|
724
807
|
return findRouteMeta(level, name) !== void 0;
|
|
@@ -792,6 +875,7 @@ function resolveApiParams(input) {
|
|
|
792
875
|
query: rawQuery,
|
|
793
876
|
body: rawBody,
|
|
794
877
|
headers: rawHeaders,
|
|
878
|
+
timeout: perCallTimeout,
|
|
795
879
|
...flatRest
|
|
796
880
|
} = input;
|
|
797
881
|
const pathParams = explicitParams ? { ...explicitParams } : {};
|
|
@@ -824,7 +908,7 @@ function resolveApiParams(input) {
|
|
|
824
908
|
pathParams.headers = rawHeaders;
|
|
825
909
|
}
|
|
826
910
|
}
|
|
827
|
-
return { pathParams, query, body, headers };
|
|
911
|
+
return { pathParams, query, body, headers, timeout: perCallTimeout };
|
|
828
912
|
}
|
|
829
913
|
|
|
830
914
|
exports.AdapterNotFoundError = AdapterNotFoundError;
|