clawfire 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +182 -0
- package/dist/admin.cjs +309 -0
- package/dist/admin.cjs.map +1 -0
- package/dist/admin.d.cts +93 -0
- package/dist/admin.d.ts +93 -0
- package/dist/admin.js +274 -0
- package/dist/admin.js.map +1 -0
- package/dist/auth-DQ3cifhb.d.cts +55 -0
- package/dist/auth-DtnUPbXT.d.ts +55 -0
- package/dist/chunk-37Y2XI7X.js +75 -0
- package/dist/chunk-YGIPORYL.js +339 -0
- package/dist/cli.js +241 -0
- package/dist/client.cjs +97 -0
- package/dist/client.cjs.map +1 -0
- package/dist/client.d.cts +4 -0
- package/dist/client.d.ts +4 -0
- package/dist/client.js +68 -0
- package/dist/client.js.map +1 -0
- package/dist/codegen.cjs +648 -0
- package/dist/codegen.cjs.map +1 -0
- package/dist/codegen.d.cts +25 -0
- package/dist/codegen.d.ts +25 -0
- package/dist/codegen.js +617 -0
- package/dist/codegen.js.map +1 -0
- package/dist/config-QMBJRn9G.d.cts +46 -0
- package/dist/config-QMBJRn9G.d.ts +46 -0
- package/dist/dev-server-QAVWINAT.js +973 -0
- package/dist/dev.cjs +1388 -0
- package/dist/dev.cjs.map +1 -0
- package/dist/dev.d.cts +111 -0
- package/dist/dev.d.ts +111 -0
- package/dist/dev.js +1349 -0
- package/dist/dev.js.map +1 -0
- package/dist/discover-BPMAZFBD.js +9 -0
- package/dist/discover-DYNqz_ym.d.cts +28 -0
- package/dist/discover-DYNqz_ym.d.ts +28 -0
- package/dist/errors-s_mP7rs9.d.cts +33 -0
- package/dist/errors-s_mP7rs9.d.ts +33 -0
- package/dist/functions.cjs +1156 -0
- package/dist/functions.cjs.map +1 -0
- package/dist/functions.d.cts +115 -0
- package/dist/functions.d.ts +115 -0
- package/dist/functions.js +1108 -0
- package/dist/functions.js.map +1 -0
- package/dist/hosting-7WVFHAYJ.js +85 -0
- package/dist/html-PCUCJGBH.js +7 -0
- package/dist/index.cjs +349 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +22 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +312 -0
- package/dist/index.js.map +1 -0
- package/dist/playground.cjs +364 -0
- package/dist/playground.cjs.map +1 -0
- package/dist/playground.d.cts +12 -0
- package/dist/playground.d.ts +12 -0
- package/dist/playground.js +337 -0
- package/dist/playground.js.map +1 -0
- package/dist/router-BVB_I-tu.d.ts +65 -0
- package/dist/router-Cikk8Heq.d.cts +65 -0
- package/dist/schema-BJsictSV.d.cts +172 -0
- package/dist/schema-BJsictSV.d.ts +172 -0
- package/package.json +150 -0
- package/templates/CLAUDE.md +71 -0
- package/templates/app/routes/auth/login.ts +35 -0
- package/templates/app/routes/health.ts +20 -0
- package/templates/app/schemas/user.ts +26 -0
- package/templates/clawfire.config.ts +25 -0
- package/templates/functions/index.ts +43 -0
- package/templates/starter/.claude/skills/clawfire-api/SKILL.md +131 -0
- package/templates/starter/.claude/skills/clawfire-auth/SKILL.md +111 -0
- package/templates/starter/.claude/skills/clawfire-deploy/SKILL.md +95 -0
- package/templates/starter/.claude/skills/clawfire-diagnose/SKILL.md +99 -0
- package/templates/starter/.claude/skills/clawfire-model/SKILL.md +128 -0
- package/templates/starter/CLAUDE.md +227 -0
- package/templates/starter/app/routes/health.ts +20 -0
- package/templates/starter/app/routes/todos/create.ts +25 -0
- package/templates/starter/app/routes/todos/delete.ts +20 -0
- package/templates/starter/app/routes/todos/list.ts +26 -0
- package/templates/starter/app/routes/todos/update.ts +32 -0
- package/templates/starter/app/schemas/todo.ts +16 -0
- package/templates/starter/app/store.ts +56 -0
- package/templates/starter/clawfire.config.ts +25 -0
- package/templates/starter/dev.ts +12 -0
- package/templates/starter/package.json +19 -0
- package/templates/starter/public/index.html +365 -0
- package/templates/starter/tsconfig.json +17 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/codegen.ts","../src/codegen/client-gen.ts","../src/playground/html.ts","../src/core/schema.ts","../src/routing/discover.ts"],"sourcesContent":["/**\n * Clawfire Codegen Entry Point\n *\n * 클라이언트 코드 생성, 매니페스트 생성\n */\n\nexport { generateClientCode, generateManifestJson } from \"./codegen/index.js\";\nexport { generatePlaygroundHtml } from \"./playground/index.js\";\nexport { discoverRoutes, generateRouteImports, type DiscoveredRoute } from \"./routing/index.js\";\n","/**\n * Clawfire Client Code Generator\n *\n * 라우트 계약에서 타입 안전한 api-client.ts 자동 생성\n * api.products.list(), api.auth.login() 형태로 호출 가능\n */\nimport type { Manifest, ManifestEntry } from \"../core/schema.js\";\n\n/**\n * 매니페스트에서 타입 안전한 API 클라이언트 코드 생성\n */\nexport function generateClientCode(manifest: Manifest, options?: {\n baseUrl?: string;\n importPath?: string;\n}): string {\n const baseUrl = options?.baseUrl || \"\";\n const lines: string[] = [];\n\n lines.push(\"// AUTO-GENERATED by Clawfire — DO NOT EDIT\");\n lines.push(\"// Regenerate: clawfire codegen\");\n lines.push(\"\");\n lines.push(\"/* eslint-disable */\");\n lines.push(\"\");\n\n // 타입 정의 생성\n lines.push(\"// ─── Types ───────────────────────────────────────────────\");\n lines.push(\"\");\n\n for (const api of manifest.apis) {\n const typeName = pathToTypeName(api.path);\n lines.push(`/** ${api.meta.description} */`);\n lines.push(`export interface ${typeName}Input ${jsonSchemaToTsType(api.inputSchema)}`);\n lines.push(\"\");\n lines.push(`export interface ${typeName}Output ${jsonSchemaToTsType(api.outputSchema)}`);\n lines.push(\"\");\n }\n\n // API 응답 래퍼\n lines.push(\"// ─── Response Wrapper ─────────────────────────────────────\");\n lines.push(\"\");\n lines.push(\"interface ClawfireResponse<T> {\");\n lines.push(\" data: T;\");\n lines.push(\"}\");\n lines.push(\"\");\n lines.push(\"interface ClawfireError {\");\n lines.push(\" error: {\");\n lines.push(\" code: string;\");\n lines.push(\" message: string;\");\n lines.push(\" details?: unknown;\");\n lines.push(\" };\");\n lines.push(\"}\");\n lines.push(\"\");\n\n // HTTP 클라이언트\n lines.push(\"// ─── HTTP Client ─────────────────────────────────────────\");\n lines.push(\"\");\n lines.push(\"type GetTokenFn = () => Promise<string | null>;\");\n lines.push(\"\");\n lines.push(\"let _baseUrl = \" + JSON.stringify(baseUrl) + \";\");\n lines.push(\"let _getToken: GetTokenFn = async () => null;\");\n lines.push(\"\");\n lines.push(\"/**\");\n lines.push(\" * API 클라이언트 설정\");\n lines.push(\" * @param baseUrl - API 기본 URL (예: https://us-central1-myproject.cloudfunctions.net/api)\");\n lines.push(\" * @param getToken - 인증 토큰 반환 함수\");\n lines.push(\" */\");\n lines.push(\"export function configureClient(baseUrl: string, getToken?: GetTokenFn) {\");\n lines.push(\" _baseUrl = baseUrl;\");\n lines.push(\" if (getToken) _getToken = getToken;\");\n lines.push(\"}\");\n lines.push(\"\");\n lines.push(\"async function call<TInput, TOutput>(path: string, input: TInput): Promise<TOutput> {\");\n lines.push(\" const token = await _getToken();\");\n lines.push(\" const headers: Record<string, string> = {\");\n lines.push(' \"Content-Type\": \"application/json\",');\n lines.push(\" };\");\n lines.push(' if (token) headers[\"Authorization\"] = `Bearer ${token}`;');\n lines.push(\"\");\n lines.push(\" const res = await fetch(`${_baseUrl}/api${path}`, {\");\n lines.push(' method: \"POST\",');\n lines.push(\" headers,\");\n lines.push(\" body: JSON.stringify(input),\");\n lines.push(\" });\");\n lines.push(\"\");\n lines.push(\" const json = await res.json();\");\n lines.push(\"\");\n lines.push(\" if (!res.ok) {\");\n lines.push(\" const err = json as ClawfireError;\");\n lines.push(\" throw new Error(err.error?.message || `API error: ${res.status}`);\");\n lines.push(\" }\");\n lines.push(\"\");\n lines.push(\" return (json as ClawfireResponse<TOutput>).data;\");\n lines.push(\"}\");\n lines.push(\"\");\n\n // API 네임스페이스 객체 생성\n lines.push(\"// ─── API Client ────────────────────────────────────────────\");\n lines.push(\"\");\n\n const tree = buildApiTree(manifest.apis);\n lines.push(generateApiObject(tree));\n\n return lines.join(\"\\n\");\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────\n\nfunction pathToTypeName(path: string): string {\n return path\n .split(\"/\")\n .filter(Boolean)\n .filter((p) => !p.startsWith(\":\"))\n .map((p) => p.charAt(0).toUpperCase() + p.slice(1))\n .join(\"\");\n}\n\ninterface ApiTreeNode {\n apis: Array<{ name: string; path: string; typeName: string; meta: ManifestEntry[\"meta\"] }>;\n children: Record<string, ApiTreeNode>;\n}\n\nfunction buildApiTree(apis: ManifestEntry[]): ApiTreeNode {\n const root: ApiTreeNode = { apis: [], children: {} };\n\n for (const api of apis) {\n const parts = api.path.split(\"/\").filter(Boolean).filter((p) => !p.startsWith(\":\"));\n let node = root;\n\n for (let i = 0; i < parts.length - 1; i++) {\n if (!node.children[parts[i]]) {\n node.children[parts[i]] = { apis: [], children: {} };\n }\n node = node.children[parts[i]];\n }\n\n const name = parts[parts.length - 1] || \"index\";\n const typeName = pathToTypeName(api.path);\n node.apis.push({ name, path: api.path, typeName, meta: api.meta });\n }\n\n return root;\n}\n\nfunction generateApiObject(tree: ApiTreeNode, indent = \"\"): string {\n const lines: string[] = [];\n lines.push(`${indent}export const api = {`);\n\n for (const [name, child] of Object.entries(tree.children)) {\n lines.push(`${indent} ${name}: {`);\n\n // 자식 API\n for (const api of child.apis) {\n lines.push(`${indent} /** ${api.meta.description}${api.meta.auth ? ` [${api.meta.auth}]` : \"\"} */`);\n lines.push(\n `${indent} ${api.name}: (input: ${api.typeName}Input): Promise<${api.typeName}Output> => call(\"${api.path}\", input),`,\n );\n }\n\n // 자식 네임스페이스\n for (const [subName, subChild] of Object.entries(child.children)) {\n lines.push(`${indent} ${subName}: {`);\n for (const subApi of subChild.apis) {\n lines.push(`${indent} /** ${subApi.meta.description}${subApi.meta.auth ? ` [${subApi.meta.auth}]` : \"\"} */`);\n lines.push(\n `${indent} ${subApi.name}: (input: ${subApi.typeName}Input): Promise<${subApi.typeName}Output> => call(\"${subApi.path}\", input),`,\n );\n }\n // 추가 깊이 지원\n for (const [deepName, deepChild] of Object.entries(subChild.children)) {\n lines.push(`${indent} ${deepName}: {`);\n for (const deepApi of deepChild.apis) {\n lines.push(`${indent} /** ${deepApi.meta.description} */`);\n lines.push(\n `${indent} ${deepApi.name}: (input: ${deepApi.typeName}Input): Promise<${deepApi.typeName}Output> => call(\"${deepApi.path}\", input),`,\n );\n }\n lines.push(`${indent} },`);\n }\n lines.push(`${indent} },`);\n }\n\n lines.push(`${indent} },`);\n }\n\n // 루트 API\n for (const api of tree.apis) {\n lines.push(`${indent} /** ${api.meta.description} */`);\n lines.push(\n `${indent} ${api.name}: (input: ${api.typeName}Input): Promise<${api.typeName}Output> => call(\"${api.path}\", input),`,\n );\n }\n\n lines.push(`${indent}};`);\n return lines.join(\"\\n\");\n}\n\nfunction jsonSchemaToTsType(schema: Record<string, unknown>): string {\n if (!schema) return \"{}\";\n\n const type = schema.type as string;\n\n switch (type) {\n case \"object\": {\n const props = schema.properties as Record<string, Record<string, unknown>> | undefined;\n if (!props) return \"Record<string, unknown>\";\n const required = (schema.required as string[]) || [];\n\n const fields: string[] = [];\n for (const [key, propSchema] of Object.entries(props)) {\n const isRequired = required.includes(key);\n const tsType = jsonSchemaToInlineType(propSchema);\n fields.push(` ${key}${isRequired ? \"\" : \"?\"}: ${tsType};`);\n }\n return `{\\n${fields.join(\"\\n\")}\\n}`;\n }\n default:\n return \"{}\";\n }\n}\n\nfunction jsonSchemaToInlineType(schema: Record<string, unknown>): string {\n if (!schema) return \"unknown\";\n\n // const 값\n if (\"const\" in schema) return JSON.stringify(schema.const);\n\n // enum\n if (schema.enum) return (schema.enum as string[]).map((v) => JSON.stringify(v)).join(\" | \");\n\n // nullable\n const nullable = schema.nullable ? \" | null\" : \"\";\n const optional = schema.optional ? \"\" : \"\";\n\n const type = schema.type as string;\n\n switch (type) {\n case \"string\": return `string${nullable}`;\n case \"number\": return `number${nullable}`;\n case \"boolean\": return `boolean${nullable}`;\n case \"array\": {\n const items = schema.items as Record<string, unknown> | undefined;\n const itemType = items ? jsonSchemaToInlineType(items) : \"unknown\";\n return `${itemType}[]${nullable}`;\n }\n case \"object\": {\n const props = schema.properties as Record<string, Record<string, unknown>> | undefined;\n if (!props) {\n const additionalProps = schema.additionalProperties as Record<string, unknown> | undefined;\n if (additionalProps) return `Record<string, ${jsonSchemaToInlineType(additionalProps)}>${nullable}`;\n return `Record<string, unknown>${nullable}`;\n }\n const required = (schema.required as string[]) || [];\n const fields = Object.entries(props)\n .map(([k, v]) => `${k}${required.includes(k) ? \"\" : \"?\"}: ${jsonSchemaToInlineType(v)}`)\n .join(\"; \");\n return `{ ${fields} }${nullable}`;\n }\n default:\n if (schema.oneOf) {\n return (schema.oneOf as Record<string, unknown>[])\n .map(jsonSchemaToInlineType)\n .join(\" | \");\n }\n return \"unknown\";\n }\n}\n\n/**\n * 매니페스트에서 manifest.json 파일 내용 생성\n */\nexport function generateManifestJson(manifest: Manifest): string {\n return JSON.stringify(manifest, null, 2);\n}\n","/**\n * Clawfire Playground\n *\n * 웹 기반 API 탐색기: API 목록, 인증 테스트, 요청/응답 뷰어\n * 단일 HTML 파일로 생성되어 Firebase Hosting에 배포됩니다.\n */\n\nexport function generatePlaygroundHtml(options?: {\n title?: string;\n apiBaseUrl?: string;\n}): string {\n const title = options?.title || \"Clawfire Playground\";\n const apiBaseUrl = options?.apiBaseUrl || \"\";\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>${title}</title>\n <style>\n :root {\n --bg: #0a0a0a;\n --surface: #141414;\n --surface2: #1e1e1e;\n --border: #2a2a2a;\n --text: #e5e5e5;\n --text2: #a3a3a3;\n --accent: #f97316;\n --accent2: #fb923c;\n --green: #22c55e;\n --red: #ef4444;\n --blue: #3b82f6;\n --yellow: #eab308;\n --radius: 8px;\n --font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n --mono: 'JetBrains Mono', 'Fira Code', monospace;\n }\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body { font-family: var(--font); background: var(--bg); color: var(--text); min-height: 100vh; }\n\n .layout { display: grid; grid-template-columns: 320px 1fr; min-height: 100vh; }\n .sidebar { background: var(--surface); border-right: 1px solid var(--border); overflow-y: auto; }\n .main { padding: 24px; overflow-y: auto; }\n\n .logo { padding: 20px; border-bottom: 1px solid var(--border); }\n .logo h1 { font-size: 20px; font-weight: 700; color: var(--accent); }\n .logo p { font-size: 12px; color: var(--text2); margin-top: 4px; }\n\n .auth-section { padding: 16px; border-bottom: 1px solid var(--border); }\n .auth-section label { font-size: 12px; color: var(--text2); display: block; margin-bottom: 6px; }\n .auth-input { width: 100%; padding: 8px 12px; background: var(--surface2); border: 1px solid var(--border);\n border-radius: var(--radius); color: var(--text); font-family: var(--mono); font-size: 12px; }\n .auth-status { font-size: 11px; margin-top: 6px; }\n .auth-status.ok { color: var(--green); }\n .auth-status.no { color: var(--text2); }\n\n .search { padding: 12px 16px; border-bottom: 1px solid var(--border); }\n .search input { width: 100%; padding: 8px 12px; background: var(--surface2); border: 1px solid var(--border);\n border-radius: var(--radius); color: var(--text); font-size: 13px; }\n\n .api-list { padding: 8px 0; }\n .api-group { padding: 4px 0; }\n .api-group-title { padding: 8px 16px; font-size: 11px; color: var(--text2); text-transform: uppercase;\n letter-spacing: 0.5px; font-weight: 600; }\n .api-item { padding: 8px 16px; cursor: pointer; transition: background 0.15s; display: flex; align-items: center;\n gap: 8px; font-size: 13px; }\n .api-item:hover { background: var(--surface2); }\n .api-item.active { background: var(--surface2); border-left: 2px solid var(--accent); }\n .api-badge { font-size: 10px; padding: 2px 6px; border-radius: 4px; font-weight: 600; }\n .badge-public { background: #22c55e20; color: var(--green); }\n .badge-auth { background: #3b82f620; color: var(--blue); }\n .badge-role { background: #eab30820; color: var(--yellow); }\n .badge-reauth { background: #ef444420; color: var(--red); }\n\n .panel { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); margin-bottom: 16px; }\n .panel-header { padding: 16px; border-bottom: 1px solid var(--border); display: flex; align-items: center;\n justify-content: space-between; }\n .panel-header h2 { font-size: 16px; font-weight: 600; }\n .panel-body { padding: 16px; }\n\n .field { margin-bottom: 12px; }\n .field label { font-size: 12px; color: var(--text2); display: block; margin-bottom: 4px; }\n .field-type { font-size: 11px; color: var(--text2); font-family: var(--mono); }\n .field-required { color: var(--red); font-size: 11px; }\n\n textarea, input[type=\"text\"] { width: 100%; padding: 10px 14px; background: var(--surface2);\n border: 1px solid var(--border); border-radius: var(--radius); color: var(--text);\n font-family: var(--mono); font-size: 13px; resize: vertical; }\n textarea { min-height: 200px; }\n\n .btn { padding: 10px 20px; border: none; border-radius: var(--radius); font-size: 14px;\n font-weight: 600; cursor: pointer; transition: all 0.15s; }\n .btn-primary { background: var(--accent); color: white; }\n .btn-primary:hover { background: var(--accent2); }\n .btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }\n\n .response-section { margin-top: 16px; }\n .status-badge { font-size: 12px; padding: 4px 8px; border-radius: 4px; font-weight: 600; }\n .status-ok { background: #22c55e20; color: var(--green); }\n .status-err { background: #ef444420; color: var(--red); }\n pre { background: var(--surface2); padding: 16px; border-radius: var(--radius); overflow-x: auto;\n font-family: var(--mono); font-size: 13px; line-height: 1.5; white-space: pre-wrap; }\n\n .schema-info { font-size: 12px; color: var(--text2); line-height: 1.6; }\n .schema-info code { background: var(--surface2); padding: 2px 6px; border-radius: 4px; font-family: var(--mono);\n font-size: 11px; }\n\n .empty-state { text-align: center; padding: 80px 40px; color: var(--text2); }\n .empty-state h2 { font-size: 24px; margin-bottom: 8px; color: var(--text); }\n\n .timer { font-size: 12px; color: var(--text2); font-family: var(--mono); }\n\n @media (max-width: 768px) {\n .layout { grid-template-columns: 1fr; }\n .sidebar { max-height: 40vh; }\n }\n </style>\n</head>\n<body>\n <div class=\"layout\">\n <div class=\"sidebar\">\n <div class=\"logo\">\n <h1>Clawfire</h1>\n <p>API Playground</p>\n </div>\n <div class=\"auth-section\">\n <label>Bearer Token</label>\n <input type=\"text\" class=\"auth-input\" id=\"token-input\" placeholder=\"Paste your ID token...\">\n <div class=\"auth-status no\" id=\"auth-status\">Not authenticated</div>\n </div>\n <div class=\"search\">\n <input type=\"text\" id=\"search-input\" placeholder=\"Search APIs...\">\n </div>\n <div class=\"api-list\" id=\"api-list\"></div>\n </div>\n <div class=\"main\" id=\"main-content\">\n <div class=\"empty-state\">\n <h2>Select an API</h2>\n <p>Choose an API from the sidebar to test it.</p>\n </div>\n </div>\n </div>\n\n <script>\n const BASE_URL = ${JSON.stringify(apiBaseUrl)} || window.location.origin;\n let manifest = null;\n let selectedApi = null;\n\n async function loadManifest() {\n try {\n const res = await fetch(BASE_URL + '/api/__manifest', { method: 'POST' });\n manifest = await res.json();\n renderApiList(manifest.apis);\n } catch (e) {\n document.getElementById('api-list').innerHTML =\n '<div style=\"padding:16px;color:var(--red);font-size:13px;\">Failed to load API manifest. Make sure your server is running.</div>';\n }\n }\n\n function renderApiList(apis) {\n const groups = {};\n apis.forEach(api => {\n const parts = api.path.split('/').filter(Boolean);\n const group = parts.length > 1 ? parts[0] : 'root';\n if (!groups[group]) groups[group] = [];\n groups[group].push(api);\n });\n\n const el = document.getElementById('api-list');\n el.innerHTML = Object.entries(groups).map(([group, items]) =>\n '<div class=\"api-group\">' +\n '<div class=\"api-group-title\">' + group + '</div>' +\n items.map(api => {\n const auth = api.meta.auth || 'public';\n const badgeClass = 'badge-' + auth;\n return '<div class=\"api-item\" onclick=\"selectApi(\\\\'' + api.path + '\\\\')\">' +\n '<span class=\"api-badge ' + badgeClass + '\">' + auth.toUpperCase() + '</span>' +\n '<span>' + api.path + '</span>' +\n '</div>';\n }).join('') +\n '</div>'\n ).join('');\n }\n\n function selectApi(path) {\n selectedApi = manifest.apis.find(a => a.path === path);\n if (!selectedApi) return;\n\n document.querySelectorAll('.api-item').forEach(el => el.classList.remove('active'));\n event.currentTarget?.classList.add('active');\n\n const main = document.getElementById('main-content');\n const exampleInput = selectedApi.meta.exampleInput\n ? JSON.stringify(selectedApi.meta.exampleInput, null, 2)\n : generateExampleFromSchema(selectedApi.inputSchema);\n\n main.innerHTML =\n '<div class=\"panel\">' +\n '<div class=\"panel-header\">' +\n '<h2>POST ' + selectedApi.path + '</h2>' +\n '<span class=\"api-badge badge-' + (selectedApi.meta.auth || 'public') + '\">' +\n (selectedApi.meta.auth || 'public').toUpperCase() + '</span>' +\n '</div>' +\n '<div class=\"panel-body\">' +\n '<p style=\"color:var(--text2);margin-bottom:16px;\">' + (selectedApi.meta.description || '') + '</p>' +\n (selectedApi.meta.tags ? '<div style=\"margin-bottom:12px;\">' + selectedApi.meta.tags.map(t =>\n '<span style=\"background:var(--surface2);padding:2px 8px;border-radius:4px;font-size:11px;margin-right:4px;\">' + t + '</span>'\n ).join('') + '</div>' : '') +\n '<div class=\"schema-info\" style=\"margin-bottom:16px;\">' +\n '<strong>Input Schema:</strong><br>' + renderSchemaInfo(selectedApi.inputSchema) +\n '</div>' +\n '<div class=\"schema-info\" style=\"margin-bottom:16px;\">' +\n '<strong>Output Schema:</strong><br>' + renderSchemaInfo(selectedApi.outputSchema) +\n '</div>' +\n '</div>' +\n '</div>' +\n '<div class=\"panel\">' +\n '<div class=\"panel-header\"><h2>Request</h2></div>' +\n '<div class=\"panel-body\">' +\n '<textarea id=\"req-body\" placeholder=\"Request JSON body\">' + exampleInput + '</textarea>' +\n '<div style=\"margin-top:12px;display:flex;align-items:center;gap:12px;\">' +\n '<button class=\"btn btn-primary\" onclick=\"sendRequest()\">Send Request</button>' +\n '<span class=\"timer\" id=\"timer\"></span>' +\n '</div>' +\n '</div>' +\n '</div>' +\n '<div class=\"response-section\" id=\"response-section\"></div>';\n }\n\n function renderSchemaInfo(schema) {\n if (!schema || !schema.properties) return '<code>void</code>';\n return Object.entries(schema.properties).map(([key, prop]) => {\n const required = schema.required?.includes(key);\n const type = prop.type || 'unknown';\n const enumVals = prop.enum ? ' (' + prop.enum.join(', ') + ')' : '';\n return '<code>' + key + '</code>: <span class=\"field-type\">' + type + enumVals + '</span>' +\n (required ? ' <span class=\"field-required\">required</span>' : ' <span style=\"color:var(--text2);font-size:11px;\">optional</span>');\n }).join('<br>');\n }\n\n function generateExampleFromSchema(schema) {\n if (!schema || !schema.properties) return '{}';\n const obj = {};\n for (const [key, prop] of Object.entries(schema.properties)) {\n if (prop.enum) { obj[key] = prop.enum[0]; continue; }\n switch (prop.type) {\n case 'string': obj[key] = prop.format === 'email' ? 'user@example.com' : 'string'; break;\n case 'number': obj[key] = 0; break;\n case 'boolean': obj[key] = false; break;\n case 'array': obj[key] = []; break;\n case 'object': obj[key] = {}; break;\n default: obj[key] = null;\n }\n }\n return JSON.stringify(obj, null, 2);\n }\n\n async function sendRequest() {\n if (!selectedApi) return;\n const body = document.getElementById('req-body').value;\n const token = document.getElementById('token-input').value;\n const timer = document.getElementById('timer');\n const section = document.getElementById('response-section');\n\n let parsed;\n try { parsed = JSON.parse(body); } catch {\n section.innerHTML = '<div class=\"panel\"><div class=\"panel-body\"><pre style=\"color:var(--red)\">Invalid JSON</pre></div></div>';\n return;\n }\n\n const start = performance.now();\n timer.textContent = 'Sending...';\n\n try {\n const headers = { 'Content-Type': 'application/json' };\n if (token) headers['Authorization'] = 'Bearer ' + token;\n\n const res = await fetch(BASE_URL + '/api' + selectedApi.path, {\n method: 'POST', headers, body: JSON.stringify(parsed)\n });\n const elapsed = Math.round(performance.now() - start);\n timer.textContent = elapsed + 'ms';\n\n const json = await res.json();\n const isOk = res.ok;\n\n section.innerHTML =\n '<div class=\"panel\">' +\n '<div class=\"panel-header\">' +\n '<h2>Response</h2>' +\n '<span class=\"status-badge ' + (isOk ? 'status-ok' : 'status-err') + '\">' +\n res.status + ' ' + res.statusText + '</span>' +\n '</div>' +\n '<div class=\"panel-body\"><pre>' + syntaxHighlight(JSON.stringify(json, null, 2)) + '</pre></div>' +\n '</div>';\n } catch (e) {\n const elapsed = Math.round(performance.now() - start);\n timer.textContent = elapsed + 'ms';\n section.innerHTML =\n '<div class=\"panel\"><div class=\"panel-body\"><pre style=\"color:var(--red)\">Network error: ' + e.message + '</pre></div></div>';\n }\n }\n\n function syntaxHighlight(json) {\n return json.replace(/(\"(\\\\\\\\u[a-fA-F0-9]{4}|\\\\\\\\[^u]|[^\\\\\\\\\"])*\"(\\\\s*:)?)|\\\\b(true|false|null)\\\\b|-?\\\\d+(\\\\.\\\\d+)?([eE][+-]?\\\\d+)?/g,\n function(match) {\n let cls = 'color:#eab308';\n if (/^\"/.test(match)) {\n if (/:$/.test(match)) cls = 'color:#3b82f6';\n else cls = 'color:#22c55e';\n } else if (/true|false/.test(match)) cls = 'color:#f97316';\n else if (/null/.test(match)) cls = 'color:#ef4444';\n return '<span style=\"' + cls + '\">' + match + '</span>';\n }\n );\n }\n\n // Search\n document.getElementById('search-input')?.addEventListener('input', (e) => {\n if (!manifest) return;\n const q = e.target.value.toLowerCase();\n const filtered = manifest.apis.filter(a => a.path.toLowerCase().includes(q) || a.meta.description?.toLowerCase().includes(q));\n renderApiList(filtered);\n });\n\n // Token status\n document.getElementById('token-input')?.addEventListener('input', (e) => {\n const el = document.getElementById('auth-status');\n if (e.target.value) {\n el.textContent = 'Token set';\n el.className = 'auth-status ok';\n } else {\n el.textContent = 'Not authenticated';\n el.className = 'auth-status no';\n }\n });\n\n loadManifest();\n </script>\n</body>\n</html>`;\n}\n","/**\n * Clawfire Schema & Contract System\n *\n * 모든 API는 input/output schema + meta + handler로 구성된 \"계약(Contract)\"으로 정의됩니다.\n * Zod를 사용하여 타입 안전성과 런타임 검증을 동시에 보장합니다.\n */\nimport { z, type ZodType, type ZodObject, type ZodRawShape } from \"zod\";\n\n// ─── Types ───────────────────────────────────────────────────────────\n\n/** 인증 컨텍스트 */\nexport interface AuthContext {\n uid: string;\n email?: string;\n emailVerified?: boolean;\n role?: string;\n customClaims?: Record<string, unknown>;\n token?: string;\n}\n\n/** API 핸들러에 전달되는 컨텍스트 */\nexport interface HandlerContext {\n auth: AuthContext | null;\n /** 재인증 여부 (민감 작업용) */\n reauthenticated?: boolean;\n /** 원본 요청 헤더 */\n headers?: Record<string, string>;\n /** 요청 IP */\n ip?: string;\n}\n\n/** 권한 수준 */\nexport type AuthLevel = \"public\" | \"authenticated\" | \"role\" | \"reauth\";\n\n/** API 메타데이터 */\nexport interface APIMeta {\n /** API 설명 (AI/Playground용) */\n description: string;\n /** 태그 (그룹화용) */\n tags?: string[];\n /** 인증 요구 수준 */\n auth?: AuthLevel;\n /** 필요 역할 (auth가 'role'일 때) */\n roles?: string[];\n /** 재인증 필요 여부 */\n reauth?: boolean;\n /** Rate limit (초당 요청 수) */\n rateLimit?: number;\n /** 비활성화 여부 */\n deprecated?: boolean;\n /** 예시 입력값 */\n exampleInput?: unknown;\n /** 예시 출력값 */\n exampleOutput?: unknown;\n}\n\n/** API 계약 정의 */\nexport interface APIContract<\n TInput extends ZodType = ZodType,\n TOutput extends ZodType = ZodType,\n> {\n /** 입력 스키마 */\n input: TInput;\n /** 출력 스키마 */\n output: TOutput;\n /** 메타데이터 */\n meta: APIMeta;\n /** 핸들러 함수 */\n handler: (\n input: z.infer<TInput>,\n ctx: HandlerContext,\n ) => Promise<z.infer<TOutput>>;\n}\n\n/** 모델 필드 정의 */\nexport interface ModelField {\n type: \"string\" | \"number\" | \"boolean\" | \"timestamp\" | \"array\" | \"map\" | \"reference\" | \"geopoint\";\n required?: boolean;\n description?: string;\n default?: unknown;\n /** 배열 아이템 타입 */\n items?: ModelField;\n /** 맵 값 타입 */\n values?: ModelField;\n /** 참조 대상 컬렉션 */\n ref?: string;\n /** enum 값 리스트 */\n enum?: string[];\n}\n\n/** 모델 정의 */\nexport interface ModelDefinition {\n /** 컬렉션 이름 */\n collection: string;\n /** 필드 정의 */\n fields: Record<string, ModelField>;\n /** 서브컬렉션 */\n subcollections?: Record<string, ModelDefinition>;\n /** 인덱스 */\n indexes?: ModelIndex[];\n /** 보안 규칙 */\n rules?: ModelRules;\n /** 타임스탬프 자동 생성 */\n timestamps?: boolean;\n /** 소프트 삭제 */\n softDelete?: boolean;\n}\n\n/** Firestore 인덱스 */\nexport interface ModelIndex {\n fields: Array<{ field: string; order?: \"asc\" | \"desc\" }>;\n}\n\n/** 모델 보안 규칙 */\nexport interface ModelRules {\n read?: AuthLevel;\n create?: AuthLevel;\n update?: AuthLevel;\n delete?: AuthLevel;\n readRoles?: string[];\n createRoles?: string[];\n updateRoles?: string[];\n deleteRoles?: string[];\n /** 소유자만 읽기/쓰기 가능 필드 */\n ownerField?: string;\n}\n\n// ─── Builders ────────────────────────────────────────────────────────\n\n/**\n * API 계약 정의\n *\n * @example\n * ```ts\n * export default defineAPI({\n * input: z.object({ name: z.string() }),\n * output: z.object({ id: z.string(), name: z.string() }),\n * meta: { description: \"상품 생성\", auth: \"authenticated\" },\n * handler: async (input, ctx) => {\n * const id = await db.create(\"products\", input);\n * return { id, ...input };\n * }\n * });\n * ```\n */\nexport function defineAPI<\n TInput extends ZodType,\n TOutput extends ZodType,\n>(contract: APIContract<TInput, TOutput>): APIContract<TInput, TOutput> {\n return contract;\n}\n\n/**\n * 모델(Firestore 컬렉션) 정의\n *\n * @example\n * ```ts\n * export const Product = defineModel({\n * collection: \"products\",\n * fields: {\n * name: { type: \"string\", required: true },\n * price: { type: \"number\", required: true },\n * tags: { type: \"array\", items: { type: \"string\" } },\n * },\n * timestamps: true,\n * rules: { read: \"public\", create: \"authenticated\" }\n * });\n * ```\n */\nexport function defineModel(definition: ModelDefinition): ModelDefinition {\n return {\n timestamps: true,\n ...definition,\n };\n}\n\n// ─── Schema Utilities ────────────────────────────────────────────────\n\n/** Zod 스키마에서 JSON Schema 생성 (Playground/문서용) */\nexport function zodToJsonSchema(schema: ZodType): Record<string, unknown> {\n return extractZodShape(schema);\n}\n\nfunction extractZodShape(schema: ZodType): Record<string, unknown> {\n const def = (schema as any)._def;\n\n if (!def) return { type: \"unknown\" };\n\n switch (def.typeName) {\n case \"ZodObject\": {\n const shape = (schema as ZodObject<ZodRawShape>).shape;\n const properties: Record<string, unknown> = {};\n const required: string[] = [];\n\n for (const [key, value] of Object.entries(shape)) {\n properties[key] = extractZodShape(value as ZodType);\n if (!(value as any).isOptional?.()) {\n const innerDef = (value as any)._def;\n if (innerDef?.typeName !== \"ZodOptional\" && innerDef?.typeName !== \"ZodDefault\") {\n required.push(key);\n }\n }\n }\n\n return { type: \"object\", properties, ...(required.length > 0 ? { required } : {}) };\n }\n case \"ZodString\":\n return { type: \"string\", ...(def.checks?.length ? extractStringChecks(def.checks) : {}) };\n case \"ZodNumber\":\n return { type: \"number\" };\n case \"ZodBoolean\":\n return { type: \"boolean\" };\n case \"ZodArray\":\n return { type: \"array\", items: extractZodShape(def.type) };\n case \"ZodEnum\":\n return { type: \"string\", enum: def.values };\n case \"ZodOptional\":\n return { ...extractZodShape(def.innerType), optional: true };\n case \"ZodDefault\":\n return { ...extractZodShape(def.innerType), default: def.defaultValue() };\n case \"ZodNullable\":\n return { ...extractZodShape(def.innerType), nullable: true };\n case \"ZodLiteral\":\n return { type: typeof def.value, const: def.value };\n case \"ZodUnion\":\n return { oneOf: def.options.map((o: ZodType) => extractZodShape(o)) };\n case \"ZodRecord\":\n return { type: \"object\", additionalProperties: extractZodShape(def.valueType) };\n case \"ZodDate\":\n return { type: \"string\", format: \"date-time\" };\n default:\n return { type: \"unknown\" };\n }\n}\n\nfunction extractStringChecks(checks: Array<{ kind: string; value?: unknown }>): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const check of checks) {\n switch (check.kind) {\n case \"min\": result.minLength = check.value; break;\n case \"max\": result.maxLength = check.value; break;\n case \"email\": result.format = \"email\"; break;\n case \"url\": result.format = \"uri\"; break;\n case \"uuid\": result.format = \"uuid\"; break;\n }\n }\n return result;\n}\n\n/** 모델 정의에서 Zod 스키마 자동 생성 */\nexport function modelToZodSchema(model: ModelDefinition): ZodObject<ZodRawShape> {\n const shape: ZodRawShape = {};\n\n for (const [key, field] of Object.entries(model.fields)) {\n let fieldSchema: ZodType = fieldToZod(field);\n if (!field.required) {\n fieldSchema = fieldSchema.optional();\n }\n shape[key] = fieldSchema;\n }\n\n if (model.timestamps) {\n shape.createdAt = z.string().datetime().optional();\n shape.updatedAt = z.string().datetime().optional();\n }\n\n if (model.softDelete) {\n shape.deletedAt = z.string().datetime().nullable().optional();\n }\n\n return z.object(shape);\n}\n\nfunction fieldToZod(field: ModelField): ZodType {\n switch (field.type) {\n case \"string\":\n if (field.enum) return z.enum(field.enum as [string, ...string[]]);\n return z.string();\n case \"number\":\n return z.number();\n case \"boolean\":\n return z.boolean();\n case \"timestamp\":\n return z.string().datetime();\n case \"array\":\n if (field.items) return z.array(fieldToZod(field.items));\n return z.array(z.unknown());\n case \"map\":\n if (field.values) return z.record(z.string(), fieldToZod(field.values));\n return z.record(z.string(), z.unknown());\n case \"reference\":\n return z.string(); // 참조는 문서 경로 문자열\n case \"geopoint\":\n return z.object({ latitude: z.number(), longitude: z.number() });\n default:\n return z.unknown();\n }\n}\n\n// ─── Manifest ────────────────────────────────────────────────────────\n\n/** API 매니페스트 항목 */\nexport interface ManifestEntry {\n path: string;\n method: \"POST\"; // Clawfire는 모두 POST\n meta: APIMeta;\n inputSchema: Record<string, unknown>;\n outputSchema: Record<string, unknown>;\n}\n\n/** 전체 매니페스트 */\nexport interface Manifest {\n version: string;\n generatedAt: string;\n apis: ManifestEntry[];\n models: Record<string, ModelDefinition>;\n}\n\n/** 계약에서 매니페스트 항목 생성 */\nexport function contractToManifest(\n path: string,\n contract: APIContract,\n): ManifestEntry {\n return {\n path,\n method: \"POST\",\n meta: contract.meta,\n inputSchema: zodToJsonSchema(contract.input),\n outputSchema: zodToJsonSchema(contract.output),\n };\n}\n","/**\n * Clawfire Route Discovery\n *\n * 파일 시스템에서 라우트 자동 발견 (빌드 타임 & 런타임)\n */\nimport { resolve, relative, join } from \"path\";\nimport { existsSync, readdirSync, statSync } from \"fs\";\n\nexport interface DiscoveredRoute {\n /** 파일 경로 (상대) */\n filePath: string;\n /** API 경로 (/products/list) */\n apiPath: string;\n /** 동적 파라미터 이름 */\n params: string[];\n}\n\n/**\n * routes 디렉터리에서 라우트 파일 자동 발견\n *\n * @param routesDir - routes 디렉터리 절대 경로\n * @returns 발견된 라우트 목록\n *\n * @example\n * ```\n * app/routes/products/list.ts → { apiPath: \"/products/list\", params: [] }\n * app/routes/products/[id]/get.ts → { apiPath: \"/products/:id/get\", params: [\"id\"] }\n * app/routes/health.ts → { apiPath: \"/health\", params: [] }\n * ```\n */\nexport function discoverRoutes(routesDir: string): DiscoveredRoute[] {\n if (!existsSync(routesDir)) {\n return [];\n }\n\n const routes: DiscoveredRoute[] = [];\n scanDirectory(routesDir, routesDir, routes);\n return routes.sort((a, b) => a.apiPath.localeCompare(b.apiPath));\n}\n\nfunction scanDirectory(baseDir: string, currentDir: string, routes: DiscoveredRoute[]): void {\n const entries = readdirSync(currentDir);\n\n for (const entry of entries) {\n const fullPath = join(currentDir, entry);\n const stat = statSync(fullPath);\n\n if (stat.isDirectory()) {\n // 숨김 디렉터리, node_modules 무시\n if (entry.startsWith(\".\") || entry === \"node_modules\") continue;\n scanDirectory(baseDir, fullPath, routes);\n } else if (stat.isFile()) {\n // .ts, .js 파일만\n if (!entry.endsWith(\".ts\") && !entry.endsWith(\".js\")) continue;\n // index, _로 시작하는 파일 무시\n if (entry.startsWith(\"_\")) continue;\n // .d.ts 무시\n if (entry.endsWith(\".d.ts\")) continue;\n\n const relativePath = relative(baseDir, fullPath);\n const route = filePathToRoute(relativePath);\n routes.push(route);\n }\n }\n}\n\nfunction filePathToRoute(filePath: string): DiscoveredRoute {\n const params: string[] = [];\n\n // 확장자 제거\n let routePath = filePath.replace(/\\.(ts|js)$/, \"\");\n\n // Windows 경로 → POSIX\n routePath = routePath.replace(/\\\\/g, \"/\");\n\n // index 파일은 디렉터리 자체\n if (routePath.endsWith(\"/index\") || routePath === \"index\") {\n routePath = routePath.replace(/\\/?index$/, \"\");\n }\n\n // [param] → :param 변환\n routePath = routePath.replace(/\\[([^\\]]+)\\]/g, (_, param) => {\n params.push(param);\n return `:${param}`;\n });\n\n // 앞에 / 추가\n const apiPath = `/${routePath}`;\n\n return {\n filePath,\n apiPath,\n params,\n };\n}\n\n/**\n * 라우트 파일에서 import하여 라우터에 등록하는 코드 생성 (빌드 타임)\n */\nexport function generateRouteImports(routes: DiscoveredRoute[], routesDir: string): string {\n const lines: string[] = [\n '// AUTO-GENERATED by Clawfire — DO NOT EDIT',\n '// This file is regenerated whenever routes change.',\n '',\n 'import { createRouter } from \"clawfire/functions\";',\n '',\n ];\n\n routes.forEach((route, i) => {\n const importPath = `./${route.filePath.replace(/\\.(ts|js)$/, \".js\")}`;\n lines.push(`import route_${i} from \"${importPath}\";`);\n });\n\n lines.push('');\n lines.push('export function registerAllRoutes(router: ReturnType<typeof createRouter>) {');\n\n routes.forEach((route, i) => {\n lines.push(` router.register(\"${route.apiPath}\", route_${i});`);\n });\n\n lines.push(' return router;');\n lines.push('}');\n\n return lines.join('\\n');\n}\n"],"mappings":"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AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,uBA8HO,KAAK,UAAU,UAAU,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAqMjD;;;AChVA,iBAAkE;;;ACDlE,kBAAwC;AACxC,gBAAkD;AAwB3C,SAAS,eAAe,WAAsC;AACnE,MAAI,KAAC,sBAAW,SAAS,GAAG;AAC1B,WAAO,CAAC;AAAA,EACV;AAEA,QAAM,SAA4B,CAAC;AACnC,gBAAc,WAAW,WAAW,MAAM;AAC1C,SAAO,OAAO,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,cAAc,EAAE,OAAO,CAAC;AACjE;AAEA,SAAS,cAAc,SAAiB,YAAoB,QAAiC;AAC3F,QAAM,cAAU,uBAAY,UAAU;AAEtC,aAAW,SAAS,SAAS;AAC3B,UAAM,eAAW,kBAAK,YAAY,KAAK;AACvC,UAAM,WAAO,oBAAS,QAAQ;AAE9B,QAAI,KAAK,YAAY,GAAG;AAEtB,UAAI,MAAM,WAAW,GAAG,KAAK,UAAU,eAAgB;AACvD,oBAAc,SAAS,UAAU,MAAM;AAAA,IACzC,WAAW,KAAK,OAAO,GAAG;AAExB,UAAI,CAAC,MAAM,SAAS,KAAK,KAAK,CAAC,MAAM,SAAS,KAAK,EAAG;AAEtD,UAAI,MAAM,WAAW,GAAG,EAAG;AAE3B,UAAI,MAAM,SAAS,OAAO,EAAG;AAE7B,YAAM,mBAAe,sBAAS,SAAS,QAAQ;AAC/C,YAAM,QAAQ,gBAAgB,YAAY;AAC1C,aAAO,KAAK,KAAK;AAAA,IACnB;AAAA,EACF;AACF;AAEA,SAAS,gBAAgB,UAAmC;AAC1D,QAAM,SAAmB,CAAC;AAG1B,MAAI,YAAY,SAAS,QAAQ,cAAc,EAAE;AAGjD,cAAY,UAAU,QAAQ,OAAO,GAAG;AAGxC,MAAI,UAAU,SAAS,QAAQ,KAAK,cAAc,SAAS;AACzD,gBAAY,UAAU,QAAQ,aAAa,EAAE;AAAA,EAC/C;AAGA,cAAY,UAAU,QAAQ,iBAAiB,CAAC,GAAG,UAAU;AAC3D,WAAO,KAAK,KAAK;AACjB,WAAO,IAAI,KAAK;AAAA,EAClB,CAAC;AAGD,QAAM,UAAU,IAAI,SAAS;AAE7B,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAKO,SAAS,qBAAqB,QAA2B,WAA2B;AACzF,QAAM,QAAkB;AAAA,IACtB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,SAAO,QAAQ,CAAC,OAAO,MAAM;AAC3B,UAAM,aAAa,KAAK,MAAM,SAAS,QAAQ,cAAc,KAAK,CAAC;AACnE,UAAM,KAAK,gBAAgB,CAAC,UAAU,UAAU,IAAI;AAAA,EACtD,CAAC;AAED,QAAM,KAAK,EAAE;AACb,QAAM,KAAK,8EAA8E;AAEzF,SAAO,QAAQ,CAAC,OAAO,MAAM;AAC3B,UAAM,KAAK,sBAAsB,MAAM,OAAO,YAAY,CAAC,IAAI;AAAA,EACjE,CAAC;AAED,QAAM,KAAK,kBAAkB;AAC7B,QAAM,KAAK,GAAG;AAEd,SAAO,MAAM,KAAK,IAAI;AACxB;","names":[]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { M as Manifest } from './schema-BJsictSV.cjs';
|
|
2
|
+
export { generatePlaygroundHtml } from './playground.cjs';
|
|
3
|
+
export { D as DiscoveredRoute, d as discoverRoutes, g as generateRouteImports } from './discover-DYNqz_ym.cjs';
|
|
4
|
+
import 'zod';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Clawfire Client Code Generator
|
|
8
|
+
*
|
|
9
|
+
* 라우트 계약에서 타입 안전한 api-client.ts 자동 생성
|
|
10
|
+
* api.products.list(), api.auth.login() 형태로 호출 가능
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 매니페스트에서 타입 안전한 API 클라이언트 코드 생성
|
|
15
|
+
*/
|
|
16
|
+
declare function generateClientCode(manifest: Manifest, options?: {
|
|
17
|
+
baseUrl?: string;
|
|
18
|
+
importPath?: string;
|
|
19
|
+
}): string;
|
|
20
|
+
/**
|
|
21
|
+
* 매니페스트에서 manifest.json 파일 내용 생성
|
|
22
|
+
*/
|
|
23
|
+
declare function generateManifestJson(manifest: Manifest): string;
|
|
24
|
+
|
|
25
|
+
export { generateClientCode, generateManifestJson };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { M as Manifest } from './schema-BJsictSV.js';
|
|
2
|
+
export { generatePlaygroundHtml } from './playground.js';
|
|
3
|
+
export { D as DiscoveredRoute, d as discoverRoutes, g as generateRouteImports } from './discover-DYNqz_ym.js';
|
|
4
|
+
import 'zod';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Clawfire Client Code Generator
|
|
8
|
+
*
|
|
9
|
+
* 라우트 계약에서 타입 안전한 api-client.ts 자동 생성
|
|
10
|
+
* api.products.list(), api.auth.login() 형태로 호출 가능
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 매니페스트에서 타입 안전한 API 클라이언트 코드 생성
|
|
15
|
+
*/
|
|
16
|
+
declare function generateClientCode(manifest: Manifest, options?: {
|
|
17
|
+
baseUrl?: string;
|
|
18
|
+
importPath?: string;
|
|
19
|
+
}): string;
|
|
20
|
+
/**
|
|
21
|
+
* 매니페스트에서 manifest.json 파일 내용 생성
|
|
22
|
+
*/
|
|
23
|
+
declare function generateManifestJson(manifest: Manifest): string;
|
|
24
|
+
|
|
25
|
+
export { generateClientCode, generateManifestJson };
|