@biggora/claude-plugins 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -4
- package/package.json +1 -1
- package/registry/registry.json +334 -244
- package/specs/coding.md +30 -0
- package/specs/pod.md +2 -0
- package/src/commands/skills/add.js +63 -7
- package/src/commands/skills/list.js +23 -52
- package/src/commands/skills/remove.js +26 -27
- package/src/commands/skills/resolve.js +155 -0
- package/src/commands/skills/update.js +58 -74
- package/src/skills/captcha/README.md +221 -0
- package/src/skills/captcha/SKILL.md +355 -0
- package/src/skills/captcha/references/captcha-types.md +254 -0
- package/src/skills/captcha/references/services.md +172 -0
- package/src/skills/captcha/references/stealth.md +238 -0
- package/src/skills/captcha/scripts/solve_captcha.py +323 -0
- package/src/skills/captcha/scripts/solve_image_grid.py +350 -0
- package/src/skills/google-merchant-api/SKILL.md +581 -0
- package/src/skills/google-merchant-api/references/accounts.md +247 -0
- package/src/skills/google-merchant-api/references/content-api-legacy.md +216 -0
- package/src/skills/google-merchant-api/references/datasources.md +233 -0
- package/src/skills/google-merchant-api/references/inventories.md +201 -0
- package/src/skills/google-merchant-api/references/migration.md +267 -0
- package/src/skills/google-merchant-api/references/products.md +316 -0
- package/src/skills/google-merchant-api/references/promotions.md +201 -0
- package/src/skills/google-merchant-api/references/reports.md +240 -0
- package/src/skills/lv-aggregators-api/SKILL.md +113 -0
- package/src/skills/lv-aggregators-api/references/integration-guide.md +368 -0
- package/src/skills/lv-aggregators-api/references/kurpirkt.md +103 -0
- package/src/skills/lv-aggregators-api/references/salidzini.md +122 -0
- package/src/skills/nest-best-practices/SKILL.md +251 -0
- package/src/skills/nest-best-practices/references/best-practices-request-lifecycle.md +158 -0
- package/src/skills/nest-best-practices/references/cli-monorepo.md +106 -0
- package/src/skills/nest-best-practices/references/cli-overview.md +157 -0
- package/src/skills/nest-best-practices/references/core-controllers.md +165 -0
- package/src/skills/nest-best-practices/references/core-dependency-injection.md +179 -0
- package/src/skills/nest-best-practices/references/core-middleware.md +139 -0
- package/src/skills/nest-best-practices/references/core-modules.md +138 -0
- package/src/skills/nest-best-practices/references/core-providers.md +188 -0
- package/src/skills/nest-best-practices/references/faq-raw-body-hybrid.md +122 -0
- package/src/skills/nest-best-practices/references/fundamentals-circular-dependency.md +89 -0
- package/src/skills/nest-best-practices/references/fundamentals-custom-decorators.md +107 -0
- package/src/skills/nest-best-practices/references/fundamentals-dynamic-modules.md +125 -0
- package/src/skills/nest-best-practices/references/fundamentals-exception-filters.md +202 -0
- package/src/skills/nest-best-practices/references/fundamentals-execution-context.md +107 -0
- package/src/skills/nest-best-practices/references/fundamentals-guards.md +136 -0
- package/src/skills/nest-best-practices/references/fundamentals-interceptors.md +187 -0
- package/src/skills/nest-best-practices/references/fundamentals-lazy-loading.md +89 -0
- package/src/skills/nest-best-practices/references/fundamentals-lifecycle-events.md +87 -0
- package/src/skills/nest-best-practices/references/fundamentals-module-reference.md +107 -0
- package/src/skills/nest-best-practices/references/fundamentals-pipes.md +197 -0
- package/src/skills/nest-best-practices/references/fundamentals-provider-scopes.md +92 -0
- package/src/skills/nest-best-practices/references/fundamentals-testing.md +142 -0
- package/src/skills/nest-best-practices/references/graphql-overview.md +233 -0
- package/src/skills/nest-best-practices/references/graphql-resolvers-mutations.md +199 -0
- package/src/skills/nest-best-practices/references/graphql-scalars-unions-enums.md +180 -0
- package/src/skills/nest-best-practices/references/graphql-subscriptions.md +228 -0
- package/src/skills/nest-best-practices/references/microservices-grpc.md +175 -0
- package/src/skills/nest-best-practices/references/microservices-overview.md +221 -0
- package/src/skills/nest-best-practices/references/microservices-transports.md +119 -0
- package/src/skills/nest-best-practices/references/openapi-swagger.md +207 -0
- package/src/skills/nest-best-practices/references/recipes-authentication.md +97 -0
- package/src/skills/nest-best-practices/references/recipes-cqrs.md +176 -0
- package/src/skills/nest-best-practices/references/recipes-crud-generator.md +87 -0
- package/src/skills/nest-best-practices/references/recipes-documentation.md +93 -0
- package/src/skills/nest-best-practices/references/recipes-mongoose.md +153 -0
- package/src/skills/nest-best-practices/references/recipes-prisma.md +98 -0
- package/src/skills/nest-best-practices/references/recipes-terminus.md +148 -0
- package/src/skills/nest-best-practices/references/recipes-typeorm.md +122 -0
- package/src/skills/nest-best-practices/references/security-authorization.md +196 -0
- package/src/skills/nest-best-practices/references/security-cors-helmet-rate-limiting.md +204 -0
- package/src/skills/nest-best-practices/references/security-encryption-hashing.md +93 -0
- package/src/skills/nest-best-practices/references/techniques-caching.md +142 -0
- package/src/skills/nest-best-practices/references/techniques-compression-streaming-sse.md +194 -0
- package/src/skills/nest-best-practices/references/techniques-configuration.md +132 -0
- package/src/skills/nest-best-practices/references/techniques-database.md +153 -0
- package/src/skills/nest-best-practices/references/techniques-events.md +163 -0
- package/src/skills/nest-best-practices/references/techniques-fastify.md +137 -0
- package/src/skills/nest-best-practices/references/techniques-file-upload.md +140 -0
- package/src/skills/nest-best-practices/references/techniques-http-module.md +176 -0
- package/src/skills/nest-best-practices/references/techniques-logging.md +146 -0
- package/src/skills/nest-best-practices/references/techniques-mvc-serve-static.md +132 -0
- package/src/skills/nest-best-practices/references/techniques-queues.md +162 -0
- package/src/skills/nest-best-practices/references/techniques-serialization.md +158 -0
- package/src/skills/nest-best-practices/references/techniques-sessions-cookies.md +167 -0
- package/src/skills/nest-best-practices/references/techniques-task-scheduling.md +166 -0
- package/src/skills/nest-best-practices/references/techniques-validation.md +126 -0
- package/src/skills/nest-best-practices/references/techniques-versioning.md +153 -0
- package/src/skills/nest-best-practices/references/websockets-advanced.md +96 -0
- package/src/skills/nest-best-practices/references/websockets-gateways.md +215 -0
- package/src/skills/tailwindcss-best-practices/SKILL.md +180 -0
- package/src/skills/tailwindcss-best-practices/references/best-practices-utility-patterns.md +87 -0
- package/src/skills/tailwindcss-best-practices/references/core-installation.md +109 -0
- package/src/skills/tailwindcss-best-practices/references/core-preflight.md +200 -0
- package/src/skills/tailwindcss-best-practices/references/core-responsive.md +163 -0
- package/src/skills/tailwindcss-best-practices/references/core-source-detection.md +114 -0
- package/src/skills/tailwindcss-best-practices/references/core-theme.md +108 -0
- package/src/skills/tailwindcss-best-practices/references/core-utility-classes.md +59 -0
- package/src/skills/tailwindcss-best-practices/references/core-variants.md +204 -0
- package/src/skills/tailwindcss-best-practices/references/effects-form-controls.md +76 -0
- package/src/skills/tailwindcss-best-practices/references/effects-mask.md +91 -0
- package/src/skills/tailwindcss-best-practices/references/effects-scroll-snap.md +59 -0
- package/src/skills/tailwindcss-best-practices/references/effects-text-shadow.md +78 -0
- package/src/skills/tailwindcss-best-practices/references/effects-transition-animation.md +80 -0
- package/src/skills/tailwindcss-best-practices/references/effects-visibility-interactivity.md +82 -0
- package/src/skills/tailwindcss-best-practices/references/features-content-detection.md +175 -0
- package/src/skills/tailwindcss-best-practices/references/features-custom-styles.md +203 -0
- package/src/skills/tailwindcss-best-practices/references/features-dark-mode.md +137 -0
- package/src/skills/tailwindcss-best-practices/references/features-functions-directives.md +241 -0
- package/src/skills/tailwindcss-best-practices/references/features-upgrade.md +160 -0
- package/src/skills/tailwindcss-best-practices/references/layout-aspect-ratio.md +39 -0
- package/src/skills/tailwindcss-best-practices/references/layout-columns.md +80 -0
- package/src/skills/tailwindcss-best-practices/references/layout-display.md +110 -0
- package/src/skills/tailwindcss-best-practices/references/layout-flexbox.md +112 -0
- package/src/skills/tailwindcss-best-practices/references/layout-grid.md +87 -0
- package/src/skills/tailwindcss-best-practices/references/layout-height.md +97 -0
- package/src/skills/tailwindcss-best-practices/references/layout-inset.md +103 -0
- package/src/skills/tailwindcss-best-practices/references/layout-logical-properties.md +92 -0
- package/src/skills/tailwindcss-best-practices/references/layout-margin.md +126 -0
- package/src/skills/tailwindcss-best-practices/references/layout-min-max-sizing.md +63 -0
- package/src/skills/tailwindcss-best-practices/references/layout-object-fit-position.md +64 -0
- package/src/skills/tailwindcss-best-practices/references/layout-overflow.md +57 -0
- package/src/skills/tailwindcss-best-practices/references/layout-padding.md +77 -0
- package/src/skills/tailwindcss-best-practices/references/layout-position.md +85 -0
- package/src/skills/tailwindcss-best-practices/references/layout-tables.md +67 -0
- package/src/skills/tailwindcss-best-practices/references/layout-width.md +102 -0
- package/src/skills/tailwindcss-best-practices/references/transform-base.md +68 -0
- package/src/skills/tailwindcss-best-practices/references/transform-rotate.md +70 -0
- package/src/skills/tailwindcss-best-practices/references/transform-scale.md +83 -0
- package/src/skills/tailwindcss-best-practices/references/transform-skew.md +62 -0
- package/src/skills/tailwindcss-best-practices/references/transform-translate.md +77 -0
- package/src/skills/tailwindcss-best-practices/references/typography-font-text.md +142 -0
- package/src/skills/tailwindcss-best-practices/references/typography-list-style.md +65 -0
- package/src/skills/tailwindcss-best-practices/references/typography-text-align.md +60 -0
- package/src/skills/tailwindcss-best-practices/references/visual-background.md +76 -0
- package/src/skills/tailwindcss-best-practices/references/visual-border.md +108 -0
- package/src/skills/tailwindcss-best-practices/references/visual-effects.md +111 -0
- package/src/skills/tailwindcss-best-practices/references/visual-svg.md +82 -0
- package/src/skills/test-mobile-app/SKILL.md +11 -6
- package/src/skills/test-mobile-app/scripts/analyze_apk.py +15 -4
- package/src/skills/test-mobile-app/scripts/check_environment.py +5 -5
- package/src/skills/test-mobile-app/scripts/run_tests.py +1 -1
- package/src/skills/test-web-ui/SKILL.md +264 -84
- package/src/skills/test-web-ui/scripts/discover.py +25 -12
- package/src/skills/test-web-ui/scripts/run_tests.py +3 -2
- package/src/skills/typescript-expert/SKILL.md +145 -0
- package/src/skills/typescript-expert/commands/typescript-fix.md +65 -0
- package/src/skills/typescript-expert/references/advanced-conditional-types.md +190 -0
- package/src/skills/typescript-expert/references/advanced-decorators.md +243 -0
- package/src/skills/typescript-expert/references/advanced-mapped-types.md +223 -0
- package/src/skills/typescript-expert/references/advanced-template-literals.md +209 -0
- package/src/skills/typescript-expert/references/advanced-type-guards.md +308 -0
- package/src/skills/typescript-expert/references/best-practices-patterns.md +313 -0
- package/src/skills/typescript-expert/references/best-practices-performance.md +185 -0
- package/src/skills/typescript-expert/references/best-practices-tsconfig.md +242 -0
- package/src/skills/typescript-expert/references/core-generics.md +246 -0
- package/src/skills/typescript-expert/references/core-interfaces-types.md +231 -0
- package/src/skills/typescript-expert/references/core-type-system.md +261 -0
- package/src/skills/typescript-expert/references/core-utility-types.md +235 -0
- package/src/skills/typescript-expert/references/features-ts5x.md +370 -0
- package/src/skills/vite-best-practices/SKILL.md +115 -0
- package/src/skills/vite-best-practices/references/build-and-ssr.md +255 -0
- package/src/skills/vite-best-practices/references/core-config.md +231 -0
- package/src/skills/vite-best-practices/references/core-features.md +222 -0
- package/src/skills/vite-best-practices/references/core-plugin-api.md +294 -0
- package/src/skills/vite-best-practices/references/environment-api.md +108 -0
- package/src/skills/vite-best-practices/references/rolldown-migration.md +242 -0
- package/codex-cli-workspace/iteration-1/benchmark.json +0 -122
- package/codex-cli-workspace/iteration-1/eval-1-ci-integration/eval_metadata.json +0 -13
- package/codex-cli-workspace/iteration-1/eval-1-ci-integration/with_skill/grading.json +0 -52
- package/codex-cli-workspace/iteration-1/eval-1-ci-integration/with_skill/outputs/response.md +0 -163
- package/codex-cli-workspace/iteration-1/eval-1-ci-integration/with_skill/timing.json +0 -5
- package/codex-cli-workspace/iteration-1/eval-1-ci-integration/without_skill/grading.json +0 -58
- package/codex-cli-workspace/iteration-1/eval-1-ci-integration/without_skill/outputs/response.md +0 -151
- package/codex-cli-workspace/iteration-1/eval-1-ci-integration/without_skill/timing.json +0 -5
- package/codex-cli-workspace/iteration-1/eval-2-mcp-server-config/eval_metadata.json +0 -13
- package/codex-cli-workspace/iteration-1/eval-2-mcp-server-config/with_skill/grading.json +0 -52
- package/codex-cli-workspace/iteration-1/eval-2-mcp-server-config/with_skill/outputs/response.md +0 -86
- package/codex-cli-workspace/iteration-1/eval-2-mcp-server-config/with_skill/timing.json +0 -5
- package/codex-cli-workspace/iteration-1/eval-2-mcp-server-config/without_skill/grading.json +0 -58
- package/codex-cli-workspace/iteration-1/eval-2-mcp-server-config/without_skill/outputs/response.md +0 -164
- package/codex-cli-workspace/iteration-1/eval-2-mcp-server-config/without_skill/timing.json +0 -5
- package/codex-cli-workspace/iteration-1/eval-3-profiles-troubleshooting/eval_metadata.json +0 -13
- package/codex-cli-workspace/iteration-1/eval-3-profiles-troubleshooting/with_skill/grading.json +0 -52
- package/codex-cli-workspace/iteration-1/eval-3-profiles-troubleshooting/with_skill/outputs/response.md +0 -130
- package/codex-cli-workspace/iteration-1/eval-3-profiles-troubleshooting/with_skill/timing.json +0 -5
- package/codex-cli-workspace/iteration-1/eval-3-profiles-troubleshooting/without_skill/grading.json +0 -64
- package/codex-cli-workspace/iteration-1/eval-3-profiles-troubleshooting/without_skill/outputs/response.md +0 -209
- package/codex-cli-workspace/iteration-1/eval-3-profiles-troubleshooting/without_skill/timing.json +0 -5
- package/codex-cli-workspace/iteration-1/review.html +0 -1325
- package/gemini-cli-workspace/iteration-1/benchmark.json +0 -86
- package/gemini-cli-workspace/iteration-1/eval-1-cicd-setup/eval_metadata.json +0 -37
- package/gemini-cli-workspace/iteration-1/eval-1-cicd-setup/with_skill/grading.json +0 -37
- package/gemini-cli-workspace/iteration-1/eval-1-cicd-setup/with_skill/outputs/response.md +0 -401
- package/gemini-cli-workspace/iteration-1/eval-1-cicd-setup/with_skill/timing.json +0 -5
- package/gemini-cli-workspace/iteration-1/eval-1-cicd-setup/without_skill/grading.json +0 -37
- package/gemini-cli-workspace/iteration-1/eval-1-cicd-setup/without_skill/outputs/response.md +0 -405
- package/gemini-cli-workspace/iteration-1/eval-1-cicd-setup/without_skill/timing.json +0 -5
- package/gemini-cli-workspace/iteration-1/eval-2-mcp-server-config/eval_metadata.json +0 -37
- package/gemini-cli-workspace/iteration-1/eval-2-mcp-server-config/with_skill/grading.json +0 -37
- package/gemini-cli-workspace/iteration-1/eval-2-mcp-server-config/with_skill/outputs/response.md +0 -212
- package/gemini-cli-workspace/iteration-1/eval-2-mcp-server-config/with_skill/timing.json +0 -5
- package/gemini-cli-workspace/iteration-1/eval-2-mcp-server-config/without_skill/grading.json +0 -37
- package/gemini-cli-workspace/iteration-1/eval-2-mcp-server-config/without_skill/outputs/response.md +0 -427
- package/gemini-cli-workspace/iteration-1/eval-2-mcp-server-config/without_skill/timing.json +0 -5
- package/gemini-cli-workspace/iteration-1/eval-3-custom-slash-command/eval_metadata.json +0 -32
- package/gemini-cli-workspace/iteration-1/eval-3-custom-slash-command/with_skill/grading.json +0 -32
- package/gemini-cli-workspace/iteration-1/eval-3-custom-slash-command/with_skill/outputs/response.md +0 -171
- package/gemini-cli-workspace/iteration-1/eval-3-custom-slash-command/with_skill/timing.json +0 -5
- package/gemini-cli-workspace/iteration-1/eval-3-custom-slash-command/without_skill/grading.json +0 -32
- package/gemini-cli-workspace/iteration-1/eval-3-custom-slash-command/without_skill/outputs/response.md +0 -199
- package/gemini-cli-workspace/iteration-1/eval-3-custom-slash-command/without_skill/timing.json +0 -5
- package/gemini-cli-workspace/iteration-1/review.html +0 -1325
- package/gemini-cli-workspace/iteration-2/benchmark.json +0 -173
- package/gemini-cli-workspace/iteration-2/benchmark.md +0 -28
- package/gemini-cli-workspace/iteration-2/eval-1-cicd-setup/eval_metadata.json +0 -37
- package/gemini-cli-workspace/iteration-2/eval-1-cicd-setup/with_skill/grading.json +0 -37
- package/gemini-cli-workspace/iteration-2/eval-1-cicd-setup/with_skill/outputs/response.md +0 -195
- package/gemini-cli-workspace/iteration-2/eval-1-cicd-setup/with_skill/timing.json +0 -5
- package/gemini-cli-workspace/iteration-2/eval-1-cicd-setup/without_skill/grading.json +0 -37
- package/gemini-cli-workspace/iteration-2/eval-1-cicd-setup/without_skill/outputs/response.md +0 -377
- package/gemini-cli-workspace/iteration-2/eval-1-cicd-setup/without_skill/timing.json +0 -5
- package/gemini-cli-workspace/iteration-2/eval-2-mcp-server-config/eval_metadata.json +0 -37
- package/gemini-cli-workspace/iteration-2/eval-2-mcp-server-config/with_skill/grading.json +0 -37
- package/gemini-cli-workspace/iteration-2/eval-2-mcp-server-config/with_skill/outputs/response.md +0 -127
- package/gemini-cli-workspace/iteration-2/eval-2-mcp-server-config/with_skill/timing.json +0 -5
- package/gemini-cli-workspace/iteration-2/eval-2-mcp-server-config/without_skill/grading.json +0 -37
- package/gemini-cli-workspace/iteration-2/eval-2-mcp-server-config/without_skill/outputs/response.md +0 -164
- package/gemini-cli-workspace/iteration-2/eval-2-mcp-server-config/without_skill/timing.json +0 -5
- package/gemini-cli-workspace/iteration-2/eval-3-custom-slash-command/eval_metadata.json +0 -32
- package/gemini-cli-workspace/iteration-2/eval-3-custom-slash-command/with_skill/grading.json +0 -32
- package/gemini-cli-workspace/iteration-2/eval-3-custom-slash-command/with_skill/outputs/response.md +0 -91
- package/gemini-cli-workspace/iteration-2/eval-3-custom-slash-command/with_skill/timing.json +0 -5
- package/gemini-cli-workspace/iteration-2/eval-3-custom-slash-command/without_skill/grading.json +0 -32
- package/gemini-cli-workspace/iteration-2/eval-3-custom-slash-command/without_skill/outputs/response.md +0 -112
- package/gemini-cli-workspace/iteration-2/eval-3-custom-slash-command/without_skill/timing.json +0 -5
- package/gemini-cli-workspace/iteration-2/eval-viewer.html +0 -1325
- package/screen-recording-workspace/evals.json +0 -41
- package/screen-recording-workspace/iteration-1/benchmark.json +0 -102
- package/screen-recording-workspace/iteration-1/eval-0-fullscreen/eval_metadata.json +0 -31
- package/screen-recording-workspace/iteration-1/eval-0-fullscreen/with_skill/grading.json +0 -11
- package/screen-recording-workspace/iteration-1/eval-0-fullscreen/with_skill/outputs/demo.mp4 +0 -0
- package/screen-recording-workspace/iteration-1/eval-0-fullscreen/with_skill/timing.json +0 -5
- package/screen-recording-workspace/iteration-1/eval-0-fullscreen/without_skill/grading.json +0 -11
- package/screen-recording-workspace/iteration-1/eval-0-fullscreen/without_skill/outputs/demo.mp4 +0 -0
- package/screen-recording-workspace/iteration-1/eval-0-fullscreen/without_skill/timing.json +0 -5
- package/screen-recording-workspace/iteration-1/eval-1-region-audio/eval_metadata.json +0 -31
- package/screen-recording-workspace/iteration-1/eval-1-region-audio/with_skill/grading.json +0 -11
- package/screen-recording-workspace/iteration-1/eval-1-region-audio/with_skill/outputs/region_capture.mp4 +0 -0
- package/screen-recording-workspace/iteration-1/eval-1-region-audio/with_skill/timing.json +0 -5
- package/screen-recording-workspace/iteration-1/eval-1-region-audio/without_skill/grading.json +0 -11
- package/screen-recording-workspace/iteration-1/eval-1-region-audio/without_skill/outputs/region_capture.mp4 +0 -0
- package/screen-recording-workspace/iteration-1/eval-1-region-audio/without_skill/timing.json +0 -5
- package/screen-recording-workspace/iteration-1/eval-2-python-fallback/eval_metadata.json +0 -31
- package/screen-recording-workspace/iteration-1/eval-2-python-fallback/with_skill/grading.json +0 -11
- package/screen-recording-workspace/iteration-1/eval-2-python-fallback/with_skill/outputs/fallback_recording.mp4 +0 -0
- package/screen-recording-workspace/iteration-1/eval-2-python-fallback/with_skill/timing.json +0 -5
- package/screen-recording-workspace/iteration-1/eval-2-python-fallback/without_skill/grading.json +0 -11
- package/screen-recording-workspace/iteration-1/eval-2-python-fallback/without_skill/outputs/fallback_recording.mp4 +0 -0
- package/screen-recording-workspace/iteration-1/eval-2-python-fallback/without_skill/outputs/record_screen.py +0 -67
- package/screen-recording-workspace/iteration-1/eval-2-python-fallback/without_skill/timing.json +0 -5
- package/screen-recording-workspace/iteration-1/review.html +0 -1325
- package/src/skills/codex-cli/evals/evals.json +0 -47
- package/src/skills/gemini-cli/evals/evals.json +0 -46
- package/src/skills/tm-search/evals/evals.json +0 -23
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
# Type Guards and Discriminated Unions
|
|
2
|
+
|
|
3
|
+
Type guards let you narrow a type within a conditional block. Discriminated unions combine literal types with exhaustive checking for robust state modeling.
|
|
4
|
+
|
|
5
|
+
## Built-in Narrowing
|
|
6
|
+
|
|
7
|
+
TypeScript narrows types automatically in many control flow patterns:
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
// typeof
|
|
11
|
+
function process(x: string | number) {
|
|
12
|
+
if (typeof x === "string") {
|
|
13
|
+
x.toUpperCase(); // x is string
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// instanceof
|
|
18
|
+
function handle(err: Error | string) {
|
|
19
|
+
if (err instanceof Error) {
|
|
20
|
+
err.message; // err is Error
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// in operator
|
|
25
|
+
function move(animal: { swim?: () => void; fly?: () => void }) {
|
|
26
|
+
if ("swim" in animal) {
|
|
27
|
+
animal.swim!();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Truthiness
|
|
32
|
+
function print(name: string | null | undefined) {
|
|
33
|
+
if (name) {
|
|
34
|
+
name.toUpperCase(); // string (null and undefined removed)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Equality
|
|
39
|
+
function compare(a: string | number, b: string | boolean) {
|
|
40
|
+
if (a === b) {
|
|
41
|
+
a; // string (only common type)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## User-Defined Type Guards
|
|
47
|
+
|
|
48
|
+
When built-in narrowing isn't enough, write custom type guard functions:
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
// Type predicate — return type is `paramName is Type`
|
|
52
|
+
function isString(value: unknown): value is string {
|
|
53
|
+
return typeof value === "string";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function process(value: unknown) {
|
|
57
|
+
if (isString(value)) {
|
|
58
|
+
value.toUpperCase(); // TypeScript knows value is string
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Checking object shapes
|
|
63
|
+
interface User { type: "user"; name: string; email: string }
|
|
64
|
+
interface Admin { type: "admin"; name: string; permissions: string[] }
|
|
65
|
+
|
|
66
|
+
function isAdmin(person: User | Admin): person is Admin {
|
|
67
|
+
return person.type === "admin";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Array filtering with type guards
|
|
71
|
+
const items: (string | null)[] = ["hello", null, "world", null];
|
|
72
|
+
const strings: string[] = items.filter((x): x is string => x !== null);
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Inferred Type Predicates (TS 5.5+)
|
|
76
|
+
|
|
77
|
+
TypeScript 5.5 can infer type predicates for simple arrow functions:
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
// Before 5.5: needed explicit annotation
|
|
81
|
+
const strings = items.filter((x): x is string => x !== null);
|
|
82
|
+
|
|
83
|
+
// TS 5.5+: inferred automatically
|
|
84
|
+
const strings = items.filter(x => x !== null); // string[]
|
|
85
|
+
|
|
86
|
+
// Also works with Boolean
|
|
87
|
+
const truthy = items.filter(Boolean); // string[] (nulls removed)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Assertion Functions
|
|
91
|
+
|
|
92
|
+
Assertion functions narrow the type for all subsequent code (not just the if-block):
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
function assertIsString(value: unknown): asserts value is string {
|
|
96
|
+
if (typeof value !== "string") {
|
|
97
|
+
throw new Error(`Expected string, got ${typeof value}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function process(value: unknown) {
|
|
102
|
+
assertIsString(value);
|
|
103
|
+
// From here on, value is string
|
|
104
|
+
value.toUpperCase();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Assert non-null
|
|
108
|
+
function assertDefined<T>(value: T): asserts value is NonNullable<T> {
|
|
109
|
+
if (value == null) {
|
|
110
|
+
throw new Error("Value must be defined");
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Discriminated Unions
|
|
116
|
+
|
|
117
|
+
A discriminated union is a union where each member has a literal property (the "discriminant") that uniquely identifies it:
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
interface Circle {
|
|
121
|
+
kind: "circle";
|
|
122
|
+
radius: number;
|
|
123
|
+
}
|
|
124
|
+
interface Rectangle {
|
|
125
|
+
kind: "rectangle";
|
|
126
|
+
width: number;
|
|
127
|
+
height: number;
|
|
128
|
+
}
|
|
129
|
+
interface Triangle {
|
|
130
|
+
kind: "triangle";
|
|
131
|
+
base: number;
|
|
132
|
+
height: number;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
type Shape = Circle | Rectangle | Triangle;
|
|
136
|
+
|
|
137
|
+
function area(shape: Shape): number {
|
|
138
|
+
switch (shape.kind) {
|
|
139
|
+
case "circle":
|
|
140
|
+
return Math.PI * shape.radius ** 2;
|
|
141
|
+
case "rectangle":
|
|
142
|
+
return shape.width * shape.height;
|
|
143
|
+
case "triangle":
|
|
144
|
+
return 0.5 * shape.base * shape.height;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Exhaustive Checking
|
|
150
|
+
|
|
151
|
+
Ensure all union members are handled:
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
// Method 1: never in default
|
|
155
|
+
function area(shape: Shape): number {
|
|
156
|
+
switch (shape.kind) {
|
|
157
|
+
case "circle":
|
|
158
|
+
return Math.PI * shape.radius ** 2;
|
|
159
|
+
case "rectangle":
|
|
160
|
+
return shape.width * shape.height;
|
|
161
|
+
case "triangle":
|
|
162
|
+
return 0.5 * shape.base * shape.height;
|
|
163
|
+
default:
|
|
164
|
+
// If Shape gains a new member, this line errors
|
|
165
|
+
const _exhaustive: never = shape;
|
|
166
|
+
return _exhaustive;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Method 2: Helper function
|
|
171
|
+
function assertNever(x: never): never {
|
|
172
|
+
throw new Error(`Unexpected value: ${x}`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Method 3: satisfies never
|
|
176
|
+
function area(shape: Shape): number {
|
|
177
|
+
switch (shape.kind) {
|
|
178
|
+
case "circle": return Math.PI * shape.radius ** 2;
|
|
179
|
+
case "rectangle": return shape.width * shape.height;
|
|
180
|
+
case "triangle": return 0.5 * shape.base * shape.height;
|
|
181
|
+
default: return shape satisfies never;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Modeling Application State
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
type RequestState<T> =
|
|
190
|
+
| { status: "idle" }
|
|
191
|
+
| { status: "loading" }
|
|
192
|
+
| { status: "success"; data: T }
|
|
193
|
+
| { status: "error"; error: Error };
|
|
194
|
+
|
|
195
|
+
function renderUser(state: RequestState<User>) {
|
|
196
|
+
switch (state.status) {
|
|
197
|
+
case "idle":
|
|
198
|
+
return "Click to load";
|
|
199
|
+
case "loading":
|
|
200
|
+
return "Loading...";
|
|
201
|
+
case "success":
|
|
202
|
+
return `Hello, ${state.data.name}`; // data is available
|
|
203
|
+
case "error":
|
|
204
|
+
return `Error: ${state.error.message}`; // error is available
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Result Type Pattern
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
type Result<T, E = Error> =
|
|
213
|
+
| { ok: true; value: T }
|
|
214
|
+
| { ok: false; error: E };
|
|
215
|
+
|
|
216
|
+
function divide(a: number, b: number): Result<number, string> {
|
|
217
|
+
if (b === 0) return { ok: false, error: "Division by zero" };
|
|
218
|
+
return { ok: true, value: a / b };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const result = divide(10, 3);
|
|
222
|
+
if (result.ok) {
|
|
223
|
+
console.log(result.value); // number
|
|
224
|
+
} else {
|
|
225
|
+
console.log(result.error); // string
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Narrowing with Control Flow
|
|
230
|
+
|
|
231
|
+
TypeScript tracks narrowing through assignments, returns, and throws:
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
function process(value: string | null) {
|
|
235
|
+
if (value === null) {
|
|
236
|
+
return; // Early return narrows the rest
|
|
237
|
+
}
|
|
238
|
+
// value is string from here
|
|
239
|
+
value.toUpperCase();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function validate(value: unknown): string {
|
|
243
|
+
if (typeof value !== "string") {
|
|
244
|
+
throw new Error("Not a string");
|
|
245
|
+
}
|
|
246
|
+
// value is string from here
|
|
247
|
+
return value.trim();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Assignment narrowing
|
|
251
|
+
let x: string | number;
|
|
252
|
+
x = "hello";
|
|
253
|
+
x.toUpperCase(); // OK — x is string after assignment
|
|
254
|
+
x = 42;
|
|
255
|
+
x.toFixed(2); // OK — x is number after assignment
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Narrowing Gotchas
|
|
259
|
+
|
|
260
|
+
### Narrowing Doesn't Survive Callbacks
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
function process(value: string | null) {
|
|
264
|
+
if (value !== null) {
|
|
265
|
+
// value is string here
|
|
266
|
+
setTimeout(() => {
|
|
267
|
+
value.toUpperCase(); // Still OK — value is const in closure
|
|
268
|
+
}, 100);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// But with reassignable variables:
|
|
273
|
+
let value: string | null = "hello";
|
|
274
|
+
if (value !== null) {
|
|
275
|
+
setTimeout(() => {
|
|
276
|
+
value.toUpperCase(); // Error! value might have been reassigned
|
|
277
|
+
}, 100);
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Objects and Aliased Conditions
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
// This doesn't narrow:
|
|
285
|
+
function isValid(obj: { x?: string }) {
|
|
286
|
+
const hasX = obj.x !== undefined;
|
|
287
|
+
if (hasX) {
|
|
288
|
+
obj.x.toUpperCase(); // Error! TypeScript doesn't track aliased conditions
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// This works:
|
|
292
|
+
if (obj.x !== undefined) {
|
|
293
|
+
obj.x.toUpperCase(); // OK
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Narrowing with Generics
|
|
299
|
+
|
|
300
|
+
```typescript
|
|
301
|
+
// TypeScript can't narrow generic types well
|
|
302
|
+
function process<T extends string | number>(value: T) {
|
|
303
|
+
if (typeof value === "string") {
|
|
304
|
+
// value is still T, not string — narrowing is limited with generics
|
|
305
|
+
// Use overloads or conditional types instead
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
```
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
# Common TypeScript Patterns
|
|
2
|
+
|
|
3
|
+
## Branded Types (Nominal Typing)
|
|
4
|
+
|
|
5
|
+
TypeScript's structural typing means two identical shapes are interchangeable. Branded types add a phantom property to create distinct types:
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
// Brand type helper
|
|
9
|
+
type Brand<T, B extends string> = T & { readonly __brand: B };
|
|
10
|
+
|
|
11
|
+
// Create distinct ID types
|
|
12
|
+
type UserId = Brand<string, "UserId">;
|
|
13
|
+
type OrderId = Brand<string, "OrderId">;
|
|
14
|
+
type ProductId = Brand<string, "ProductId">;
|
|
15
|
+
|
|
16
|
+
// Constructor functions
|
|
17
|
+
function UserId(id: string): UserId { return id as UserId; }
|
|
18
|
+
function OrderId(id: string): OrderId { return id as OrderId; }
|
|
19
|
+
|
|
20
|
+
// Now they can't be mixed up
|
|
21
|
+
function getOrder(orderId: OrderId): Order { ... }
|
|
22
|
+
|
|
23
|
+
const userId = UserId("user-123");
|
|
24
|
+
const orderId = OrderId("order-456");
|
|
25
|
+
|
|
26
|
+
getOrder(orderId); // OK
|
|
27
|
+
getOrder(userId); // Error! UserId not assignable to OrderId
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Validated Branded Types
|
|
31
|
+
|
|
32
|
+
Brands can enforce invariants at construction time:
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
type Email = Brand<string, "Email">;
|
|
36
|
+
type PositiveNumber = Brand<number, "PositiveNumber">;
|
|
37
|
+
|
|
38
|
+
function Email(input: string): Email {
|
|
39
|
+
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(input)) {
|
|
40
|
+
throw new Error(`Invalid email: ${input}`);
|
|
41
|
+
}
|
|
42
|
+
return input as Email;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function PositiveNumber(n: number): PositiveNumber {
|
|
46
|
+
if (n <= 0) throw new Error(`Must be positive: ${n}`);
|
|
47
|
+
return n as PositiveNumber;
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Result Type (Error Handling Without Exceptions)
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
type Result<T, E = Error> =
|
|
55
|
+
| { success: true; data: T }
|
|
56
|
+
| { success: false; error: E };
|
|
57
|
+
|
|
58
|
+
function ok<T>(data: T): Result<T, never> {
|
|
59
|
+
return { success: true, data };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function err<E>(error: E): Result<never, E> {
|
|
63
|
+
return { success: false, error };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Usage
|
|
67
|
+
function parseJSON(input: string): Result<unknown, string> {
|
|
68
|
+
try {
|
|
69
|
+
return ok(JSON.parse(input));
|
|
70
|
+
} catch {
|
|
71
|
+
return err("Invalid JSON");
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const result = parseJSON('{"a": 1}');
|
|
76
|
+
if (result.success) {
|
|
77
|
+
console.log(result.data); // unknown
|
|
78
|
+
} else {
|
|
79
|
+
console.log(result.error); // string
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Builder Pattern
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
class RequestBuilder {
|
|
87
|
+
private config: Partial<RequestConfig> = {};
|
|
88
|
+
|
|
89
|
+
url(url: string): this {
|
|
90
|
+
this.config.url = url;
|
|
91
|
+
return this;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
method(method: "GET" | "POST" | "PUT" | "DELETE"): this {
|
|
95
|
+
this.config.method = method;
|
|
96
|
+
return this;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
header(key: string, value: string): this {
|
|
100
|
+
this.config.headers = { ...this.config.headers, [key]: value };
|
|
101
|
+
return this;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
build(): RequestConfig {
|
|
105
|
+
if (!this.config.url) throw new Error("URL is required");
|
|
106
|
+
return this.config as RequestConfig;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Type-safe builder with required fields tracked in the type
|
|
111
|
+
type Builder<T, Required extends keyof T = never> = {
|
|
112
|
+
[K in keyof T]-?: (value: T[K]) => Builder<T, Required | K>;
|
|
113
|
+
} & ([Required] extends [keyof T]
|
|
114
|
+
? { build(): T }
|
|
115
|
+
: { build: never });
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Discriminated Union State Machines
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
type ConnectionState =
|
|
122
|
+
| { state: "disconnected" }
|
|
123
|
+
| { state: "connecting"; attempt: number }
|
|
124
|
+
| { state: "connected"; socket: WebSocket }
|
|
125
|
+
| { state: "error"; error: Error; retryAfter: number };
|
|
126
|
+
|
|
127
|
+
// Transition functions enforce valid state transitions
|
|
128
|
+
function connect(state: ConnectionState & { state: "disconnected" }): ConnectionState {
|
|
129
|
+
return { state: "connecting", attempt: 1 };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function onConnected(
|
|
133
|
+
state: ConnectionState & { state: "connecting" },
|
|
134
|
+
socket: WebSocket
|
|
135
|
+
): ConnectionState {
|
|
136
|
+
return { state: "connected", socket };
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Immutable Data
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
// Readonly utility for shallow immutability
|
|
144
|
+
type Config = Readonly<{
|
|
145
|
+
host: string;
|
|
146
|
+
port: number;
|
|
147
|
+
options: string[];
|
|
148
|
+
}>;
|
|
149
|
+
|
|
150
|
+
// as const for deep immutability of literals
|
|
151
|
+
const ROUTES = {
|
|
152
|
+
home: "/",
|
|
153
|
+
users: "/users",
|
|
154
|
+
userDetail: "/users/:id",
|
|
155
|
+
} as const;
|
|
156
|
+
|
|
157
|
+
type Route = (typeof ROUTES)[keyof typeof ROUTES];
|
|
158
|
+
// "/" | "/users" | "/users/:id"
|
|
159
|
+
|
|
160
|
+
// Readonly collections
|
|
161
|
+
function processItems(items: readonly string[]): void {
|
|
162
|
+
// items.push("x"); // Error! push doesn't exist on readonly array
|
|
163
|
+
items.forEach(console.log); // Reading is fine
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Type-Safe Event Emitter
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
type EventMap = Record<string, any[]>;
|
|
171
|
+
|
|
172
|
+
class TypedEmitter<Events extends EventMap> {
|
|
173
|
+
private handlers = new Map<keyof Events, Set<Function>>();
|
|
174
|
+
|
|
175
|
+
on<K extends keyof Events>(event: K, handler: (...args: Events[K]) => void): void {
|
|
176
|
+
if (!this.handlers.has(event)) this.handlers.set(event, new Set());
|
|
177
|
+
this.handlers.get(event)!.add(handler);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
emit<K extends keyof Events>(event: K, ...args: Events[K]): void {
|
|
181
|
+
this.handlers.get(event)?.forEach(fn => fn(...args));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
off<K extends keyof Events>(event: K, handler: (...args: Events[K]) => void): void {
|
|
185
|
+
this.handlers.get(event)?.delete(handler);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Usage
|
|
190
|
+
interface AppEvents {
|
|
191
|
+
login: [user: User];
|
|
192
|
+
logout: [];
|
|
193
|
+
error: [code: number, message: string];
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const emitter = new TypedEmitter<AppEvents>();
|
|
197
|
+
emitter.on("login", (user) => console.log(user.name));
|
|
198
|
+
emitter.on("error", (code, msg) => console.error(code, msg));
|
|
199
|
+
emitter.emit("login", currentUser);
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Exhaustive Map/Object
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
// Ensure all enum/union members are mapped
|
|
206
|
+
type Status = "active" | "inactive" | "pending" | "archived";
|
|
207
|
+
|
|
208
|
+
const STATUS_LABELS: Record<Status, string> = {
|
|
209
|
+
active: "Active",
|
|
210
|
+
inactive: "Inactive",
|
|
211
|
+
pending: "Pending",
|
|
212
|
+
archived: "Archived",
|
|
213
|
+
// Missing a key = compile error
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
// Function version
|
|
217
|
+
function getStatusColor(status: Status): string {
|
|
218
|
+
const colors = {
|
|
219
|
+
active: "#00ff00",
|
|
220
|
+
inactive: "#999999",
|
|
221
|
+
pending: "#ffaa00",
|
|
222
|
+
archived: "#ff0000",
|
|
223
|
+
} satisfies Record<Status, string>;
|
|
224
|
+
|
|
225
|
+
return colors[status];
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Opaque Type Pattern
|
|
230
|
+
|
|
231
|
+
For when you want nominal types that are assignable from their base type in specific contexts:
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
declare const __opaque: unique symbol;
|
|
235
|
+
type Opaque<T, Token> = T & { readonly [__opaque]: Token };
|
|
236
|
+
|
|
237
|
+
type Seconds = Opaque<number, "Seconds">;
|
|
238
|
+
type Milliseconds = Opaque<number, "Milliseconds">;
|
|
239
|
+
|
|
240
|
+
function wait(duration: Milliseconds): Promise<void> {
|
|
241
|
+
return new Promise(resolve => setTimeout(resolve, duration));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function toMilliseconds(seconds: Seconds): Milliseconds {
|
|
245
|
+
return (seconds * 1000) as Milliseconds;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const sec = 5 as Seconds;
|
|
249
|
+
wait(sec); // Error! Seconds not assignable to Milliseconds
|
|
250
|
+
wait(toMilliseconds(sec)); // OK
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Safe Dictionary Access
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
// With noUncheckedIndexedAccess (recommended)
|
|
257
|
+
const dict: Record<string, string> = { a: "hello" };
|
|
258
|
+
const val = dict["a"]; // string | undefined — forces null check
|
|
259
|
+
|
|
260
|
+
// Safe access pattern
|
|
261
|
+
function getOrThrow<T>(dict: Record<string, T>, key: string): T {
|
|
262
|
+
const value = dict[key];
|
|
263
|
+
if (value === undefined) throw new Error(`Missing key: ${key}`);
|
|
264
|
+
return value;
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## Const Assertions for Enum-Like Objects
|
|
269
|
+
|
|
270
|
+
Prefer `as const` objects over TypeScript enums:
|
|
271
|
+
|
|
272
|
+
```typescript
|
|
273
|
+
// Instead of:
|
|
274
|
+
enum Direction { North, South, East, West }
|
|
275
|
+
|
|
276
|
+
// Use:
|
|
277
|
+
const Direction = {
|
|
278
|
+
North: "north",
|
|
279
|
+
South: "south",
|
|
280
|
+
East: "east",
|
|
281
|
+
West: "west",
|
|
282
|
+
} as const;
|
|
283
|
+
|
|
284
|
+
type Direction = (typeof Direction)[keyof typeof Direction];
|
|
285
|
+
// "north" | "south" | "east" | "west"
|
|
286
|
+
|
|
287
|
+
// Benefits:
|
|
288
|
+
// - No runtime code beyond the object
|
|
289
|
+
// - Values are string literals (not numbers)
|
|
290
|
+
// - Easily iterable with Object.values()
|
|
291
|
+
// - Works with JSON serialization
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
## Assertion Signatures for Validation
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
function assertNotNull<T>(value: T, message?: string): asserts value is NonNullable<T> {
|
|
298
|
+
if (value == null) throw new Error(message ?? "Unexpected null");
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function assertType<T>(value: unknown, check: (v: unknown) => v is T): asserts value is T {
|
|
302
|
+
if (!check(value)) throw new Error("Type assertion failed");
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Chain assertions for parsing
|
|
306
|
+
function parseConfig(raw: unknown): Config {
|
|
307
|
+
assertType(raw, isObject);
|
|
308
|
+
assertNotNull(raw.host);
|
|
309
|
+
assertType(raw.host, isString);
|
|
310
|
+
assertType(raw.port, isNumber);
|
|
311
|
+
return raw as Config;
|
|
312
|
+
}
|
|
313
|
+
```
|