@codyswann/lisa 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +867 -0
- package/all/copy-overwrite/.claude/README.md +205 -0
- package/all/copy-overwrite/.claude/agents/agent-architect.md +311 -0
- package/all/copy-overwrite/.claude/agents/codebase-analyzer.md +146 -0
- package/all/copy-overwrite/.claude/agents/codebase-locator.md +125 -0
- package/all/copy-overwrite/.claude/agents/codebase-pattern-finder.md +237 -0
- package/all/copy-overwrite/.claude/agents/git-history-analyzer.md +183 -0
- package/all/copy-overwrite/.claude/agents/hooks-expert.md +74 -0
- package/all/copy-overwrite/.claude/agents/skill-evaluator.md +246 -0
- package/all/copy-overwrite/.claude/agents/slash-command-architect.md +87 -0
- package/all/copy-overwrite/.claude/agents/web-search-researcher.md +112 -0
- package/all/copy-overwrite/.claude/commands/git/commit-and-submit-pr.md +8 -0
- package/all/copy-overwrite/.claude/commands/git/commit.md +44 -0
- package/all/copy-overwrite/.claude/commands/git/prune.md +34 -0
- package/all/copy-overwrite/.claude/commands/git/submit-pr.md +50 -0
- package/all/copy-overwrite/.claude/commands/jira/create.md +50 -0
- package/all/copy-overwrite/.claude/commands/jira/verify.md +34 -0
- package/all/copy-overwrite/.claude/commands/project/archive.md +8 -0
- package/all/copy-overwrite/.claude/commands/project/bootstrap.md +49 -0
- package/all/copy-overwrite/.claude/commands/project/complete-task.md +7 -0
- package/all/copy-overwrite/.claude/commands/project/debrief.md +65 -0
- package/all/copy-overwrite/.claude/commands/project/execute.md +94 -0
- package/all/copy-overwrite/.claude/commands/project/implement.md +42 -0
- package/all/copy-overwrite/.claude/commands/project/local-code-review.md +88 -0
- package/all/copy-overwrite/.claude/commands/project/lower-code-complexity.md +74 -0
- package/all/copy-overwrite/.claude/commands/project/plan.md +314 -0
- package/all/copy-overwrite/.claude/commands/project/research.md +248 -0
- package/all/copy-overwrite/.claude/commands/project/review.md +63 -0
- package/all/copy-overwrite/.claude/commands/project/setup.md +19 -0
- package/all/copy-overwrite/.claude/commands/project/verify.md +38 -0
- package/all/copy-overwrite/.claude/commands/pull-request/review.md +12 -0
- package/all/copy-overwrite/.claude/commands/rules/format-md.md +72 -0
- package/all/copy-overwrite/.claude/commands/sonarqube/check.md +6 -0
- package/all/copy-overwrite/.claude/commands/sonarqube/fix.md +3 -0
- package/all/copy-overwrite/.claude/hooks/README.md +301 -0
- package/all/copy-overwrite/.claude/hooks/notify-ntfy.sh +181 -0
- package/all/copy-overwrite/.claude/settings.json +41 -0
- package/all/copy-overwrite/.claude/settings.local.json.example +14 -0
- package/all/copy-overwrite/.claude/skills/coding-philosophy/SKILL.md +405 -0
- package/all/copy-overwrite/.claude/skills/coding-philosophy/references/function-structure.md +416 -0
- package/all/copy-overwrite/.claude/skills/coding-philosophy/references/immutable-patterns.md +316 -0
- package/all/copy-overwrite/.claude/skills/prompt-complexity-scorer/SKILL.md +118 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/LICENSE.txt +202 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/SKILL.md +210 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/scripts/__pycache__/quick_validate.cpython-312.pyc +0 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/scripts/init_skill.py +303 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/scripts/package_skill.py +110 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/scripts/quick_validate.py +65 -0
- package/all/copy-overwrite/CLAUDE.md +77 -0
- package/all/copy-overwrite/HUMAN.md +17 -0
- package/all/copy-overwrite/specs/.keep +0 -0
- package/all/create-only/PROJECT_RULES.md +0 -0
- package/cdk/merge/package.json +20 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +107 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/prompts.d.ts +45 -0
- package/dist/cli/prompts.d.ts.map +1 -0
- package/dist/cli/prompts.js +58 -0
- package/dist/cli/prompts.js.map +1 -0
- package/dist/core/config.d.ts +73 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +36 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/index.d.ts +4 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +4 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/lisa.d.ts +81 -0
- package/dist/core/lisa.d.ts.map +1 -0
- package/dist/core/lisa.js +459 -0
- package/dist/core/lisa.js.map +1 -0
- package/dist/core/manifest.d.ts +58 -0
- package/dist/core/manifest.d.ts.map +1 -0
- package/dist/core/manifest.js +104 -0
- package/dist/core/manifest.js.map +1 -0
- package/dist/detection/detector.interface.d.ts +15 -0
- package/dist/detection/detector.interface.d.ts.map +1 -0
- package/dist/detection/detector.interface.js +2 -0
- package/dist/detection/detector.interface.js.map +1 -0
- package/dist/detection/detectors/cdk.d.ts +10 -0
- package/dist/detection/detectors/cdk.d.ts.map +1 -0
- package/dist/detection/detectors/cdk.js +34 -0
- package/dist/detection/detectors/cdk.js.map +1 -0
- package/dist/detection/detectors/expo.d.ts +10 -0
- package/dist/detection/detectors/expo.d.ts.map +1 -0
- package/dist/detection/detectors/expo.js +30 -0
- package/dist/detection/detectors/expo.js.map +1 -0
- package/dist/detection/detectors/nestjs.d.ts +10 -0
- package/dist/detection/detectors/nestjs.d.ts.map +1 -0
- package/dist/detection/detectors/nestjs.js +34 -0
- package/dist/detection/detectors/nestjs.js.map +1 -0
- package/dist/detection/detectors/npm-package.d.ts +13 -0
- package/dist/detection/detectors/npm-package.d.ts.map +1 -0
- package/dist/detection/detectors/npm-package.js +30 -0
- package/dist/detection/detectors/npm-package.js.map +1 -0
- package/dist/detection/detectors/typescript.d.ts +10 -0
- package/dist/detection/detectors/typescript.d.ts.map +1 -0
- package/dist/detection/detectors/typescript.js +25 -0
- package/dist/detection/detectors/typescript.js.map +1 -0
- package/dist/detection/index.d.ts +24 -0
- package/dist/detection/index.d.ts.map +1 -0
- package/dist/detection/index.js +57 -0
- package/dist/detection/index.js.map +1 -0
- package/dist/errors/index.d.ts +69 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +110 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/logging/console-logger.d.ts +12 -0
- package/dist/logging/console-logger.d.ts.map +1 -0
- package/dist/logging/console-logger.js +22 -0
- package/dist/logging/console-logger.js.map +1 -0
- package/dist/logging/index.d.ts +4 -0
- package/dist/logging/index.d.ts.map +1 -0
- package/dist/logging/index.js +3 -0
- package/dist/logging/index.js.map +1 -0
- package/dist/logging/logger.interface.d.ts +20 -0
- package/dist/logging/logger.interface.d.ts.map +1 -0
- package/dist/logging/logger.interface.js +2 -0
- package/dist/logging/logger.interface.js.map +1 -0
- package/dist/logging/silent-logger.d.ts +12 -0
- package/dist/logging/silent-logger.d.ts.map +1 -0
- package/dist/logging/silent-logger.js +21 -0
- package/dist/logging/silent-logger.js.map +1 -0
- package/dist/strategies/copy-contents.d.ts +14 -0
- package/dist/strategies/copy-contents.d.ts.map +1 -0
- package/dist/strategies/copy-contents.js +69 -0
- package/dist/strategies/copy-contents.js.map +1 -0
- package/dist/strategies/copy-overwrite.d.ts +14 -0
- package/dist/strategies/copy-overwrite.d.ts.map +1 -0
- package/dist/strategies/copy-overwrite.js +47 -0
- package/dist/strategies/copy-overwrite.js.map +1 -0
- package/dist/strategies/create-only.d.ts +13 -0
- package/dist/strategies/create-only.d.ts.map +1 -0
- package/dist/strategies/create-only.js +30 -0
- package/dist/strategies/create-only.js.map +1 -0
- package/dist/strategies/index.d.ts +31 -0
- package/dist/strategies/index.d.ts.map +1 -0
- package/dist/strategies/index.js +52 -0
- package/dist/strategies/index.js.map +1 -0
- package/dist/strategies/merge.d.ts +13 -0
- package/dist/strategies/merge.d.ts.map +1 -0
- package/dist/strategies/merge.js +60 -0
- package/dist/strategies/merge.js.map +1 -0
- package/dist/strategies/strategy.interface.d.ts +31 -0
- package/dist/strategies/strategy.interface.d.ts.map +1 -0
- package/dist/strategies/strategy.interface.js +2 -0
- package/dist/strategies/strategy.interface.js.map +1 -0
- package/dist/transaction/backup.d.ts +38 -0
- package/dist/transaction/backup.d.ts.map +1 -0
- package/dist/transaction/backup.js +97 -0
- package/dist/transaction/backup.js.map +1 -0
- package/dist/transaction/index.d.ts +4 -0
- package/dist/transaction/index.d.ts.map +1 -0
- package/dist/transaction/index.js +3 -0
- package/dist/transaction/index.js.map +1 -0
- package/dist/transaction/transaction.d.ts +34 -0
- package/dist/transaction/transaction.d.ts.map +1 -0
- package/dist/transaction/transaction.js +68 -0
- package/dist/transaction/transaction.js.map +1 -0
- package/dist/utils/file-operations.d.ts +29 -0
- package/dist/utils/file-operations.d.ts.map +1 -0
- package/dist/utils/file-operations.js +84 -0
- package/dist/utils/file-operations.js.map +1 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +4 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/json-utils.d.ts +22 -0
- package/dist/utils/json-utils.d.ts.map +1 -0
- package/dist/utils/json-utils.js +57 -0
- package/dist/utils/json-utils.js.map +1 -0
- package/dist/utils/path-utils.d.ts +21 -0
- package/dist/utils/path-utils.d.ts.map +1 -0
- package/dist/utils/path-utils.js +35 -0
- package/dist/utils/path-utils.js.map +1 -0
- package/eslint-plugin-code-organization/README.md +149 -0
- package/eslint-plugin-code-organization/__tests__/enforce-statement-order.test.js +468 -0
- package/eslint-plugin-code-organization/index.js +23 -0
- package/eslint-plugin-code-organization/package.json +10 -0
- package/eslint-plugin-code-organization/rules/enforce-statement-order.js +157 -0
- package/expo/copy-overwrite/.claude/skills/apollo-client/SKILL.md +238 -0
- package/expo/copy-overwrite/.claude/skills/apollo-client/references/mutation-patterns.md +360 -0
- package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/SKILL.md +360 -0
- package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/references/atomic-levels.md +417 -0
- package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/references/folder-structure.md +257 -0
- package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/references/gluestack-mapping.md +233 -0
- package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/scripts/validate_atomic_structure.py +327 -0
- package/expo/copy-overwrite/.claude/skills/container-view-pattern/SKILL.md +299 -0
- package/expo/copy-overwrite/.claude/skills/container-view-pattern/references/examples.md +749 -0
- package/expo/copy-overwrite/.claude/skills/container-view-pattern/references/patterns.md +318 -0
- package/expo/copy-overwrite/.claude/skills/container-view-pattern/scripts/create_component.py +198 -0
- package/expo/copy-overwrite/.claude/skills/container-view-pattern/scripts/validate_component.py +207 -0
- package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/SKILL.md +268 -0
- package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/references/common-issues.md +619 -0
- package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/references/file-extensions.md +340 -0
- package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/references/platform-api.md +276 -0
- package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/scripts/validate_cross_platform.py +414 -0
- package/expo/copy-overwrite/.claude/skills/directory-structure/SKILL.md +202 -0
- package/expo/copy-overwrite/.claude/skills/directory-structure/scripts/validate_structure.py +443 -0
- package/expo/copy-overwrite/.claude/skills/expo-env-config/SKILL.md +309 -0
- package/expo/copy-overwrite/.claude/skills/expo-env-config/references/validation-patterns.md +417 -0
- package/expo/copy-overwrite/.claude/skills/expo-router-best-practices/SKILL.md +431 -0
- package/expo/copy-overwrite/.claude/skills/expo-router-best-practices/references/official-docs.md +290 -0
- package/expo/copy-overwrite/.claude/skills/expo-router-best-practices/scripts/generate-route.py +169 -0
- package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/SKILL.md +411 -0
- package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/references/color-tokens.md +343 -0
- package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/references/component-mapping.md +307 -0
- package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/references/spacing-scale.md +300 -0
- package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/scripts/validate_styling.py +354 -0
- package/expo/copy-overwrite/.claude/skills/local-state/SKILL.md +362 -0
- package/expo/copy-overwrite/.claude/skills/local-state/references/async-storage.md +505 -0
- package/expo/copy-overwrite/.claude/skills/local-state/references/persistence-patterns.md +711 -0
- package/expo/copy-overwrite/.claude/skills/local-state/references/reactive-variables.md +446 -0
- package/expo/copy-overwrite/.claude/skills/playwright-selectors/SKILL.md +223 -0
- package/expo/copy-overwrite/.claude/skills/testing-library/SKILL.md +319 -0
- package/expo/copy-overwrite/.claude/skills/testing-library/references/async-patterns.md +420 -0
- package/expo/copy-overwrite/.claude/skills/testing-library/references/expo-router-testing.md +556 -0
- package/expo/copy-overwrite/.claude/skills/testing-library/references/mocking-patterns.md +590 -0
- package/expo/copy-overwrite/.claude/skills/testing-library/references/query-priority.md +291 -0
- package/expo/copy-overwrite/.easignore.extra +2 -0
- package/expo/copy-overwrite/.mcp.json +33 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/README.md +234 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/__tests__/plugin-index.test.js +84 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/__tests__/require-memo-in-view.test.js +196 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/__tests__/single-component-per-file.test.js +289 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/index.js +32 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/package.json +10 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/rules/enforce-component-structure.js +230 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/rules/no-return-in-view.js +91 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/rules/require-memo-in-view.js +178 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/rules/single-component-per-file.js +238 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/README.md +260 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/index.js +29 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/package.json +10 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/rules/no-classname-outside-ui.js +51 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/rules/no-direct-rn-imports.js +55 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/rules/no-inline-styles.js +73 -0
- package/expo/copy-overwrite/eslint.config.mjs +560 -0
- package/expo/copy-overwrite/lighthouserc.js +194 -0
- package/expo/create-only/lighthouserc-config.json +28 -0
- package/expo/merge/package.json +132 -0
- package/lisa.sh +35 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/SKILL.md +176 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/advanced-features.md +527 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/project-patterns.md +483 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/quick-start.md +257 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/resolvers-mutations.md +413 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/types-scalars.md +513 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-rules/SKILL.md +536 -0
- package/nestjs/copy-overwrite/.claude/skills/typeorm-patterns/SKILL.md +275 -0
- package/nestjs/copy-overwrite/.claude/skills/typeorm-patterns/references/configuration-patterns.md +487 -0
- package/nestjs/copy-overwrite/.claude/skills/typeorm-patterns/references/entity-patterns.md +450 -0
- package/nestjs/copy-overwrite/.claude/skills/typeorm-patterns/references/observability-patterns.md +536 -0
- package/nestjs/merge/package.json +75 -0
- package/package.json +124 -0
- package/typescript/copy-contents/.husky/commit-msg +91 -0
- package/typescript/copy-contents/.husky/pre-commit +96 -0
- package/typescript/copy-contents/.husky/pre-push +211 -0
- package/typescript/copy-overwrite/.claude/hooks/format-on-edit.sh +74 -0
- package/typescript/copy-overwrite/.claude/hooks/install_pkgs.sh +59 -0
- package/typescript/copy-overwrite/.claude/hooks/lint-on-edit.sh +103 -0
- package/typescript/copy-overwrite/.claude/skills/jsdoc-best-practices/SKILL.md +388 -0
- package/typescript/copy-overwrite/.github/README.md +455 -0
- package/typescript/copy-overwrite/.github/dependabot.yml +40 -0
- package/typescript/copy-overwrite/.github/k6/BROWSER_TESTING_NOTE.md +129 -0
- package/typescript/copy-overwrite/.github/k6/INTEGRATION_GUIDE.md +354 -0
- package/typescript/copy-overwrite/.github/k6/README.md +386 -0
- package/typescript/copy-overwrite/.github/k6/SCENARIO_SELECTION_GUIDE.md +264 -0
- package/typescript/copy-overwrite/.github/k6/examples/customer-deploy-integration.yml +115 -0
- package/typescript/copy-overwrite/.github/k6/examples/data-driven-test.js +268 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/load.js +142 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/load.json +27 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/smoke.js +26 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/smoke.json +20 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/soak.js +244 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/soak.json +29 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/spike.js +180 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/spike.json +32 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/stress.js +206 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/stress.json +38 -0
- package/typescript/copy-overwrite/.github/k6/scripts/api-test.js +452 -0
- package/typescript/copy-overwrite/.github/k6/scripts/default-test.js +185 -0
- package/typescript/copy-overwrite/.github/k6/thresholds/normal.json +30 -0
- package/typescript/copy-overwrite/.github/k6/thresholds/relaxed.json +21 -0
- package/typescript/copy-overwrite/.github/k6/thresholds/strict.json +29 -0
- package/typescript/copy-overwrite/.github/workflows/build.yml +72 -0
- package/typescript/copy-overwrite/.github/workflows/ci.yml +49 -0
- package/typescript/copy-overwrite/.github/workflows/claude.yml +51 -0
- package/typescript/copy-overwrite/.github/workflows/create-github-issue-on-failure.yml +113 -0
- package/typescript/copy-overwrite/.github/workflows/create-jira-issue-on-failure.yml +195 -0
- package/typescript/copy-overwrite/.github/workflows/create-sentry-issue-on-failure.yml +267 -0
- package/typescript/copy-overwrite/.github/workflows/deploy.yml +228 -0
- package/typescript/copy-overwrite/.github/workflows/k6-load-test-README.md +230 -0
- package/typescript/copy-overwrite/.github/workflows/lighthouse.yml +68 -0
- package/typescript/copy-overwrite/.github/workflows/load-test.yml +282 -0
- package/typescript/copy-overwrite/.github/workflows/quality.yml +1737 -0
- package/typescript/copy-overwrite/.github/workflows/release.yml +1599 -0
- package/typescript/copy-overwrite/.gitleaksignore +28 -0
- package/typescript/copy-overwrite/.nvmrc +1 -0
- package/typescript/copy-overwrite/.prettierignore +23 -0
- package/typescript/copy-overwrite/.prettierrc.json +22 -0
- package/typescript/copy-overwrite/.versionrc +42 -0
- package/typescript/copy-overwrite/.yamllint +20 -0
- package/typescript/copy-overwrite/commitlint.config.js +11 -0
- package/typescript/copy-overwrite/eslint-plugin-code-organization/README.md +149 -0
- package/typescript/copy-overwrite/eslint-plugin-code-organization/__tests__/enforce-statement-order.test.js +468 -0
- package/typescript/copy-overwrite/eslint-plugin-code-organization/index.js +23 -0
- package/typescript/copy-overwrite/eslint-plugin-code-organization/package.json +10 -0
- package/typescript/copy-overwrite/eslint-plugin-code-organization/rules/enforce-statement-order.js +157 -0
- package/typescript/copy-overwrite/eslint.config.mjs +390 -0
- package/typescript/copy-overwrite/eslint.ignore.config.json +57 -0
- package/typescript/copy-overwrite/eslint.thresholds.config.json +5 -0
- package/typescript/github-rulesets/base.json +106 -0
- package/typescript/merge/.claude/settings.json +28 -0
- package/typescript/merge/package.json +71 -0
package/nestjs/copy-overwrite/.claude/skills/typeorm-patterns/references/configuration-patterns.md
ADDED
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
# Configuration Patterns
|
|
2
|
+
|
|
3
|
+
Detailed patterns for TypeORM configuration using the official NestJS `TypeOrmModule.forRootAsync()` with `dataSourceFactory` and ConfigService.
|
|
4
|
+
|
|
5
|
+
## Why This Approach?
|
|
6
|
+
|
|
7
|
+
Per [NestJS documentation](https://docs.nestjs.com/techniques/database), `TypeOrmModule.forRootAsync()` is the recommended approach for async configuration. Adding `dataSourceFactory` gives full control over DataSource initialization while staying within NestJS patterns.
|
|
8
|
+
|
|
9
|
+
**Benefits**:
|
|
10
|
+
- `@InjectRepository()` works automatically
|
|
11
|
+
- Health checks integrate seamlessly via `@nestjs/terminus`
|
|
12
|
+
- Proper NestJS lifecycle management
|
|
13
|
+
- Less boilerplate than custom providers
|
|
14
|
+
- Type-safe configuration via ConfigService
|
|
15
|
+
|
|
16
|
+
## Database Module
|
|
17
|
+
|
|
18
|
+
### database.module.ts
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
import { Module } from "@nestjs/common";
|
|
22
|
+
import { ConfigService } from "@nestjs/config";
|
|
23
|
+
import { TypeOrmModule } from "@nestjs/typeorm";
|
|
24
|
+
import { DataSource, DataSourceOptions } from "typeorm";
|
|
25
|
+
import { Configuration } from "../config/configuration";
|
|
26
|
+
import { createTypeOrmOptionsFromConfigService } from "./database.config";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Database module using official NestJS TypeORM integration.
|
|
30
|
+
*
|
|
31
|
+
* @remarks
|
|
32
|
+
* Uses forRootAsync with dataSourceFactory for:
|
|
33
|
+
* - Async configuration via ConfigService
|
|
34
|
+
* - Custom DataSource initialization
|
|
35
|
+
* - Replication support with dynamic passwords
|
|
36
|
+
*/
|
|
37
|
+
@Module({
|
|
38
|
+
imports: [
|
|
39
|
+
TypeOrmModule.forRootAsync({
|
|
40
|
+
inject: [ConfigService],
|
|
41
|
+
useFactory: (configService: ConfigService<Configuration, true>) =>
|
|
42
|
+
createTypeOrmOptionsFromConfigService(configService),
|
|
43
|
+
dataSourceFactory: async (options?: DataSourceOptions) => {
|
|
44
|
+
if (!options) {
|
|
45
|
+
throw new Error("DataSource options are required");
|
|
46
|
+
}
|
|
47
|
+
const dataSource = new DataSource(options);
|
|
48
|
+
return dataSource.initialize();
|
|
49
|
+
},
|
|
50
|
+
}),
|
|
51
|
+
],
|
|
52
|
+
})
|
|
53
|
+
export class DatabaseModule {}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Configuration Factory
|
|
57
|
+
|
|
58
|
+
### database.config.ts
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import { ConfigService } from "@nestjs/config";
|
|
62
|
+
import type { TypeOrmModuleOptions } from "@nestjs/typeorm";
|
|
63
|
+
import type { DataSourceOptions, LoggerOptions } from "typeorm";
|
|
64
|
+
import { SnakeNamingStrategy } from "typeorm-naming-strategies";
|
|
65
|
+
import { Configuration, getStandaloneConfig } from "../config/configuration";
|
|
66
|
+
import * as entities from "./entities";
|
|
67
|
+
import { generateRdsAuthToken } from "./rds-signer";
|
|
68
|
+
import { TypeOrmXRayLogger } from "./typeorm-xray-logger";
|
|
69
|
+
|
|
70
|
+
/** Default database host for local development */
|
|
71
|
+
const DEFAULT_DATABASE_HOST = "localhost";
|
|
72
|
+
|
|
73
|
+
/** Default SSL setting (disabled for local development) */
|
|
74
|
+
const DEFAULT_DATABASE_SSL = false;
|
|
75
|
+
|
|
76
|
+
/** Default logging configuration */
|
|
77
|
+
const DEFAULT_LOGGING_OPTIONS: LoggerOptions = ["error", "warn", "migration"];
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Creates the base TypeORM configuration shared by all environments.
|
|
81
|
+
*/
|
|
82
|
+
function createBaseConfig(): Partial<DataSourceOptions> & { type: "postgres" } {
|
|
83
|
+
return {
|
|
84
|
+
type: "postgres",
|
|
85
|
+
synchronize: false,
|
|
86
|
+
namingStrategy: new SnakeNamingStrategy(),
|
|
87
|
+
logger: new TypeOrmXRayLogger(),
|
|
88
|
+
logging: DEFAULT_LOGGING_OPTIONS,
|
|
89
|
+
entities: Object.values(entities),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Determines if running in local environment.
|
|
95
|
+
*/
|
|
96
|
+
function isLocalEnvironmentFromService(
|
|
97
|
+
configService: ConfigService<Configuration, true>
|
|
98
|
+
): boolean {
|
|
99
|
+
const isOffline = configService.get("app.isOffline", { infer: true });
|
|
100
|
+
const isTest = configService.get("app.nodeEnv", { infer: true }) === "test";
|
|
101
|
+
return isOffline || isTest;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Creates local development configuration using ConfigService.
|
|
106
|
+
*/
|
|
107
|
+
function createLocalConfigFromService(
|
|
108
|
+
configService: ConfigService<Configuration, true>
|
|
109
|
+
): DataSourceOptions {
|
|
110
|
+
const baseConfig = createBaseConfig();
|
|
111
|
+
const host = configService.get("database.host", { infer: true });
|
|
112
|
+
const port = configService.get("database.port", { infer: true });
|
|
113
|
+
const username = configService.get("database.username", { infer: true });
|
|
114
|
+
const password = configService.get("database.password", { infer: true });
|
|
115
|
+
const database = configService.get("database.name", { infer: true });
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
...baseConfig,
|
|
119
|
+
host,
|
|
120
|
+
port,
|
|
121
|
+
username,
|
|
122
|
+
password,
|
|
123
|
+
database,
|
|
124
|
+
ssl: DEFAULT_DATABASE_SSL,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Creates production configuration with replication using ConfigService.
|
|
130
|
+
*
|
|
131
|
+
* @remarks IAM tokens are generated at initialization. Lambda functions
|
|
132
|
+
* typically have short lifespans, so token expiration is not a concern.
|
|
133
|
+
*/
|
|
134
|
+
async function createProductionConfigFromService(
|
|
135
|
+
configService: ConfigService<Configuration, true>
|
|
136
|
+
): Promise<DataSourceOptions> {
|
|
137
|
+
const baseConfig = createBaseConfig();
|
|
138
|
+
const masterHost =
|
|
139
|
+
configService.get("database.proxyHost", { infer: true }) ??
|
|
140
|
+
DEFAULT_DATABASE_HOST;
|
|
141
|
+
const readHost =
|
|
142
|
+
configService.get("database.proxyHostRead", { infer: true }) ?? masterHost;
|
|
143
|
+
const port = configService.get("database.port", { infer: true });
|
|
144
|
+
const username = configService.get("database.username", { infer: true });
|
|
145
|
+
const database = configService.get("database.name", { infer: true });
|
|
146
|
+
const ssl = configService.get("database.ssl", { infer: true })
|
|
147
|
+
? { rejectUnauthorized: configService.get("database.sslRejectUnauthorized", { infer: true }) }
|
|
148
|
+
: DEFAULT_DATABASE_SSL;
|
|
149
|
+
|
|
150
|
+
const masterToken = await generateRdsAuthToken(masterHost, port, username);
|
|
151
|
+
const readToken = await generateRdsAuthToken(readHost, port, username);
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
...baseConfig,
|
|
155
|
+
ssl,
|
|
156
|
+
replication: {
|
|
157
|
+
master: {
|
|
158
|
+
host: masterHost,
|
|
159
|
+
port,
|
|
160
|
+
username,
|
|
161
|
+
password: masterToken,
|
|
162
|
+
database,
|
|
163
|
+
},
|
|
164
|
+
slaves: [
|
|
165
|
+
{
|
|
166
|
+
host: readHost,
|
|
167
|
+
port,
|
|
168
|
+
username,
|
|
169
|
+
password: readToken,
|
|
170
|
+
database,
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Creates TypeORM module options for NestJS using ConfigService.
|
|
179
|
+
*
|
|
180
|
+
* @param configService - NestJS ConfigService instance
|
|
181
|
+
* @returns Promise resolving to TypeOrmModuleOptions
|
|
182
|
+
*/
|
|
183
|
+
export async function createTypeOrmOptionsFromConfigService(
|
|
184
|
+
configService: ConfigService<Configuration, true>
|
|
185
|
+
): Promise<TypeOrmModuleOptions> {
|
|
186
|
+
const config = isLocalEnvironmentFromService(configService)
|
|
187
|
+
? createLocalConfigFromService(configService)
|
|
188
|
+
: await createProductionConfigFromService(configService);
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
...config,
|
|
192
|
+
autoLoadEntities: false,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// =============================================================================
|
|
197
|
+
// Standalone functions for TypeORM CLI (migrations)
|
|
198
|
+
// These functions use getStandaloneConfig() for environments without ConfigService
|
|
199
|
+
// =============================================================================
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Determines if running in local environment (standalone).
|
|
203
|
+
* @remarks Used by TypeORM CLI and other contexts without ConfigService
|
|
204
|
+
*/
|
|
205
|
+
export function isLocalEnvironment(): boolean {
|
|
206
|
+
const config = getStandaloneConfig();
|
|
207
|
+
return config.app.isOffline || config.app.nodeEnv === "test";
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Creates local development configuration (standalone).
|
|
212
|
+
* @remarks Used by TypeORM CLI (typeorm.config.ts) for migrations
|
|
213
|
+
*/
|
|
214
|
+
export function createLocalConfig(): DataSourceOptions {
|
|
215
|
+
const baseConfig = createBaseConfig();
|
|
216
|
+
const config = getStandaloneConfig();
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
...baseConfig,
|
|
220
|
+
host: config.database.host,
|
|
221
|
+
port: config.database.port,
|
|
222
|
+
username: config.database.username,
|
|
223
|
+
password: config.database.password,
|
|
224
|
+
database: config.database.name,
|
|
225
|
+
ssl: DEFAULT_DATABASE_SSL,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Configuration Schema
|
|
231
|
+
|
|
232
|
+
All configuration is centralized in `src/config/configuration.ts`:
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
export interface Configuration {
|
|
236
|
+
readonly app: {
|
|
237
|
+
readonly nodeEnv: string;
|
|
238
|
+
readonly isOffline: boolean;
|
|
239
|
+
};
|
|
240
|
+
readonly database: {
|
|
241
|
+
readonly host: string;
|
|
242
|
+
readonly port: number;
|
|
243
|
+
readonly username: string;
|
|
244
|
+
readonly password: string;
|
|
245
|
+
readonly name: string;
|
|
246
|
+
readonly ssl: boolean;
|
|
247
|
+
readonly sslRejectUnauthorized: boolean;
|
|
248
|
+
readonly proxyHost: string | undefined;
|
|
249
|
+
readonly proxyHostRead: string | undefined;
|
|
250
|
+
};
|
|
251
|
+
// ... other namespaces
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export const configuration = (): Configuration => ({
|
|
255
|
+
app: {
|
|
256
|
+
nodeEnv: process.env.NODE_ENV ?? "development",
|
|
257
|
+
isOffline: process.env.IS_OFFLINE === "true",
|
|
258
|
+
},
|
|
259
|
+
database: {
|
|
260
|
+
host: process.env.DATABASE_HOST ?? "localhost",
|
|
261
|
+
port: parseInt(process.env.DATABASE_PORT ?? "5432", 10),
|
|
262
|
+
username: process.env.DATABASE_USER ?? "thumbwar",
|
|
263
|
+
password: process.env.DATABASE_PASSWORD ?? "thumbwar_local",
|
|
264
|
+
name: process.env.DATABASE_NAME ?? "thumbwar",
|
|
265
|
+
ssl: process.env.DATABASE_SSL === "true",
|
|
266
|
+
sslRejectUnauthorized: process.env.DATABASE_SSL_REJECT_UNAUTHORIZED !== "false",
|
|
267
|
+
proxyHost: process.env.DATABASE_PROXY_HOST,
|
|
268
|
+
proxyHostRead: process.env.DATABASE_PROXY_HOST_READ_1,
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## AWS RDS Signer Integration
|
|
274
|
+
|
|
275
|
+
### rds-signer.ts
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
import { Signer } from "@aws-sdk/rds-signer";
|
|
279
|
+
import { Logger } from "@nestjs/common";
|
|
280
|
+
|
|
281
|
+
const logger = new Logger("RdsSigner");
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Generate an IAM authentication token for RDS.
|
|
285
|
+
*
|
|
286
|
+
* @param hostname - The RDS endpoint hostname
|
|
287
|
+
* @param port - The database port
|
|
288
|
+
* @param username - The database username
|
|
289
|
+
* @returns A temporary authentication token valid for 15 minutes
|
|
290
|
+
*/
|
|
291
|
+
export const generateRdsAuthToken = async (
|
|
292
|
+
hostname: string,
|
|
293
|
+
port: number,
|
|
294
|
+
username: string
|
|
295
|
+
): Promise<string> => {
|
|
296
|
+
const signer = new Signer({
|
|
297
|
+
hostname,
|
|
298
|
+
port,
|
|
299
|
+
username,
|
|
300
|
+
region: process.env.AWS_REGION ?? "us-east-1",
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
const token = await signer.getAuthToken();
|
|
304
|
+
logger.debug(`Generated RDS auth token for ${username}@${hostname}:${port}`);
|
|
305
|
+
|
|
306
|
+
return token;
|
|
307
|
+
};
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
## Read-Write Replication Behavior
|
|
311
|
+
|
|
312
|
+
When replication is configured, TypeORM automatically routes queries:
|
|
313
|
+
|
|
314
|
+
### Automatic Routing
|
|
315
|
+
|
|
316
|
+
| Method | Connection |
|
|
317
|
+
|--------|------------|
|
|
318
|
+
| `find()`, `findOne()`, `findBy()` | Slave (read) |
|
|
319
|
+
| `count()`, `exists()` | Slave (read) |
|
|
320
|
+
| `query()` with SELECT | Slave (read) |
|
|
321
|
+
| `save()`, `insert()` | Master (write) |
|
|
322
|
+
| `update()`, `delete()`, `remove()` | Master (write) |
|
|
323
|
+
| `query()` with INSERT/UPDATE/DELETE | Master (write) |
|
|
324
|
+
|
|
325
|
+
### Forcing Master for Reads
|
|
326
|
+
|
|
327
|
+
When read-after-write consistency is required:
|
|
328
|
+
|
|
329
|
+
```typescript
|
|
330
|
+
// Option 1: Use transaction (always uses master)
|
|
331
|
+
async findUserWithConsistency(id: string): Promise<User | null> {
|
|
332
|
+
return this.dataSource.transaction(async manager => {
|
|
333
|
+
return manager.findOne(User, { where: { id } });
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Option 2: Explicit QueryRunner
|
|
338
|
+
async findUserFromMaster(id: string): Promise<User | null> {
|
|
339
|
+
const queryRunner = this.dataSource.createQueryRunner("master");
|
|
340
|
+
try {
|
|
341
|
+
return await queryRunner.manager.findOne(User, { where: { id } });
|
|
342
|
+
} finally {
|
|
343
|
+
await queryRunner.release();
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
## Registering Entities in Feature Modules
|
|
349
|
+
|
|
350
|
+
Use `TypeOrmModule.forFeature()` in feature modules:
|
|
351
|
+
|
|
352
|
+
```typescript
|
|
353
|
+
import { Module } from "@nestjs/common";
|
|
354
|
+
import { TypeOrmModule } from "@nestjs/typeorm";
|
|
355
|
+
import { User } from "../database/entities/user.entity";
|
|
356
|
+
import { UserService } from "./user.service";
|
|
357
|
+
import { UserResolver } from "./user.resolver";
|
|
358
|
+
|
|
359
|
+
@Module({
|
|
360
|
+
imports: [TypeOrmModule.forFeature([User])],
|
|
361
|
+
providers: [UserService, UserResolver],
|
|
362
|
+
exports: [UserService],
|
|
363
|
+
})
|
|
364
|
+
export class UserModule {}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
## CLI Configuration
|
|
368
|
+
|
|
369
|
+
For migrations and CLI operations, maintain a separate configuration file.
|
|
370
|
+
|
|
371
|
+
### typeorm.config.ts (project root)
|
|
372
|
+
|
|
373
|
+
```typescript
|
|
374
|
+
import { DataSource } from "typeorm";
|
|
375
|
+
import { createLocalConfig } from "./src/database/database.config";
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* TypeORM CLI DataSource for migrations.
|
|
379
|
+
*
|
|
380
|
+
* @remarks
|
|
381
|
+
* This configuration is used by typeorm-ts-node-commonjs for:
|
|
382
|
+
* - migration:generate
|
|
383
|
+
* - migration:run
|
|
384
|
+
* - migration:revert
|
|
385
|
+
*
|
|
386
|
+
* Uses createLocalConfig() which uses getStandaloneConfig() internally
|
|
387
|
+
* for type-safe configuration access.
|
|
388
|
+
*/
|
|
389
|
+
export default new DataSource({
|
|
390
|
+
...createLocalConfig(),
|
|
391
|
+
migrations: ["src/database/migrations/*.ts"],
|
|
392
|
+
});
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
## Environment Variables
|
|
396
|
+
|
|
397
|
+
### .env.example
|
|
398
|
+
|
|
399
|
+
```bash
|
|
400
|
+
# Database Configuration
|
|
401
|
+
DATABASE_HOST=localhost
|
|
402
|
+
DATABASE_PORT=5432
|
|
403
|
+
DATABASE_USER=thumbwar
|
|
404
|
+
DATABASE_PASSWORD=thumbwar_local
|
|
405
|
+
DATABASE_NAME=thumbwar
|
|
406
|
+
DATABASE_SSL=false
|
|
407
|
+
DATABASE_SSL_REJECT_UNAUTHORIZED=true
|
|
408
|
+
|
|
409
|
+
# Local development flag (set by serverless-offline)
|
|
410
|
+
IS_OFFLINE=true
|
|
411
|
+
|
|
412
|
+
# Production Only - RDS Proxy endpoints
|
|
413
|
+
# DATABASE_PROXY_HOST=thumbwar-proxy.proxy-xxxxx.us-east-1.rds.amazonaws.com
|
|
414
|
+
# DATABASE_PROXY_HOST_READ_1=thumbwar-proxy-read.proxy-xxxxx.us-east-1.rds.amazonaws.com
|
|
415
|
+
|
|
416
|
+
# AWS Configuration (production)
|
|
417
|
+
# AWS_REGION=us-east-1
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
## Dependencies
|
|
421
|
+
|
|
422
|
+
Required packages:
|
|
423
|
+
|
|
424
|
+
```bash
|
|
425
|
+
bun add @nestjs/config @nestjs/typeorm typeorm pg typeorm-naming-strategies @aws-sdk/rds-signer
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
| Package | Purpose |
|
|
429
|
+
|---------|---------|
|
|
430
|
+
| `@nestjs/config` | Type-safe configuration via ConfigService |
|
|
431
|
+
| `@nestjs/typeorm` | Official NestJS TypeORM integration |
|
|
432
|
+
| `typeorm` | TypeORM core |
|
|
433
|
+
| `pg` | PostgreSQL driver |
|
|
434
|
+
| `typeorm-naming-strategies` | SnakeNamingStrategy for camelCase → snake_case |
|
|
435
|
+
| `@aws-sdk/rds-signer` | IAM authentication token generation (production) |
|
|
436
|
+
|
|
437
|
+
## Health Check Integration
|
|
438
|
+
|
|
439
|
+
With `TypeOrmModule`, health checks work automatically:
|
|
440
|
+
|
|
441
|
+
### health.module.ts
|
|
442
|
+
|
|
443
|
+
```typescript
|
|
444
|
+
import { Module } from "@nestjs/common";
|
|
445
|
+
import { TerminusModule } from "@nestjs/terminus";
|
|
446
|
+
import { HealthController } from "./health.controller";
|
|
447
|
+
|
|
448
|
+
@Module({
|
|
449
|
+
imports: [TerminusModule],
|
|
450
|
+
controllers: [HealthController],
|
|
451
|
+
})
|
|
452
|
+
export class HealthModule {}
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
### health.controller.ts
|
|
456
|
+
|
|
457
|
+
```typescript
|
|
458
|
+
import { Controller, Get } from "@nestjs/common";
|
|
459
|
+
import {
|
|
460
|
+
HealthCheck,
|
|
461
|
+
HealthCheckResult,
|
|
462
|
+
HealthCheckService,
|
|
463
|
+
TypeOrmHealthIndicator,
|
|
464
|
+
} from "@nestjs/terminus";
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Health check controller for load balancer probes.
|
|
468
|
+
*/
|
|
469
|
+
@Controller("health")
|
|
470
|
+
export class HealthController {
|
|
471
|
+
constructor(
|
|
472
|
+
private readonly health: HealthCheckService,
|
|
473
|
+
private readonly db: TypeOrmHealthIndicator
|
|
474
|
+
) {}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Perform health check including database connectivity.
|
|
478
|
+
*/
|
|
479
|
+
@Get()
|
|
480
|
+
@HealthCheck()
|
|
481
|
+
check(): Promise<HealthCheckResult> {
|
|
482
|
+
return this.health.check([
|
|
483
|
+
() => this.db.pingCheck("database", { timeout: 3000 }),
|
|
484
|
+
]);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
```
|