opencode-agent-kit 1.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 +796 -0
- package/bin/commands/init.mjs +221 -0
- package/bin/init.mjs +21 -0
- package/package.json +22 -0
- package/template/.opencode/agent-docs/backend/README.md +0 -0
- package/template/.opencode/agent-docs/backend/node/BACKEND_PATTERNS.md +82 -0
- package/template/.opencode/agent-docs/backend/node/BACKEND_QUICK_START.md +49 -0
- package/template/.opencode/agent-docs/frontend/next/README.md +0 -0
- package/template/.opencode/agent-docs/frontend/nuxt/API_PATTERNS.md +807 -0
- package/template/.opencode/agent-docs/frontend/nuxt/CHEATSHEET.md +676 -0
- package/template/.opencode/agent-docs/frontend/nuxt/COMPLETION_REPORT.md +613 -0
- package/template/.opencode/agent-docs/frontend/nuxt/EXAMPLES.md +956 -0
- package/template/.opencode/agent-docs/frontend/nuxt/INDEX.md +596 -0
- package/template/.opencode/agent-docs/frontend/nuxt/MCP_GUIDE.md +881 -0
- package/template/.opencode/agent-docs/frontend/nuxt/MENTOR_CURRICULUM_30_DAYS.md +256 -0
- package/template/.opencode/agent-docs/frontend/nuxt/MENTOR_CURRICULUM_CHECKLIST.md +156 -0
- package/template/.opencode/agent-docs/frontend/nuxt/MENTOR_WEEKLY_ASSIGNMENTS.md +191 -0
- package/template/.opencode/agent-docs/frontend/nuxt/QUICK_START.md +509 -0
- package/template/.opencode/agent-docs/frontend/nuxt/README.md +506 -0
- package/template/.opencode/agent-docs/frontend/nuxt/README_AGENTS.md +140 -0
- package/template/.opencode/agent-docs/frontend/nuxt/README_DOCS.md +65 -0
- package/template/.opencode/agent-docs/frontend/nuxt/SUMMARY.md +474 -0
- package/template/.opencode/agent-docs/frontend/nuxt/TEAM_OPERATING_GUIDE.md +54 -0
- package/template/.opencode/agent-docs/frontend/nuxt/TESTING_GUIDE.md +904 -0
- package/template/.opencode/agent-docs/frontend/nuxt/WORKFLOWS.md +758 -0
- package/template/.opencode/agent-docs/frontend/react/API_PATTERNS.md +187 -0
- package/template/.opencode/agent-docs/frontend/react/CHEATSHEET.md +87 -0
- package/template/.opencode/agent-docs/frontend/react/INDEX.md +45 -0
- package/template/.opencode/agent-docs/frontend/react/QUICK_START.md +43 -0
- package/template/.opencode/agent-docs/frontend/react/README.md +159 -0
- package/template/.opencode/agent-docs/frontend/vue/README.md +0 -0
- package/template/.opencode/agent-docs/mobile/android/README.md +45 -0
- package/template/.opencode/agent-docs/mobile/flutter/README.md +44 -0
- package/template/.opencode/agents/android-developer.md +418 -0
- package/template/.opencode/agents/code-igniter-3-fullstack.md +345 -0
- package/template/.opencode/agents/code-reviewer.md +517 -0
- package/template/.opencode/agents/database-specialist.md +455 -0
- package/template/.opencode/agents/devops-specialist.md +562 -0
- package/template/.opencode/agents/flutter-developer.md +556 -0
- package/template/.opencode/agents/it-leader.md +911 -0
- package/template/.opencode/agents/laravel-advanced.md +691 -0
- package/template/.opencode/agents/node-backend-developer.md +343 -0
- package/template/.opencode/agents/nuxt-frontend-developer-mentor.md +402 -0
- package/template/.opencode/agents/nuxt-frontend-developer.md +1573 -0
- package/template/.opencode/agents/react-frontend-developer.md +1017 -0
- package/template/.opencode/agents/seo-specialist.md +681 -0
- package/template/.opencode/agents/ui-ux-designer.md +783 -0
- package/template/.opencode/commands/android-build/command.md +25 -0
- package/template/.opencode/commands/android-test/command.md +23 -0
- package/template/.opencode/commands/build-fix.md +29 -0
- package/template/.opencode/commands/checkpoint.md +74 -0
- package/template/.opencode/commands/code-review.md +40 -0
- package/template/.opencode/commands/e2e.md +363 -0
- package/template/.opencode/commands/eval.md +120 -0
- package/template/.opencode/commands/evolve.md +193 -0
- package/template/.opencode/commands/flutter-build/command.md +25 -0
- package/template/.opencode/commands/flutter-test/command.md +24 -0
- package/template/.opencode/commands/go-build.md +183 -0
- package/template/.opencode/commands/go-review.md +148 -0
- package/template/.opencode/commands/go-test.md +268 -0
- package/template/.opencode/commands/gpc-release/command.md +30 -0
- package/template/.opencode/commands/instinct-export.md +91 -0
- package/template/.opencode/commands/instinct-import.md +142 -0
- package/template/.opencode/commands/instinct-status.md +86 -0
- package/template/.opencode/commands/learn.md +70 -0
- package/template/.opencode/commands/multi-backend.md +158 -0
- package/template/.opencode/commands/multi-execute.md +310 -0
- package/template/.opencode/commands/multi-frontend.md +158 -0
- package/template/.opencode/commands/multi-plan.md +261 -0
- package/template/.opencode/commands/multi-workflow.md +183 -0
- package/template/.opencode/commands/orchestrate.md +172 -0
- package/template/.opencode/commands/plan.md +113 -0
- package/template/.opencode/commands/pm2.md +271 -0
- package/template/.opencode/commands/python-review.md +297 -0
- package/template/.opencode/commands/refactor-clean.md +28 -0
- package/template/.opencode/commands/sessions.md +305 -0
- package/template/.opencode/commands/setup-pm.md +80 -0
- package/template/.opencode/commands/skill-create.md +174 -0
- package/template/.opencode/commands/tdd.md +326 -0
- package/template/.opencode/commands/test-coverage.md +27 -0
- package/template/.opencode/commands/update-codemaps.md +17 -0
- package/template/.opencode/commands/update-docs.md +31 -0
- package/template/.opencode/commands/verify.md +59 -0
- package/template/.opencode/config.example.json +309 -0
- package/template/.opencode/config.json +341 -0
- package/template/.opencode/contexts/dev.md +20 -0
- package/template/.opencode/contexts/research.md +26 -0
- package/template/.opencode/contexts/review.md +22 -0
- package/template/.opencode/hooks/hooks.json +169 -0
- package/template/.opencode/instructions/INSTRUCTIONS.md +388 -0
- package/template/.opencode/package.json +5 -0
- package/template/.opencode/rules/README.md +82 -0
- package/template/.opencode/rules/android/gradle.md +62 -0
- package/template/.opencode/rules/android/testing.md +27 -0
- package/template/.opencode/rules/common/agents.md +49 -0
- package/template/.opencode/rules/common/coding-style.md +48 -0
- package/template/.opencode/rules/common/git-workflow.md +45 -0
- package/template/.opencode/rules/common/hooks.md +30 -0
- package/template/.opencode/rules/common/patterns.md +31 -0
- package/template/.opencode/rules/common/performance.md +55 -0
- package/template/.opencode/rules/common/security.md +29 -0
- package/template/.opencode/rules/common/testing.md +29 -0
- package/template/.opencode/rules/flutter/state-management.md +57 -0
- package/template/.opencode/rules/flutter/testing.md +42 -0
- package/template/.opencode/rules/golang/coding-style.md +26 -0
- package/template/.opencode/rules/golang/hooks.md +11 -0
- package/template/.opencode/rules/golang/patterns.md +39 -0
- package/template/.opencode/rules/golang/security.md +28 -0
- package/template/.opencode/rules/golang/testing.md +25 -0
- package/template/.opencode/rules/mobile/performance.md +36 -0
- package/template/.opencode/rules/python/coding-style.md +37 -0
- package/template/.opencode/rules/python/hooks.md +14 -0
- package/template/.opencode/rules/python/patterns.md +34 -0
- package/template/.opencode/rules/python/security.md +25 -0
- package/template/.opencode/rules/python/testing.md +33 -0
- package/template/.opencode/rules/typescript/coding-style.md +58 -0
- package/template/.opencode/rules/typescript/hooks.md +15 -0
- package/template/.opencode/rules/typescript/patterns.md +45 -0
- package/template/.opencode/rules/typescript/security.md +21 -0
- package/template/.opencode/rules/typescript/testing.md +11 -0
- package/template/.opencode/skills/api-documentation/SKILL.md +188 -0
- package/template/.opencode/skills/backend-patterns/SKILL.md +587 -0
- package/template/.opencode/skills/building-components/SKILL.md +37 -0
- package/template/.opencode/skills/building-components/references/accessibility.mdx +819 -0
- package/template/.opencode/skills/building-components/references/as-child.mdx +324 -0
- package/template/.opencode/skills/building-components/references/composition.mdx +239 -0
- package/template/.opencode/skills/building-components/references/data-attributes.mdx +413 -0
- package/template/.opencode/skills/building-components/references/definitions.mdx +258 -0
- package/template/.opencode/skills/building-components/references/design-tokens.mdx +57 -0
- package/template/.opencode/skills/building-components/references/docs.mdx +155 -0
- package/template/.opencode/skills/building-components/references/marketplaces.mdx +144 -0
- package/template/.opencode/skills/building-components/references/npm.mdx +166 -0
- package/template/.opencode/skills/building-components/references/polymorphism.mdx +583 -0
- package/template/.opencode/skills/building-components/references/principles.mdx +61 -0
- package/template/.opencode/skills/building-components/references/registry.mdx +169 -0
- package/template/.opencode/skills/building-components/references/state.mdx +99 -0
- package/template/.opencode/skills/building-components/references/styling.mdx +286 -0
- package/template/.opencode/skills/building-components/references/types.mdx +191 -0
- package/template/.opencode/skills/clickhouse-io/SKILL.md +429 -0
- package/template/.opencode/skills/coding-standards/SKILL.md +520 -0
- package/template/.opencode/skills/configure-ecc/SKILL.md +298 -0
- package/template/.opencode/skills/continuous-learning/SKILL.md +110 -0
- package/template/.opencode/skills/continuous-learning/config.json +18 -0
- package/template/.opencode/skills/continuous-learning/evaluate-session.sh +60 -0
- package/template/.opencode/skills/continuous-learning-v2/SKILL.md +284 -0
- package/template/.opencode/skills/continuous-learning-v2/agents/observer.md +137 -0
- package/template/.opencode/skills/continuous-learning-v2/agents/start-observer.sh +134 -0
- package/template/.opencode/skills/continuous-learning-v2/config.json +41 -0
- package/template/.opencode/skills/continuous-learning-v2/hooks/observe.sh +153 -0
- package/template/.opencode/skills/continuous-learning-v2/scripts/instinct-cli.py +489 -0
- package/template/.opencode/skills/continuous-learning-v2/scripts/test_parse_instinct.py +82 -0
- package/template/.opencode/skills/dart-add-unit-test/SKILL.md +122 -0
- package/template/.opencode/skills/dart-build-cli-app/SKILL.md +185 -0
- package/template/.opencode/skills/dart-collect-coverage/SKILL.md +141 -0
- package/template/.opencode/skills/dart-fix-runtime-errors/SKILL.md +166 -0
- package/template/.opencode/skills/dart-generate-test-mocks/SKILL.md +155 -0
- package/template/.opencode/skills/dart-migrate-to-checks-package/SKILL.md +126 -0
- package/template/.opencode/skills/dart-resolve-package-conflicts/SKILL.md +116 -0
- package/template/.opencode/skills/dart-run-static-analysis/SKILL.md +104 -0
- package/template/.opencode/skills/dart-use-pattern-matching/SKILL.md +146 -0
- package/template/.opencode/skills/django-patterns/SKILL.md +733 -0
- package/template/.opencode/skills/django-security/SKILL.md +592 -0
- package/template/.opencode/skills/django-tdd/SKILL.md +728 -0
- package/template/.opencode/skills/django-verification/SKILL.md +460 -0
- package/template/.opencode/skills/eval-harness/SKILL.md +227 -0
- package/template/.opencode/skills/firebase-basics/SKILL.md +103 -0
- package/template/.opencode/skills/firebase-basics/references/additional-skills.md +113 -0
- package/template/.opencode/skills/firebase-basics/references/cli-usage.md +31 -0
- package/template/.opencode/skills/firebase-basics/references/client-library-usage.md +45 -0
- package/template/.opencode/skills/firebase-basics/references/core-concepts.md +61 -0
- package/template/.opencode/skills/firebase-basics/references/iac-usage.md +40 -0
- package/template/.opencode/skills/firebase-basics/references/iam-security.md +74 -0
- package/template/.opencode/skills/firebase-basics/references/mcp-usage.md +63 -0
- package/template/.opencode/skills/flutter/SKILL.md +292 -0
- package/template/.opencode/skills/flutter-add-integration-test/SKILL.md +163 -0
- package/template/.opencode/skills/flutter-add-widget-preview/SKILL.md +145 -0
- package/template/.opencode/skills/flutter-add-widget-test/SKILL.md +154 -0
- package/template/.opencode/skills/flutter-apply-architecture-best-practices/SKILL.md +162 -0
- package/template/.opencode/skills/flutter-build-responsive-layout/SKILL.md +139 -0
- package/template/.opencode/skills/flutter-fix-layout-issues/SKILL.md +130 -0
- package/template/.opencode/skills/flutter-implement-json-serialization/SKILL.md +153 -0
- package/template/.opencode/skills/flutter-setup-declarative-routing/SKILL.md +255 -0
- package/template/.opencode/skills/flutter-setup-localization/SKILL.md +210 -0
- package/template/.opencode/skills/flutter-use-http-package/SKILL.md +174 -0
- package/template/.opencode/skills/frontend-design/SKILL.md +89 -0
- package/template/.opencode/skills/frontend-patterns/SKILL.md +631 -0
- package/template/.opencode/skills/golang-patterns/SKILL.md +673 -0
- package/template/.opencode/skills/golang-testing/SKILL.md +719 -0
- package/template/.opencode/skills/impeccable/SKILL.md +165 -0
- package/template/.opencode/skills/impeccable/agents/impeccable-asset-producer.md +101 -0
- package/template/.opencode/skills/impeccable/reference/adapt.md +190 -0
- package/template/.opencode/skills/impeccable/reference/animate.md +175 -0
- package/template/.opencode/skills/impeccable/reference/audit.md +133 -0
- package/template/.opencode/skills/impeccable/reference/bolder.md +113 -0
- package/template/.opencode/skills/impeccable/reference/brand.md +118 -0
- package/template/.opencode/skills/impeccable/reference/clarify.md +174 -0
- package/template/.opencode/skills/impeccable/reference/codex.md +105 -0
- package/template/.opencode/skills/impeccable/reference/cognitive-load.md +106 -0
- package/template/.opencode/skills/impeccable/reference/color-and-contrast.md +105 -0
- package/template/.opencode/skills/impeccable/reference/colorize.md +154 -0
- package/template/.opencode/skills/impeccable/reference/craft.md +123 -0
- package/template/.opencode/skills/impeccable/reference/critique.md +273 -0
- package/template/.opencode/skills/impeccable/reference/delight.md +302 -0
- package/template/.opencode/skills/impeccable/reference/distill.md +111 -0
- package/template/.opencode/skills/impeccable/reference/document.md +427 -0
- package/template/.opencode/skills/impeccable/reference/extract.md +69 -0
- package/template/.opencode/skills/impeccable/reference/harden.md +347 -0
- package/template/.opencode/skills/impeccable/reference/heuristics-scoring.md +234 -0
- package/template/.opencode/skills/impeccable/reference/interaction-design.md +195 -0
- package/template/.opencode/skills/impeccable/reference/layout.md +141 -0
- package/template/.opencode/skills/impeccable/reference/live.md +622 -0
- package/template/.opencode/skills/impeccable/reference/motion-design.md +109 -0
- package/template/.opencode/skills/impeccable/reference/onboard.md +234 -0
- package/template/.opencode/skills/impeccable/reference/optimize.md +258 -0
- package/template/.opencode/skills/impeccable/reference/overdrive.md +130 -0
- package/template/.opencode/skills/impeccable/reference/personas.md +179 -0
- package/template/.opencode/skills/impeccable/reference/polish.md +242 -0
- package/template/.opencode/skills/impeccable/reference/product.md +62 -0
- package/template/.opencode/skills/impeccable/reference/quieter.md +99 -0
- package/template/.opencode/skills/impeccable/reference/responsive-design.md +114 -0
- package/template/.opencode/skills/impeccable/reference/shape.md +165 -0
- package/template/.opencode/skills/impeccable/reference/spatial-design.md +100 -0
- package/template/.opencode/skills/impeccable/reference/teach.md +156 -0
- package/template/.opencode/skills/impeccable/reference/typeset.md +124 -0
- package/template/.opencode/skills/impeccable/reference/typography.md +159 -0
- package/template/.opencode/skills/impeccable/reference/ux-writing.md +107 -0
- package/template/.opencode/skills/impeccable/scripts/cleanup-deprecated.mjs +284 -0
- package/template/.opencode/skills/impeccable/scripts/command-metadata.json +94 -0
- package/template/.opencode/skills/impeccable/scripts/critique-storage.mjs +242 -0
- package/template/.opencode/skills/impeccable/scripts/design-parser.mjs +820 -0
- package/template/.opencode/skills/impeccable/scripts/detect-csp.mjs +198 -0
- package/template/.opencode/skills/impeccable/scripts/detect.mjs +21 -0
- package/template/.opencode/skills/impeccable/scripts/impeccable-paths.mjs +110 -0
- package/template/.opencode/skills/impeccable/scripts/is-generated.mjs +69 -0
- package/template/.opencode/skills/impeccable/scripts/live-accept.mjs +595 -0
- package/template/.opencode/skills/impeccable/scripts/live-browser-session.js +123 -0
- package/template/.opencode/skills/impeccable/scripts/live-browser.js +4860 -0
- package/template/.opencode/skills/impeccable/scripts/live-complete.mjs +75 -0
- package/template/.opencode/skills/impeccable/scripts/live-completion.mjs +18 -0
- package/template/.opencode/skills/impeccable/scripts/live-inject.mjs +446 -0
- package/template/.opencode/skills/impeccable/scripts/live-poll.mjs +200 -0
- package/template/.opencode/skills/impeccable/scripts/live-resume.mjs +48 -0
- package/template/.opencode/skills/impeccable/scripts/live-server.mjs +838 -0
- package/template/.opencode/skills/impeccable/scripts/live-session-store.mjs +254 -0
- package/template/.opencode/skills/impeccable/scripts/live-status.mjs +47 -0
- package/template/.opencode/skills/impeccable/scripts/live-wrap.mjs +632 -0
- package/template/.opencode/skills/impeccable/scripts/live.mjs +247 -0
- package/template/.opencode/skills/impeccable/scripts/load-context.mjs +141 -0
- package/template/.opencode/skills/impeccable/scripts/modern-screenshot.umd.js +14 -0
- package/template/.opencode/skills/impeccable/scripts/pin.mjs +214 -0
- package/template/.opencode/skills/iterative-retrieval/SKILL.md +202 -0
- package/template/.opencode/skills/java-coding-standards/SKILL.md +138 -0
- package/template/.opencode/skills/jetpack-compose/.skillfish.json +10 -0
- package/template/.opencode/skills/jetpack-compose/SKILL.md +420 -0
- package/template/.opencode/skills/jpa-patterns/SKILL.md +141 -0
- package/template/.opencode/skills/nutrient-document-processing/SKILL.md +165 -0
- package/template/.opencode/skills/nuxt-ui/SKILL.md +334 -0
- package/template/.opencode/skills/nuxt-ui/references/components.md +377 -0
- package/template/.opencode/skills/nuxt-ui/references/composables.md +127 -0
- package/template/.opencode/skills/nuxt-ui/references/layouts/chat.md +266 -0
- package/template/.opencode/skills/nuxt-ui/references/layouts/dashboard.md +220 -0
- package/template/.opencode/skills/nuxt-ui/references/layouts/docs.md +141 -0
- package/template/.opencode/skills/nuxt-ui/references/layouts/editor.md +168 -0
- package/template/.opencode/skills/nuxt-ui/references/layouts/page.md +260 -0
- package/template/.opencode/skills/nuxt-ui/references/theming.md +427 -0
- package/template/.opencode/skills/postgres-patterns/SKILL.md +146 -0
- package/template/.opencode/skills/project-guidelines-example/SKILL.md +345 -0
- package/template/.opencode/skills/python-patterns/SKILL.md +749 -0
- package/template/.opencode/skills/python-testing/SKILL.md +815 -0
- package/template/.opencode/skills/security-review/SKILL.md +494 -0
- package/template/.opencode/skills/security-review/cloud-infrastructure-security.md +361 -0
- package/template/.opencode/skills/shadcn-ui/README.md +248 -0
- package/template/.opencode/skills/shadcn-ui/SKILL.md +326 -0
- package/template/.opencode/skills/shadcn-ui/examples/auth-layout.tsx +177 -0
- package/template/.opencode/skills/shadcn-ui/examples/data-table.tsx +313 -0
- package/template/.opencode/skills/shadcn-ui/examples/form-pattern.tsx +177 -0
- package/template/.opencode/skills/shadcn-ui/resources/component-catalog.md +481 -0
- package/template/.opencode/skills/shadcn-ui/resources/customization-guide.md +516 -0
- package/template/.opencode/skills/shadcn-ui/resources/migration-guide.md +463 -0
- package/template/.opencode/skills/shadcn-ui/resources/setup-guide.md +412 -0
- package/template/.opencode/skills/shadcn-ui/scripts/verify-setup.sh +134 -0
- package/template/.opencode/skills/springboot-patterns/SKILL.md +304 -0
- package/template/.opencode/skills/springboot-security/SKILL.md +119 -0
- package/template/.opencode/skills/springboot-tdd/SKILL.md +157 -0
- package/template/.opencode/skills/springboot-verification/SKILL.md +100 -0
- package/template/.opencode/skills/strategic-compact/SKILL.md +63 -0
- package/template/.opencode/skills/strategic-compact/suggest-compact.sh +52 -0
- package/template/.opencode/skills/tdd-workflow/SKILL.md +409 -0
- package/template/.opencode/skills/vercel-composition-patterns/AGENTS.md +946 -0
- package/template/.opencode/skills/vercel-composition-patterns/SKILL.md +89 -0
- package/template/.opencode/skills/vercel-composition-patterns/rules/architecture-avoid-boolean-props.md +100 -0
- package/template/.opencode/skills/vercel-composition-patterns/rules/architecture-compound-components.md +112 -0
- package/template/.opencode/skills/vercel-composition-patterns/rules/patterns-children-over-render-props.md +87 -0
- package/template/.opencode/skills/vercel-composition-patterns/rules/patterns-explicit-variants.md +100 -0
- package/template/.opencode/skills/vercel-composition-patterns/rules/react19-no-forwardref.md +42 -0
- package/template/.opencode/skills/vercel-composition-patterns/rules/state-context-interface.md +191 -0
- package/template/.opencode/skills/vercel-composition-patterns/rules/state-decouple-implementation.md +113 -0
- package/template/.opencode/skills/vercel-composition-patterns/rules/state-lift-state.md +125 -0
- package/template/.opencode/skills/vercel-react-best-practices/AGENTS.md +2934 -0
- package/template/.opencode/skills/vercel-react-best-practices/SKILL.md +136 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md +55 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/advanced-init-once.md +42 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/advanced-use-latest.md +39 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/async-api-routes.md +38 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/async-defer-await.md +80 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/async-dependencies.md +51 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/async-parallel.md +28 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md +99 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md +59 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/bundle-conditional.md +31 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md +49 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md +35 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/bundle-preload.md +50 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/client-event-listeners.md +74 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/client-localstorage-schema.md +71 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md +48 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/client-swr-dedup.md +56 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/js-batch-dom-css.md +107 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/js-cache-function-results.md +80 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/js-cache-property-access.md +28 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/js-cache-storage.md +70 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/js-combine-iterations.md +32 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/js-early-exit.md +50 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/js-hoist-regexp.md +45 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/js-index-maps.md +37 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/js-length-check-first.md +49 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/js-min-max-loop.md +82 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/js-set-map-lookups.md +24 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md +57 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/rendering-activity.md +26 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/rendering-conditional-render.md +40 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/rendering-content-visibility.md +38 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md +46 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/rendering-hydration-suppress-warning.md +30 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/rendering-svg-precision.md +28 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/rendering-usetransition-loading.md +75 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/rerender-defer-reads.md +39 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/rerender-dependencies.md +45 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/rerender-derived-state-no-effect.md +40 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/rerender-derived-state.md +29 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md +74 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md +58 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/rerender-memo-with-default-value.md +38 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/rerender-memo.md +44 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/rerender-move-effect-to-event.md +45 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/rerender-simple-expression-in-memo.md +35 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/rerender-transitions.md +40 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/rerender-use-ref-transient-values.md +73 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/server-after-nonblocking.md +73 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/server-auth-actions.md +96 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/server-cache-lru.md +41 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/server-cache-react.md +76 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/server-dedup-props.md +65 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/server-parallel-fetching.md +83 -0
- package/template/.opencode/skills/vercel-react-best-practices/rules/server-serialization.md +38 -0
- package/template/.opencode/skills/verification-loop/SKILL.md +120 -0
- package/template/.opencode/skills/web-design-guidelines/SKILL.md +39 -0
- package/template/AGENTS.md +32 -0
- package/template/opencode.json +354 -0
|
@@ -0,0 +1,807 @@
|
|
|
1
|
+
# API Patterns Guide - useApi Composable
|
|
2
|
+
|
|
3
|
+
Comprehensive guide untuk menggunakan custom `useApi` composable dalam project ini.
|
|
4
|
+
|
|
5
|
+
## 📋 Daftar Isi
|
|
6
|
+
|
|
7
|
+
1. [Overview](#overview)
|
|
8
|
+
2. [Why useApi?](#why-useapi)
|
|
9
|
+
3. [Basic Usage](#basic-usage)
|
|
10
|
+
4. [Advanced Patterns](#advanced-patterns)
|
|
11
|
+
5. [Error Handling](#error-handling)
|
|
12
|
+
6. [Authentication](#authentication)
|
|
13
|
+
7. [Best Practices](#best-practices)
|
|
14
|
+
8. [Migration Guide](#migration-guide)
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Overview
|
|
19
|
+
|
|
20
|
+
`useApi` adalah custom composable yang menyatukan `useFetch` dan `$fetch` dengan satu interface yang konsisten, plus automatic authentication dan enhanced error handling.
|
|
21
|
+
|
|
22
|
+
**Location:** `app/composables/useApi.ts`
|
|
23
|
+
|
|
24
|
+
### Key Features
|
|
25
|
+
|
|
26
|
+
- ✅ **Unified API** - Single interface untuk GET, POST, PUT, DELETE
|
|
27
|
+
- ✅ **Smart SSR/CSR** - Otomatis pakai `useFetch` di SSR, `$fetch` di CSR
|
|
28
|
+
- ✅ **Auto Authentication** - Cookies forwarded otomatis
|
|
29
|
+
- ✅ **Enhanced Errors** - `error.value.message` langsung accessible
|
|
30
|
+
- ✅ **Auto Redirect** - 401 otomatis redirect ke login
|
|
31
|
+
- ✅ **Type Safe** - Full TypeScript support dengan generics
|
|
32
|
+
- ✅ **Promise-like** - Bisa di-await seperti Promise
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Why useApi?
|
|
37
|
+
|
|
38
|
+
### Problem dengan Native Composables
|
|
39
|
+
|
|
40
|
+
**useFetch:**
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
// ❌ Auth tidak automatic
|
|
44
|
+
const { data } = await useFetch('/api/users', {
|
|
45
|
+
headers: useRequestHeaders(['cookie']), // Manual!
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
// ❌ Error message susah diakses
|
|
49
|
+
if (error.value) {
|
|
50
|
+
// error.value.data?.message? error.value.message? Who knows!
|
|
51
|
+
console.log(error.value?.data?.message)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ❌ POST/PUT/DELETE butuh useAsyncData
|
|
55
|
+
const { data } = await useAsyncData('create-user', () =>
|
|
56
|
+
$fetch('/api/users', { method: 'POST', body: formData })
|
|
57
|
+
)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**$fetch:**
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
// ❌ Tidak bisa di-await di component root
|
|
64
|
+
const data = await $fetch('/api/users') // Hydration error!
|
|
65
|
+
|
|
66
|
+
// ❌ No reactive state
|
|
67
|
+
const loading = ref(false)
|
|
68
|
+
const error = ref(null)
|
|
69
|
+
// Manual state management diperlukan
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Solution dengan useApi
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
// ✅ Simple, automatic, type-safe
|
|
76
|
+
const { data, pending, error } = await useApi<User[]>('/users')
|
|
77
|
+
|
|
78
|
+
// ✅ Works untuk semua HTTP methods
|
|
79
|
+
const { data: created } = await useApi('/users', {
|
|
80
|
+
method: 'POST',
|
|
81
|
+
body: newUser,
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
// ✅ Error message langsung accessible
|
|
85
|
+
if (error.value) {
|
|
86
|
+
console.log(error.value.message) // Consistent!
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Basic Usage
|
|
93
|
+
|
|
94
|
+
### GET Request
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
<script setup lang="ts">
|
|
98
|
+
interface User {
|
|
99
|
+
id: string
|
|
100
|
+
name: string
|
|
101
|
+
email: string
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Simple GET
|
|
105
|
+
const { data, pending, error } = await useApi<User[]>('/users')
|
|
106
|
+
|
|
107
|
+
// With query parameters
|
|
108
|
+
const { data: filteredUsers } = await useApi<User[]>('/users', {
|
|
109
|
+
query: {
|
|
110
|
+
role: 'admin',
|
|
111
|
+
page: 1,
|
|
112
|
+
limit: 10
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
// Reactive query
|
|
117
|
+
const search = ref('')
|
|
118
|
+
const { data: searchResults } = await useApi<User[]>('/users', {
|
|
119
|
+
query: { q: search },
|
|
120
|
+
watch: [search] // Refetch when search changes
|
|
121
|
+
})
|
|
122
|
+
</script>
|
|
123
|
+
|
|
124
|
+
<template>
|
|
125
|
+
<div v-if="pending">Loading...</div>
|
|
126
|
+
<div v-else-if="error">{{ error.message }}</div>
|
|
127
|
+
<ul v-else>
|
|
128
|
+
<li v-for="user in data?.data" :key="user.id">
|
|
129
|
+
{{ user.name }}
|
|
130
|
+
</li>
|
|
131
|
+
</ul>
|
|
132
|
+
</template>
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### POST Request
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
<script setup lang="ts">
|
|
139
|
+
interface CreateUserDto {
|
|
140
|
+
name: string
|
|
141
|
+
email: string
|
|
142
|
+
password: string
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
interface User {
|
|
146
|
+
id: string
|
|
147
|
+
name: string
|
|
148
|
+
email: string
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const form = reactive<CreateUserDto>({
|
|
152
|
+
name: '',
|
|
153
|
+
email: '',
|
|
154
|
+
password: ''
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
const createUser = async () => {
|
|
158
|
+
const { data, error } = await useApi<User>('/users', {
|
|
159
|
+
method: 'POST',
|
|
160
|
+
body: form
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
if (error.value) {
|
|
164
|
+
useToast().add({
|
|
165
|
+
title: 'Error',
|
|
166
|
+
description: error.value.message,
|
|
167
|
+
color: 'red'
|
|
168
|
+
})
|
|
169
|
+
return
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
useToast().add({
|
|
173
|
+
title: 'Success',
|
|
174
|
+
description: `User ${data.value?.data.name} created!`
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
navigateTo(`/users/${data.value?.data.id}`)
|
|
178
|
+
}
|
|
179
|
+
</script>
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### PUT/PATCH Request
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
const updateUser = async (userId: string) => {
|
|
186
|
+
const { data, error } = await useApi<User>(`/users/${userId}`, {
|
|
187
|
+
method: 'PATCH',
|
|
188
|
+
body: {
|
|
189
|
+
name: 'Updated Name',
|
|
190
|
+
email: 'updated@example.com',
|
|
191
|
+
},
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
if (!error.value) {
|
|
195
|
+
console.log('Updated:', data.value?.data)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### DELETE Request
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
const deleteUser = async (userId: string) => {
|
|
204
|
+
const confirmed = confirm('Are you sure?')
|
|
205
|
+
if (!confirmed) return
|
|
206
|
+
|
|
207
|
+
const { error } = await useApi(`/users/${userId}`, {
|
|
208
|
+
method: 'DELETE',
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
if (error.value) {
|
|
212
|
+
alert(error.value.message)
|
|
213
|
+
} else {
|
|
214
|
+
// Refresh list atau remove dari UI
|
|
215
|
+
users.value = users.value.filter((u) => u.id !== userId)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## Advanced Patterns
|
|
223
|
+
|
|
224
|
+
### Manual Trigger (immediate: false)
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
<script setup lang="ts">
|
|
228
|
+
// Don't execute immediately
|
|
229
|
+
const { data, execute, pending } = await useApi<User>('/users/123', {
|
|
230
|
+
immediate: false
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
const loadUser = async () => {
|
|
234
|
+
await execute()
|
|
235
|
+
// data.value now populated
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Or use in onMounted
|
|
239
|
+
onMounted(() => {
|
|
240
|
+
loadUser()
|
|
241
|
+
})
|
|
242
|
+
</script>
|
|
243
|
+
|
|
244
|
+
<template>
|
|
245
|
+
<button @click="loadUser" :disabled="pending">
|
|
246
|
+
Load User
|
|
247
|
+
</button>
|
|
248
|
+
</template>
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Optimistic Updates
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
const deleteUser = async (userId: string) => {
|
|
255
|
+
// Save original state
|
|
256
|
+
const originalUsers = [...users.value]
|
|
257
|
+
|
|
258
|
+
// Optimistic update
|
|
259
|
+
users.value = users.value.filter((u) => u.id !== userId)
|
|
260
|
+
|
|
261
|
+
// Perform API call
|
|
262
|
+
const { error } = await useApi(`/users/${userId}`, {
|
|
263
|
+
method: 'DELETE',
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
if (error.value) {
|
|
267
|
+
// Rollback on error
|
|
268
|
+
users.value = originalUsers
|
|
269
|
+
useToast().add({
|
|
270
|
+
title: 'Error',
|
|
271
|
+
description: error.value.message,
|
|
272
|
+
color: 'red',
|
|
273
|
+
})
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### Refresh & Refetch
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
const { data, refresh, execute } = await useApi<User[]>('/users')
|
|
282
|
+
|
|
283
|
+
// Refresh (same parameters)
|
|
284
|
+
const reloadUsers = async () => {
|
|
285
|
+
await refresh()
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Execute again
|
|
289
|
+
const loadMore = async () => {
|
|
290
|
+
await execute()
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Auto-refresh every 30s
|
|
294
|
+
onMounted(() => {
|
|
295
|
+
const interval = setInterval(refresh, 30000)
|
|
296
|
+
onUnmounted(() => clearInterval(interval))
|
|
297
|
+
})
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### Dependent Requests
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
// First request
|
|
304
|
+
const { data: user } = await useApi<User>(`/users/${userId}`)
|
|
305
|
+
|
|
306
|
+
// Second request depends on first
|
|
307
|
+
const { data: posts } = await useApi<Post[]>('/posts', {
|
|
308
|
+
query: {
|
|
309
|
+
authorId: computed(() => user.value?.data.id),
|
|
310
|
+
},
|
|
311
|
+
immediate: computed(() => !!user.value), // Only fetch when user loaded
|
|
312
|
+
})
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### Polling
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
const { data: status, refresh } = await useApi<JobStatus>(`/jobs/${jobId}`)
|
|
319
|
+
|
|
320
|
+
// Poll every 2 seconds until complete
|
|
321
|
+
const pollJob = () => {
|
|
322
|
+
const interval = setInterval(async () => {
|
|
323
|
+
await refresh()
|
|
324
|
+
|
|
325
|
+
if (status.value?.data.status === 'completed') {
|
|
326
|
+
clearInterval(interval)
|
|
327
|
+
useToast().add({ title: 'Job completed!' })
|
|
328
|
+
}
|
|
329
|
+
}, 2000)
|
|
330
|
+
|
|
331
|
+
onUnmounted(() => clearInterval(interval))
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
onMounted(pollJob)
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Pagination
|
|
338
|
+
|
|
339
|
+
```typescript
|
|
340
|
+
<script setup lang="ts">
|
|
341
|
+
const page = ref(1)
|
|
342
|
+
const limit = ref(10)
|
|
343
|
+
|
|
344
|
+
const { data: response, pending } = await useApi<User[]>('/users', {
|
|
345
|
+
query: { page, limit },
|
|
346
|
+
watch: [page, limit]
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
const users = computed(() => response.value?.data || [])
|
|
350
|
+
const total = computed(() => response.value?.total || 0)
|
|
351
|
+
|
|
352
|
+
const nextPage = () => {
|
|
353
|
+
if (page.value * limit.value < total.value) {
|
|
354
|
+
page.value++
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const prevPage = () => {
|
|
359
|
+
if (page.value > 1) {
|
|
360
|
+
page.value--
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
</script>
|
|
364
|
+
|
|
365
|
+
<template>
|
|
366
|
+
<div>
|
|
367
|
+
<div v-if="pending">Loading...</div>
|
|
368
|
+
<ul v-else>
|
|
369
|
+
<li v-for="user in users" :key="user.id">{{ user.name }}</li>
|
|
370
|
+
</ul>
|
|
371
|
+
|
|
372
|
+
<div class="flex gap-2">
|
|
373
|
+
<button @click="prevPage" :disabled="page === 1">Previous</button>
|
|
374
|
+
<span>Page {{ page }} of {{ Math.ceil(total / limit) }}</span>
|
|
375
|
+
<button @click="nextPage" :disabled="page * limit >= total">Next</button>
|
|
376
|
+
</div>
|
|
377
|
+
</div>
|
|
378
|
+
</template>
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### Infinite Scroll
|
|
382
|
+
|
|
383
|
+
```typescript
|
|
384
|
+
<script setup lang="ts">
|
|
385
|
+
const page = ref(1)
|
|
386
|
+
const allUsers = ref<User[]>([])
|
|
387
|
+
|
|
388
|
+
const { data, pending, execute } = await useApi<User[]>('/users', {
|
|
389
|
+
query: computed(() => ({ page: page.value, limit: 20 })),
|
|
390
|
+
immediate: false
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
// Load initial page
|
|
394
|
+
onMounted(() => execute())
|
|
395
|
+
|
|
396
|
+
// Append results
|
|
397
|
+
watch(data, (newData) => {
|
|
398
|
+
if (newData?.data) {
|
|
399
|
+
allUsers.value = [...allUsers.value, ...newData.data]
|
|
400
|
+
}
|
|
401
|
+
})
|
|
402
|
+
|
|
403
|
+
// Load more
|
|
404
|
+
const loadMore = async () => {
|
|
405
|
+
page.value++
|
|
406
|
+
await execute()
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Infinite scroll observer
|
|
410
|
+
const sentinel = ref<HTMLElement>()
|
|
411
|
+
onMounted(() => {
|
|
412
|
+
const observer = new IntersectionObserver((entries) => {
|
|
413
|
+
if (entries[0].isIntersecting && !pending.value) {
|
|
414
|
+
loadMore()
|
|
415
|
+
}
|
|
416
|
+
})
|
|
417
|
+
|
|
418
|
+
if (sentinel.value) {
|
|
419
|
+
observer.observe(sentinel.value)
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
onUnmounted(() => observer.disconnect())
|
|
423
|
+
})
|
|
424
|
+
</script>
|
|
425
|
+
|
|
426
|
+
<template>
|
|
427
|
+
<div>
|
|
428
|
+
<div v-for="user in allUsers" :key="user.id">
|
|
429
|
+
{{ user.name }}
|
|
430
|
+
</div>
|
|
431
|
+
<div ref="sentinel" class="h-4" />
|
|
432
|
+
<div v-if="pending">Loading more...</div>
|
|
433
|
+
</div>
|
|
434
|
+
</template>
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
---
|
|
438
|
+
|
|
439
|
+
## Error Handling
|
|
440
|
+
|
|
441
|
+
### Basic Error Handling
|
|
442
|
+
|
|
443
|
+
```typescript
|
|
444
|
+
const { data, error } = await useApi<User>('/users/123')
|
|
445
|
+
|
|
446
|
+
if (error.value) {
|
|
447
|
+
// error.value.message is always accessible
|
|
448
|
+
console.error(error.value.message)
|
|
449
|
+
|
|
450
|
+
// Show user-friendly message
|
|
451
|
+
useToast().add({
|
|
452
|
+
title: 'Error',
|
|
453
|
+
description: error.value.message,
|
|
454
|
+
color: 'red',
|
|
455
|
+
})
|
|
456
|
+
}
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
### Error Types
|
|
460
|
+
|
|
461
|
+
```typescript
|
|
462
|
+
interface ApiErrorResponse {
|
|
463
|
+
status?: boolean | string
|
|
464
|
+
message?: string // Always present
|
|
465
|
+
data?: unknown // Additional error data
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Usage
|
|
469
|
+
const { error } = await useApi('/users')
|
|
470
|
+
|
|
471
|
+
if (error.value) {
|
|
472
|
+
console.log(error.value.message) // "User not found"
|
|
473
|
+
console.log(error.value.status) // false
|
|
474
|
+
console.log(error.value.data) // Additional error context
|
|
475
|
+
}
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
### Global Error Handler
|
|
479
|
+
|
|
480
|
+
```typescript
|
|
481
|
+
// Create composable for consistent error handling
|
|
482
|
+
export const useApiError = () => {
|
|
483
|
+
const toast = useToast()
|
|
484
|
+
|
|
485
|
+
const handleError = (error: Ref<ApiErrorResponse | null>) => {
|
|
486
|
+
if (!error.value) return
|
|
487
|
+
|
|
488
|
+
const message = error.value.message || 'An error occurred'
|
|
489
|
+
|
|
490
|
+
// Log to monitoring service
|
|
491
|
+
if (import.meta.client) {
|
|
492
|
+
console.error('API Error:', error.value)
|
|
493
|
+
// trackError(error.value)
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Show toast
|
|
497
|
+
toast.add({
|
|
498
|
+
title: 'Error',
|
|
499
|
+
description: message,
|
|
500
|
+
color: 'red',
|
|
501
|
+
})
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
return { handleError }
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Usage
|
|
508
|
+
const { data, error } = await useApi('/users')
|
|
509
|
+
const { handleError } = useApiError()
|
|
510
|
+
|
|
511
|
+
watch(error, () => handleError(error))
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
### Retry Logic
|
|
515
|
+
|
|
516
|
+
```typescript
|
|
517
|
+
const maxRetries = 3
|
|
518
|
+
let retryCount = 0
|
|
519
|
+
|
|
520
|
+
const { data, error, execute } = await useApi<User[]>('/users', {
|
|
521
|
+
immediate: false,
|
|
522
|
+
})
|
|
523
|
+
|
|
524
|
+
const fetchWithRetry = async () => {
|
|
525
|
+
while (retryCount < maxRetries) {
|
|
526
|
+
await execute()
|
|
527
|
+
|
|
528
|
+
if (!error.value) {
|
|
529
|
+
retryCount = 0
|
|
530
|
+
return
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
retryCount++
|
|
534
|
+
|
|
535
|
+
if (retryCount < maxRetries) {
|
|
536
|
+
await new Promise((resolve) => setTimeout(resolve, 1000 * retryCount))
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// Max retries reached
|
|
541
|
+
useToast().add({
|
|
542
|
+
title: 'Failed after 3 retries',
|
|
543
|
+
description: error.value?.message,
|
|
544
|
+
color: 'red',
|
|
545
|
+
})
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
onMounted(fetchWithRetry)
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
---
|
|
552
|
+
|
|
553
|
+
## Authentication
|
|
554
|
+
|
|
555
|
+
### Automatic Cookie Forwarding
|
|
556
|
+
|
|
557
|
+
```typescript
|
|
558
|
+
// ✅ Cookies automatically forwarded on SSR
|
|
559
|
+
const { data } = await useApi<User>('/me')
|
|
560
|
+
|
|
561
|
+
// No manual header management needed!
|
|
562
|
+
// useApi handles:
|
|
563
|
+
// - Server: useRequestHeaders(['cookie'])
|
|
564
|
+
// - Client: credentials: 'include'
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
### 401 Auto-Redirect
|
|
568
|
+
|
|
569
|
+
```typescript
|
|
570
|
+
// If API returns 401, useApi automatically redirects to:
|
|
571
|
+
// - /cms/auth/login (for /cms/* routes)
|
|
572
|
+
// - /auth/login (for other routes)
|
|
573
|
+
|
|
574
|
+
// No manual redirect logic needed!
|
|
575
|
+
const { data } = await useApi<User>('/protected-resource')
|
|
576
|
+
// If unauthorized, user is redirected automatically
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
### Custom Auth Headers
|
|
580
|
+
|
|
581
|
+
```typescript
|
|
582
|
+
// For Bearer token authentication (if needed)
|
|
583
|
+
const token = useCookie('auth_token')
|
|
584
|
+
|
|
585
|
+
const { data } = await useApi<User>('/users', {
|
|
586
|
+
headers: {
|
|
587
|
+
Authorization: `Bearer ${token.value}`,
|
|
588
|
+
},
|
|
589
|
+
})
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
### Skip Auto-Redirect
|
|
593
|
+
|
|
594
|
+
```typescript
|
|
595
|
+
// Prevent auto-redirect on specific pages
|
|
596
|
+
// Already handled in useApi for public pages:
|
|
597
|
+
const publicPages = [
|
|
598
|
+
'/cms/auth/login',
|
|
599
|
+
'/blog',
|
|
600
|
+
'/services',
|
|
601
|
+
'/about',
|
|
602
|
+
// etc.
|
|
603
|
+
]
|
|
604
|
+
|
|
605
|
+
// If you need custom handling, use try-catch
|
|
606
|
+
const { error } = await useApi('/protected')
|
|
607
|
+
|
|
608
|
+
if (error.value && error.value.status === 401) {
|
|
609
|
+
// Custom 401 handling
|
|
610
|
+
showLoginModal()
|
|
611
|
+
}
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
---
|
|
615
|
+
|
|
616
|
+
## Best Practices
|
|
617
|
+
|
|
618
|
+
### 1. Always Use Type Generics
|
|
619
|
+
|
|
620
|
+
```typescript
|
|
621
|
+
// ❌ Bad - No type safety
|
|
622
|
+
const { data } = await useApi('/users')
|
|
623
|
+
|
|
624
|
+
// ✅ Good - Type safe
|
|
625
|
+
interface User {
|
|
626
|
+
id: string
|
|
627
|
+
name: string
|
|
628
|
+
email: string
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
const { data } = await useApi<User[]>('/users')
|
|
632
|
+
// data.value?.data is now typed as User[]
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
### 2. Handle Errors Gracefully
|
|
636
|
+
|
|
637
|
+
```typescript
|
|
638
|
+
// ❌ Bad - Ignore errors
|
|
639
|
+
const { data } = await useApi('/users')
|
|
640
|
+
|
|
641
|
+
// ✅ Good - Always check errors
|
|
642
|
+
const { data, error } = await useApi('/users')
|
|
643
|
+
|
|
644
|
+
if (error.value) {
|
|
645
|
+
useToast().add({
|
|
646
|
+
title: 'Error',
|
|
647
|
+
description: error.value.message,
|
|
648
|
+
color: 'red',
|
|
649
|
+
})
|
|
650
|
+
return
|
|
651
|
+
}
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
### 3. Use Computed for Response Data
|
|
655
|
+
|
|
656
|
+
```typescript
|
|
657
|
+
// ✅ Good - Extract data into computed
|
|
658
|
+
const { data: response } = await useApi<User[]>('/users')
|
|
659
|
+
const users = computed(() => response.value?.data || [])
|
|
660
|
+
|
|
661
|
+
// Now use users in template
|
|
662
|
+
<template>
|
|
663
|
+
<div v-for="user in users" :key="user.id">
|
|
664
|
+
{{ user.name }}
|
|
665
|
+
</div>
|
|
666
|
+
</template>
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
### 4. Leverage Immediate: false for Forms
|
|
670
|
+
|
|
671
|
+
```typescript
|
|
672
|
+
// ✅ Good for submit handlers
|
|
673
|
+
const { execute, pending, error } = await useApi('/users', {
|
|
674
|
+
method: 'POST',
|
|
675
|
+
body: form,
|
|
676
|
+
immediate: false,
|
|
677
|
+
})
|
|
678
|
+
|
|
679
|
+
const handleSubmit = async () => {
|
|
680
|
+
await execute()
|
|
681
|
+
// Handle response
|
|
682
|
+
}
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
### 5. Use Watch for Reactive Queries
|
|
686
|
+
|
|
687
|
+
```typescript
|
|
688
|
+
// ✅ Good - Reactive filters
|
|
689
|
+
const filters = reactive({
|
|
690
|
+
role: 'admin',
|
|
691
|
+
status: 'active',
|
|
692
|
+
})
|
|
693
|
+
|
|
694
|
+
const { data } = await useApi('/users', {
|
|
695
|
+
query: filters,
|
|
696
|
+
watch: [() => filters.role, () => filters.status],
|
|
697
|
+
})
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
---
|
|
701
|
+
|
|
702
|
+
## Migration Guide
|
|
703
|
+
|
|
704
|
+
### From useFetch to useApi
|
|
705
|
+
|
|
706
|
+
```typescript
|
|
707
|
+
// Before
|
|
708
|
+
const { data, pending, error, refresh } = await useFetch('/api/users', {
|
|
709
|
+
headers: useRequestHeaders(['cookie']),
|
|
710
|
+
query: { page: 1 },
|
|
711
|
+
})
|
|
712
|
+
|
|
713
|
+
// After
|
|
714
|
+
const { data, pending, error, refresh } = await useApi('/users', {
|
|
715
|
+
query: { page: 1 },
|
|
716
|
+
})
|
|
717
|
+
// Simpler! Auth automatic.
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
### From $fetch to useApi
|
|
721
|
+
|
|
722
|
+
```typescript
|
|
723
|
+
// Before
|
|
724
|
+
const loading = ref(false)
|
|
725
|
+
const error = ref(null)
|
|
726
|
+
const users = ref([])
|
|
727
|
+
|
|
728
|
+
const fetchUsers = async () => {
|
|
729
|
+
loading.value = true
|
|
730
|
+
try {
|
|
731
|
+
users.value = await $fetch('/api/users')
|
|
732
|
+
} catch (err) {
|
|
733
|
+
error.value = err
|
|
734
|
+
} finally {
|
|
735
|
+
loading.value = false
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
onMounted(fetchUsers)
|
|
740
|
+
|
|
741
|
+
// After
|
|
742
|
+
const { data: users, pending, error } = await useApi('/users')
|
|
743
|
+
// Much simpler! Reactive state automatic.
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
### From useAsyncData to useApi
|
|
747
|
+
|
|
748
|
+
```typescript
|
|
749
|
+
// Before
|
|
750
|
+
const { data, pending, error } = await useAsyncData('users', () => $fetch('/api/users'))
|
|
751
|
+
|
|
752
|
+
// After
|
|
753
|
+
const { data, pending, error } = await useApi('/users')
|
|
754
|
+
// Cleaner! No manual key needed.
|
|
755
|
+
```
|
|
756
|
+
|
|
757
|
+
---
|
|
758
|
+
|
|
759
|
+
## Common Patterns Cheatsheet
|
|
760
|
+
|
|
761
|
+
| Use Case | Code |
|
|
762
|
+
| -------------- | ------------------------------------------------------------ |
|
|
763
|
+
| Simple GET | `await useApi<T>('/endpoint')` |
|
|
764
|
+
| GET with query | `await useApi('/endpoint', { query: { key: 'value' } })` |
|
|
765
|
+
| POST | `await useApi('/endpoint', { method: 'POST', body: data })` |
|
|
766
|
+
| PUT/PATCH | `await useApi('/endpoint', { method: 'PATCH', body: data })` |
|
|
767
|
+
| DELETE | `await useApi('/endpoint', { method: 'DELETE' })` |
|
|
768
|
+
| Manual trigger | `await useApi('/endpoint', { immediate: false })` |
|
|
769
|
+
| Reactive query | `await useApi('/endpoint', { query, watch: [query] })` |
|
|
770
|
+
| Error handling | `if (error.value) { console.log(error.value.message) }` |
|
|
771
|
+
| Refresh | `await refresh()` |
|
|
772
|
+
| Re-execute | `await execute()` |
|
|
773
|
+
|
|
774
|
+
---
|
|
775
|
+
|
|
776
|
+
## Summary
|
|
777
|
+
|
|
778
|
+
**When to use useApi:**
|
|
779
|
+
|
|
780
|
+
- ✅ All internal API calls (recommended default)
|
|
781
|
+
- ✅ Protected routes requiring authentication
|
|
782
|
+
- ✅ Consistent error handling needed
|
|
783
|
+
- ✅ Want automatic SSR/CSR optimization
|
|
784
|
+
|
|
785
|
+
**When to use useFetch:**
|
|
786
|
+
|
|
787
|
+
- ⚠️ External third-party APIs
|
|
788
|
+
- ⚠️ Custom caching requirements
|
|
789
|
+
- ⚠️ Special configurations not supported by useApi
|
|
790
|
+
|
|
791
|
+
**When to use $fetch:**
|
|
792
|
+
|
|
793
|
+
- ⚠️ Server-side only code (server/api/\*)
|
|
794
|
+
- ⚠️ One-off requests without reactive state
|
|
795
|
+
- ⚠️ Background jobs or scheduled tasks
|
|
796
|
+
|
|
797
|
+
**Default choice: useApi for 99% of cases in this project.**
|
|
798
|
+
|
|
799
|
+
---
|
|
800
|
+
|
|
801
|
+
For more examples, see:
|
|
802
|
+
|
|
803
|
+
- [EXAMPLES.md](./EXAMPLES.md) - Section 3: API Integration
|
|
804
|
+
- [CHEATSHEET.md](./CHEATSHEET.md) - Data Fetching section
|
|
805
|
+
- [frontend-developer.md](./frontend-developer.md) - Data Fetching Patterns
|
|
806
|
+
|
|
807
|
+
**Happy coding with useApi! 🚀**
|