oh-my-agent-commandcode 0.0.0-stage → 20.38.4
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 +35 -2
- package/filter-test-output.sh +52 -0
- package/mods/oma.ts +347 -0
- package/package.json +30 -4
- package/skills/_shared/conditional/experiment-ledger.md +28 -0
- package/skills/_shared/conditional/exploration-loop.md +22 -0
- package/skills/_shared/conditional/quality-score.md +33 -0
- package/skills/_shared/core/anti-ai-prose.md +191 -0
- package/skills/_shared/core/api-contracts/README.md +28 -0
- package/skills/_shared/core/api-contracts/template.md +90 -0
- package/skills/_shared/core/clarification-protocol.md +23 -0
- package/skills/_shared/core/code-intelligence.md +46 -0
- package/skills/_shared/core/collaboration-policy.md +28 -0
- package/skills/_shared/core/common-checklist.md +28 -0
- package/skills/_shared/core/context-budget.md +22 -0
- package/skills/_shared/core/context-loading.md +34 -0
- package/skills/_shared/core/difficulty-guide.md +26 -0
- package/skills/_shared/core/execution-policy.md +31 -0
- package/skills/_shared/core/lessons-learned.md +33 -0
- package/skills/_shared/core/prompt-structure.md +18 -0
- package/skills/_shared/core/quality-principles.md +11 -0
- package/skills/_shared/core/session-metrics.md +35 -0
- package/skills/_shared/core/skill-routing.md +39 -0
- package/skills/_shared/core/test-approach.md +54 -0
- package/skills/_shared/core/vendor-detection.md +52 -0
- package/skills/_shared/runtime/memory-protocol.md +118 -0
- package/skills/_shared/runtime/result-contract.md +64 -0
- package/skills/_shared/ui-interaction-quality.md +133 -0
- package/skills/oma-academic-writing/SKILL.md +181 -0
- package/skills/oma-academic-writing/resources/abstract-guide.md +51 -0
- package/skills/oma-academic-writing/resources/academic-verb-tiers.md +42 -0
- package/skills/oma-academic-writing/resources/anti-ai-checklist.md +52 -0
- package/skills/oma-academic-writing/resources/hedging-guide.md +132 -0
- package/skills/oma-academic-writing/resources/sentence-structure-reference.md +151 -0
- package/skills/oma-architecture/SKILL.md +203 -0
- package/skills/oma-architecture/resources/api-evolution.md +41 -0
- package/skills/oma-architecture/resources/checklist.md +42 -0
- package/skills/oma-architecture/resources/execution-protocol.md +115 -0
- package/skills/oma-architecture/resources/methodology-selection.md +92 -0
- package/skills/oma-architecture/resources/migration-patterns.md +28 -0
- package/skills/oma-architecture/resources/output-templates.md +111 -0
- package/skills/oma-architecture/resources/stakeholder-synthesis.md +80 -0
- package/skills/oma-backend/SKILL.md +202 -0
- package/skills/oma-backend/resources/checklist.md +45 -0
- package/skills/oma-backend/resources/error-playbook.md +97 -0
- package/skills/oma-backend/resources/execution-protocol.md +39 -0
- package/skills/oma-backend/resources/orm-reference.md +61 -0
- package/skills/oma-backend/variants/node/api-template.ts +315 -0
- package/skills/oma-backend/variants/node/snippets.md +475 -0
- package/skills/oma-backend/variants/node/stack.yaml +21 -0
- package/skills/oma-backend/variants/node/tech-stack.md +33 -0
- package/skills/oma-backend/variants/python/api-template.py +378 -0
- package/skills/oma-backend/variants/python/snippets.md +199 -0
- package/skills/oma-backend/variants/python/stack.yaml +18 -0
- package/skills/oma-backend/variants/python/tech-stack.md +45 -0
- package/skills/oma-backend/variants/rust/api-template.rs +635 -0
- package/skills/oma-backend/variants/rust/snippets.md +471 -0
- package/skills/oma-backend/variants/rust/stack.yaml +20 -0
- package/skills/oma-backend/variants/rust/tech-stack.md +36 -0
- package/skills/oma-backend/variants/stack.schema.json +73 -0
- package/skills/oma-brainstorm/SKILL.md +166 -0
- package/skills/oma-brainstorm/resources/triz-lite.md +117 -0
- package/skills/oma-coordination/SKILL.md +171 -0
- package/skills/oma-db/SKILL.md +201 -0
- package/skills/oma-db/resources/anti-patterns.md +177 -0
- package/skills/oma-db/resources/checklist.md +67 -0
- package/skills/oma-db/resources/document-templates.md +107 -0
- package/skills/oma-db/resources/error-playbook.md +50 -0
- package/skills/oma-db/resources/examples.md +170 -0
- package/skills/oma-db/resources/execution-protocol.md +127 -0
- package/skills/oma-db/resources/iso-controls.md +98 -0
- package/skills/oma-db/resources/migration-playbook.md +93 -0
- package/skills/oma-db/resources/query-tuning.md +57 -0
- package/skills/oma-db/resources/vector-db.md +159 -0
- package/skills/oma-debug/SKILL.md +146 -0
- package/skills/oma-debug/resources/bug-report-template.md +327 -0
- package/skills/oma-debug/resources/checklist.md +31 -0
- package/skills/oma-debug/resources/common-patterns.md +734 -0
- package/skills/oma-debug/resources/debugging-checklist.md +364 -0
- package/skills/oma-debug/resources/error-playbook.md +94 -0
- package/skills/oma-debug/resources/execution-protocol.md +40 -0
- package/skills/oma-design/SKILL.md +135 -0
- package/skills/oma-design/resources/asset-strategy.md +34 -0
- package/skills/oma-design/resources/checklist.md +63 -0
- package/skills/oma-design/resources/data-grid.md +48 -0
- package/skills/oma-design/resources/design-md-spec.md +107 -0
- package/skills/oma-design/resources/design-tokens.md +53 -0
- package/skills/oma-design/resources/error-playbook.md +18 -0
- package/skills/oma-design/resources/execution-protocol.md +95 -0
- package/skills/oma-design/resources/external-design-skills.md +130 -0
- package/skills/oma-design/resources/getdesign-fetcher.md +104 -0
- package/skills/oma-design/resources/product-interface-review.md +59 -0
- package/skills/oma-design/resources/redesign-protocol.md +52 -0
- package/skills/oma-design/resources/stitch-integration.md +244 -0
- package/skills/oma-dev-workflow/SKILL.md +375 -0
- package/skills/oma-dev-workflow/resources/affected-checks.py +58 -0
- package/skills/oma-dev-workflow/resources/api-workflows.md +58 -0
- package/skills/oma-dev-workflow/resources/database-patterns.md +107 -0
- package/skills/oma-dev-workflow/resources/i18n-patterns.md +63 -0
- package/skills/oma-dev-workflow/resources/release-coordination.md +240 -0
- package/skills/oma-dev-workflow/resources/tooling-profiles.md +217 -0
- package/skills/oma-dev-workflow/resources/troubleshooting.md +78 -0
- package/skills/oma-dev-workflow/resources/validation-pipeline.md +246 -0
- package/skills/oma-docs/SKILL.md +77 -0
- package/skills/oma-docs/resources/commands.md +76 -0
- package/skills/oma-explanation/SKILL.md +212 -0
- package/skills/oma-explanation/resources/document-structure.md +115 -0
- package/skills/oma-explanation/resources/draft-format.md +199 -0
- package/skills/oma-explanation/resources/html-contract.md +102 -0
- package/skills/oma-frontend/SKILL.md +227 -0
- package/skills/oma-frontend/resources/angular-rules.md +110 -0
- package/skills/oma-frontend/resources/checklist.md +76 -0
- package/skills/oma-frontend/resources/error-playbook.md +125 -0
- package/skills/oma-frontend/resources/execution-protocol.md +46 -0
- package/skills/oma-frontend/resources/snippets.md +354 -0
- package/skills/oma-frontend/resources/specialized-visual-implementation.md +193 -0
- package/skills/oma-frontend/resources/tailwind-rules.md +83 -0
- package/skills/oma-frontend/resources/tech-stack.md +156 -0
- package/skills/oma-hwp/SKILL.md +166 -0
- package/skills/oma-hwp/config/hwp-config.yaml +35 -0
- package/skills/oma-hwp/resources/balanced-tables.ts +39 -0
- package/skills/oma-hwp/resources/execution-protocol.md +145 -0
- package/skills/oma-hwp/resources/flatten-tables.ts +101 -0
- package/skills/oma-hwp/resources/package.json +13 -0
- package/skills/oma-hwp/resources/troubleshooting.md +70 -0
- package/skills/oma-hwp/resources/tsconfig.json +10 -0
- package/skills/oma-hwp/resources/turndown-plugin-gfm.d.ts +9 -0
- package/skills/oma-image/SKILL.md +142 -0
- package/skills/oma-image/config/image-config.yaml +70 -0
- package/skills/oma-image/resources/checklist.md +18 -0
- package/skills/oma-image/resources/execution-protocol.md +117 -0
- package/skills/oma-image/resources/invocation.md +105 -0
- package/skills/oma-image/resources/prompt-tips.md +71 -0
- package/skills/oma-image/resources/vendor-matrix.md +90 -0
- package/skills/oma-market/SKILL.md +142 -0
- package/skills/oma-market/resources/checklist.md +12 -0
- package/skills/oma-market/resources/error-playbook.md +12 -0
- package/skills/oma-market/resources/execution-protocol.md +60 -0
- package/skills/oma-market/resources/frameworks/pestel.md +101 -0
- package/skills/oma-market/resources/frameworks/porters-5f.md +90 -0
- package/skills/oma-market/resources/frameworks/swot.md +61 -0
- package/skills/oma-market/resources/intent-rules.md +35 -0
- package/skills/oma-market/resources/output-laws.md +53 -0
- package/skills/oma-mobile/SKILL.md +168 -0
- package/skills/oma-mobile/resources/checklist.md +77 -0
- package/skills/oma-mobile/resources/error-playbook.md +198 -0
- package/skills/oma-mobile/resources/execution-protocol.md +47 -0
- package/skills/oma-mobile/resources/screen-template.dart +331 -0
- package/skills/oma-mobile/resources/screen-template.swift +239 -0
- package/skills/oma-mobile/resources/screen-template.tsx +179 -0
- package/skills/oma-mobile/resources/tech-stack.md +69 -0
- package/skills/oma-mobile/variants/README.md +45 -0
- package/skills/oma-mobile/variants/flutter/api-template.dart +461 -0
- package/skills/oma-mobile/variants/flutter/snippets.md +1353 -0
- package/skills/oma-mobile/variants/flutter/stack.yaml +21 -0
- package/skills/oma-mobile/variants/flutter/tech-stack.md +201 -0
- package/skills/oma-mobile/variants/kotlin-android/api-template.kt +182 -0
- package/skills/oma-mobile/variants/kotlin-android/snippets.md +871 -0
- package/skills/oma-mobile/variants/kotlin-android/stack.yaml +32 -0
- package/skills/oma-mobile/variants/kotlin-android/tech-stack.md +159 -0
- package/skills/oma-mobile/variants/react-native/api-template.ts +531 -0
- package/skills/oma-mobile/variants/react-native/snippets.md +1014 -0
- package/skills/oma-mobile/variants/react-native/stack.yaml +22 -0
- package/skills/oma-mobile/variants/react-native/tech-stack.md +192 -0
- package/skills/oma-mobile/variants/stack.schema.json +68 -0
- package/skills/oma-mobile/variants/swift-ios/api-template.swift +165 -0
- package/skills/oma-mobile/variants/swift-ios/snippets.md +1028 -0
- package/skills/oma-mobile/variants/swift-ios/stack.yaml +25 -0
- package/skills/oma-mobile/variants/swift-ios/tech-stack.md +174 -0
- package/skills/oma-observability/SKILL.md +225 -0
- package/skills/oma-observability/resources/anti-patterns.md +555 -0
- package/skills/oma-observability/resources/boundaries/cross-application.md +348 -0
- package/skills/oma-observability/resources/boundaries/multi-tenant.md +290 -0
- package/skills/oma-observability/resources/boundaries/release.md +372 -0
- package/skills/oma-observability/resources/boundaries/slo.md +225 -0
- package/skills/oma-observability/resources/checklist.md +123 -0
- package/skills/oma-observability/resources/examples.md +448 -0
- package/skills/oma-observability/resources/execution-protocol.md +166 -0
- package/skills/oma-observability/resources/incident-forensics.md +348 -0
- package/skills/oma-observability/resources/intent-rules.md +307 -0
- package/skills/oma-observability/resources/layers/L3-network.md +281 -0
- package/skills/oma-observability/resources/layers/L4-transport.md +299 -0
- package/skills/oma-observability/resources/layers/L7-application/crash-analytics.md +359 -0
- package/skills/oma-observability/resources/layers/L7-application/mobile-rum.md +278 -0
- package/skills/oma-observability/resources/layers/L7-application/waf.md +246 -0
- package/skills/oma-observability/resources/layers/L7-application/web-rum.md +350 -0
- package/skills/oma-observability/resources/layers/mesh.md +368 -0
- package/skills/oma-observability/resources/matrix.md +177 -0
- package/skills/oma-observability/resources/meta-observability.md +605 -0
- package/skills/oma-observability/resources/observability-as-code.md +666 -0
- package/skills/oma-observability/resources/signals/audit.md +310 -0
- package/skills/oma-observability/resources/signals/cost.md +303 -0
- package/skills/oma-observability/resources/signals/logs.md +263 -0
- package/skills/oma-observability/resources/signals/metrics.md +478 -0
- package/skills/oma-observability/resources/signals/privacy.md +351 -0
- package/skills/oma-observability/resources/signals/profiles.md +245 -0
- package/skills/oma-observability/resources/signals/traces.md +360 -0
- package/skills/oma-observability/resources/standards.md +214 -0
- package/skills/oma-observability/resources/transport/collector-topology.md +308 -0
- package/skills/oma-observability/resources/transport/otlp-grpc-vs-http.md +256 -0
- package/skills/oma-observability/resources/transport/sampling-recipes.md +297 -0
- package/skills/oma-observability/resources/transport/udp-statsd-mtu.md +167 -0
- package/skills/oma-observability/resources/vendor-categories.md +352 -0
- package/skills/oma-orchestration/SKILL.md +302 -0
- package/skills/oma-orchestration/config/cli-config.yaml +92 -0
- package/skills/oma-orchestration/resources/memory-schema.md +232 -0
- package/skills/oma-orchestration/resources/subagent-prompt-template.md +30 -0
- package/skills/oma-orchestration/scripts/parallel-run.sh +6 -0
- package/skills/oma-orchestration/scripts/spawn-agent.sh +5 -0
- package/skills/oma-orchestration/scripts/verify.sh +20 -0
- package/skills/oma-orchestration/templates/backend-task.md +19 -0
- package/skills/oma-orchestration/templates/debug-task.md +16 -0
- package/skills/oma-orchestration/templates/frontend-task.md +18 -0
- package/skills/oma-orchestration/templates/mobile-task.md +18 -0
- package/skills/oma-orchestration/templates/qa-task.md +16 -0
- package/skills/oma-orchestration/templates/tasks-example.yaml +15 -0
- package/skills/oma-pdf/SKILL.md +162 -0
- package/skills/oma-pdf/config/pdf-config.yaml +27 -0
- package/skills/oma-pdf/resources/execution-protocol.md +127 -0
- package/skills/oma-pm/SKILL.md +157 -0
- package/skills/oma-pm/resources/error-playbook.md +75 -0
- package/skills/oma-pm/resources/examples.md +439 -0
- package/skills/oma-pm/resources/execution-protocol.md +62 -0
- package/skills/oma-pm/resources/iso-planning.md +110 -0
- package/skills/oma-pm/resources/plan-phase-protocol.md +66 -0
- package/skills/oma-pm/resources/task-template.json +86 -0
- package/skills/oma-qa/SKILL.md +141 -0
- package/skills/oma-qa/resources/checklist.md +339 -0
- package/skills/oma-qa/resources/error-playbook.md +98 -0
- package/skills/oma-qa/resources/execution-protocol.md +169 -0
- package/skills/oma-qa/resources/iso-quality.md +82 -0
- package/skills/oma-qa/resources/verify-ship-protocol.md +116 -0
- package/skills/oma-recap/SKILL.md +245 -0
- package/skills/oma-recap/resources/output-formats.md +83 -0
- package/skills/oma-refactor/SKILL.md +184 -0
- package/skills/oma-refactor/resources/definition.md +108 -0
- package/skills/oma-refactor/resources/governance.md +33 -0
- package/skills/oma-refactor/resources/hotspots.py +44 -0
- package/skills/oma-refactor/resources/measurement.md +93 -0
- package/skills/oma-scholar/SKILL.md +276 -0
- package/skills/oma-scholar/config/scholar-config.yaml +79 -0
- package/skills/oma-scholar/resources/api-endpoints.md +174 -0
- package/skills/oma-scholar/resources/checklist.md +92 -0
- package/skills/oma-scholar/resources/execution-protocol.md +224 -0
- package/skills/oma-scholar/resources/fallback-providers.md +143 -0
- package/skills/oma-scholar/resources/setup-openalex.md +85 -0
- package/skills/oma-scholar/resources/sidecar-spec.md +341 -0
- package/skills/oma-scholar/resources/upstream-spec-cache.md +972 -0
- package/skills/oma-scm/SKILL.md +103 -0
- package/skills/oma-scm/resources/cm-operations.md +56 -0
- package/skills/oma-scm/resources/codeowners-playbook.md +45 -0
- package/skills/oma-scm/resources/conventional-commits.md +108 -0
- package/skills/oma-scm/resources/merge-risk.md +76 -0
- package/skills/oma-scm/resources/onboarding-risk-signals.md +71 -0
- package/skills/oma-search/SKILL.md +125 -0
- package/skills/oma-search/resources/checklist.md +36 -0
- package/skills/oma-search/resources/error-playbook.md +66 -0
- package/skills/oma-search/resources/examples.md +80 -0
- package/skills/oma-search/resources/execution-protocol.md +86 -0
- package/skills/oma-search/resources/intent-rules.md +47 -0
- package/skills/oma-search/resources/trust-registry.md +53 -0
- package/skills/oma-security/SKILL.md +172 -0
- package/skills/oma-security/resources/artex.md +108 -0
- package/skills/oma-security/resources/ci.md +134 -0
- package/skills/oma-security/resources/cisco-source.md +108 -0
- package/skills/oma-security/resources/deepsec-config.md +45 -0
- package/skills/oma-security/resources/deepsec-matchers.md +35 -0
- package/skills/oma-security/resources/deepsec-scanning.md +45 -0
- package/skills/oma-security/resources/deepsec-setup.md +41 -0
- package/skills/oma-security/resources/findings-contract.md +408 -0
- package/skills/oma-security/resources/mcp-scanning.md +122 -0
- package/skills/oma-security/resources/skill-scanning.md +106 -0
- package/skills/oma-security/resources/validation.md +71 -0
- package/skills/oma-skill-creation/SKILL.md +84 -0
- package/skills/oma-skill-creation/resources/prompt-evaluation.md +36 -0
- package/skills/oma-skill-creation/resources/ssl-lite-template.md +108 -0
- package/skills/oma-skill-creation/resources/validation-checklist.md +101 -0
- package/skills/oma-slide/SKILL.md +277 -0
- package/skills/oma-slide/resources/animation-patterns.md +245 -0
- package/skills/oma-slide/resources/assets/deck-stage.js +514 -0
- package/skills/oma-slide/resources/assets/viewport-base.css +257 -0
- package/skills/oma-slide/resources/canva-integration.md +203 -0
- package/skills/oma-slide/resources/checklist.md +46 -0
- package/skills/oma-slide/resources/design-doctrine.md +260 -0
- package/skills/oma-slide/resources/fixed-stage.md +232 -0
- package/skills/oma-slide/resources/generation-protocol.md +393 -0
- package/skills/oma-slide/resources/native-pptx.md +84 -0
- package/skills/oma-slide/resources/selection-index.json +461 -0
- package/skills/oma-slide/resources/style-presets.LICENSE +10 -0
- package/skills/oma-slide/resources/style-presets.md +520 -0
- package/skills/oma-tf-infra/SKILL.md +201 -0
- package/skills/oma-tf-infra/resources/checklist.md +53 -0
- package/skills/oma-tf-infra/resources/cost-optimization.md +574 -0
- package/skills/oma-tf-infra/resources/error-playbook.md +68 -0
- package/skills/oma-tf-infra/resources/examples.md +38 -0
- package/skills/oma-tf-infra/resources/execution-protocol.md +129 -0
- package/skills/oma-tf-infra/resources/iso-42001-infra.md +229 -0
- package/skills/oma-tf-infra/resources/multi-cloud-examples.md +241 -0
- package/skills/oma-tf-infra/resources/policy-testing-examples.md +332 -0
- package/skills/oma-translation/SKILL.md +229 -0
- package/skills/oma-translation/resources/anti-ai-patterns.md +104 -0
- package/skills/oma-translation/resources/lang/_template.md +73 -0
- package/skills/oma-translation/resources/lang/en.md +123 -0
- package/skills/oma-translation/resources/lang/ja.md +274 -0
- package/skills/oma-translation/resources/lang/ko.md +228 -0
- package/skills/oma-translation/resources/lang/zh.md +285 -0
- package/skills/oma-translation/resources/style-skills.md +104 -0
- package/skills/oma-translation/resources/translation-rubric.md +126 -0
- package/skills/oma-video/SKILL.md +229 -0
- package/skills/oma-video/config/video-config.yaml +60 -0
- package/skills/oma-video/resources/checklist.md +26 -0
- package/skills/oma-video/resources/execution-protocol.md +122 -0
- package/skills/oma-video/resources/existing-media.md +185 -0
- package/skills/oma-video/resources/hyperframes-authoring/README.md +44 -0
- package/skills/oma-video/resources/hyperframes-authoring/demo.md +31 -0
- package/skills/oma-video/resources/hyperframes-authoring/explainer.md +10 -0
- package/skills/oma-video/resources/hyperframes-authoring/shorts.md +10 -0
- package/skills/oma-video/resources/motion-principles.md +81 -0
- package/skills/oma-video/resources/mpt/driver.py +301 -0
- package/skills/oma-video/resources/prompt-tips.md +75 -0
- package/skills/oma-video/resources/script-schema.md +81 -0
- package/skills/oma-video/resources/sound-design.md +127 -0
- package/skills/oma-video/resources/strudel/package.json +11 -0
- package/skills/oma-video/resources/strudel/page.html +87 -0
- package/skills/oma-video/resources/strudel/render.mjs +295 -0
- package/skills/oma-video/resources/vendor-matrix.md +124 -0
- package/skills/oma-voice/SKILL.md +249 -0
- package/skills/oma-voice/config/voice-config.yaml +30 -0
- package/skills/oma-voice/resources/audio-processing.md +119 -0
- package/skills/oma-voice/resources/checklist.md +47 -0
- package/skills/oma-voice/resources/execution-protocol.md +215 -0
- package/skills/oma-voice/resources/prompt-tips.md +43 -0
- package/skills/oma-voice/resources/voice-matrix.md +51 -0
- package/skills/oma-voice/resources/voicestudio.md +87 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# yaml-language-server: $schema=../stack.schema.json
|
|
2
|
+
language: react-native
|
|
3
|
+
framework: react-native
|
|
4
|
+
ui: react-native
|
|
5
|
+
state: zustand
|
|
6
|
+
navigation: react-navigation
|
|
7
|
+
http_client: axios
|
|
8
|
+
data_fetching: tanstack-query
|
|
9
|
+
local_storage: mmkv
|
|
10
|
+
response_cache: tanstack-query
|
|
11
|
+
structure: src/api,src/features/<feature>/{model,ui,queries.ts,mutations.ts},src/shared,src/navigation,src/store,e2e
|
|
12
|
+
source: variant
|
|
13
|
+
|
|
14
|
+
verify:
|
|
15
|
+
detect: package.json
|
|
16
|
+
syntax:
|
|
17
|
+
cmd: "npx tsc --noEmit"
|
|
18
|
+
skip_if_missing: npx
|
|
19
|
+
tests:
|
|
20
|
+
cmd: "npx jest --ci"
|
|
21
|
+
pass_signal: "Tests:"
|
|
22
|
+
skip_if_missing: npx
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# Mobile Agent - Tech Stack Reference (React Native)
|
|
2
|
+
|
|
3
|
+
Starter reference for the selected platform. Preserve existing project choices. The caching implementation below applies only when caching is required; do not add it for an unrelated screen or widget change.
|
|
4
|
+
|
|
5
|
+
## Framework: React Native + TypeScript
|
|
6
|
+
|
|
7
|
+
- **Language**: TypeScript (strict mode; `"strict": true` in `tsconfig.json`)
|
|
8
|
+
- **UI Framework**: React Native 0.86 with React 19 (core components); Expo is the recommended toolchain for new projects (managed or bare workflow)
|
|
9
|
+
- **Architecture**: New Architecture (Fabric + TurboModules) — default since RN 0.76 and the only mode from 0.82 onward
|
|
10
|
+
- **Minimum OS**: iOS 16.0 / Android API 26 (Oreo)
|
|
11
|
+
- **Tooling**: Metro bundler, Expo CLI or React Native CLI, React Native DevTools for dev debugging
|
|
12
|
+
|
|
13
|
+
React Native renders to native UI components — not a WebView or canvas. Expo adds a curated native module layer, OTA updates, and a first-class build service (EAS Build), which removes native toolchain friction for most projects. Bare workflow gives full native access when Expo modules do not cover a capability.
|
|
14
|
+
|
|
15
|
+
## State Management: Zustand
|
|
16
|
+
|
|
17
|
+
Client/UI state is managed with **Zustand**. Zustand is a minimal, hook-first store with no boilerplate and no context wrapping required. Redux Toolkit (RTK) is the heavier alternative for teams that need time-travel debugging, a stricter action/reducer discipline, or RTK Query integration.
|
|
18
|
+
|
|
19
|
+
| Concern | Tool |
|
|
20
|
+
|---------|------|
|
|
21
|
+
| Client / UI state | Zustand |
|
|
22
|
+
| Server state + caching | TanStack Query (see Response Cache section) |
|
|
23
|
+
| Heavy team state / RTK Query | Redux Toolkit (alternative) |
|
|
24
|
+
|
|
25
|
+
Zustand stores live in `src/store/`. Keep stores small and feature-scoped — one store per feature domain is preferred over a single monolithic store.
|
|
26
|
+
|
|
27
|
+
## HTTP Transport: Axios
|
|
28
|
+
|
|
29
|
+
<!-- oma-docs:ignore-start -->
|
|
30
|
+
Network calls are made through a **singleton Axios instance** configured in `src/api/client.ts`. The instance wires request interceptors for bearer-token injection and response interceptors for 401 handling / token refresh. Retry logic (exponential back-off) is attached via `axios-retry`.
|
|
31
|
+
<!-- oma-docs:ignore-end -->
|
|
32
|
+
|
|
33
|
+
**Transport vs. server-state distinction.** Axios is a transport concern only — it sends and receives bytes. It knows nothing about caching, stale data, or background revalidation. TanStack Query (below) owns those concerns. React components never import the Axios instance directly; they consume typed hooks built on TanStack Query.
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
axios instance (transport)
|
|
37
|
+
└── used by: src/api/*.ts (typed data functions)
|
|
38
|
+
└── consumed by: TanStack Query hooks (useQuery / useMutation)
|
|
39
|
+
└── consumed by: Screens / components
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
See `snippets.md §3` for the canonical Axios instance and auth interceptor.
|
|
43
|
+
|
|
44
|
+
## Response Cache: TanStack Query (@tanstack/react-query)
|
|
45
|
+
|
|
46
|
+
**When response caching is required, keep it at the data-fetching layer.** TanStack Query owns the repository-layer cache: it caches **decoded JavaScript objects** (not raw response bytes), provides stale-while-revalidate out of the box, and centralizes invalidation so the data-fetching layer is the single source of caching truth.
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
Screen / Component
|
|
50
|
+
| renders from
|
|
51
|
+
v
|
|
52
|
+
useQuery / useMutation hook (src/features/<feature>/queries.ts / mutations.ts)
|
|
53
|
+
| on miss / revalidate
|
|
54
|
+
+──────────────────────► QueryClient in-memory cache (decoded JS objects)
|
|
55
|
+
| + MMKV persister (survives restarts)
|
|
56
|
+
| on cache miss
|
|
57
|
+
v
|
|
58
|
+
src/api/<resource>.ts (typed axios functions — transport only)
|
|
59
|
+
| HTTP via axios instance
|
|
60
|
+
v
|
|
61
|
+
Backend REST API
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Mandatory rules:**
|
|
65
|
+
|
|
66
|
+
1. Every read is wrapped in a `useQuery` call with **explicit `staleTime` and `gcTime`** — no implicit infinite TTL. `staleTime` controls when a cached entry is considered stale and triggers a background revalidation; `gcTime` (formerly `cacheTime`) controls when an unused entry is garbage-collected from memory. For offline-first, `gcTime` **must stay `>=` the persister's `maxAge`** (default 24h) or the persisted cache is discarded before it can be restored on the next launch.
|
|
67
|
+
2. Query keys include the validated account/tenant identity: `[account, accountId, operation, ...params]`, for example `['account', accountId, 'todos', 'list']`. **Never key on URLs** (they conflate transport with cache identity and break under proxy/versioning changes).
|
|
68
|
+
3. Every mutation (`useMutation`) **must call `queryClient.invalidateQueries({ queryKey })`** for all affected query keys on `onSuccess`. Optimistic updates are optional but, when used, must pair a `rollback` in `onError`.
|
|
69
|
+
4. When offline persistence is required: `@tanstack/react-query-persist-client` (the `PersistQueryClientProvider`) + `@tanstack/query-sync-storage-persister` driving a custom MMKV persister (`react-native-mmkv`) so the query cache survives app restarts. Wire `onlineManager` to `@react-native-community/netinfo` so paused mutations resume and stale queries refetch when connectivity returns.
|
|
70
|
+
<!-- oma-docs:ignore-start -->
|
|
71
|
+
5. Create one `QueryClient` per authenticated session (`src/api/queryClient.ts`), with an account-scoped disk key. On account change/logout, unmount the provider, cancel queries, clear memory and the previous account disk cache, and replace the client. Block delayed writes and mutations from retired sessions; never create per-component clients.
|
|
72
|
+
<!-- oma-docs:ignore-end -->
|
|
73
|
+
6. Network calls are isolated in `src/api/` typed functions (axios). **Screens and components never call axios directly** — this is the repository seam. Components consume hooks; hooks consume the api layer.
|
|
74
|
+
7. The query cache is for **transient, server-owned data**. Durable non-secret user-owned data belongs in MMKV; secrets (the access token) live only in memory in a Zustand `authStore` hydrated from `react-native-keychain` — never in plain-text MMKV. TanStack Query is never a system of record.
|
|
75
|
+
|
|
76
|
+
See `snippets.md §2` for QueryClient + MMKV persister setup, `snippets.md §5` for `queries.ts` / `mutations.ts`, and `snippets.md §9` for the cache rules recap.
|
|
77
|
+
|
|
78
|
+
## Local Storage
|
|
79
|
+
|
|
80
|
+
| Layer | Library | Purpose |
|
|
81
|
+
|-------|---------|---------|
|
|
82
|
+
| Fast KV (non-secret) | `react-native-mmkv` | User preferences, offline flags, TanStack Query persistence |
|
|
83
|
+
| Secrets / tokens | `expo-secure-store` or `react-native-keychain` | Auth tokens, API keys |
|
|
84
|
+
| Query cache persistence | MMKV persister (`@tanstack/query-sync-storage-persister` + `@tanstack/react-query-persist-client`) | Offline-first server-state survival across restarts |
|
|
85
|
+
|
|
86
|
+
**MMKV** is a C++-backed key-value store (the same one WeChat uses) that is 30× faster than `AsyncStorage` on both platforms. Use it for all non-secret durable state. Use `expo-secure-store` (Expo projects) or `react-native-keychain` (bare RN) for anything that must live in the platform secure enclave (iOS Keychain / Android Keystore).
|
|
87
|
+
|
|
88
|
+
<!-- oma-docs:ignore-start -->
|
|
89
|
+
Never store secrets in MMKV — it stores plain text unless an `encryptionKey` is passed. The access token lives in an in-memory Zustand `authStore` (`src/store/authStore.ts`) that is hydrated from the Keychain at app start; the axios request interceptor reads it synchronously via `useAuthStore.getState().accessToken`, and a 401 clears both the Keychain and the store. See `snippets.md §10`.
|
|
90
|
+
<!-- oma-docs:ignore-end -->
|
|
91
|
+
|
|
92
|
+
## Testing
|
|
93
|
+
|
|
94
|
+
| Layer | Framework |
|
|
95
|
+
|-------|-----------|
|
|
96
|
+
| Unit (domain logic, stores) | Jest |
|
|
97
|
+
| Component / screen | `@testing-library/react-native` |
|
|
98
|
+
| E2E / critical flows | Maestro |
|
|
99
|
+
|
|
100
|
+
Components under test are wrapped in a `QueryClientProvider` with a fresh `QueryClient` (no retries, zero stale time) so tests do not make real network calls. The `src/api/` module is mocked at the Jest module boundary — tests assert on hook behavior, not axios internals.
|
|
101
|
+
|
|
102
|
+
Run tests with `npx jest --ci`. Maestro flow files live in `e2e/` and exercise critical user journeys (login, create-todo, toggle, delete) via `maestro test e2e/`.
|
|
103
|
+
|
|
104
|
+
## Project Layout
|
|
105
|
+
|
|
106
|
+
Feature-sliced structure with a clean api / feature / shared separation:
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
src/
|
|
110
|
+
api/
|
|
111
|
+
client.ts # Axios singleton instance + interceptors
|
|
112
|
+
queryClient.ts # QueryClient creation + MMKV persister setup
|
|
113
|
+
todos.ts # Typed axios functions for /todos resource
|
|
114
|
+
auth.ts # Typed axios functions for /auth resource
|
|
115
|
+
features/
|
|
116
|
+
todos/
|
|
117
|
+
model/
|
|
118
|
+
todo.ts # TypeScript types / interfaces for the domain
|
|
119
|
+
ui/
|
|
120
|
+
TodosScreen.tsx # Screen consuming useQuery / useMutation hooks
|
|
121
|
+
TodoItem.tsx # Presentational component
|
|
122
|
+
queries.ts # useTodosQuery, useTodoDetailQuery (useQuery wrappers)
|
|
123
|
+
mutations.ts # useCreateTodo, useToggleTodo, useDeleteTodo (useMutation)
|
|
124
|
+
auth/
|
|
125
|
+
model/
|
|
126
|
+
auth.ts
|
|
127
|
+
ui/
|
|
128
|
+
LoginScreen.tsx
|
|
129
|
+
queries.ts
|
|
130
|
+
mutations.ts
|
|
131
|
+
navigation/
|
|
132
|
+
RootNavigator.tsx # Stack + Tab navigator composition
|
|
133
|
+
types.ts # Typed RootStackParamList, TabParamList
|
|
134
|
+
shared/
|
|
135
|
+
components/
|
|
136
|
+
LoadingView.tsx
|
|
137
|
+
ErrorView.tsx
|
|
138
|
+
EmptyStateView.tsx
|
|
139
|
+
hooks/
|
|
140
|
+
useNetworkStatus.ts # NetInfo wrapper for offline detection
|
|
141
|
+
utils/
|
|
142
|
+
storage.ts # MMKV instance export
|
|
143
|
+
store/
|
|
144
|
+
authStore.ts # Zustand store for auth session (token, user)
|
|
145
|
+
uiStore.ts # Zustand store for global UI flags
|
|
146
|
+
App.tsx # Root component — providers, NavigationContainer
|
|
147
|
+
e2e/
|
|
148
|
+
todos.yaml # Maestro E2E flow for todos feature
|
|
149
|
+
auth.yaml # Maestro E2E flow for login
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Architecture Pattern
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
Screen / Component (src/features/<feature>/ui/)
|
|
156
|
+
| calls (React hook)
|
|
157
|
+
v
|
|
158
|
+
useQuery / useMutation hook (src/features/<feature>/queries.ts, mutations.ts)
|
|
159
|
+
| reads from in-memory + MMKV-persisted cache (stale-while-revalidate)
|
|
160
|
+
+──────────────────────────────────► QueryClient cache (decoded JS objects)
|
|
161
|
+
| on cache miss or revalidation
|
|
162
|
+
v
|
|
163
|
+
src/api/<resource>.ts (typed axios functions — transport only)
|
|
164
|
+
| HTTP via singleton axios instance with auth interceptor
|
|
165
|
+
v
|
|
166
|
+
Backend REST API
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Each `features/<name>/` folder is a vertical slice owning its types, UI components, and data hooks. `shared/` contains stateless, reusable components and utilities with no feature knowledge. The `api/` layer is the only place that touches axios and the network.
|
|
170
|
+
|
|
171
|
+
## Navigation: React Navigation v7 (native-stack)
|
|
172
|
+
|
|
173
|
+
Use **`@react-navigation/native-stack`** (backed by `react-native-screens` native primitives) for push navigation — it is measurably faster than the JS-animated `@react-navigation/stack`. Tab navigation uses `@react-navigation/bottom-tabs`.
|
|
174
|
+
|
|
175
|
+
<!-- oma-docs:ignore-start -->
|
|
176
|
+
Route param lists are **fully typed** in `src/navigation/types.ts` with a `RootStackParamList`. Screen components receive `NativeStackScreenProps<RootStackParamList, 'ScreenName'>` as props, giving compile-time checked navigation calls.
|
|
177
|
+
<!-- oma-docs:ignore-end -->
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
// src/navigation/types.ts
|
|
181
|
+
export type RootStackParamList = {
|
|
182
|
+
TodoList: undefined;
|
|
183
|
+
TodoDetail: { id: string };
|
|
184
|
+
Login: undefined;
|
|
185
|
+
};
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
<!-- oma-docs:ignore-start -->
|
|
189
|
+
Pass `<NavigationContainer>` and navigator composition to `src/navigation/RootNavigator.tsx`; never embed navigation logic inside screen components.
|
|
190
|
+
<!-- oma-docs:ignore-end -->
|
|
191
|
+
|
|
192
|
+
See `snippets.md §7` for the typed navigator setup and `snippets.md §6` for a screen consuming typed route params.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"title": "Mobile Stack Manifest",
|
|
4
|
+
"description": "Per-variant stack declaration and verify contract consumed by `oma verify agent mobile` and agent structured lookup.",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"required": ["language"],
|
|
7
|
+
"properties": {
|
|
8
|
+
"$schema": { "type": "string" },
|
|
9
|
+
"language": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"enum": ["swift", "kotlin", "flutter", "react-native"],
|
|
12
|
+
"description": "Primary implementation language of the variant."
|
|
13
|
+
},
|
|
14
|
+
"framework": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"description": "Primary UI framework (e.g., swiftui, flutter, react-native)."
|
|
17
|
+
},
|
|
18
|
+
"ui": {
|
|
19
|
+
"type": "string",
|
|
20
|
+
"description": "UI toolkit or paradigm (e.g., swiftui, material3, react-native)."
|
|
21
|
+
},
|
|
22
|
+
"state": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"description": "State-management approach (e.g., observation, riverpod, zustand)."
|
|
25
|
+
},
|
|
26
|
+
"navigation": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"description": "Navigation/router library (e.g., navigationstack, gorouter, react-navigation)."
|
|
29
|
+
},
|
|
30
|
+
"http_client": {
|
|
31
|
+
"type": "string",
|
|
32
|
+
"description": "HTTP transport client (e.g., urlsession, dio, axios)."
|
|
33
|
+
},
|
|
34
|
+
"data_fetching": {
|
|
35
|
+
"type": "string",
|
|
36
|
+
"description": "Server-state / data-fetching layer where distinct from the raw HTTP client (e.g., tanstack-query)."
|
|
37
|
+
},
|
|
38
|
+
"local_storage": {
|
|
39
|
+
"type": "string",
|
|
40
|
+
"description": "Durable on-device persistence (e.g., swiftdata, drift, mmkv)."
|
|
41
|
+
},
|
|
42
|
+
"api_generator": {
|
|
43
|
+
"type": "string",
|
|
44
|
+
"description": "API client generator used (e.g., swift-openapi-generator)."
|
|
45
|
+
},
|
|
46
|
+
"api_spec": {
|
|
47
|
+
"type": "string",
|
|
48
|
+
"description": "Relative path to the OpenAPI document consumed by the generator (e.g., Core/Networking/openapi.yaml)."
|
|
49
|
+
},
|
|
50
|
+
"response_cache": {
|
|
51
|
+
"type": "string",
|
|
52
|
+
"description": "Repository-layer response cache library used to memoize decoded API models (e.g., hyperoslo-cache)."
|
|
53
|
+
},
|
|
54
|
+
"structure": {
|
|
55
|
+
"type": "string",
|
|
56
|
+
"description": "Top-level module layout (e.g., App/Core/Features/Shared)."
|
|
57
|
+
},
|
|
58
|
+
"source": {
|
|
59
|
+
"type": "string",
|
|
60
|
+
"enum": ["variant", "migrated", "user", "detected"],
|
|
61
|
+
"description": "Origin marker. `variant` = shipped with repo, `migrated` = produced by legacy migration, `user` = hand-customized, `detected` = generated by the stack-set workflow from codebase detection."
|
|
62
|
+
},
|
|
63
|
+
"verify": {
|
|
64
|
+
"$ref": "../../_shared/core/stack-verify.schema.json#/definitions/verify"
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"additionalProperties": true
|
|
68
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Service Template for Mobile Agent (Swift iOS Native)
|
|
3
|
+
*
|
|
4
|
+
* This file wraps the generated `Client` produced by `swift-openapi-generator`
|
|
5
|
+
* from `Core/Networking/openapi.yaml`. It is the **only** place that calls the
|
|
6
|
+
* backend API — never construct `URLRequest` or decode `Data` manually for
|
|
7
|
+
* endpoints that exist in the OpenAPI spec.
|
|
8
|
+
*
|
|
9
|
+
* Dependencies (all auto-generated at `swift build` time):
|
|
10
|
+
* - `Client` — generated root type; one instance per app
|
|
11
|
+
* - `Operations` — one namespace per operation (e.g., `Operations.listTodos`)
|
|
12
|
+
* - `Components` — shared schema types (e.g., `Components.Schemas.Todo`)
|
|
13
|
+
*
|
|
14
|
+
* Caching: read-through is MANDATORY at this Repository layer via a
|
|
15
|
+
* `ResponseCache` actor over `hyperoslo/Cache` (see Core/Cache/ResponseCache.swift
|
|
16
|
+
* and snippets.md §10). It caches DECODED models — never `HTTPBody`. Reads use
|
|
17
|
+
* stale-while-revalidate; writes invalidate the affected keys.
|
|
18
|
+
*
|
|
19
|
+
* File layout (split into real files in production):
|
|
20
|
+
* Core/Networking/
|
|
21
|
+
* openapi.yaml <- vendored OpenAPI spec (source of truth)
|
|
22
|
+
* openapi-generator-config.yaml <- generator config (types + client, public)
|
|
23
|
+
* APIClient.swift <- URLSession transport + auth middleware wiring
|
|
24
|
+
* TodoService.swift <- this file
|
|
25
|
+
* Core/Cache/
|
|
26
|
+
* ResponseCache.swift <- actor wrapping hyperoslo/Cache Storage
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// Core/Networking/TodoService.swift
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
import Foundation
|
|
34
|
+
import OpenAPIRuntime
|
|
35
|
+
|
|
36
|
+
/// Typed errors surfaced by `TodoService`.
|
|
37
|
+
public enum TodoServiceError: Error, LocalizedError {
|
|
38
|
+
case notFound
|
|
39
|
+
case conflict
|
|
40
|
+
case undocumented(statusCode: Int)
|
|
41
|
+
|
|
42
|
+
public var errorDescription: String? {
|
|
43
|
+
switch self {
|
|
44
|
+
case .notFound: return "The requested todo was not found."
|
|
45
|
+
case .conflict: return "A todo with that title already exists."
|
|
46
|
+
case .undocumented(let code): return "Unexpected server response: HTTP \(code)."
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/// Protocol seam the view models depend on, so the cached `TodoService` is
|
|
52
|
+
/// swappable for a protocol-based mock in tests (no third-party mock lib).
|
|
53
|
+
public protocol TodoProviding: Sendable {
|
|
54
|
+
func todosStream() -> AsyncThrowingStream<[Components.Schemas.Todo], Error>
|
|
55
|
+
func createTodo(title: String) async throws -> Components.Schemas.Todo
|
|
56
|
+
func toggleTodo(id: String) async throws -> Components.Schemas.Todo
|
|
57
|
+
func deleteTodo(id: String) async throws
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/// CRUD service for the `/todos` resource.
|
|
61
|
+
///
|
|
62
|
+
/// Depends on `Client` (generated from `Core/Networking/openapi.yaml`) and a
|
|
63
|
+
/// `ResponseCache` (Core/Cache/ResponseCache.swift, an actor over `hyperoslo/Cache`).
|
|
64
|
+
/// Inject via `AppDependencies` at app startup; never instantiate directly in views.
|
|
65
|
+
///
|
|
66
|
+
/// Caching contract:
|
|
67
|
+
/// - Reads cache DECODED models (never `HTTPBody`) and serve stale-while-revalidate.
|
|
68
|
+
/// - Writes invalidate the affected cache keys so the next read repopulates.
|
|
69
|
+
public final class TodoService: TodoProviding {
|
|
70
|
+
private let client: Client
|
|
71
|
+
private let cache: ResponseCache<[Components.Schemas.Todo]>
|
|
72
|
+
|
|
73
|
+
/// Cache key for the list endpoint. Key on `operationID` + params, never URLs.
|
|
74
|
+
private static let listKey = "listTodos"
|
|
75
|
+
|
|
76
|
+
public init(client: Client, cache: ResponseCache<[Components.Schemas.Todo]>) {
|
|
77
|
+
self.client = client
|
|
78
|
+
self.cache = cache
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// MARK: - List (stale-while-revalidate)
|
|
82
|
+
|
|
83
|
+
/// Yields the cached list first (if present), then the freshly-fetched list.
|
|
84
|
+
/// The view model iterates with `for try await` and updates state per yield.
|
|
85
|
+
/// On a network failure with a warm cache, the stale value stands and the
|
|
86
|
+
/// error is swallowed; with no cache, the error surfaces to the caller.
|
|
87
|
+
public func todosStream() -> AsyncThrowingStream<[Components.Schemas.Todo], Error> {
|
|
88
|
+
AsyncThrowingStream { continuation in
|
|
89
|
+
let task = Task {
|
|
90
|
+
let cached = await cache.value(forKey: Self.listKey)
|
|
91
|
+
if let cached { continuation.yield(cached) }
|
|
92
|
+
do {
|
|
93
|
+
let fresh = try await fetchTodos()
|
|
94
|
+
await cache.store(fresh, forKey: Self.listKey)
|
|
95
|
+
continuation.yield(fresh)
|
|
96
|
+
continuation.finish()
|
|
97
|
+
} catch {
|
|
98
|
+
cached == nil ? continuation.finish(throwing: error)
|
|
99
|
+
: continuation.finish()
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
continuation.onTermination = { _ in task.cancel() }
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private func fetchTodos() async throws -> [Components.Schemas.Todo] {
|
|
107
|
+
let response = try await client.listTodos(.init())
|
|
108
|
+
switch response {
|
|
109
|
+
case .ok(let ok):
|
|
110
|
+
return try ok.body.json
|
|
111
|
+
case .undocumented(let statusCode, _):
|
|
112
|
+
throw TodoServiceError.undocumented(statusCode: statusCode)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// MARK: - Create
|
|
117
|
+
|
|
118
|
+
/// Creates a new todo with the given title, then invalidates the list cache.
|
|
119
|
+
public func createTodo(title: String) async throws -> Components.Schemas.Todo {
|
|
120
|
+
let body = Components.Schemas.CreateTodoRequest(title: title)
|
|
121
|
+
let response = try await client.createTodo(.init(body: .json(body)))
|
|
122
|
+
switch response {
|
|
123
|
+
case .created(let created):
|
|
124
|
+
await cache.invalidate(Self.listKey)
|
|
125
|
+
return try created.body.json
|
|
126
|
+
case .conflict:
|
|
127
|
+
throw TodoServiceError.conflict
|
|
128
|
+
case .undocumented(let statusCode, _):
|
|
129
|
+
throw TodoServiceError.undocumented(statusCode: statusCode)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// MARK: - Toggle
|
|
134
|
+
|
|
135
|
+
/// Toggles the `completed` flag on the todo with the given ID, then invalidates
|
|
136
|
+
/// the list cache.
|
|
137
|
+
public func toggleTodo(id: String) async throws -> Components.Schemas.Todo {
|
|
138
|
+
let response = try await client.toggleTodo(.init(path: .init(id: id)))
|
|
139
|
+
switch response {
|
|
140
|
+
case .ok(let ok):
|
|
141
|
+
await cache.invalidate(Self.listKey)
|
|
142
|
+
return try ok.body.json
|
|
143
|
+
case .notFound:
|
|
144
|
+
throw TodoServiceError.notFound
|
|
145
|
+
case .undocumented(let statusCode, _):
|
|
146
|
+
throw TodoServiceError.undocumented(statusCode: statusCode)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// MARK: - Delete
|
|
151
|
+
|
|
152
|
+
/// Permanently deletes the todo with the given ID, then invalidates the list cache.
|
|
153
|
+
public func deleteTodo(id: String) async throws {
|
|
154
|
+
let response = try await client.deleteTodo(.init(path: .init(id: id)))
|
|
155
|
+
switch response {
|
|
156
|
+
case .noContent:
|
|
157
|
+
await cache.invalidate(Self.listKey)
|
|
158
|
+
return
|
|
159
|
+
case .notFound:
|
|
160
|
+
throw TodoServiceError.notFound
|
|
161
|
+
case .undocumented(let statusCode, _):
|
|
162
|
+
throw TodoServiceError.undocumented(statusCode: statusCode)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|