@hivehub/rulebook 1.2.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/LICENSE +191 -0
- package/README.md +539 -0
- package/dist/agents/claude-code.d.ts +69 -0
- package/dist/agents/claude-code.d.ts.map +1 -0
- package/dist/agents/claude-code.js +180 -0
- package/dist/agents/claude-code.js.map +1 -0
- package/dist/agents/cursor-agent.d.ts +184 -0
- package/dist/agents/cursor-agent.d.ts.map +1 -0
- package/dist/agents/cursor-agent.js +299 -0
- package/dist/agents/cursor-agent.js.map +1 -0
- package/dist/agents/gemini-cli.d.ts +69 -0
- package/dist/agents/gemini-cli.d.ts.map +1 -0
- package/dist/agents/gemini-cli.js +180 -0
- package/dist/agents/gemini-cli.js.map +1 -0
- package/dist/cli/commands.d.ts +57 -0
- package/dist/cli/commands.d.ts.map +1 -0
- package/dist/cli/commands.js +1370 -0
- package/dist/cli/commands.js.map +1 -0
- package/dist/cli/docs-prompts.d.ts +3 -0
- package/dist/cli/docs-prompts.d.ts.map +1 -0
- package/dist/cli/docs-prompts.js +45 -0
- package/dist/cli/docs-prompts.js.map +1 -0
- package/dist/cli/prompts.d.ts +6 -0
- package/dist/cli/prompts.d.ts.map +1 -0
- package/dist/cli/prompts.js +376 -0
- package/dist/cli/prompts.js.map +1 -0
- package/dist/core/agent-manager.d.ts +89 -0
- package/dist/core/agent-manager.d.ts.map +1 -0
- package/dist/core/agent-manager.js +546 -0
- package/dist/core/agent-manager.js.map +1 -0
- package/dist/core/auto-fixer.d.ts +14 -0
- package/dist/core/auto-fixer.d.ts.map +1 -0
- package/dist/core/auto-fixer.js +207 -0
- package/dist/core/auto-fixer.js.map +1 -0
- package/dist/core/changelog-generator.d.ts +44 -0
- package/dist/core/changelog-generator.d.ts.map +1 -0
- package/dist/core/changelog-generator.js +222 -0
- package/dist/core/changelog-generator.js.map +1 -0
- package/dist/core/cli-bridge.d.ts +113 -0
- package/dist/core/cli-bridge.d.ts.map +1 -0
- package/dist/core/cli-bridge.js +1094 -0
- package/dist/core/cli-bridge.js.map +1 -0
- package/dist/core/config-manager.d.ts +65 -0
- package/dist/core/config-manager.d.ts.map +1 -0
- package/dist/core/config-manager.js +266 -0
- package/dist/core/config-manager.js.map +1 -0
- package/dist/core/coverage-checker.d.ts +14 -0
- package/dist/core/coverage-checker.d.ts.map +1 -0
- package/dist/core/coverage-checker.js +176 -0
- package/dist/core/coverage-checker.js.map +1 -0
- package/dist/core/custom-templates.d.ts +27 -0
- package/dist/core/custom-templates.d.ts.map +1 -0
- package/dist/core/custom-templates.js +122 -0
- package/dist/core/custom-templates.js.map +1 -0
- package/dist/core/dependency-checker.d.ts +21 -0
- package/dist/core/dependency-checker.d.ts.map +1 -0
- package/dist/core/dependency-checker.js +247 -0
- package/dist/core/dependency-checker.js.map +1 -0
- package/dist/core/detector.d.ts +3 -0
- package/dist/core/detector.d.ts.map +1 -0
- package/dist/core/detector.js +1443 -0
- package/dist/core/detector.js.map +1 -0
- package/dist/core/docs-generator.d.ts +9 -0
- package/dist/core/docs-generator.d.ts.map +1 -0
- package/dist/core/docs-generator.js +531 -0
- package/dist/core/docs-generator.js.map +1 -0
- package/dist/core/generator.d.ts +16 -0
- package/dist/core/generator.d.ts.map +1 -0
- package/dist/core/generator.js +561 -0
- package/dist/core/generator.js.map +1 -0
- package/dist/core/gitignore-generator.d.ts +13 -0
- package/dist/core/gitignore-generator.d.ts.map +1 -0
- package/dist/core/gitignore-generator.js +307 -0
- package/dist/core/gitignore-generator.js.map +1 -0
- package/dist/core/health-scorer.d.ts +22 -0
- package/dist/core/health-scorer.d.ts.map +1 -0
- package/dist/core/health-scorer.js +395 -0
- package/dist/core/health-scorer.js.map +1 -0
- package/dist/core/logger.d.ts +116 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/logger.js +289 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/merger.d.ts +6 -0
- package/dist/core/merger.d.ts.map +1 -0
- package/dist/core/merger.js +131 -0
- package/dist/core/merger.js.map +1 -0
- package/dist/core/migrator.d.ts +19 -0
- package/dist/core/migrator.d.ts.map +1 -0
- package/dist/core/migrator.js +102 -0
- package/dist/core/migrator.js.map +1 -0
- package/dist/core/minimal-scaffolder.d.ts +8 -0
- package/dist/core/minimal-scaffolder.d.ts.map +1 -0
- package/dist/core/minimal-scaffolder.js +51 -0
- package/dist/core/minimal-scaffolder.js.map +1 -0
- package/dist/core/modern-console-new.d.ts +81 -0
- package/dist/core/modern-console-new.d.ts.map +1 -0
- package/dist/core/modern-console-new.js +340 -0
- package/dist/core/modern-console-new.js.map +1 -0
- package/dist/core/modern-console.d.ts +99 -0
- package/dist/core/modern-console.d.ts.map +1 -0
- package/dist/core/modern-console.js +568 -0
- package/dist/core/modern-console.js.map +1 -0
- package/dist/core/openspec-manager.d.ts +133 -0
- package/dist/core/openspec-manager.d.ts.map +1 -0
- package/dist/core/openspec-manager.js +605 -0
- package/dist/core/openspec-manager.js.map +1 -0
- package/dist/core/openspec-migrator.d.ts +27 -0
- package/dist/core/openspec-migrator.d.ts.map +1 -0
- package/dist/core/openspec-migrator.js +255 -0
- package/dist/core/openspec-migrator.js.map +1 -0
- package/dist/core/task-manager.d.ts +65 -0
- package/dist/core/task-manager.d.ts.map +1 -0
- package/dist/core/task-manager.js +318 -0
- package/dist/core/task-manager.js.map +1 -0
- package/dist/core/test-task-manager.d.ts +49 -0
- package/dist/core/test-task-manager.d.ts.map +1 -0
- package/dist/core/test-task-manager.js +121 -0
- package/dist/core/test-task-manager.js.map +1 -0
- package/dist/core/validator.d.ts +21 -0
- package/dist/core/validator.d.ts.map +1 -0
- package/dist/core/validator.js +177 -0
- package/dist/core/validator.js.map +1 -0
- package/dist/core/version-bumper.d.ts +19 -0
- package/dist/core/version-bumper.d.ts.map +1 -0
- package/dist/core/version-bumper.js +180 -0
- package/dist/core/version-bumper.js.map +1 -0
- package/dist/core/watcher.d.ts +9 -0
- package/dist/core/watcher.d.ts.map +1 -0
- package/dist/core/watcher.js +22 -0
- package/dist/core/watcher.js.map +1 -0
- package/dist/core/workflow-generator.d.ts +10 -0
- package/dist/core/workflow-generator.d.ts.map +1 -0
- package/dist/core/workflow-generator.js +279 -0
- package/dist/core/workflow-generator.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +159 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/handlers/archive-task.d.ts +17 -0
- package/dist/mcp/handlers/archive-task.d.ts.map +1 -0
- package/dist/mcp/handlers/archive-task.js +36 -0
- package/dist/mcp/handlers/archive-task.js.map +1 -0
- package/dist/mcp/handlers/create-task.d.ts +17 -0
- package/dist/mcp/handlers/create-task.d.ts.map +1 -0
- package/dist/mcp/handlers/create-task.js +56 -0
- package/dist/mcp/handlers/create-task.js.map +1 -0
- package/dist/mcp/handlers/list-tasks.d.ts +22 -0
- package/dist/mcp/handlers/list-tasks.d.ts.map +1 -0
- package/dist/mcp/handlers/list-tasks.js +42 -0
- package/dist/mcp/handlers/list-tasks.js.map +1 -0
- package/dist/mcp/handlers/show-task.d.ts +25 -0
- package/dist/mcp/handlers/show-task.d.ts.map +1 -0
- package/dist/mcp/handlers/show-task.js +43 -0
- package/dist/mcp/handlers/show-task.js.map +1 -0
- package/dist/mcp/handlers/update-task.d.ts +17 -0
- package/dist/mcp/handlers/update-task.d.ts.map +1 -0
- package/dist/mcp/handlers/update-task.js +35 -0
- package/dist/mcp/handlers/update-task.js.map +1 -0
- package/dist/mcp/handlers/validate-task.d.ts +15 -0
- package/dist/mcp/handlers/validate-task.d.ts.map +1 -0
- package/dist/mcp/handlers/validate-task.js +27 -0
- package/dist/mcp/handlers/validate-task.js.map +1 -0
- package/dist/mcp/rulebook-config.d.ts +22 -0
- package/dist/mcp/rulebook-config.d.ts.map +1 -0
- package/dist/mcp/rulebook-config.js +65 -0
- package/dist/mcp/rulebook-config.js.map +1 -0
- package/dist/mcp/rulebook-server.d.ts +4 -0
- package/dist/mcp/rulebook-server.d.ts.map +1 -0
- package/dist/mcp/rulebook-server.js +246 -0
- package/dist/mcp/rulebook-server.js.map +1 -0
- package/dist/types.d.ts +190 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/file-system.d.ts +9 -0
- package/dist/utils/file-system.d.ts.map +1 -0
- package/dist/utils/file-system.js +51 -0
- package/dist/utils/file-system.js.map +1 -0
- package/dist/utils/git-hooks.d.ts +8 -0
- package/dist/utils/git-hooks.d.ts.map +1 -0
- package/dist/utils/git-hooks.js +440 -0
- package/dist/utils/git-hooks.js.map +1 -0
- package/dist/utils/rulesignore.d.ts +9 -0
- package/dist/utils/rulesignore.d.ts.map +1 -0
- package/dist/utils/rulesignore.js +42 -0
- package/dist/utils/rulesignore.js.map +1 -0
- package/package.json +106 -0
- package/templates/cli/AIDER.md +49 -0
- package/templates/cli/AMAZON_Q.md +25 -0
- package/templates/cli/AUGGIE.md +32 -0
- package/templates/cli/CLAUDE.md +32 -0
- package/templates/cli/CLAUDE_CODE.md +35 -0
- package/templates/cli/CLINE.md +32 -0
- package/templates/cli/CODEBUDDY.md +20 -0
- package/templates/cli/CODEIUM.md +20 -0
- package/templates/cli/CODEX.md +21 -0
- package/templates/cli/CONTINUE.md +34 -0
- package/templates/cli/CURSOR_CLI.md +28 -0
- package/templates/cli/FACTORY.md +18 -0
- package/templates/cli/GEMINI.md +35 -0
- package/templates/cli/KILOCODE.md +18 -0
- package/templates/cli/OPENCODE.md +18 -0
- package/templates/cli/_GENERIC_TEMPLATE.md +29 -0
- package/templates/commands/rulebook-task-apply.md +67 -0
- package/templates/commands/rulebook-task-archive.md +70 -0
- package/templates/commands/rulebook-task-create.md +93 -0
- package/templates/commands/rulebook-task-list.md +42 -0
- package/templates/commands/rulebook-task-show.md +52 -0
- package/templates/commands/rulebook-task-validate.md +53 -0
- package/templates/core/AGENT_AUTOMATION.md +184 -0
- package/templates/core/DAG.md +304 -0
- package/templates/core/DOCUMENTATION_RULES.md +37 -0
- package/templates/core/QUALITY_ENFORCEMENT.md +68 -0
- package/templates/core/RULEBOOK.md +1874 -0
- package/templates/frameworks/ANGULAR.md +36 -0
- package/templates/frameworks/DJANGO.md +83 -0
- package/templates/frameworks/ELECTRON.md +147 -0
- package/templates/frameworks/FLASK.md +38 -0
- package/templates/frameworks/FLUTTER.md +55 -0
- package/templates/frameworks/JQUERY.md +32 -0
- package/templates/frameworks/LARAVEL.md +38 -0
- package/templates/frameworks/NESTJS.md +43 -0
- package/templates/frameworks/NEXTJS.md +127 -0
- package/templates/frameworks/NUXT.md +40 -0
- package/templates/frameworks/RAILS.md +66 -0
- package/templates/frameworks/REACT.md +38 -0
- package/templates/frameworks/REACT_NATIVE.md +47 -0
- package/templates/frameworks/SPRING.md +39 -0
- package/templates/frameworks/SYMFONY.md +36 -0
- package/templates/frameworks/VUE.md +36 -0
- package/templates/frameworks/ZEND.md +35 -0
- package/templates/git/CI_CD_PATTERNS.md +661 -0
- package/templates/git/GITHUB_ACTIONS.md +728 -0
- package/templates/git/GITLAB_CI.md +730 -0
- package/templates/git/GIT_WORKFLOW.md +1157 -0
- package/templates/git/SECRETS_MANAGEMENT.md +585 -0
- package/templates/hooks/COMMIT_MSG.md +530 -0
- package/templates/hooks/POST_CHECKOUT.md +546 -0
- package/templates/hooks/PREPARE_COMMIT_MSG.md +619 -0
- package/templates/hooks/PRE_COMMIT.md +414 -0
- package/templates/hooks/PRE_PUSH.md +601 -0
- package/templates/hooks/csharp-pre-commit.sh +23 -0
- package/templates/hooks/csharp-pre-push.sh +23 -0
- package/templates/hooks/dart-pre-commit.sh +30 -0
- package/templates/hooks/dart-pre-push.sh +25 -0
- package/templates/hooks/elixir-pre-commit.sh +32 -0
- package/templates/hooks/elixir-pre-push.sh +31 -0
- package/templates/hooks/erlang-pre-commit.sh +30 -0
- package/templates/hooks/erlang-pre-push.sh +37 -0
- package/templates/hooks/go-pre-commit.sh +40 -0
- package/templates/hooks/go-pre-push.sh +31 -0
- package/templates/hooks/haskell-pre-commit.sh +41 -0
- package/templates/hooks/haskell-pre-push.sh +37 -0
- package/templates/hooks/java-pre-commit.sh +34 -0
- package/templates/hooks/java-pre-push.sh +24 -0
- package/templates/hooks/kotlin-pre-commit.sh +32 -0
- package/templates/hooks/kotlin-pre-push.sh +16 -0
- package/templates/hooks/php-pre-commit.sh +36 -0
- package/templates/hooks/php-pre-push.sh +26 -0
- package/templates/hooks/python-pre-commit.sh +51 -0
- package/templates/hooks/python-pre-push.sh +25 -0
- package/templates/hooks/ruby-pre-commit.sh +33 -0
- package/templates/hooks/ruby-pre-push.sh +32 -0
- package/templates/hooks/rust-pre-commit.sh +30 -0
- package/templates/hooks/rust-pre-push.sh +30 -0
- package/templates/hooks/scala-pre-commit.sh +32 -0
- package/templates/hooks/scala-pre-push.sh +24 -0
- package/templates/hooks/swift-pre-commit.sh +25 -0
- package/templates/hooks/swift-pre-push.sh +23 -0
- package/templates/hooks/typescript-pre-commit.sh +37 -0
- package/templates/hooks/typescript-pre-push.sh +36 -0
- package/templates/ides/COPILOT.md +37 -0
- package/templates/ides/CURSOR.md +43 -0
- package/templates/ides/JETBRAINS_AI.md +35 -0
- package/templates/ides/REPLIT.md +36 -0
- package/templates/ides/TABNINE.md +29 -0
- package/templates/ides/VSCODE.md +40 -0
- package/templates/ides/WINDSURF.md +36 -0
- package/templates/ides/ZED.md +32 -0
- package/templates/languages/ADA.md +58 -0
- package/templates/languages/C.md +333 -0
- package/templates/languages/CPP.md +743 -0
- package/templates/languages/CSHARP.md +417 -0
- package/templates/languages/DART.md +332 -0
- package/templates/languages/ELIXIR.md +454 -0
- package/templates/languages/ERLANG.md +361 -0
- package/templates/languages/GO.md +645 -0
- package/templates/languages/HASKELL.md +177 -0
- package/templates/languages/JAVA.md +607 -0
- package/templates/languages/JAVASCRIPT.md +631 -0
- package/templates/languages/JULIA.md +97 -0
- package/templates/languages/KOTLIN.md +511 -0
- package/templates/languages/LISP.md +100 -0
- package/templates/languages/LUA.md +74 -0
- package/templates/languages/OBJECTIVEC.md +90 -0
- package/templates/languages/PHP.md +416 -0
- package/templates/languages/PYTHON.md +682 -0
- package/templates/languages/R.md +350 -0
- package/templates/languages/RUBY.md +421 -0
- package/templates/languages/RUST.md +477 -0
- package/templates/languages/SAS.md +73 -0
- package/templates/languages/SCALA.md +348 -0
- package/templates/languages/SOLIDITY.md +580 -0
- package/templates/languages/SQL.md +137 -0
- package/templates/languages/SWIFT.md +466 -0
- package/templates/languages/TYPESCRIPT.md +591 -0
- package/templates/languages/ZIG.md +265 -0
- package/templates/modules/ATLASSIAN.md +255 -0
- package/templates/modules/CONTEXT7.md +54 -0
- package/templates/modules/FIGMA.md +267 -0
- package/templates/modules/GITHUB_MCP.md +64 -0
- package/templates/modules/GRAFANA.md +328 -0
- package/templates/modules/NOTION.md +247 -0
- package/templates/modules/PLAYWRIGHT.md +90 -0
- package/templates/modules/RULEBOOK_MCP.md +156 -0
- package/templates/modules/SERENA.md +337 -0
- package/templates/modules/SUPABASE.md +223 -0
- package/templates/modules/SYNAP.md +69 -0
- package/templates/modules/VECTORIZER.md +63 -0
- package/templates/services/AZURE_BLOB.md +184 -0
- package/templates/services/CASSANDRA.md +239 -0
- package/templates/services/DYNAMODB.md +308 -0
- package/templates/services/ELASTICSEARCH.md +347 -0
- package/templates/services/GCS.md +178 -0
- package/templates/services/INFLUXDB.md +265 -0
- package/templates/services/KAFKA.md +341 -0
- package/templates/services/MARIADB.md +183 -0
- package/templates/services/MEMCACHED.md +242 -0
- package/templates/services/MINIO.md +201 -0
- package/templates/services/MONGODB.md +268 -0
- package/templates/services/MYSQL.md +358 -0
- package/templates/services/NEO4J.md +247 -0
- package/templates/services/ORACLE.md +290 -0
- package/templates/services/POSTGRESQL.md +326 -0
- package/templates/services/RABBITMQ.md +286 -0
- package/templates/services/REDIS.md +292 -0
- package/templates/services/S3.md +298 -0
- package/templates/services/SQLITE.md +294 -0
- package/templates/services/SQLSERVER.md +294 -0
- package/templates/workflows/codespell.yml +31 -0
- package/templates/workflows/cpp-lint.yml +47 -0
- package/templates/workflows/cpp-publish.yml +119 -0
- package/templates/workflows/cpp-test.yml +77 -0
- package/templates/workflows/dotnet-lint.yml +29 -0
- package/templates/workflows/dotnet-publish.yml +40 -0
- package/templates/workflows/dotnet-test.yml +41 -0
- package/templates/workflows/elixir-lint.yml +45 -0
- package/templates/workflows/elixir-publish.yml +49 -0
- package/templates/workflows/elixir-test.yml +54 -0
- package/templates/workflows/erlang-lint.yml +47 -0
- package/templates/workflows/erlang-test.yml +62 -0
- package/templates/workflows/go-lint.yml +39 -0
- package/templates/workflows/go-publish.yml +95 -0
- package/templates/workflows/go-test.yml +59 -0
- package/templates/workflows/java-lint.yml +60 -0
- package/templates/workflows/java-publish.yml +120 -0
- package/templates/workflows/java-test.yml +85 -0
- package/templates/workflows/kotlin-lint.yml +34 -0
- package/templates/workflows/kotlin-publish.yml +56 -0
- package/templates/workflows/kotlin-test.yml +48 -0
- package/templates/workflows/php-lint.yml +39 -0
- package/templates/workflows/php-publish.yml +50 -0
- package/templates/workflows/php-test.yml +54 -0
- package/templates/workflows/python-lint.yml +47 -0
- package/templates/workflows/python-publish.yml +91 -0
- package/templates/workflows/python-test.yml +59 -0
- package/templates/workflows/rust-lint.yml +54 -0
- package/templates/workflows/rust-publish.yml +66 -0
- package/templates/workflows/rust-test.yml +75 -0
- package/templates/workflows/solidity-lint.yml +41 -0
- package/templates/workflows/solidity-test.yml +47 -0
- package/templates/workflows/swift-lint.yml +32 -0
- package/templates/workflows/swift-publish.yml +58 -0
- package/templates/workflows/swift-test.yml +44 -0
- package/templates/workflows/typescript-lint.yml +61 -0
- package/templates/workflows/typescript-publish.yml +60 -0
- package/templates/workflows/typescript-test.yml +73 -0
- package/templates/workflows/zig-lint.yml +27 -0
- package/templates/workflows/zig-test.yml +40 -0
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
<!-- REDIS:START -->
|
|
2
|
+
# Redis Cache Instructions
|
|
3
|
+
|
|
4
|
+
**CRITICAL**: Use Redis for high-performance caching, session storage, pub/sub messaging, and real-time features.
|
|
5
|
+
|
|
6
|
+
## Core Features
|
|
7
|
+
|
|
8
|
+
### Connection
|
|
9
|
+
```typescript
|
|
10
|
+
// Using redis (Node.js)
|
|
11
|
+
import { createClient } from 'redis'
|
|
12
|
+
|
|
13
|
+
const client = createClient({
|
|
14
|
+
url: process.env.REDIS_URL || 'redis://localhost:6379',
|
|
15
|
+
socket: {
|
|
16
|
+
reconnectStrategy: (retries) => {
|
|
17
|
+
if (retries > 10) {
|
|
18
|
+
return new Error('Too many reconnection attempts')
|
|
19
|
+
}
|
|
20
|
+
return Math.min(retries * 100, 3000)
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
await client.connect()
|
|
26
|
+
|
|
27
|
+
// Using ioredis
|
|
28
|
+
import Redis from 'ioredis'
|
|
29
|
+
|
|
30
|
+
const redis = new Redis({
|
|
31
|
+
host: process.env.REDIS_HOST || 'localhost',
|
|
32
|
+
port: parseInt(process.env.REDIS_PORT || '6379'),
|
|
33
|
+
password: process.env.REDIS_PASSWORD,
|
|
34
|
+
retryStrategy: (times) => Math.min(times * 50, 2000),
|
|
35
|
+
maxRetriesPerRequest: 3,
|
|
36
|
+
})
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Basic Operations
|
|
40
|
+
```typescript
|
|
41
|
+
// String operations
|
|
42
|
+
await client.set('user:1', JSON.stringify({ name: 'John', email: 'john@example.com' }))
|
|
43
|
+
await client.set('user:1', 'value', { EX: 3600 }) // Expire in 1 hour
|
|
44
|
+
const user = await client.get('user:1')
|
|
45
|
+
await client.del('user:1')
|
|
46
|
+
|
|
47
|
+
// Multiple operations
|
|
48
|
+
await client.mSet({
|
|
49
|
+
'key1': 'value1',
|
|
50
|
+
'key2': 'value2',
|
|
51
|
+
})
|
|
52
|
+
const values = await client.mGet(['key1', 'key2'])
|
|
53
|
+
|
|
54
|
+
// Increment/Decrement
|
|
55
|
+
await client.incr('counter')
|
|
56
|
+
await client.incrBy('counter', 5)
|
|
57
|
+
await client.decr('counter')
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Data Structures
|
|
61
|
+
```typescript
|
|
62
|
+
// Lists
|
|
63
|
+
await client.lPush('tasks', 'task1', 'task2')
|
|
64
|
+
await client.rPush('tasks', 'task3')
|
|
65
|
+
const task = await client.lPop('tasks')
|
|
66
|
+
const tasks = await client.lRange('tasks', 0, -1)
|
|
67
|
+
|
|
68
|
+
// Sets
|
|
69
|
+
await client.sAdd('tags', 'javascript', 'typescript', 'nodejs')
|
|
70
|
+
const tags = await client.sMembers('tags')
|
|
71
|
+
const exists = await client.sIsMember('tags', 'javascript')
|
|
72
|
+
await client.sRem('tags', 'javascript')
|
|
73
|
+
|
|
74
|
+
// Sorted Sets
|
|
75
|
+
await client.zAdd('leaderboard', {
|
|
76
|
+
score: 100,
|
|
77
|
+
value: 'player1',
|
|
78
|
+
})
|
|
79
|
+
const topPlayers = await client.zRange('leaderboard', 0, 9, { REV: true })
|
|
80
|
+
|
|
81
|
+
// Hashes
|
|
82
|
+
await client.hSet('user:1', {
|
|
83
|
+
name: 'John',
|
|
84
|
+
email: 'john@example.com',
|
|
85
|
+
age: '30',
|
|
86
|
+
})
|
|
87
|
+
const user = await client.hGetAll('user:1')
|
|
88
|
+
await client.hIncrBy('user:1', 'age', 1)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Advanced Features
|
|
92
|
+
```typescript
|
|
93
|
+
// Pub/Sub
|
|
94
|
+
const publisher = client.duplicate()
|
|
95
|
+
await publisher.connect()
|
|
96
|
+
|
|
97
|
+
const subscriber = client.duplicate()
|
|
98
|
+
await subscriber.connect()
|
|
99
|
+
|
|
100
|
+
await subscriber.subscribe('notifications', (message) => {
|
|
101
|
+
console.log('Received:', message)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
await publisher.publish('notifications', JSON.stringify({ type: 'alert', message: 'Hello' }))
|
|
105
|
+
|
|
106
|
+
// Streams
|
|
107
|
+
await client.xAdd('events', '*', {
|
|
108
|
+
type: 'user_login',
|
|
109
|
+
userId: '123',
|
|
110
|
+
timestamp: Date.now().toString(),
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
const events = await client.xRead({
|
|
114
|
+
key: 'events',
|
|
115
|
+
id: '0',
|
|
116
|
+
}, {
|
|
117
|
+
COUNT: 10,
|
|
118
|
+
BLOCK: 1000,
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
// Transactions
|
|
122
|
+
const multi = client.multi()
|
|
123
|
+
multi.set('key1', 'value1')
|
|
124
|
+
multi.set('key2', 'value2')
|
|
125
|
+
multi.incr('counter')
|
|
126
|
+
await multi.exec()
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Common Patterns
|
|
130
|
+
|
|
131
|
+
### Caching
|
|
132
|
+
```typescript
|
|
133
|
+
async function getCachedUser(userId: string) {
|
|
134
|
+
const cached = await client.get(`user:${userId}`)
|
|
135
|
+
if (cached) {
|
|
136
|
+
return JSON.parse(cached)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const user = await fetchUserFromDatabase(userId)
|
|
140
|
+
await client.set(`user:${userId}`, JSON.stringify(user), { EX: 3600 })
|
|
141
|
+
return user
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Cache with tags (for invalidation)
|
|
145
|
+
async function cacheWithTags(key: string, value: any, tags: string[], ttl: number) {
|
|
146
|
+
await client.set(key, JSON.stringify(value), { EX: ttl })
|
|
147
|
+
for (const tag of tags) {
|
|
148
|
+
await client.sAdd(`tag:${tag}`, key)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function invalidateByTag(tag: string) {
|
|
153
|
+
const keys = await client.sMembers(`tag:${tag}`)
|
|
154
|
+
if (keys.length > 0) {
|
|
155
|
+
await client.del(...keys)
|
|
156
|
+
}
|
|
157
|
+
await client.del(`tag:${tag}`)
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Rate Limiting
|
|
162
|
+
```typescript
|
|
163
|
+
async function checkRateLimit(userId: string, limit: number, window: number): Promise<boolean> {
|
|
164
|
+
const key = `ratelimit:${userId}`
|
|
165
|
+
const current = await client.incr(key)
|
|
166
|
+
|
|
167
|
+
if (current === 1) {
|
|
168
|
+
await client.expire(key, window)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return current <= limit
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Session Storage
|
|
176
|
+
```typescript
|
|
177
|
+
async function setSession(sessionId: string, data: any, ttl: number) {
|
|
178
|
+
await client.set(`session:${sessionId}`, JSON.stringify(data), { EX: ttl })
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async function getSession(sessionId: string) {
|
|
182
|
+
const data = await client.get(`session:${sessionId}`)
|
|
183
|
+
return data ? JSON.parse(data) : null
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async function deleteSession(sessionId: string) {
|
|
187
|
+
await client.del(`session:${sessionId}`)
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Distributed Locks
|
|
192
|
+
```typescript
|
|
193
|
+
async function acquireLock(key: string, ttl: number): Promise<boolean> {
|
|
194
|
+
const result = await client.set(key, 'locked', {
|
|
195
|
+
EX: ttl,
|
|
196
|
+
NX: true, // Only set if not exists
|
|
197
|
+
})
|
|
198
|
+
return result === 'OK'
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function releaseLock(key: string) {
|
|
202
|
+
await client.del(key)
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Best Practices
|
|
207
|
+
|
|
208
|
+
✅ **DO:**
|
|
209
|
+
- Use connection pooling
|
|
210
|
+
- Set appropriate TTL for cached data
|
|
211
|
+
- Use pipelining for multiple operations
|
|
212
|
+
- Use transactions (MULTI/EXEC) for atomic operations
|
|
213
|
+
- Monitor memory usage
|
|
214
|
+
- Use appropriate data structures (hash for objects, set for unique values)
|
|
215
|
+
- Implement cache invalidation strategies
|
|
216
|
+
- Use Redis Cluster for high availability
|
|
217
|
+
- Enable persistence (RDB or AOF) for production
|
|
218
|
+
- Use Redis Sentinel for failover
|
|
219
|
+
|
|
220
|
+
❌ **DON'T:**
|
|
221
|
+
- Store large values (> 100KB, use compression or external storage)
|
|
222
|
+
- Use Redis as primary database (it's a cache)
|
|
223
|
+
- Skip error handling
|
|
224
|
+
- Ignore memory limits
|
|
225
|
+
- Hardcode connection strings
|
|
226
|
+
- Use KEYS command in production (use SCAN instead)
|
|
227
|
+
- Store sensitive data without encryption
|
|
228
|
+
- Skip connection retry logic
|
|
229
|
+
- Ignore eviction policies
|
|
230
|
+
- Use blocking operations without timeouts
|
|
231
|
+
|
|
232
|
+
## Configuration
|
|
233
|
+
|
|
234
|
+
### Environment Variables
|
|
235
|
+
```bash
|
|
236
|
+
REDIS_URL=redis://localhost:6379
|
|
237
|
+
REDIS_URL=redis://:password@host:6379
|
|
238
|
+
REDIS_URL=rediss://host:6380 # SSL
|
|
239
|
+
REDIS_HOST=localhost
|
|
240
|
+
REDIS_PORT=6379
|
|
241
|
+
REDIS_PASSWORD=securepassword
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Docker Compose
|
|
245
|
+
```yaml
|
|
246
|
+
services:
|
|
247
|
+
redis:
|
|
248
|
+
image: redis:7-alpine
|
|
249
|
+
ports:
|
|
250
|
+
- "6379:6379"
|
|
251
|
+
command: redis-server --requirepass securepassword --appendonly yes
|
|
252
|
+
volumes:
|
|
253
|
+
- redis_data:/data
|
|
254
|
+
healthcheck:
|
|
255
|
+
test: ["CMD", "redis-cli", "ping"]
|
|
256
|
+
interval: 10s
|
|
257
|
+
timeout: 5s
|
|
258
|
+
retries: 5
|
|
259
|
+
|
|
260
|
+
volumes:
|
|
261
|
+
redis_data:
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## Integration with Development
|
|
265
|
+
|
|
266
|
+
### Testing
|
|
267
|
+
```typescript
|
|
268
|
+
// Use test Redis instance
|
|
269
|
+
const testClient = createClient({
|
|
270
|
+
url: 'redis://localhost:6380', // Different port for tests
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
// Clean up after tests
|
|
274
|
+
afterEach(async () => {
|
|
275
|
+
await testClient.flushDb()
|
|
276
|
+
})
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Health Checks
|
|
280
|
+
```typescript
|
|
281
|
+
async function checkRedisHealth(): Promise<boolean> {
|
|
282
|
+
try {
|
|
283
|
+
await client.ping()
|
|
284
|
+
return true
|
|
285
|
+
} catch {
|
|
286
|
+
return false
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
<!-- REDIS:END -->
|
|
292
|
+
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
<!-- S3:START -->
|
|
2
|
+
# AWS S3 Storage Instructions
|
|
3
|
+
|
|
4
|
+
**CRITICAL**: Use AWS S3 for object storage, file uploads, static assets, and backup storage with high availability.
|
|
5
|
+
|
|
6
|
+
## Core Features
|
|
7
|
+
|
|
8
|
+
### Connection
|
|
9
|
+
```typescript
|
|
10
|
+
// Using @aws-sdk/client-s3
|
|
11
|
+
import { S3Client, PutObjectCommand, GetObjectCommand, DeleteObjectCommand } from '@aws-sdk/client-s3'
|
|
12
|
+
|
|
13
|
+
const s3Client = new S3Client({
|
|
14
|
+
region: process.env.AWS_REGION || 'us-east-1',
|
|
15
|
+
credentials: {
|
|
16
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID || '',
|
|
17
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY || '',
|
|
18
|
+
},
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
// Using AWS SDK v2
|
|
22
|
+
import AWS from 'aws-sdk'
|
|
23
|
+
|
|
24
|
+
const s3 = new AWS.S3({
|
|
25
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
26
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
|
27
|
+
region: process.env.AWS_REGION || 'us-east-1',
|
|
28
|
+
})
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Basic Operations
|
|
32
|
+
```typescript
|
|
33
|
+
// Upload file
|
|
34
|
+
const uploadParams = {
|
|
35
|
+
Bucket: process.env.S3_BUCKET || 'my-bucket',
|
|
36
|
+
Key: 'path/to/file.jpg',
|
|
37
|
+
Body: fileBuffer,
|
|
38
|
+
ContentType: 'image/jpeg',
|
|
39
|
+
ACL: 'private', // or 'public-read'
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
await s3Client.send(new PutObjectCommand(uploadParams))
|
|
43
|
+
|
|
44
|
+
// Upload with metadata
|
|
45
|
+
await s3Client.send(new PutObjectCommand({
|
|
46
|
+
...uploadParams,
|
|
47
|
+
Metadata: {
|
|
48
|
+
userId: '123',
|
|
49
|
+
originalName: 'photo.jpg',
|
|
50
|
+
},
|
|
51
|
+
TagSet: [
|
|
52
|
+
{ Key: 'category', Value: 'profile' },
|
|
53
|
+
],
|
|
54
|
+
}))
|
|
55
|
+
|
|
56
|
+
// Get file
|
|
57
|
+
const getParams = {
|
|
58
|
+
Bucket: process.env.S3_BUCKET,
|
|
59
|
+
Key: 'path/to/file.jpg',
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const response = await s3Client.send(new GetObjectCommand(getParams))
|
|
63
|
+
const fileContent = await response.Body?.transformToByteArray()
|
|
64
|
+
|
|
65
|
+
// Delete file
|
|
66
|
+
await s3Client.send(new DeleteObjectCommand({
|
|
67
|
+
Bucket: process.env.S3_BUCKET,
|
|
68
|
+
Key: 'path/to/file.jpg',
|
|
69
|
+
}))
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Advanced Features
|
|
73
|
+
```typescript
|
|
74
|
+
// Presigned URL for upload
|
|
75
|
+
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
|
|
76
|
+
|
|
77
|
+
const command = new PutObjectCommand({
|
|
78
|
+
Bucket: process.env.S3_BUCKET,
|
|
79
|
+
Key: 'uploads/file.jpg',
|
|
80
|
+
ContentType: 'image/jpeg',
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
const presignedUrl = await getSignedUrl(s3Client, command, { expiresIn: 3600 })
|
|
84
|
+
|
|
85
|
+
// Presigned URL for download
|
|
86
|
+
const getCommand = new GetObjectCommand({
|
|
87
|
+
Bucket: process.env.S3_BUCKET,
|
|
88
|
+
Key: 'path/to/file.jpg',
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
const downloadUrl = await getSignedUrl(s3Client, getCommand, { expiresIn: 3600 })
|
|
92
|
+
|
|
93
|
+
// Multipart upload (for large files)
|
|
94
|
+
import { CreateMultipartUploadCommand, UploadPartCommand, CompleteMultipartUploadCommand } from '@aws-sdk/client-s3'
|
|
95
|
+
|
|
96
|
+
const createCommand = new CreateMultipartUploadCommand({
|
|
97
|
+
Bucket: process.env.S3_BUCKET,
|
|
98
|
+
Key: 'large-file.zip',
|
|
99
|
+
ContentType: 'application/zip',
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
const { UploadId } = await s3Client.send(createCommand)
|
|
103
|
+
|
|
104
|
+
// Upload parts
|
|
105
|
+
const part1 = await s3Client.send(new UploadPartCommand({
|
|
106
|
+
Bucket: process.env.S3_BUCKET,
|
|
107
|
+
Key: 'large-file.zip',
|
|
108
|
+
PartNumber: 1,
|
|
109
|
+
UploadId,
|
|
110
|
+
Body: part1Buffer,
|
|
111
|
+
}))
|
|
112
|
+
|
|
113
|
+
// Complete upload
|
|
114
|
+
await s3Client.send(new CompleteMultipartUploadCommand({
|
|
115
|
+
Bucket: process.env.S3_BUCKET,
|
|
116
|
+
Key: 'large-file.zip',
|
|
117
|
+
UploadId,
|
|
118
|
+
MultipartUpload: {
|
|
119
|
+
Parts: [
|
|
120
|
+
{ PartNumber: 1, ETag: part1.ETag },
|
|
121
|
+
],
|
|
122
|
+
},
|
|
123
|
+
}))
|
|
124
|
+
|
|
125
|
+
// List objects
|
|
126
|
+
import { ListObjectsV2Command } from '@aws-sdk/client-s3'
|
|
127
|
+
|
|
128
|
+
const listCommand = new ListObjectsV2Command({
|
|
129
|
+
Bucket: process.env.S3_BUCKET,
|
|
130
|
+
Prefix: 'uploads/',
|
|
131
|
+
MaxKeys: 100,
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
const { Contents } = await s3Client.send(listCommand)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Common Patterns
|
|
138
|
+
|
|
139
|
+
### File Upload Handler
|
|
140
|
+
```typescript
|
|
141
|
+
async function uploadFile(file: Buffer, filename: string, userId: string) {
|
|
142
|
+
const key = `users/${userId}/${Date.now()}-${filename}`
|
|
143
|
+
|
|
144
|
+
await s3Client.send(new PutObjectCommand({
|
|
145
|
+
Bucket: process.env.S3_BUCKET,
|
|
146
|
+
Key: key,
|
|
147
|
+
Body: file,
|
|
148
|
+
ContentType: getContentType(filename),
|
|
149
|
+
Metadata: {
|
|
150
|
+
userId,
|
|
151
|
+
originalName: filename,
|
|
152
|
+
uploadedAt: new Date().toISOString(),
|
|
153
|
+
},
|
|
154
|
+
}))
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
key,
|
|
158
|
+
url: `https://${process.env.S3_BUCKET}.s3.${process.env.AWS_REGION}.amazonaws.com/${key}`,
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Temporary File Access
|
|
164
|
+
```typescript
|
|
165
|
+
async function generateTemporaryDownloadUrl(key: string, expiresIn: number = 3600) {
|
|
166
|
+
const command = new GetObjectCommand({
|
|
167
|
+
Bucket: process.env.S3_BUCKET,
|
|
168
|
+
Key: key,
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
return await getSignedUrl(s3Client, command, { expiresIn })
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### File Cleanup
|
|
176
|
+
```typescript
|
|
177
|
+
async function deleteFilesByPrefix(prefix: string) {
|
|
178
|
+
const listCommand = new ListObjectsV2Command({
|
|
179
|
+
Bucket: process.env.S3_BUCKET,
|
|
180
|
+
Prefix: prefix,
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
let continuationToken: string | undefined
|
|
184
|
+
|
|
185
|
+
do {
|
|
186
|
+
const response = await s3Client.send({
|
|
187
|
+
...listCommand,
|
|
188
|
+
ContinuationToken: continuationToken,
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
if (response.Contents) {
|
|
192
|
+
for (const object of response.Contents) {
|
|
193
|
+
if (object.Key) {
|
|
194
|
+
await s3Client.send(new DeleteObjectCommand({
|
|
195
|
+
Bucket: process.env.S3_BUCKET,
|
|
196
|
+
Key: object.Key,
|
|
197
|
+
}))
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
continuationToken = response.NextContinuationToken
|
|
203
|
+
} while (continuationToken)
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Best Practices
|
|
208
|
+
|
|
209
|
+
✅ **DO:**
|
|
210
|
+
- Use presigned URLs for client uploads
|
|
211
|
+
- Set appropriate Content-Type headers
|
|
212
|
+
- Use versioning for important files
|
|
213
|
+
- Implement lifecycle policies for cleanup
|
|
214
|
+
- Use multipart upload for files > 5MB
|
|
215
|
+
- Enable encryption (SSE-S3 or SSE-KMS)
|
|
216
|
+
- Use appropriate storage classes (Standard, IA, Glacier)
|
|
217
|
+
- Implement proper error handling
|
|
218
|
+
- Use bucket policies for access control
|
|
219
|
+
- Monitor bucket usage and costs
|
|
220
|
+
|
|
221
|
+
❌ **DON'T:**
|
|
222
|
+
- Store sensitive data without encryption
|
|
223
|
+
- Use public-read ACL unnecessarily
|
|
224
|
+
- Hardcode credentials
|
|
225
|
+
- Ignore error handling
|
|
226
|
+
- Skip content-type validation
|
|
227
|
+
- Store large files without multipart upload
|
|
228
|
+
- Ignore lifecycle policies
|
|
229
|
+
- Skip access logging
|
|
230
|
+
- Use default bucket policies
|
|
231
|
+
- Ignore cost optimization
|
|
232
|
+
|
|
233
|
+
## Configuration
|
|
234
|
+
|
|
235
|
+
### Environment Variables
|
|
236
|
+
```bash
|
|
237
|
+
AWS_REGION=us-east-1
|
|
238
|
+
AWS_ACCESS_KEY_ID=your-access-key
|
|
239
|
+
AWS_SECRET_ACCESS_KEY=your-secret-key
|
|
240
|
+
S3_BUCKET=my-bucket
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### IAM Policy
|
|
244
|
+
```json
|
|
245
|
+
{
|
|
246
|
+
"Version": "2012-10-17",
|
|
247
|
+
"Statement": [
|
|
248
|
+
{
|
|
249
|
+
"Effect": "Allow",
|
|
250
|
+
"Action": [
|
|
251
|
+
"s3:PutObject",
|
|
252
|
+
"s3:GetObject",
|
|
253
|
+
"s3:DeleteObject"
|
|
254
|
+
],
|
|
255
|
+
"Resource": "arn:aws:s3:::my-bucket/*"
|
|
256
|
+
}
|
|
257
|
+
]
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## Integration with Development
|
|
262
|
+
|
|
263
|
+
### Testing
|
|
264
|
+
```typescript
|
|
265
|
+
// Use test bucket or LocalStack
|
|
266
|
+
const testS3Client = new S3Client({
|
|
267
|
+
endpoint: 'http://localhost:4566', // LocalStack
|
|
268
|
+
region: 'us-east-1',
|
|
269
|
+
credentials: {
|
|
270
|
+
accessKeyId: 'test',
|
|
271
|
+
secretAccessKey: 'test',
|
|
272
|
+
},
|
|
273
|
+
forcePathStyle: true,
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
// Clean up after tests
|
|
277
|
+
afterEach(async () => {
|
|
278
|
+
// Delete test files
|
|
279
|
+
})
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Health Checks
|
|
283
|
+
```typescript
|
|
284
|
+
async function checkS3Health(): Promise<boolean> {
|
|
285
|
+
try {
|
|
286
|
+
await s3Client.send(new ListObjectsV2Command({
|
|
287
|
+
Bucket: process.env.S3_BUCKET,
|
|
288
|
+
MaxKeys: 1,
|
|
289
|
+
}))
|
|
290
|
+
return true
|
|
291
|
+
} catch {
|
|
292
|
+
return false
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
<!-- S3:END -->
|
|
298
|
+
|