@jhm1909/ag-kit 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/.agent/ARCHITECTURE.md +189 -0
- package/.agent/known-registries.json +181 -0
- package/.agent/mcp_config.json +19 -0
- package/.agent/rules/clean-code.md +107 -0
- package/.agent/rules/documents.md +177 -0
- package/.agent/rules/git-workflow.md +68 -0
- package/.agent/rules/nano-banana.md +46 -0
- package/.agent/rules/research.md +35 -0
- package/.agent/rules/skill-loading.md +100 -0
- package/.agent/rules/skill-suggestion.md +47 -0
- package/.agent/rules/testing.md +52 -0
- package/.agent/rules/workflow-advisor.md +108 -0
- package/.agent/rules/workflow-skill-convention.md +127 -0
- package/.agent/skills/ai-engineer/SKILL.md +824 -0
- package/.agent/skills/ai-engineer/references/agentic-patterns.md +329 -0
- package/.agent/skills/ai-engineer/references/evaluation.md +493 -0
- package/.agent/skills/ai-engineer/references/llm.md +490 -0
- package/.agent/skills/ai-engineer/references/rag-advanced.md +444 -0
- package/.agent/skills/ai-engineer/references/serving-optimization.md +531 -0
- package/.agent/skills/ai-engineer/vector-db/README.md +137 -0
- package/.agent/skills/app-builder/SKILL.md +75 -0
- package/.agent/skills/app-builder/agent-coordination.md +71 -0
- package/.agent/skills/app-builder/feature-building.md +53 -0
- package/.agent/skills/app-builder/project-detection.md +34 -0
- package/.agent/skills/app-builder/scaffolding.md +118 -0
- package/.agent/skills/app-builder/tech-stack.md +41 -0
- package/.agent/skills/app-builder/templates/SKILL.md +39 -0
- package/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +76 -0
- package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +92 -0
- package/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +88 -0
- package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +88 -0
- package/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +83 -0
- package/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +90 -0
- package/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +90 -0
- package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +122 -0
- package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +122 -0
- package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +169 -0
- package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +134 -0
- package/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +83 -0
- package/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +119 -0
- package/.agent/skills/backend-developer/SKILL.md +763 -0
- package/.agent/skills/backend-developer/references/general-patterns.md +65 -0
- package/.agent/skills/backend-developer/references/go-echo.md +68 -0
- package/.agent/skills/backend-developer/references/go-gin.md +76 -0
- package/.agent/skills/backend-developer/references/java-springboot.md +83 -0
- package/.agent/skills/backend-developer/references/node-express.md +64 -0
- package/.agent/skills/backend-developer/references/node-nestjs.md +69 -0
- package/.agent/skills/backend-developer/references/python-django.md +67 -0
- package/.agent/skills/backend-developer/references/python-fastapi.md +80 -0
- package/.agent/skills/blockchain-engineer/SKILL.md +975 -0
- package/.agent/skills/blockchain-engineer/references/deployment.md +28 -0
- package/.agent/skills/blockchain-engineer/references/evm.md +14 -0
- package/.agent/skills/blockchain-engineer/references/mechanisms.md +32 -0
- package/.agent/skills/blockchain-engineer/references/solidity.md +32 -0
- package/.agent/skills/business-analysis/SKILL.md +85 -0
- package/.agent/skills/business-analysis/references/best-practices/diagrams.md +141 -0
- package/.agent/skills/business-analysis/references/domains/ai-agent.md +94 -0
- package/.agent/skills/business-analysis/references/domains/blockchain-dapp.md +86 -0
- package/.agent/skills/business-analysis/references/domains/ecommerce.md +77 -0
- package/.agent/skills/business-analysis/references/domains/education.md +42 -0
- package/.agent/skills/business-analysis/references/domains/fintech.md +44 -0
- package/.agent/skills/business-analysis/references/domains/fnb.md +82 -0
- package/.agent/skills/business-analysis/references/domains/healthtech.md +44 -0
- package/.agent/skills/business-analysis/references/domains/internal-tools.md +38 -0
- package/.agent/skills/business-analysis/references/domains/marketplace.md +52 -0
- package/.agent/skills/business-analysis/references/domains/saas.md +36 -0
- package/.agent/skills/business-analysis/references/workflows/collaboration.md +41 -0
- package/.agent/skills/business-analysis/scripts/verify_mermaid.py +86 -0
- package/.agent/skills/business-analysis/templates/brd.md +46 -0
- package/.agent/skills/business-analysis/templates/change-request.md +41 -0
- package/.agent/skills/business-analysis/templates/prd-functional.md +38 -0
- package/.agent/skills/business-analysis/templates/use-case.md +40 -0
- package/.agent/skills/business-analysis/templates/user-story-detailed.md +36 -0
- package/.agent/skills/code-review/SKILL.md +113 -0
- package/.agent/skills/code-review/references/code-review-reception.md +209 -0
- package/.agent/skills/code-review/references/differential_review.md +59 -0
- package/.agent/skills/code-review/references/requesting-code-review.md +105 -0
- package/.agent/skills/code-review/references/spec_compliance.md +43 -0
- package/.agent/skills/code-review/references/verification-before-completion.md +139 -0
- package/.agent/skills/context-engineering/SKILL.md +68 -0
- package/.agent/skills/context-engineering/references/context-compression.md +84 -0
- package/.agent/skills/context-engineering/references/context-degradation.md +93 -0
- package/.agent/skills/context-engineering/references/context-fundamentals.md +75 -0
- package/.agent/skills/context-engineering/references/context-optimization.md +82 -0
- package/.agent/skills/context-engineering/references/evaluation.md +89 -0
- package/.agent/skills/context-engineering/references/memory-systems.md +88 -0
- package/.agent/skills/context-engineering/references/multi-agent-patterns.md +90 -0
- package/.agent/skills/context-engineering/references/project-development.md +97 -0
- package/.agent/skills/context-engineering/references/tool-design.md +86 -0
- package/.agent/skills/debugging/SKILL.md +60 -0
- package/.agent/skills/debugging/references/defense-in-depth.md +130 -0
- package/.agent/skills/debugging/references/root-cause-tracing.md +177 -0
- package/.agent/skills/debugging/references/systematic-debugging.md +295 -0
- package/.agent/skills/debugging/references/verification-before-completion.md +142 -0
- package/.agent/skills/designer/SKILL.md +159 -0
- package/.agent/skills/designer/concepts/apple-glass.md +48 -0
- package/.agent/skills/designer/concepts/aurora-gradients.md +26 -0
- package/.agent/skills/designer/concepts/bento-grids.md +14 -0
- package/.agent/skills/designer/concepts/claymorphism.md +27 -0
- package/.agent/skills/designer/concepts/neo-brutalism.md +32 -0
- package/.agent/skills/designer/data/app-interface.csv +31 -0
- package/.agent/skills/designer/data/charts.csv +26 -0
- package/.agent/skills/designer/data/colors.csv +162 -0
- package/.agent/skills/designer/data/design.csv +1776 -0
- package/.agent/skills/designer/data/icons.csv +106 -0
- package/.agent/skills/designer/data/landing.csv +35 -0
- package/.agent/skills/designer/data/products.csv +162 -0
- package/.agent/skills/designer/data/react-performance.csv +45 -0
- package/.agent/skills/designer/data/styles.csv +85 -0
- package/.agent/skills/designer/data/typography.csv +74 -0
- package/.agent/skills/designer/data/ui-reasoning.csv +162 -0
- package/.agent/skills/designer/data/ux-guidelines.csv +100 -0
- package/.agent/skills/designer/references/accessibility.md +172 -0
- package/.agent/skills/designer/references/branding.md +88 -0
- package/.agent/skills/designer/references/color-theory.md +139 -0
- package/.agent/skills/designer/references/creation.md +118 -0
- package/.agent/skills/designer/references/design-systems.md +219 -0
- package/.agent/skills/designer/references/frontend_design_aesthetics.md +57 -0
- package/.agent/skills/designer/references/layout.md +200 -0
- package/.agent/skills/designer/references/motion.md +92 -0
- package/.agent/skills/designer/references/review.md +100 -0
- package/.agent/skills/designer/references/trends.md +209 -0
- package/.agent/skills/designer/references/typography.md +190 -0
- package/.agent/skills/designer/scripts/remove_background.py +135 -0
- package/.agent/skills/designer/scripts/ui-search/__pycache__/core.cpython-314.pyc +0 -0
- package/.agent/skills/designer/scripts/ui-search/__pycache__/design_system.cpython-314.pyc +0 -0
- package/.agent/skills/designer/scripts/ui-search/core.py +217 -0
- package/.agent/skills/designer/scripts/ui-search/design_system.py +1067 -0
- package/.agent/skills/designer/scripts/ui-search/search.py +114 -0
- package/.agent/skills/designer/templates/design-motion-spec.md +30 -0
- package/.agent/skills/devops-engineer/SKILL.md +90 -0
- package/.agent/skills/devops-engineer/docker-compose/README.md +47 -0
- package/.agent/skills/devops-engineer/references/ci-cd-pipelines.md +76 -0
- package/.agent/skills/devops-engineer/references/cloud-providers.md +57 -0
- package/.agent/skills/devops-engineer/references/codebase-normalization.md +104 -0
- package/.agent/skills/devops-engineer/references/container-orchestration.md +69 -0
- package/.agent/skills/devops-engineer/references/iac-tools.md +63 -0
- package/.agent/skills/devops-engineer/references/observability-security.md +45 -0
- package/.agent/skills/devops-engineer/references/vercel-supabase.md +17 -0
- package/.agent/skills/devops-engineer/templates/release-notes.md +8 -0
- package/.agent/skills/frontend-developer/SKILL.md +125 -0
- package/.agent/skills/frontend-developer/react-nextjs/README.md +90 -0
- package/.agent/skills/frontend-developer/references/angular.md +52 -0
- package/.agent/skills/frontend-developer/references/composition_patterns.md +60 -0
- package/.agent/skills/frontend-developer/references/core-performance.md +68 -0
- package/.agent/skills/frontend-developer/references/modern-signals.md +43 -0
- package/.agent/skills/frontend-developer/references/react_performance_rules.md +55 -0
- package/.agent/skills/frontend-developer/references/vue-nuxt.md +55 -0
- package/.agent/skills/frontend-developer/scripts/validate_compliance.py +65 -0
- package/.agent/skills/frontend-developer/threejs/README.md +89 -0
- package/.agent/skills/frontend-developer/threejs/animation.md +552 -0
- package/.agent/skills/frontend-developer/threejs/fundamentals.md +488 -0
- package/.agent/skills/frontend-developer/threejs/geometry.md +548 -0
- package/.agent/skills/frontend-developer/threejs/interaction.md +660 -0
- package/.agent/skills/frontend-developer/threejs/lighting.md +481 -0
- package/.agent/skills/frontend-developer/threejs/loaders.md +623 -0
- package/.agent/skills/frontend-developer/threejs/materials.md +520 -0
- package/.agent/skills/frontend-developer/threejs/postprocessing.md +602 -0
- package/.agent/skills/frontend-developer/threejs/router.json +181 -0
- package/.agent/skills/frontend-developer/threejs/shaders.md +642 -0
- package/.agent/skills/frontend-developer/threejs/textures.md +628 -0
- package/.agent/skills/game-development/2d-games/SKILL.md +119 -0
- package/.agent/skills/game-development/3d-games/SKILL.md +135 -0
- package/.agent/skills/game-development/SKILL.md +167 -0
- package/.agent/skills/game-development/game-art/SKILL.md +185 -0
- package/.agent/skills/game-development/game-audio/SKILL.md +190 -0
- package/.agent/skills/game-development/game-design/SKILL.md +129 -0
- package/.agent/skills/game-development/mobile-games/SKILL.md +108 -0
- package/.agent/skills/game-development/multiplayer/SKILL.md +132 -0
- package/.agent/skills/game-development/pc-games/SKILL.md +144 -0
- package/.agent/skills/game-development/vr-ar/SKILL.md +123 -0
- package/.agent/skills/game-development/web-games/SKILL.md +150 -0
- package/.agent/skills/lead-architect/SKILL.md +85 -0
- package/.agent/skills/lead-architect/references/application-architecture.md +70 -0
- package/.agent/skills/lead-architect/references/infrastructure.md +51 -0
- package/.agent/skills/lead-architect/references/process.md +42 -0
- package/.agent/skills/lead-architect/references/system-architecture.md +62 -0
- package/.agent/skills/lead-architect/references/web-fullstack.md +82 -0
- package/.agent/skills/lead-architect/templates/adr.md +62 -0
- package/.agent/skills/lead-architect/templates/rfc.md +46 -0
- package/.agent/skills/lead-architect/templates/sdd.md +62 -0
- package/.agent/skills/lead-architect/templates/technical-spec.md +61 -0
- package/.agent/skills/marketer/SKILL.md +66 -0
- package/.agent/skills/marketer/remotion-best-practices/SKILL.md +58 -0
- package/.agent/skills/marketer/remotion-best-practices/rules/3d.md +86 -0
- package/.agent/skills/marketer/remotion-best-practices/rules/animations.md +29 -0
- package/.agent/skills/marketer/remotion-best-practices/rules/assets/charts-bar-chart.tsx +173 -0
- package/.agent/skills/marketer/remotion-best-practices/rules/assets/text-animations-typewriter.tsx +100 -0
- package/.agent/skills/marketer/remotion-best-practices/rules/assets/text-animations-word-highlight.tsx +108 -0
- package/.agent/skills/marketer/remotion-best-practices/rules/assets.md +78 -0
- package/.agent/skills/marketer/remotion-best-practices/rules/audio.md +172 -0
- package/.agent/skills/marketer/remotion-best-practices/rules/calculate-metadata.md +104 -0
- package/.agent/skills/marketer/remotion-best-practices/rules/can-decode.md +75 -0
- package/.agent/skills/marketer/remotion-best-practices/rules/charts.md +58 -0
- package/.agent/skills/marketer/remotion-best-practices/rules/compositions.md +146 -0
- package/.agent/skills/marketer/remotion-best-practices/rules/display-captions.md +126 -0
- package/.agent/skills/marketer/remotion-best-practices/rules/extract-frames.md +229 -0
- package/.agent/skills/marketer/remotion-best-practices/rules/fonts.md +152 -0
- package/.agent/skills/marketer/remotion-best-practices/rules/get-audio-duration.md +58 -0
- package/.agent/skills/marketer/remotion-best-practices/rules/get-video-dimensions.md +68 -0
- package/.agent/skills/marketer/remotion-best-practices/rules/get-video-duration.md +58 -0
- package/.agent/skills/marketer/remotion-best-practices/rules/gifs.md +138 -0
- package/.agent/skills/marketer/remotion-best-practices/rules/images.md +130 -0
- package/.agent/skills/marketer/remotion-best-practices/rules/import-srt-captions.md +67 -0
- package/.agent/skills/marketer/remotion-best-practices/rules/lottie.md +68 -0
- package/.agent/skills/marketer/remotion-best-practices/rules/measuring-dom-nodes.md +35 -0
- package/.agent/skills/marketer/remotion-best-practices/rules/measuring-text.md +143 -0
- package/.agent/skills/marketer/remotion-best-practices/rules/sequencing.md +106 -0
- package/.agent/skills/marketer/remotion-best-practices/rules/tailwind.md +11 -0
- package/.agent/skills/marketer/remotion-best-practices/rules/text-animations.md +20 -0
- package/.agent/skills/marketer/remotion-best-practices/rules/timing.md +179 -0
- package/.agent/skills/marketer/remotion-best-practices/rules/transcribe-captions.md +19 -0
- package/.agent/skills/marketer/remotion-best-practices/rules/transitions.md +122 -0
- package/.agent/skills/marketer/remotion-best-practices/rules/trimming.md +53 -0
- package/.agent/skills/marketer/remotion-best-practices/rules/videos.md +171 -0
- package/.agent/skills/mcp-builder/SKILL.md +76 -0
- package/.agent/skills/mcp-builder/references/evaluation.md +602 -0
- package/.agent/skills/mcp-builder/references/mcp_best_practices.md +249 -0
- package/.agent/skills/mcp-builder/references/node_mcp_server.md +970 -0
- package/.agent/skills/mcp-builder/references/python_mcp_server.md +719 -0
- package/.agent/skills/mobile-developer/SKILL.md +83 -0
- package/.agent/skills/mobile-developer/api-routes/SKILL.md +389 -0
- package/.agent/skills/mobile-developer/building-ui/SKILL.md +335 -0
- package/.agent/skills/mobile-developer/building-ui/references/animations.md +220 -0
- package/.agent/skills/mobile-developer/building-ui/references/controls.md +270 -0
- package/.agent/skills/mobile-developer/building-ui/references/form-sheet.md +227 -0
- package/.agent/skills/mobile-developer/building-ui/references/gradients.md +106 -0
- package/.agent/skills/mobile-developer/building-ui/references/icons.md +213 -0
- package/.agent/skills/mobile-developer/building-ui/references/media.md +198 -0
- package/.agent/skills/mobile-developer/building-ui/references/route-structure.md +229 -0
- package/.agent/skills/mobile-developer/building-ui/references/search.md +248 -0
- package/.agent/skills/mobile-developer/building-ui/references/storage.md +121 -0
- package/.agent/skills/mobile-developer/building-ui/references/tabs.md +368 -0
- package/.agent/skills/mobile-developer/building-ui/references/visual-effects.md +197 -0
- package/.agent/skills/mobile-developer/building-ui/references/webgpu-three.md +605 -0
- package/.agent/skills/mobile-developer/cicd-workflows/SKILL.md +107 -0
- package/.agent/skills/mobile-developer/cicd-workflows/scripts/fetch.js +109 -0
- package/.agent/skills/mobile-developer/cicd-workflows/scripts/package.json +11 -0
- package/.agent/skills/mobile-developer/cicd-workflows/scripts/validate.js +84 -0
- package/.agent/skills/mobile-developer/data-fetching/SKILL.md +508 -0
- package/.agent/skills/mobile-developer/deployment/SKILL.md +207 -0
- package/.agent/skills/mobile-developer/deployment/references/app-store-metadata.md +479 -0
- package/.agent/skills/mobile-developer/deployment/references/ios-app-store.md +355 -0
- package/.agent/skills/mobile-developer/deployment/references/play-store.md +246 -0
- package/.agent/skills/mobile-developer/deployment/references/testflight.md +58 -0
- package/.agent/skills/mobile-developer/deployment/references/workflows.md +200 -0
- package/.agent/skills/mobile-developer/dev-client/SKILL.md +181 -0
- package/.agent/skills/mobile-developer/tailwind-setup/SKILL.md +501 -0
- package/.agent/skills/mobile-developer/upgrading-expo/SKILL.md +116 -0
- package/.agent/skills/mobile-developer/upgrading-expo/references/new-architecture.md +79 -0
- package/.agent/skills/mobile-developer/upgrading-expo/references/react-19.md +79 -0
- package/.agent/skills/mobile-developer/upgrading-expo/references/react-compiler.md +59 -0
- package/.agent/skills/mobile-developer/use-dom/SKILL.md +434 -0
- package/.agent/skills/modern-python/SKILL.md +122 -0
- package/.agent/skills/project-manager/SKILL.md +110 -0
- package/.agent/skills/project-manager/references/ba-collaboration.md +62 -0
- package/.agent/skills/project-manager/references/discovery_process.md +52 -0
- package/.agent/skills/project-manager/references/jobs_to_be_done.md +51 -0
- package/.agent/skills/project-manager/references/prd_development.md +52 -0
- package/.agent/skills/project-manager/references/rules-guide.md +55 -0
- package/.agent/skills/project-manager/references/skill-creation.md +98 -0
- package/.agent/skills/project-manager/references/strategic-frameworks.md +62 -0
- package/.agent/skills/project-manager/references/task-decomposition.md +194 -0
- package/.agent/skills/project-manager/references/workflows-guide.md +44 -0
- package/.agent/skills/project-manager/router.json +160 -0
- package/.agent/skills/project-manager/scripts/compare_skill.py +177 -0
- package/.agent/skills/project-manager/scripts/encoding_utils.py +36 -0
- package/.agent/skills/project-manager/scripts/init_skill.py +190 -0
- package/.agent/skills/project-manager/scripts/quick_validate.py +123 -0
- package/.agent/skills/project-manager/templates/pm-strategy-one-pager.md +6 -0
- package/.agent/skills/project-manager/templates/prd-strategic.md +38 -0
- package/.agent/skills/project-manager/templates/skill-questionnaire.md +118 -0
- package/.agent/skills/project-manager/templates/user-story-simple.md +14 -0
- package/.agent/skills/prompt-engineer/SKILL.md +319 -0
- package/.agent/skills/prompt-engineer/skill-creator/README.md +47 -0
- package/.agent/skills/qa-tester/SKILL.md +142 -0
- package/.agent/skills/qa-tester/assets/README.md +8 -0
- package/.agent/skills/qa-tester/references/accessibility_testing.md +35 -0
- package/.agent/skills/qa-tester/references/agent_browser.md +38 -0
- package/.agent/skills/qa-tester/references/automation/api_testing.md +23 -0
- package/.agent/skills/qa-tester/references/automation/best_practices.md +14 -0
- package/.agent/skills/qa-tester/references/automation/jest_vitest.md +26 -0
- package/.agent/skills/qa-tester/references/automation/playwright.md +30 -0
- package/.agent/skills/qa-tester/references/e2e_testing.md +46 -0
- package/.agent/skills/qa-tester/references/integration_testing.md +39 -0
- package/.agent/skills/qa-tester/references/performance_testing.md +44 -0
- package/.agent/skills/qa-tester/references/property_based_testing.md +44 -0
- package/.agent/skills/qa-tester/references/security_audit.md +53 -0
- package/.agent/skills/qa-tester/references/security_testing.md +30 -0
- package/.agent/skills/qa-tester/references/sharp_edges.md +49 -0
- package/.agent/skills/qa-tester/references/static_analysis.md +52 -0
- package/.agent/skills/qa-tester/references/supply_chain_audit.md +54 -0
- package/.agent/skills/qa-tester/references/test_case_standards.md +96 -0
- package/.agent/skills/qa-tester/references/test_report_template.md +32 -0
- package/.agent/skills/qa-tester/references/unit_testing.md +50 -0
- package/.agent/skills/qa-tester/references/visual_testing.md +32 -0
- package/.agent/skills/qa-tester/templates/uat-plan.md +34 -0
- package/.agent/skills/research-first/SKILL.md +118 -0
- package/.agent/skills-manifest.json +264 -0
- package/.agent/workflows/absorb.md +176 -0
- package/.agent/workflows/bootstrap.md +91 -0
- package/.agent/workflows/brainstorm.md +168 -0
- package/.agent/workflows/break-tasks.md +77 -0
- package/.agent/workflows/commit.md +349 -0
- package/.agent/workflows/custom-behavior.md +64 -0
- package/.agent/workflows/debug.md +65 -0
- package/.agent/workflows/development.md +49 -0
- package/.agent/workflows/documentation.md +221 -0
- package/.agent/workflows/gen-tests.md +53 -0
- package/.agent/workflows/guide.md +196 -0
- package/.agent/workflows/implement-feature.md +182 -0
- package/.agent/workflows/install-skill.md +193 -0
- package/.agent/workflows/qa.md +54 -0
- package/.agent/workflows/ui-ux-design.md +108 -0
- package/LICENSE +21 -0
- package/README.md +258 -0
- package/cli/index.js +345 -0
- package/cli/migrate-skills.js +113 -0
- package/cli/verify.js +291 -0
- package/package.json +49 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
5
|
+
import { resolve } from 'node:path';
|
|
6
|
+
import process from 'node:process';
|
|
7
|
+
|
|
8
|
+
const CACHE_DIRECTORY = resolve(import.meta.dirname, '.cache');
|
|
9
|
+
const DEFAULT_TTL_SECONDS = 15 * 60; // 15 minutes
|
|
10
|
+
|
|
11
|
+
export async function fetchCached(url) {
|
|
12
|
+
await mkdir(CACHE_DIRECTORY, { recursive: true });
|
|
13
|
+
|
|
14
|
+
const cacheFile = resolve(CACHE_DIRECTORY, hashUrl(url) + '.json');
|
|
15
|
+
const cached = await loadCacheEntry(cacheFile);
|
|
16
|
+
if (cached && cached.expires > Math.floor(Date.now() / 1000)) {
|
|
17
|
+
return cached.data;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Make request, with conditional If-None-Match if we have an ETag.
|
|
21
|
+
// Cache-Control: max-age=0 overrides Node's default 'no-cache' to allow 304 responses.
|
|
22
|
+
const response = await fetch(url, {
|
|
23
|
+
headers: {
|
|
24
|
+
'Cache-Control': 'max-age=0',
|
|
25
|
+
...(cached?.etag && { 'If-None-Match': cached.etag }),
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
if (response.status === 304 && cached) {
|
|
30
|
+
// Refresh expiration and return cached data
|
|
31
|
+
const entry = { ...cached, expires: getExpires(response.headers) };
|
|
32
|
+
await saveCacheEntry(cacheFile, entry);
|
|
33
|
+
return cached.data;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!response.ok) {
|
|
37
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const etag = response.headers.get('etag');
|
|
41
|
+
const data = await response.text();
|
|
42
|
+
const expires = getExpires(response.headers);
|
|
43
|
+
|
|
44
|
+
await saveCacheEntry(cacheFile, { url, etag, expires, data });
|
|
45
|
+
|
|
46
|
+
return data;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function hashUrl(url) {
|
|
50
|
+
return createHash('sha256').update(url).digest('hex').slice(0, 16);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function loadCacheEntry(cacheFile) {
|
|
54
|
+
try {
|
|
55
|
+
return JSON.parse(await readFile(cacheFile, 'utf-8'));
|
|
56
|
+
} catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function saveCacheEntry(cacheFile, entry) {
|
|
62
|
+
await writeFile(cacheFile, JSON.stringify(entry, null, 2));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function getExpires(headers) {
|
|
66
|
+
const now = Math.floor(Date.now() / 1000);
|
|
67
|
+
|
|
68
|
+
// Prefer Cache-Control: max-age
|
|
69
|
+
const maxAgeSeconds = parseMaxAge(headers.get('cache-control'));
|
|
70
|
+
if (maxAgeSeconds != null) {
|
|
71
|
+
return now + maxAgeSeconds;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Fall back to Expires header
|
|
75
|
+
const expires = headers.get('expires');
|
|
76
|
+
if (expires) {
|
|
77
|
+
const expiresTime = Date.parse(expires);
|
|
78
|
+
if (!Number.isNaN(expiresTime)) {
|
|
79
|
+
return Math.floor(expiresTime / 1000);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Default TTL
|
|
84
|
+
return now + DEFAULT_TTL_SECONDS;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function parseMaxAge(cacheControl) {
|
|
88
|
+
if (!cacheControl) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
const match = cacheControl.match(/max-age=(\d+)/i);
|
|
92
|
+
return match ? parseInt(match[1], 10) : null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (import.meta.main) {
|
|
96
|
+
const url = process.argv[2];
|
|
97
|
+
|
|
98
|
+
if (!url || url === '--help' || url === '-h') {
|
|
99
|
+
console.log(`Usage: fetch <url>
|
|
100
|
+
|
|
101
|
+
Fetches a URL with HTTP caching (ETags + Cache-Control/Expires).
|
|
102
|
+
Default TTL: ${DEFAULT_TTL_SECONDS / 60} minutes.
|
|
103
|
+
Cache is stored in: ${CACHE_DIRECTORY}/`);
|
|
104
|
+
process.exit(url ? 0 : 1);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const data = await fetchCached(url);
|
|
108
|
+
console.log(data);
|
|
109
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
4
|
+
import { resolve } from 'node:path';
|
|
5
|
+
import process from 'node:process';
|
|
6
|
+
|
|
7
|
+
import Ajv2020 from 'ajv/dist/2020.js';
|
|
8
|
+
import addFormats from 'ajv-formats';
|
|
9
|
+
import yaml from 'js-yaml';
|
|
10
|
+
|
|
11
|
+
import { fetchCached } from './fetch.js';
|
|
12
|
+
|
|
13
|
+
const SCHEMA_URL = 'https://api.expo.dev/v2/workflows/schema';
|
|
14
|
+
|
|
15
|
+
async function fetchSchema() {
|
|
16
|
+
const data = await fetchCached(SCHEMA_URL);
|
|
17
|
+
const body = JSON.parse(data);
|
|
18
|
+
return body.data;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function createValidator(schema) {
|
|
22
|
+
const ajv = new Ajv2020({ allErrors: true, strict: true });
|
|
23
|
+
addFormats(ajv);
|
|
24
|
+
return ajv.compile(schema);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function validateFile(validator, filePath) {
|
|
28
|
+
const content = await readFile(filePath, 'utf-8');
|
|
29
|
+
|
|
30
|
+
let doc;
|
|
31
|
+
try {
|
|
32
|
+
doc = yaml.load(content);
|
|
33
|
+
} catch (e) {
|
|
34
|
+
return { valid: false, error: `YAML parse error: ${e.message}` };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const valid = validator(doc);
|
|
38
|
+
if (!valid) {
|
|
39
|
+
return { valid: false, error: formatErrors(validator.errors) };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return { valid: true };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function formatErrors(errors) {
|
|
46
|
+
return errors
|
|
47
|
+
.map((error) => {
|
|
48
|
+
const path = error.instancePath || '(root)';
|
|
49
|
+
const allowed = error.params?.allowedValues?.join(', ');
|
|
50
|
+
return ` ${path}: ${error.message}${allowed ? ` (allowed: ${allowed})` : ''}`;
|
|
51
|
+
})
|
|
52
|
+
.join('\n');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (import.meta.main) {
|
|
56
|
+
const args = process.argv.slice(2);
|
|
57
|
+
const files = args.filter((a) => !a.startsWith('-'));
|
|
58
|
+
|
|
59
|
+
if (files.length === 0 || args.includes('--help') || args.includes('-h')) {
|
|
60
|
+
console.log(`Usage: validate <workflow.yml> [workflow2.yml ...]
|
|
61
|
+
|
|
62
|
+
Validates EAS workflow YAML files against the official schema.`);
|
|
63
|
+
process.exit(files.length === 0 ? 1 : 0);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const schema = await fetchSchema();
|
|
67
|
+
const validator = createValidator(schema);
|
|
68
|
+
|
|
69
|
+
let hasErrors = false;
|
|
70
|
+
|
|
71
|
+
for (const file of files) {
|
|
72
|
+
const filePath = resolve(process.cwd(), file);
|
|
73
|
+
const result = await validateFile(validator, filePath);
|
|
74
|
+
|
|
75
|
+
if (result.valid) {
|
|
76
|
+
console.log(`âś“ ${file}`);
|
|
77
|
+
} else {
|
|
78
|
+
console.error(`âś— ${file}\n${result.error}`);
|
|
79
|
+
hasErrors = true;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
process.exit(hasErrors ? 1 : 0);
|
|
84
|
+
}
|
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: data-fetching
|
|
3
|
+
description: >
|
|
4
|
+
Use when implementing or debugging ANY network request, API call, or data fetching. Covers fetch API, axios, React Query, SWR, error handling, caching strategies, offline support.
|
|
5
|
+
license: MIT
|
|
6
|
+
compatibility: Claude Code, Cursor, Gemini CLI, GitHub Copilot
|
|
7
|
+
metadata:
|
|
8
|
+
author: jhm1909
|
|
9
|
+
version: "2.0.0"
|
|
10
|
+
domain: mobile
|
|
11
|
+
estimated_tokens: 7000
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Expo Networking
|
|
15
|
+
|
|
16
|
+
## Knowledge Graph
|
|
17
|
+
|
|
18
|
+
- **extends**: [[mobile-developer]]
|
|
19
|
+
- **requires**: []
|
|
20
|
+
- **suggests**: [[api-routes]], [[backend-developer]]
|
|
21
|
+
- **conflicts**: []
|
|
22
|
+
- **enhances**: []
|
|
23
|
+
- **moc**: [[mobile-development-moc]]
|
|
24
|
+
|
|
25
|
+
## When to Use
|
|
26
|
+
|
|
27
|
+
**You MUST use this skill for ANY networking work including API requests, data fetching, caching, or network debugging.**
|
|
28
|
+
|
|
29
|
+
## When to Use
|
|
30
|
+
|
|
31
|
+
Use this router when:
|
|
32
|
+
|
|
33
|
+
- Implementing API requests
|
|
34
|
+
- Setting up data fetching (React Query, SWR)
|
|
35
|
+
- Debugging network failures
|
|
36
|
+
- Implementing caching strategies
|
|
37
|
+
- Handling offline scenarios
|
|
38
|
+
- Authentication/token management
|
|
39
|
+
- Configuring API URLs and environment variables
|
|
40
|
+
|
|
41
|
+
## Preferences
|
|
42
|
+
|
|
43
|
+
- Avoid axios, prefer expo/fetch
|
|
44
|
+
|
|
45
|
+
## Common Issues & Solutions
|
|
46
|
+
|
|
47
|
+
### 1. Basic Fetch Usage
|
|
48
|
+
|
|
49
|
+
**Simple GET request**:
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
const fetchUser = async (userId: string) => {
|
|
53
|
+
const response = await fetch(`https://api.example.com/users/${userId}`);
|
|
54
|
+
|
|
55
|
+
if (!response.ok) {
|
|
56
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return response.json();
|
|
60
|
+
};
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**POST request with body**:
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
const createUser = async (userData: UserData) => {
|
|
67
|
+
const response = await fetch("https://api.example.com/users", {
|
|
68
|
+
method: "POST",
|
|
69
|
+
headers: {
|
|
70
|
+
"Content-Type": "application/json",
|
|
71
|
+
Authorization: `Bearer ${token}`,
|
|
72
|
+
},
|
|
73
|
+
body: JSON.stringify(userData),
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
if (!response.ok) {
|
|
77
|
+
const error = await response.json();
|
|
78
|
+
throw new Error(error.message);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return response.json();
|
|
82
|
+
};
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
### 2. React Query (TanStack Query)
|
|
88
|
+
|
|
89
|
+
**Setup**:
|
|
90
|
+
|
|
91
|
+
```tsx
|
|
92
|
+
// app/_layout.tsx
|
|
93
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
94
|
+
|
|
95
|
+
const queryClient = new QueryClient({
|
|
96
|
+
defaultOptions: {
|
|
97
|
+
queries: {
|
|
98
|
+
staleTime: 1000 * 60 * 5, // 5 minutes
|
|
99
|
+
retry: 2,
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
export default function RootLayout() {
|
|
105
|
+
return (
|
|
106
|
+
<QueryClientProvider client={queryClient}>
|
|
107
|
+
<Stack />
|
|
108
|
+
</QueryClientProvider>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**Fetching data**:
|
|
114
|
+
|
|
115
|
+
```tsx
|
|
116
|
+
import { useQuery } from "@tanstack/react-query";
|
|
117
|
+
|
|
118
|
+
function UserProfile({ userId }: { userId: string }) {
|
|
119
|
+
const { data, isLoading, error, refetch } = useQuery({
|
|
120
|
+
queryKey: ["user", userId],
|
|
121
|
+
queryFn: () => fetchUser(userId),
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
if (isLoading) return <Loading />;
|
|
125
|
+
if (error) return <Error message={error.message} />;
|
|
126
|
+
|
|
127
|
+
return <Profile user={data} />;
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**Mutations**:
|
|
132
|
+
|
|
133
|
+
```tsx
|
|
134
|
+
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
135
|
+
|
|
136
|
+
function CreateUserForm() {
|
|
137
|
+
const queryClient = useQueryClient();
|
|
138
|
+
|
|
139
|
+
const mutation = useMutation({
|
|
140
|
+
mutationFn: createUser,
|
|
141
|
+
onSuccess: () => {
|
|
142
|
+
// Invalidate and refetch
|
|
143
|
+
queryClient.invalidateQueries({ queryKey: ["users"] });
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const handleSubmit = (data: UserData) => {
|
|
148
|
+
mutation.mutate(data);
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
return <Form onSubmit={handleSubmit} isLoading={mutation.isPending} />;
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
### 3. Error Handling
|
|
158
|
+
|
|
159
|
+
**Comprehensive error handling**:
|
|
160
|
+
|
|
161
|
+
```tsx
|
|
162
|
+
class ApiError extends Error {
|
|
163
|
+
constructor(message: string, public status: number, public code?: string) {
|
|
164
|
+
super(message);
|
|
165
|
+
this.name = "ApiError";
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const fetchWithErrorHandling = async (url: string, options?: RequestInit) => {
|
|
170
|
+
try {
|
|
171
|
+
const response = await fetch(url, options);
|
|
172
|
+
|
|
173
|
+
if (!response.ok) {
|
|
174
|
+
const error = await response.json().catch(() => ({}));
|
|
175
|
+
throw new ApiError(
|
|
176
|
+
error.message || "Request failed",
|
|
177
|
+
response.status,
|
|
178
|
+
error.code
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return response.json();
|
|
183
|
+
} catch (error) {
|
|
184
|
+
if (error instanceof ApiError) {
|
|
185
|
+
throw error;
|
|
186
|
+
}
|
|
187
|
+
// Network error (no internet, timeout, etc.)
|
|
188
|
+
throw new ApiError("Network error", 0, "NETWORK_ERROR");
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**Retry logic**:
|
|
194
|
+
|
|
195
|
+
```tsx
|
|
196
|
+
const fetchWithRetry = async (
|
|
197
|
+
url: string,
|
|
198
|
+
options?: RequestInit,
|
|
199
|
+
retries = 3
|
|
200
|
+
) => {
|
|
201
|
+
for (let i = 0; i < retries; i++) {
|
|
202
|
+
try {
|
|
203
|
+
return await fetchWithErrorHandling(url, options);
|
|
204
|
+
} catch (error) {
|
|
205
|
+
if (i === retries - 1) throw error;
|
|
206
|
+
// Exponential backoff
|
|
207
|
+
await new Promise((r) => setTimeout(r, Math.pow(2, i) * 1000));
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
### 4. Authentication
|
|
216
|
+
|
|
217
|
+
**Token management**:
|
|
218
|
+
|
|
219
|
+
```tsx
|
|
220
|
+
import * as SecureStore from "expo-secure-store";
|
|
221
|
+
|
|
222
|
+
const TOKEN_KEY = "auth_token";
|
|
223
|
+
|
|
224
|
+
export const auth = {
|
|
225
|
+
getToken: () => SecureStore.getItemAsync(TOKEN_KEY),
|
|
226
|
+
setToken: (token: string) => SecureStore.setItemAsync(TOKEN_KEY, token),
|
|
227
|
+
removeToken: () => SecureStore.deleteItemAsync(TOKEN_KEY),
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
// Authenticated fetch wrapper
|
|
231
|
+
const authFetch = async (url: string, options: RequestInit = {}) => {
|
|
232
|
+
const token = await auth.getToken();
|
|
233
|
+
|
|
234
|
+
return fetch(url, {
|
|
235
|
+
...options,
|
|
236
|
+
headers: {
|
|
237
|
+
...options.headers,
|
|
238
|
+
Authorization: token ? `Bearer ${token}` : "",
|
|
239
|
+
},
|
|
240
|
+
});
|
|
241
|
+
};
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
**Token refresh**:
|
|
245
|
+
|
|
246
|
+
```tsx
|
|
247
|
+
let isRefreshing = false;
|
|
248
|
+
let refreshPromise: Promise<string> | null = null;
|
|
249
|
+
|
|
250
|
+
const getValidToken = async (): Promise<string> => {
|
|
251
|
+
const token = await auth.getToken();
|
|
252
|
+
|
|
253
|
+
if (!token || isTokenExpired(token)) {
|
|
254
|
+
if (!isRefreshing) {
|
|
255
|
+
isRefreshing = true;
|
|
256
|
+
refreshPromise = refreshToken().finally(() => {
|
|
257
|
+
isRefreshing = false;
|
|
258
|
+
refreshPromise = null;
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
return refreshPromise!;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return token;
|
|
265
|
+
};
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
### 5. Offline Support
|
|
271
|
+
|
|
272
|
+
**Check network status**:
|
|
273
|
+
|
|
274
|
+
```tsx
|
|
275
|
+
import NetInfo from "@react-native-community/netinfo";
|
|
276
|
+
|
|
277
|
+
// Hook for network status
|
|
278
|
+
function useNetworkStatus() {
|
|
279
|
+
const [isOnline, setIsOnline] = useState(true);
|
|
280
|
+
|
|
281
|
+
useEffect(() => {
|
|
282
|
+
return NetInfo.addEventListener((state) => {
|
|
283
|
+
setIsOnline(state.isConnected ?? true);
|
|
284
|
+
});
|
|
285
|
+
}, []);
|
|
286
|
+
|
|
287
|
+
return isOnline;
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
**Offline-first with React Query**:
|
|
292
|
+
|
|
293
|
+
```tsx
|
|
294
|
+
import { onlineManager } from "@tanstack/react-query";
|
|
295
|
+
import NetInfo from "@react-native-community/netinfo";
|
|
296
|
+
|
|
297
|
+
// Sync React Query with network status
|
|
298
|
+
onlineManager.setEventListener((setOnline) => {
|
|
299
|
+
return NetInfo.addEventListener((state) => {
|
|
300
|
+
setOnline(state.isConnected ?? true);
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
// Queries will pause when offline and resume when online
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
### 6. Environment Variables
|
|
310
|
+
|
|
311
|
+
**Using environment variables for API configuration**:
|
|
312
|
+
|
|
313
|
+
Expo supports environment variables with the `EXPO_PUBLIC_` prefix. These are inlined at build time and available in your JavaScript code.
|
|
314
|
+
|
|
315
|
+
```tsx
|
|
316
|
+
// .env
|
|
317
|
+
EXPO_PUBLIC_API_URL=https://api.example.com
|
|
318
|
+
EXPO_PUBLIC_API_VERSION=v1
|
|
319
|
+
|
|
320
|
+
// Usage in code
|
|
321
|
+
const API_URL = process.env.EXPO_PUBLIC_API_URL;
|
|
322
|
+
|
|
323
|
+
const fetchUsers = async () => {
|
|
324
|
+
const response = await fetch(`${API_URL}/users`);
|
|
325
|
+
return response.json();
|
|
326
|
+
};
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
**Environment-specific configuration**:
|
|
330
|
+
|
|
331
|
+
```tsx
|
|
332
|
+
// .env.development
|
|
333
|
+
EXPO_PUBLIC_API_URL=http://localhost:3000
|
|
334
|
+
|
|
335
|
+
// .env.production
|
|
336
|
+
EXPO_PUBLIC_API_URL=https://api.production.com
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
**Creating an API client with environment config**:
|
|
340
|
+
|
|
341
|
+
```tsx
|
|
342
|
+
// api/client.ts
|
|
343
|
+
const BASE_URL = process.env.EXPO_PUBLIC_API_URL;
|
|
344
|
+
|
|
345
|
+
if (!BASE_URL) {
|
|
346
|
+
throw new Error("EXPO_PUBLIC_API_URL is not defined");
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
export const apiClient = {
|
|
350
|
+
get: async <T,>(path: string): Promise<T> => {
|
|
351
|
+
const response = await fetch(`${BASE_URL}${path}`);
|
|
352
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
353
|
+
return response.json();
|
|
354
|
+
},
|
|
355
|
+
|
|
356
|
+
post: async <T,>(path: string, body: unknown): Promise<T> => {
|
|
357
|
+
const response = await fetch(`${BASE_URL}${path}`, {
|
|
358
|
+
method: "POST",
|
|
359
|
+
headers: { "Content-Type": "application/json" },
|
|
360
|
+
body: JSON.stringify(body),
|
|
361
|
+
});
|
|
362
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
363
|
+
return response.json();
|
|
364
|
+
},
|
|
365
|
+
};
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
**Important notes**:
|
|
369
|
+
|
|
370
|
+
- Only variables prefixed with `EXPO_PUBLIC_` are exposed to the client bundle
|
|
371
|
+
- Never put secrets (API keys with write access, database passwords) in `EXPO_PUBLIC_` variables—they're visible in the built app
|
|
372
|
+
- Environment variables are inlined at **build time**, not runtime
|
|
373
|
+
- Restart the dev server after changing `.env` files
|
|
374
|
+
- For server-side secrets in API routes, use variables without the `EXPO_PUBLIC_` prefix
|
|
375
|
+
|
|
376
|
+
**TypeScript support**:
|
|
377
|
+
|
|
378
|
+
```tsx
|
|
379
|
+
// types/env.d.ts
|
|
380
|
+
declare global {
|
|
381
|
+
namespace NodeJS {
|
|
382
|
+
interface ProcessEnv {
|
|
383
|
+
EXPO_PUBLIC_API_URL: string;
|
|
384
|
+
EXPO_PUBLIC_API_VERSION?: string;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
export {};
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
---
|
|
393
|
+
|
|
394
|
+
### 7. Request Cancellation
|
|
395
|
+
|
|
396
|
+
**Cancel on unmount**:
|
|
397
|
+
|
|
398
|
+
```tsx
|
|
399
|
+
useEffect(() => {
|
|
400
|
+
const controller = new AbortController();
|
|
401
|
+
|
|
402
|
+
fetch(url, { signal: controller.signal })
|
|
403
|
+
.then((response) => response.json())
|
|
404
|
+
.then(setData)
|
|
405
|
+
.catch((error) => {
|
|
406
|
+
if (error.name !== "AbortError") {
|
|
407
|
+
setError(error);
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
return () => controller.abort();
|
|
412
|
+
}, [url]);
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
**With React Query** (automatic):
|
|
416
|
+
|
|
417
|
+
```tsx
|
|
418
|
+
// React Query automatically cancels requests when queries are invalidated
|
|
419
|
+
// or components unmount
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
---
|
|
423
|
+
|
|
424
|
+
## Decision Tree
|
|
425
|
+
|
|
426
|
+
```
|
|
427
|
+
User asks about networking
|
|
428
|
+
|-- Basic fetch?
|
|
429
|
+
| \-- Use fetch API with error handling
|
|
430
|
+
|
|
|
431
|
+
|-- Need caching/state management?
|
|
432
|
+
| |-- Complex app -> React Query (TanStack Query)
|
|
433
|
+
| \-- Simpler needs -> SWR or custom hooks
|
|
434
|
+
|
|
|
435
|
+
|-- Authentication?
|
|
436
|
+
| |-- Token storage -> expo-secure-store
|
|
437
|
+
| \-- Token refresh -> Implement refresh flow
|
|
438
|
+
|
|
|
439
|
+
|-- Error handling?
|
|
440
|
+
| |-- Network errors -> Check connectivity first
|
|
441
|
+
| |-- HTTP errors -> Parse response, throw typed errors
|
|
442
|
+
| \-- Retries -> Exponential backoff
|
|
443
|
+
|
|
|
444
|
+
|-- Offline support?
|
|
445
|
+
| |-- Check status -> NetInfo
|
|
446
|
+
| \-- Queue requests -> React Query persistence
|
|
447
|
+
|
|
|
448
|
+
|-- Environment/API config?
|
|
449
|
+
| |-- Client-side URLs -> EXPO_PUBLIC_ prefix in .env
|
|
450
|
+
| |-- Server secrets -> Non-prefixed env vars (API routes only)
|
|
451
|
+
| \-- Multiple environments -> .env.development, .env.production
|
|
452
|
+
|
|
|
453
|
+
\-- Performance?
|
|
454
|
+
|-- Caching -> React Query with staleTime
|
|
455
|
+
|-- Deduplication -> React Query handles this
|
|
456
|
+
\-- Cancellation -> AbortController or React Query
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
## Common Mistakes
|
|
460
|
+
|
|
461
|
+
**Wrong: No error handling**
|
|
462
|
+
|
|
463
|
+
```tsx
|
|
464
|
+
const data = await fetch(url).then((r) => r.json());
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
**Right: Check response status**
|
|
468
|
+
|
|
469
|
+
```tsx
|
|
470
|
+
const response = await fetch(url);
|
|
471
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
472
|
+
const data = await response.json();
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
**Wrong: Storing tokens in AsyncStorage**
|
|
476
|
+
|
|
477
|
+
```tsx
|
|
478
|
+
await AsyncStorage.setItem("token", token); // Not secure!
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
**Right: Use SecureStore for sensitive data**
|
|
482
|
+
|
|
483
|
+
```tsx
|
|
484
|
+
await SecureStore.setItemAsync("token", token);
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
## Example Invocations
|
|
488
|
+
|
|
489
|
+
User: "How do I make API calls in React Native?"
|
|
490
|
+
-> Use fetch, wrap with error handling
|
|
491
|
+
|
|
492
|
+
User: "Should I use React Query or SWR?"
|
|
493
|
+
-> React Query for complex apps, SWR for simpler needs
|
|
494
|
+
|
|
495
|
+
User: "My app needs to work offline"
|
|
496
|
+
-> Use NetInfo for status, React Query persistence for caching
|
|
497
|
+
|
|
498
|
+
User: "How do I handle authentication tokens?"
|
|
499
|
+
-> Store in expo-secure-store, implement refresh flow
|
|
500
|
+
|
|
501
|
+
User: "API calls are slow"
|
|
502
|
+
-> Check caching strategy, use React Query staleTime
|
|
503
|
+
|
|
504
|
+
User: "How do I configure different API URLs for dev and prod?"
|
|
505
|
+
-> Use EXPO*PUBLIC* env vars with .env.development and .env.production files
|
|
506
|
+
|
|
507
|
+
User: "Where should I put my API key?"
|
|
508
|
+
-> Client-safe keys: EXPO*PUBLIC* in .env. Secret keys: non-prefixed env vars in API routes only
|