@mytechtoday/augment-extensions 0.1.2 → 0.4.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 +614 -39
- package/augment-extensions/coding-standards/bash/README.md +196 -0
- package/augment-extensions/coding-standards/bash/module.json +163 -0
- package/augment-extensions/coding-standards/bash/rules/naming-conventions.md +336 -0
- package/augment-extensions/coding-standards/bash/rules/universal-standards.md +289 -0
- package/augment-extensions/coding-standards/css/README.md +40 -0
- package/augment-extensions/coding-standards/css/examples/css-examples.css +550 -0
- package/augment-extensions/coding-standards/css/module.json +44 -0
- package/augment-extensions/coding-standards/css/rules/css-modern-features.md +448 -0
- package/augment-extensions/coding-standards/css/rules/css-standards.md +492 -0
- package/augment-extensions/coding-standards/html/README.md +40 -0
- package/augment-extensions/coding-standards/html/examples/html-examples.html +267 -0
- package/augment-extensions/coding-standards/html/examples/responsive-layout.html +505 -0
- package/augment-extensions/coding-standards/html/module.json +44 -0
- package/augment-extensions/coding-standards/html/rules/html-standards.md +349 -0
- package/augment-extensions/coding-standards/html-css-js/README.md +194 -0
- package/augment-extensions/coding-standards/html-css-js/examples/async-examples.js +487 -0
- package/augment-extensions/coding-standards/html-css-js/examples/css-examples.css +550 -0
- package/augment-extensions/coding-standards/html-css-js/examples/dom-examples.js +667 -0
- package/augment-extensions/coding-standards/html-css-js/examples/html-examples.html +267 -0
- package/augment-extensions/coding-standards/html-css-js/examples/javascript-examples.js +612 -0
- package/augment-extensions/coding-standards/html-css-js/examples/responsive-layout.html +505 -0
- package/augment-extensions/coding-standards/html-css-js/module.json +48 -0
- package/augment-extensions/coding-standards/html-css-js/rules/async-patterns.md +515 -0
- package/augment-extensions/coding-standards/html-css-js/rules/css-modern-features.md +448 -0
- package/augment-extensions/coding-standards/html-css-js/rules/css-standards.md +492 -0
- package/augment-extensions/coding-standards/html-css-js/rules/dom-manipulation.md +439 -0
- package/augment-extensions/coding-standards/html-css-js/rules/html-standards.md +349 -0
- package/augment-extensions/coding-standards/html-css-js/rules/javascript-standards.md +486 -0
- package/augment-extensions/coding-standards/html-css-js/rules/performance.md +463 -0
- package/augment-extensions/coding-standards/html-css-js/rules/tooling.md +543 -0
- package/augment-extensions/coding-standards/js/README.md +46 -0
- package/augment-extensions/coding-standards/js/examples/async-examples.js +487 -0
- package/augment-extensions/coding-standards/js/examples/dom-examples.js +667 -0
- package/augment-extensions/coding-standards/js/examples/javascript-examples.js +612 -0
- package/augment-extensions/coding-standards/js/module.json +49 -0
- package/augment-extensions/coding-standards/js/rules/async-patterns.md +515 -0
- package/augment-extensions/coding-standards/js/rules/dom-manipulation.md +439 -0
- package/augment-extensions/coding-standards/js/rules/javascript-standards.md +486 -0
- package/augment-extensions/coding-standards/js/rules/performance.md +463 -0
- package/augment-extensions/coding-standards/js/rules/tooling.md +543 -0
- package/augment-extensions/coding-standards/php/README.md +248 -0
- package/augment-extensions/coding-standards/php/examples/api-endpoint-example.php +204 -0
- package/augment-extensions/coding-standards/php/examples/cli-command-example.php +206 -0
- package/augment-extensions/coding-standards/php/examples/legacy-refactoring-example.php +234 -0
- package/augment-extensions/coding-standards/php/examples/web-application-example.php +211 -0
- package/augment-extensions/coding-standards/php/examples/woocommerce-extension-example.php +215 -0
- package/augment-extensions/coding-standards/php/examples/wordpress-plugin-example.php +189 -0
- package/augment-extensions/coding-standards/php/module.json +166 -0
- package/augment-extensions/coding-standards/php/rules/api-development.md +480 -0
- package/augment-extensions/coding-standards/php/rules/category-configuration.md +332 -0
- package/augment-extensions/coding-standards/php/rules/cli-tools.md +472 -0
- package/augment-extensions/coding-standards/php/rules/cms-integration.md +561 -0
- package/augment-extensions/coding-standards/php/rules/code-quality.md +402 -0
- package/augment-extensions/coding-standards/php/rules/documentation.md +425 -0
- package/augment-extensions/coding-standards/php/rules/ecommerce.md +627 -0
- package/augment-extensions/coding-standards/php/rules/error-handling.md +336 -0
- package/augment-extensions/coding-standards/php/rules/legacy-migration.md +677 -0
- package/augment-extensions/coding-standards/php/rules/naming-conventions.md +279 -0
- package/augment-extensions/coding-standards/php/rules/performance.md +392 -0
- package/augment-extensions/coding-standards/php/rules/psr-standards.md +186 -0
- package/augment-extensions/coding-standards/php/rules/security.md +358 -0
- package/augment-extensions/coding-standards/php/rules/testing.md +403 -0
- package/augment-extensions/coding-standards/php/rules/type-declarations.md +331 -0
- package/augment-extensions/coding-standards/php/rules/web-applications.md +426 -0
- package/augment-extensions/coding-standards/powershell/README.md +154 -0
- package/augment-extensions/coding-standards/powershell/examples/admin-example.ps1 +272 -0
- package/augment-extensions/coding-standards/powershell/examples/automation-example.ps1 +173 -0
- package/augment-extensions/coding-standards/powershell/examples/cloud-example.ps1 +243 -0
- package/augment-extensions/coding-standards/powershell/examples/cross-platform-example.ps1 +297 -0
- package/augment-extensions/coding-standards/powershell/examples/dsc-example.ps1 +224 -0
- package/augment-extensions/coding-standards/powershell/examples/legacy-migration-example.ps1 +340 -0
- package/augment-extensions/coding-standards/powershell/examples/module-example.psm1 +255 -0
- package/augment-extensions/coding-standards/powershell/module.json +165 -0
- package/augment-extensions/coding-standards/powershell/rules/administrative-tools.md +439 -0
- package/augment-extensions/coding-standards/powershell/rules/automation-scripts.md +240 -0
- package/augment-extensions/coding-standards/powershell/rules/cloud-orchestration.md +384 -0
- package/augment-extensions/coding-standards/powershell/rules/configuration-schema.md +383 -0
- package/augment-extensions/coding-standards/powershell/rules/cross-platform-scripts.md +482 -0
- package/augment-extensions/coding-standards/powershell/rules/dsc-configurations.md +296 -0
- package/augment-extensions/coding-standards/powershell/rules/error-handling.md +314 -0
- package/augment-extensions/coding-standards/powershell/rules/legacy-migrations.md +466 -0
- package/augment-extensions/coding-standards/powershell/rules/modules-functions.md +244 -0
- package/augment-extensions/coding-standards/powershell/rules/naming-conventions.md +266 -0
- package/augment-extensions/coding-standards/powershell/rules/performance-optimization.md +209 -0
- package/augment-extensions/coding-standards/powershell/rules/security-practices.md +314 -0
- package/augment-extensions/coding-standards/powershell/rules/testing-guidelines.md +268 -0
- package/augment-extensions/coding-standards/powershell/rules/universal-standards.md +197 -0
- package/augment-extensions/coding-standards/python/README.md +12 -8
- package/augment-extensions/coding-standards/python/examples/best-practices.py +373 -0
- package/augment-extensions/coding-standards/python/module.json +8 -4
- package/augment-extensions/coding-standards/python/rules/async-patterns.md +884 -0
- package/augment-extensions/coding-standards/python/rules/documentation.md +831 -0
- package/augment-extensions/coding-standards/python/rules/error-handling.md +855 -68
- package/augment-extensions/coding-standards/python/rules/testing.md +409 -0
- package/augment-extensions/coding-standards/python/rules/tooling.md +446 -0
- package/augment-extensions/coding-standards/python/rules/type-hints.md +115 -50
- package/augment-extensions/collections/html-css-js/README.md +82 -0
- package/augment-extensions/collections/html-css-js/collection.json +41 -0
- package/augment-extensions/domain-rules/database/README.md +161 -0
- package/augment-extensions/domain-rules/database/examples/flat-database-example.md +793 -0
- package/augment-extensions/domain-rules/database/examples/hybrid-database-example.md +1132 -0
- package/augment-extensions/domain-rules/database/examples/nosql-document-example.md +868 -0
- package/augment-extensions/domain-rules/database/examples/nosql-graph-example.md +805 -0
- package/augment-extensions/domain-rules/database/examples/relational-schema-example.md +621 -0
- package/augment-extensions/domain-rules/database/examples/vector-database-example.md +965 -0
- package/augment-extensions/domain-rules/database/module.json +28 -0
- package/augment-extensions/domain-rules/database/rules/flat-databases.md +624 -0
- package/augment-extensions/domain-rules/database/rules/nosql-databases.md +588 -0
- package/augment-extensions/domain-rules/database/rules/nosql-document-stores.md +856 -0
- package/augment-extensions/domain-rules/database/rules/nosql-graph-databases.md +778 -0
- package/augment-extensions/domain-rules/database/rules/nosql-key-value-stores.md +963 -0
- package/augment-extensions/domain-rules/database/rules/performance-optimization.md +1076 -0
- package/augment-extensions/domain-rules/database/rules/relational-databases.md +697 -0
- package/augment-extensions/domain-rules/database/rules/relational-indexing.md +671 -0
- package/augment-extensions/domain-rules/database/rules/relational-query-optimization.md +607 -0
- package/augment-extensions/domain-rules/database/rules/relational-schema-design.md +907 -0
- package/augment-extensions/domain-rules/database/rules/relational-transactions.md +783 -0
- package/augment-extensions/domain-rules/database/rules/security-standards.md +980 -0
- package/augment-extensions/domain-rules/database/rules/universal-best-practices.md +485 -0
- package/augment-extensions/domain-rules/database/rules/vector-databases.md +521 -0
- package/augment-extensions/domain-rules/database/rules/vector-embeddings.md +858 -0
- package/augment-extensions/domain-rules/database/rules/vector-indexing.md +934 -0
- package/augment-extensions/domain-rules/mcp/README.md +150 -0
- package/augment-extensions/domain-rules/mcp/examples/compressed-example.md +522 -0
- package/augment-extensions/domain-rules/mcp/examples/graph-augmented-example.md +520 -0
- package/augment-extensions/domain-rules/mcp/examples/hybrid-example.md +570 -0
- package/augment-extensions/domain-rules/mcp/examples/state-based-example.md +427 -0
- package/augment-extensions/domain-rules/mcp/examples/token-based-example.md +435 -0
- package/augment-extensions/domain-rules/mcp/examples/vector-based-example.md +502 -0
- package/augment-extensions/domain-rules/mcp/module.json +49 -0
- package/augment-extensions/domain-rules/mcp/rules/compressed-mcp.md +595 -0
- package/augment-extensions/domain-rules/mcp/rules/configuration.md +345 -0
- package/augment-extensions/domain-rules/mcp/rules/graph-augmented-mcp.md +687 -0
- package/augment-extensions/domain-rules/mcp/rules/hybrid-mcp.md +636 -0
- package/augment-extensions/domain-rules/mcp/rules/state-based-mcp.md +484 -0
- package/augment-extensions/domain-rules/mcp/rules/testing-validation.md +360 -0
- package/augment-extensions/domain-rules/mcp/rules/token-based-mcp.md +393 -0
- package/augment-extensions/domain-rules/mcp/rules/universal-rules.md +194 -0
- package/augment-extensions/domain-rules/mcp/rules/vector-based-mcp.md +625 -0
- package/augment-extensions/domain-rules/wordpress/README.md +163 -0
- package/augment-extensions/domain-rules/wordpress/module.json +32 -0
- package/augment-extensions/domain-rules/wordpress/rules/coding-standards.md +617 -0
- package/augment-extensions/domain-rules/wordpress/rules/directory-structure.md +270 -0
- package/augment-extensions/domain-rules/wordpress/rules/file-patterns.md +423 -0
- package/augment-extensions/domain-rules/wordpress/rules/gutenberg-blocks.md +493 -0
- package/augment-extensions/domain-rules/wordpress/rules/performance.md +568 -0
- package/augment-extensions/domain-rules/wordpress/rules/plugin-development.md +510 -0
- package/augment-extensions/domain-rules/wordpress/rules/project-detection.md +251 -0
- package/augment-extensions/domain-rules/wordpress/rules/rest-api.md +501 -0
- package/augment-extensions/domain-rules/wordpress/rules/security.md +564 -0
- package/augment-extensions/domain-rules/wordpress/rules/theme-development.md +388 -0
- package/augment-extensions/domain-rules/wordpress/rules/woocommerce.md +441 -0
- package/augment-extensions/domain-rules/wordpress-plugin/README.md +139 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/ajax-plugin.md +1599 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/custom-post-type-plugin.md +1727 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/gutenberg-block-plugin.md +428 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/gutenberg-block.md +422 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/mvc-plugin.md +1623 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/object-oriented-plugin.md +1343 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/rest-endpoint.md +734 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/settings-page-plugin.md +1350 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/simple-procedural-plugin.md +503 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/singleton-plugin.md +971 -0
- package/augment-extensions/domain-rules/wordpress-plugin/module.json +53 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/activation-hooks.md +770 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/admin-interface.md +874 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/ajax-handlers.md +629 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/asset-management.md +559 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/context-providers.md +709 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/cron-jobs.md +736 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/database-management.md +1057 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/documentation-standards.md +463 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/frontend-functionality.md +478 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/gutenberg-blocks.md +818 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/internationalization.md +416 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/migration.md +667 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/performance-optimization.md +878 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/plugin-architecture.md +693 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/plugin-structure.md +352 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/rest-api.md +818 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/scaffolding-workflow.md +624 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/security-best-practices.md +866 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/testing-patterns.md +1165 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/testing.md +414 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/vscode-integration.md +751 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/woocommerce-integration.md +949 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/wordpress-org-submission.md +458 -0
- package/augment-extensions/examples/gutenberg-block-plugin/README.md +101 -0
- package/augment-extensions/examples/gutenberg-block-plugin/examples/testimonial-block.md +428 -0
- package/augment-extensions/examples/gutenberg-block-plugin/module.json +40 -0
- package/augment-extensions/examples/rest-api-plugin/README.md +98 -0
- package/augment-extensions/examples/rest-api-plugin/examples/task-manager-api.md +1299 -0
- package/augment-extensions/examples/rest-api-plugin/module.json +40 -0
- package/augment-extensions/examples/woocommerce-extension/README.md +98 -0
- package/augment-extensions/examples/woocommerce-extension/examples/product-customizer.md +763 -0
- package/augment-extensions/examples/woocommerce-extension/module.json +40 -0
- package/augment-extensions/workflows/beads/module.json +4 -3
- package/augment-extensions/workflows/database/README.md +195 -0
- package/augment-extensions/workflows/database/ai-prompt-testing.md +295 -0
- package/augment-extensions/workflows/database/examples/migration-example.md +498 -0
- package/augment-extensions/workflows/database/examples/optimization-example.md +496 -0
- package/augment-extensions/workflows/database/examples/schema-design-example.md +444 -0
- package/augment-extensions/workflows/database/module.json +42 -0
- package/augment-extensions/workflows/database/rules/data-migration.md +249 -0
- package/augment-extensions/workflows/database/rules/documentation-standards.md +339 -0
- package/augment-extensions/workflows/database/rules/migration-workflow.md +352 -0
- package/augment-extensions/workflows/database/rules/optimization-workflow.md +435 -0
- package/augment-extensions/workflows/database/rules/schema-design-workflow.md +535 -0
- package/augment-extensions/workflows/database/rules/testing-patterns.md +305 -0
- package/augment-extensions/workflows/database/rules/workflow.md +458 -0
- package/augment-extensions/workflows/openspec/module.json +4 -3
- package/augment-extensions/workflows/wordpress-plugin/README.md +232 -0
- package/augment-extensions/workflows/wordpress-plugin/ai-prompts.md +839 -0
- package/augment-extensions/workflows/wordpress-plugin/bead-decomposition-patterns.md +854 -0
- package/augment-extensions/workflows/wordpress-plugin/examples/complete-plugin-example.md +540 -0
- package/augment-extensions/workflows/wordpress-plugin/examples/custom-post-type-example.md +1083 -0
- package/augment-extensions/workflows/wordpress-plugin/examples/feature-addition-workflow.md +669 -0
- package/augment-extensions/workflows/wordpress-plugin/examples/plugin-creation-workflow.md +597 -0
- package/augment-extensions/workflows/wordpress-plugin/examples/secure-form-handler-example.md +925 -0
- package/augment-extensions/workflows/wordpress-plugin/examples/security-audit-workflow.md +752 -0
- package/augment-extensions/workflows/wordpress-plugin/examples/wordpress-org-submission-workflow.md +773 -0
- package/augment-extensions/workflows/wordpress-plugin/module.json +49 -0
- package/augment-extensions/workflows/wordpress-plugin/rules/best-practices.md +942 -0
- package/augment-extensions/workflows/wordpress-plugin/rules/development-workflow.md +702 -0
- package/augment-extensions/workflows/wordpress-plugin/rules/submission-workflow.md +728 -0
- package/augment-extensions/workflows/wordpress-plugin/rules/testing-workflow.md +775 -0
- package/augment-extensions/writing-standards/screenplay/README.md +171 -0
- package/augment-extensions/writing-standards/screenplay/examples/aaa-hollywood-scene.fountain +164 -0
- package/augment-extensions/writing-standards/screenplay/module.json +124 -0
- package/augment-extensions/writing-standards/screenplay/rules/universal-formatting.md +339 -0
- package/cli/MODULES.md +302 -0
- package/cli/dist/cli.js +142 -9
- package/cli/dist/cli.js.map +1 -1
- package/cli/dist/commands/catalog.d.ts +13 -0
- package/cli/dist/commands/catalog.d.ts.map +1 -0
- package/cli/dist/commands/catalog.js +104 -0
- package/cli/dist/commands/catalog.js.map +1 -0
- package/cli/dist/commands/gui.d.ts +6 -0
- package/cli/dist/commands/gui.d.ts.map +1 -0
- package/cli/dist/commands/gui.js +211 -0
- package/cli/dist/commands/gui.js.map +1 -0
- package/cli/dist/commands/init.d.ts.map +1 -1
- package/cli/dist/commands/init.js +12 -0
- package/cli/dist/commands/init.js.map +1 -1
- package/cli/dist/commands/install-rules.d.ts +14 -0
- package/cli/dist/commands/install-rules.d.ts.map +1 -0
- package/cli/dist/commands/install-rules.js +127 -0
- package/cli/dist/commands/install-rules.js.map +1 -0
- package/cli/dist/commands/link.d.ts.map +1 -1
- package/cli/dist/commands/link.js +9 -11
- package/cli/dist/commands/link.js.map +1 -1
- package/cli/dist/commands/list.d.ts.map +1 -1
- package/cli/dist/commands/list.js +11 -28
- package/cli/dist/commands/list.js.map +1 -1
- package/cli/dist/commands/mcp.d.ts +48 -0
- package/cli/dist/commands/mcp.d.ts.map +1 -0
- package/cli/dist/commands/mcp.js +229 -0
- package/cli/dist/commands/mcp.js.map +1 -0
- package/cli/dist/commands/self-remove.d.ts +7 -0
- package/cli/dist/commands/self-remove.d.ts.map +1 -0
- package/cli/dist/commands/self-remove.js +179 -0
- package/cli/dist/commands/self-remove.js.map +1 -0
- package/cli/dist/commands/show.d.ts.map +1 -1
- package/cli/dist/commands/show.js +42 -71
- package/cli/dist/commands/show.js.map +1 -1
- package/cli/dist/commands/skill.d.ts +67 -0
- package/cli/dist/commands/skill.d.ts.map +1 -0
- package/cli/dist/commands/skill.js +513 -0
- package/cli/dist/commands/skill.js.map +1 -0
- package/cli/dist/commands/unlink.d.ts +6 -0
- package/cli/dist/commands/unlink.d.ts.map +1 -0
- package/cli/dist/commands/unlink.js +115 -0
- package/cli/dist/commands/unlink.js.map +1 -0
- package/cli/dist/commands/validate.d.ts +6 -0
- package/cli/dist/commands/validate.d.ts.map +1 -0
- package/cli/dist/commands/validate.js +159 -0
- package/cli/dist/commands/validate.js.map +1 -0
- package/cli/dist/utils/catalog-sync.d.ts +22 -0
- package/cli/dist/utils/catalog-sync.d.ts.map +1 -0
- package/cli/dist/utils/catalog-sync.js +157 -0
- package/cli/dist/utils/catalog-sync.js.map +1 -0
- package/cli/dist/utils/character-count.d.ts +56 -0
- package/cli/dist/utils/character-count.d.ts.map +1 -0
- package/cli/dist/utils/character-count.js +190 -0
- package/cli/dist/utils/character-count.js.map +1 -0
- package/cli/dist/utils/documentation-validator.d.ts +18 -0
- package/cli/dist/utils/documentation-validator.d.ts.map +1 -0
- package/cli/dist/utils/documentation-validator.js +233 -0
- package/cli/dist/utils/documentation-validator.js.map +1 -0
- package/cli/dist/utils/install-rules.d.ts +32 -0
- package/cli/dist/utils/install-rules.d.ts.map +1 -0
- package/cli/dist/utils/install-rules.js +375 -0
- package/cli/dist/utils/install-rules.js.map +1 -0
- package/cli/dist/utils/mcp-integration.d.ts +70 -0
- package/cli/dist/utils/mcp-integration.d.ts.map +1 -0
- package/cli/dist/utils/mcp-integration.js +292 -0
- package/cli/dist/utils/mcp-integration.js.map +1 -0
- package/cli/dist/utils/module-system.d.ts +153 -0
- package/cli/dist/utils/module-system.d.ts.map +1 -0
- package/cli/dist/utils/module-system.js +528 -0
- package/cli/dist/utils/module-system.js.map +1 -0
- package/cli/dist/utils/modules-catalog.d.ts +33 -0
- package/cli/dist/utils/modules-catalog.d.ts.map +1 -0
- package/cli/dist/utils/modules-catalog.js +163 -0
- package/cli/dist/utils/modules-catalog.js.map +1 -0
- package/cli/dist/utils/rule-install-hooks.d.ts +19 -0
- package/cli/dist/utils/rule-install-hooks.d.ts.map +1 -0
- package/cli/dist/utils/rule-install-hooks.js +224 -0
- package/cli/dist/utils/rule-install-hooks.js.map +1 -0
- package/cli/dist/utils/skill-system.d.ts +95 -0
- package/cli/dist/utils/skill-system.d.ts.map +1 -0
- package/cli/dist/utils/skill-system.js +313 -0
- package/cli/dist/utils/skill-system.js.map +1 -0
- package/modules.md +534 -70
- package/package.json +12 -3
|
@@ -0,0 +1,1132 @@
|
|
|
1
|
+
# Hybrid Database Example: E-Learning Platform
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
This example demonstrates a comprehensive hybrid database architecture using multiple database types together:
|
|
6
|
+
|
|
7
|
+
- **PostgreSQL** - Relational data (users, courses, enrollments)
|
|
8
|
+
- **Redis** - Caching and session management
|
|
9
|
+
- **MongoDB** - Activity logs and analytics
|
|
10
|
+
- **Pinecone** - Semantic search for course content
|
|
11
|
+
|
|
12
|
+
**Use Case**: E-learning platform with user management, course catalog, real-time analytics, and AI-powered course search.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Architecture Diagram
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
20
|
+
│ Application Layer │
|
|
21
|
+
│ (Node.js / Express API) │
|
|
22
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
23
|
+
│
|
|
24
|
+
┌────────────────┼────────────────┐
|
|
25
|
+
│ │ │
|
|
26
|
+
▼ ▼ ▼
|
|
27
|
+
┌──────────────────┐ ┌──────────────┐ ┌──────────────┐
|
|
28
|
+
│ PostgreSQL │ │ Redis │ │ MongoDB │
|
|
29
|
+
│ (Primary Data) │ │ (Cache) │ │ (Logs) │
|
|
30
|
+
└──────────────────┘ └──────────────┘ └──────────────┘
|
|
31
|
+
│ │
|
|
32
|
+
│ │
|
|
33
|
+
▼ ▼
|
|
34
|
+
┌──────────────────┐ ┌──────────────────┐
|
|
35
|
+
│ Pinecone │ │ Analytics │
|
|
36
|
+
│ (Vector Search) │ │ Pipeline │
|
|
37
|
+
└──────────────────┘ └──────────────────┘
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Data Flow
|
|
43
|
+
|
|
44
|
+
### 1. User Registration Flow
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
User Registration Request
|
|
48
|
+
│
|
|
49
|
+
├─> PostgreSQL: Create user record (ACID transaction)
|
|
50
|
+
│
|
|
51
|
+
├─> Redis: Cache user profile (TTL: 1 hour)
|
|
52
|
+
│
|
|
53
|
+
└─> MongoDB: Log registration event
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 2. Course Search Flow
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
Search Query: "machine learning fundamentals"
|
|
60
|
+
│
|
|
61
|
+
├─> Redis: Check cache for query results
|
|
62
|
+
│ └─> Cache HIT: Return cached results
|
|
63
|
+
│ └─> Cache MISS: Continue to vector search
|
|
64
|
+
│
|
|
65
|
+
├─> Pinecone: Semantic search for relevant courses
|
|
66
|
+
│ └─> Returns: [course_id_1, course_id_2, ...]
|
|
67
|
+
│
|
|
68
|
+
├─> PostgreSQL: Fetch course details by IDs
|
|
69
|
+
│
|
|
70
|
+
├─> Redis: Cache search results (TTL: 15 minutes)
|
|
71
|
+
│
|
|
72
|
+
└─> MongoDB: Log search query and results
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 3. Course Enrollment Flow
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
Enrollment Request
|
|
79
|
+
│
|
|
80
|
+
├─> PostgreSQL: Create enrollment record (transaction)
|
|
81
|
+
│ ├─> Check course capacity
|
|
82
|
+
│ ├─> Insert enrollment
|
|
83
|
+
│ └─> Update course enrollment count
|
|
84
|
+
│
|
|
85
|
+
├─> Redis: Invalidate user's enrollment cache
|
|
86
|
+
│
|
|
87
|
+
└─> MongoDB: Log enrollment event with metadata
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Database Schemas
|
|
93
|
+
|
|
94
|
+
### PostgreSQL Schema (Relational Data)
|
|
95
|
+
|
|
96
|
+
**Purpose**: Store structured, transactional data with strong consistency
|
|
97
|
+
|
|
98
|
+
```sql
|
|
99
|
+
-- Users table
|
|
100
|
+
CREATE TABLE users (
|
|
101
|
+
id SERIAL PRIMARY KEY,
|
|
102
|
+
email VARCHAR(255) UNIQUE NOT NULL,
|
|
103
|
+
username VARCHAR(100) UNIQUE NOT NULL,
|
|
104
|
+
password_hash VARCHAR(255) NOT NULL,
|
|
105
|
+
full_name VARCHAR(255),
|
|
106
|
+
role VARCHAR(50) NOT NULL DEFAULT 'student',
|
|
107
|
+
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
108
|
+
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
CREATE INDEX idx_users_email ON users(email);
|
|
112
|
+
CREATE INDEX idx_users_role ON users(role);
|
|
113
|
+
|
|
114
|
+
-- Courses table
|
|
115
|
+
CREATE TABLE courses (
|
|
116
|
+
id SERIAL PRIMARY KEY,
|
|
117
|
+
title VARCHAR(255) NOT NULL,
|
|
118
|
+
description TEXT,
|
|
119
|
+
instructor_id INT NOT NULL REFERENCES users(id),
|
|
120
|
+
category VARCHAR(100),
|
|
121
|
+
difficulty_level VARCHAR(50),
|
|
122
|
+
max_students INT,
|
|
123
|
+
current_enrollment INT NOT NULL DEFAULT 0,
|
|
124
|
+
price DECIMAL(10, 2) NOT NULL,
|
|
125
|
+
is_published BOOLEAN NOT NULL DEFAULT false,
|
|
126
|
+
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
127
|
+
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
128
|
+
|
|
129
|
+
CONSTRAINT chk_enrollment CHECK (current_enrollment <= max_students)
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
CREATE INDEX idx_courses_category ON courses(category);
|
|
133
|
+
CREATE INDEX idx_courses_instructor ON courses(instructor_id);
|
|
134
|
+
CREATE INDEX idx_courses_published ON courses(is_published) WHERE is_published = true;
|
|
135
|
+
|
|
136
|
+
-- Enrollments table
|
|
137
|
+
CREATE TABLE enrollments (
|
|
138
|
+
id SERIAL PRIMARY KEY,
|
|
139
|
+
user_id INT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
140
|
+
course_id INT NOT NULL REFERENCES courses(id) ON DELETE CASCADE,
|
|
141
|
+
enrolled_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
142
|
+
completed_at TIMESTAMP,
|
|
143
|
+
progress_percentage INT NOT NULL DEFAULT 0,
|
|
144
|
+
|
|
145
|
+
UNIQUE(user_id, course_id),
|
|
146
|
+
CONSTRAINT chk_progress CHECK (progress_percentage >= 0 AND progress_percentage <= 100)
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
CREATE INDEX idx_enrollments_user ON enrollments(user_id);
|
|
150
|
+
CREATE INDEX idx_enrollments_course ON enrollments(course_id);
|
|
151
|
+
CREATE INDEX idx_enrollments_completed ON enrollments(completed_at) WHERE completed_at IS NOT NULL;
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
### Redis Schema (Caching)
|
|
157
|
+
|
|
158
|
+
**Purpose**: Cache frequently accessed data, manage sessions, rate limiting
|
|
159
|
+
|
|
160
|
+
**Key Patterns**:
|
|
161
|
+
|
|
162
|
+
```javascript
|
|
163
|
+
// User profile cache
|
|
164
|
+
user:{user_id} // Hash: User profile data
|
|
165
|
+
user:{user_id}:enrollments // Set: Enrolled course IDs
|
|
166
|
+
user:{user_id}:session // String: Session token
|
|
167
|
+
|
|
168
|
+
// Course cache
|
|
169
|
+
course:{course_id} // Hash: Course details
|
|
170
|
+
course:{course_id}:students // Set: Enrolled student IDs
|
|
171
|
+
courses:popular // Sorted Set: Popular courses (by enrollment)
|
|
172
|
+
|
|
173
|
+
// Search cache
|
|
174
|
+
search:{query_hash} // String: Cached search results (JSON)
|
|
175
|
+
|
|
176
|
+
// Rate limiting
|
|
177
|
+
ratelimit:{user_id}:{endpoint} // String: Request count (TTL: 1 minute)
|
|
178
|
+
|
|
179
|
+
// Session management
|
|
180
|
+
session:{session_token} // Hash: Session data (TTL: 24 hours)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**Example Redis Commands**:
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
# Cache user profile (TTL: 1 hour)
|
|
187
|
+
HSET user:123 id 123 email "john@example.com" full_name "John Doe"
|
|
188
|
+
EXPIRE user:123 3600
|
|
189
|
+
|
|
190
|
+
# Cache user enrollments
|
|
191
|
+
SADD user:123:enrollments 1 5 12
|
|
192
|
+
|
|
193
|
+
# Cache popular courses (sorted by enrollment count)
|
|
194
|
+
ZADD courses:popular 150 "course:1" 200 "course:5" 75 "course:12"
|
|
195
|
+
|
|
196
|
+
# Cache search results (TTL: 15 minutes)
|
|
197
|
+
SETEX search:ml_fundamentals 900 '{"results":[1,5,12],"count":3}'
|
|
198
|
+
|
|
199
|
+
# Rate limiting (max 100 requests per minute)
|
|
200
|
+
INCR ratelimit:123:search
|
|
201
|
+
EXPIRE ratelimit:123:search 60
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
### MongoDB Schema (Activity Logs)
|
|
207
|
+
|
|
208
|
+
**Purpose**: Store semi-structured event logs and analytics data
|
|
209
|
+
|
|
210
|
+
**Collections**:
|
|
211
|
+
|
|
212
|
+
```javascript
|
|
213
|
+
// user_activity collection
|
|
214
|
+
{
|
|
215
|
+
_id: ObjectId("..."),
|
|
216
|
+
user_id: 123,
|
|
217
|
+
event_type: "course_view",
|
|
218
|
+
event_data: {
|
|
219
|
+
course_id: 5,
|
|
220
|
+
duration_seconds: 45,
|
|
221
|
+
page: "course_overview"
|
|
222
|
+
},
|
|
223
|
+
metadata: {
|
|
224
|
+
ip_address: "192.168.1.1",
|
|
225
|
+
user_agent: "Mozilla/5.0...",
|
|
226
|
+
referrer: "https://example.com/courses"
|
|
227
|
+
},
|
|
228
|
+
timestamp: ISODate("2024-01-20T10:30:00Z")
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// search_logs collection
|
|
232
|
+
{
|
|
233
|
+
_id: ObjectId("..."),
|
|
234
|
+
user_id: 123,
|
|
235
|
+
query: "machine learning fundamentals",
|
|
236
|
+
results_count: 15,
|
|
237
|
+
clicked_result: 5,
|
|
238
|
+
click_position: 2,
|
|
239
|
+
search_duration_ms: 234,
|
|
240
|
+
timestamp: ISODate("2024-01-20T10:30:00Z")
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// enrollment_events collection
|
|
244
|
+
{
|
|
245
|
+
_id: ObjectId("..."),
|
|
246
|
+
user_id: 123,
|
|
247
|
+
course_id: 5,
|
|
248
|
+
event_type: "enrollment_created",
|
|
249
|
+
event_data: {
|
|
250
|
+
payment_method: "credit_card",
|
|
251
|
+
price_paid: 49.99,
|
|
252
|
+
discount_applied: 10.00
|
|
253
|
+
},
|
|
254
|
+
timestamp: ISODate("2024-01-20T10:30:00Z")
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
**MongoDB Indexes**:
|
|
259
|
+
|
|
260
|
+
```javascript
|
|
261
|
+
// user_activity collection
|
|
262
|
+
db.user_activity.createIndex({ user_id: 1, timestamp: -1 });
|
|
263
|
+
db.user_activity.createIndex({ event_type: 1, timestamp: -1 });
|
|
264
|
+
db.user_activity.createIndex({ timestamp: -1 });
|
|
265
|
+
|
|
266
|
+
// search_logs collection
|
|
267
|
+
db.search_logs.createIndex({ user_id: 1, timestamp: -1 });
|
|
268
|
+
db.search_logs.createIndex({ query: 1, timestamp: -1 });
|
|
269
|
+
db.search_logs.createIndex({ timestamp: -1 });
|
|
270
|
+
|
|
271
|
+
// enrollment_events collection
|
|
272
|
+
db.enrollment_events.createIndex({ user_id: 1, timestamp: -1 });
|
|
273
|
+
db.enrollment_events.createIndex({ course_id: 1, timestamp: -1 });
|
|
274
|
+
db.enrollment_events.createIndex({ event_type: 1, timestamp: -1 });
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
### Pinecone Schema (Vector Search)
|
|
280
|
+
|
|
281
|
+
**Purpose**: Semantic search for course content
|
|
282
|
+
|
|
283
|
+
**Index Configuration**:
|
|
284
|
+
|
|
285
|
+
```python
|
|
286
|
+
# Pinecone index configuration
|
|
287
|
+
{
|
|
288
|
+
"name": "course-search",
|
|
289
|
+
"dimension": 1536, # OpenAI text-embedding-3-small
|
|
290
|
+
"metric": "cosine",
|
|
291
|
+
"pod_type": "p1.x1"
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
**Vector Format**:
|
|
296
|
+
|
|
297
|
+
```python
|
|
298
|
+
# Course vector record
|
|
299
|
+
{
|
|
300
|
+
"id": "course_5",
|
|
301
|
+
"values": [0.123, -0.456, ...], # 1536-dimensional embedding
|
|
302
|
+
"metadata": {
|
|
303
|
+
"course_id": 5,
|
|
304
|
+
"title": "Machine Learning Fundamentals",
|
|
305
|
+
"category": "AI/ML",
|
|
306
|
+
"difficulty": "beginner",
|
|
307
|
+
"instructor_id": 42,
|
|
308
|
+
"price": 49.99,
|
|
309
|
+
"enrollment_count": 200
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
## Implementation
|
|
317
|
+
|
|
318
|
+
### Database Connection Manager
|
|
319
|
+
|
|
320
|
+
**File: `database/connection-manager.js`**
|
|
321
|
+
|
|
322
|
+
```javascript
|
|
323
|
+
const { Pool } = require('pg');
|
|
324
|
+
const redis = require('redis');
|
|
325
|
+
const { MongoClient } = require('mongodb');
|
|
326
|
+
const { PineconeClient } = require('@pinecone-database/pinecone');
|
|
327
|
+
|
|
328
|
+
class DatabaseManager {
|
|
329
|
+
constructor() {
|
|
330
|
+
this.postgres = null;
|
|
331
|
+
this.redis = null;
|
|
332
|
+
this.mongodb = null;
|
|
333
|
+
this.pinecone = null;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
async connect() {
|
|
337
|
+
// PostgreSQL connection
|
|
338
|
+
this.postgres = new Pool({
|
|
339
|
+
host: process.env.POSTGRES_HOST,
|
|
340
|
+
port: process.env.POSTGRES_PORT,
|
|
341
|
+
database: process.env.POSTGRES_DB,
|
|
342
|
+
user: process.env.POSTGRES_USER,
|
|
343
|
+
password: process.env.POSTGRES_PASSWORD,
|
|
344
|
+
max: 20,
|
|
345
|
+
idleTimeoutMillis: 30000,
|
|
346
|
+
connectionTimeoutMillis: 2000,
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
// Redis connection
|
|
350
|
+
this.redis = redis.createClient({
|
|
351
|
+
url: process.env.REDIS_URL,
|
|
352
|
+
socket: {
|
|
353
|
+
reconnectStrategy: (retries) => Math.min(retries * 50, 500)
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
await this.redis.connect();
|
|
357
|
+
|
|
358
|
+
// MongoDB connection
|
|
359
|
+
const mongoClient = new MongoClient(process.env.MONGODB_URI);
|
|
360
|
+
await mongoClient.connect();
|
|
361
|
+
this.mongodb = mongoClient.db(process.env.MONGODB_DB);
|
|
362
|
+
|
|
363
|
+
// Pinecone connection
|
|
364
|
+
this.pinecone = new PineconeClient();
|
|
365
|
+
await this.pinecone.init({
|
|
366
|
+
apiKey: process.env.PINECONE_API_KEY,
|
|
367
|
+
environment: process.env.PINECONE_ENVIRONMENT
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
console.log('All databases connected successfully');
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
async disconnect() {
|
|
374
|
+
if (this.postgres) await this.postgres.end();
|
|
375
|
+
if (this.redis) await this.redis.quit();
|
|
376
|
+
if (this.mongodb) await this.mongodb.client.close();
|
|
377
|
+
console.log('All databases disconnected');
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
getPostgres() { return this.postgres; }
|
|
381
|
+
getRedis() { return this.redis; }
|
|
382
|
+
getMongoDB() { return this.mongodb; }
|
|
383
|
+
getPinecone() { return this.pinecone; }
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
module.exports = new DatabaseManager();
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
---
|
|
390
|
+
|
|
391
|
+
### User Service (PostgreSQL + Redis)
|
|
392
|
+
|
|
393
|
+
**File: `services/user-service.js`**
|
|
394
|
+
|
|
395
|
+
```javascript
|
|
396
|
+
const dbManager = require('../database/connection-manager');
|
|
397
|
+
|
|
398
|
+
class UserService {
|
|
399
|
+
async createUser(userData) {
|
|
400
|
+
const postgres = dbManager.getPostgres();
|
|
401
|
+
const redis = dbManager.getRedis();
|
|
402
|
+
const mongodb = dbManager.getMongoDB();
|
|
403
|
+
|
|
404
|
+
// 1. Create user in PostgreSQL (ACID transaction)
|
|
405
|
+
const result = await postgres.query(
|
|
406
|
+
`INSERT INTO users (email, username, password_hash, full_name, role)
|
|
407
|
+
VALUES ($1, $2, $3, $4, $5)
|
|
408
|
+
RETURNING id, email, username, full_name, role, created_at`,
|
|
409
|
+
[userData.email, userData.username, userData.passwordHash,
|
|
410
|
+
userData.fullName, userData.role || 'student']
|
|
411
|
+
);
|
|
412
|
+
|
|
413
|
+
const user = result.rows[0];
|
|
414
|
+
|
|
415
|
+
// 2. Cache user profile in Redis (TTL: 1 hour)
|
|
416
|
+
const cacheKey = `user:${user.id}`;
|
|
417
|
+
await redis.hSet(cacheKey, {
|
|
418
|
+
id: user.id.toString(),
|
|
419
|
+
email: user.email,
|
|
420
|
+
username: user.username,
|
|
421
|
+
full_name: user.full_name,
|
|
422
|
+
role: user.role
|
|
423
|
+
});
|
|
424
|
+
await redis.expire(cacheKey, 3600);
|
|
425
|
+
|
|
426
|
+
// 3. Log registration event in MongoDB
|
|
427
|
+
await mongodb.collection('user_activity').insertOne({
|
|
428
|
+
user_id: user.id,
|
|
429
|
+
event_type: 'user_registered',
|
|
430
|
+
event_data: {
|
|
431
|
+
email: user.email,
|
|
432
|
+
role: user.role
|
|
433
|
+
},
|
|
434
|
+
metadata: {
|
|
435
|
+
ip_address: userData.ipAddress,
|
|
436
|
+
user_agent: userData.userAgent
|
|
437
|
+
},
|
|
438
|
+
timestamp: new Date()
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
return user;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
async getUser(userId) {
|
|
445
|
+
const redis = dbManager.getRedis();
|
|
446
|
+
const postgres = dbManager.getPostgres();
|
|
447
|
+
|
|
448
|
+
// 1. Try cache first
|
|
449
|
+
const cacheKey = `user:${userId}`;
|
|
450
|
+
const cached = await redis.hGetAll(cacheKey);
|
|
451
|
+
|
|
452
|
+
if (cached && Object.keys(cached).length > 0) {
|
|
453
|
+
console.log('Cache HIT for user:', userId);
|
|
454
|
+
return {
|
|
455
|
+
id: parseInt(cached.id),
|
|
456
|
+
email: cached.email,
|
|
457
|
+
username: cached.username,
|
|
458
|
+
full_name: cached.full_name,
|
|
459
|
+
role: cached.role
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// 2. Cache MISS - query PostgreSQL
|
|
464
|
+
console.log('Cache MISS for user:', userId);
|
|
465
|
+
const result = await postgres.query(
|
|
466
|
+
'SELECT id, email, username, full_name, role, created_at FROM users WHERE id = $1',
|
|
467
|
+
[userId]
|
|
468
|
+
);
|
|
469
|
+
|
|
470
|
+
if (result.rows.length === 0) {
|
|
471
|
+
return null;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const user = result.rows[0];
|
|
475
|
+
|
|
476
|
+
// 3. Store in cache
|
|
477
|
+
await redis.hSet(cacheKey, {
|
|
478
|
+
id: user.id.toString(),
|
|
479
|
+
email: user.email,
|
|
480
|
+
username: user.username,
|
|
481
|
+
full_name: user.full_name,
|
|
482
|
+
role: user.role
|
|
483
|
+
});
|
|
484
|
+
await redis.expire(cacheKey, 3600);
|
|
485
|
+
|
|
486
|
+
return user;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
async updateUser(userId, updates) {
|
|
490
|
+
const postgres = dbManager.getPostgres();
|
|
491
|
+
const redis = dbManager.getRedis();
|
|
492
|
+
|
|
493
|
+
// 1. Update in PostgreSQL
|
|
494
|
+
const result = await postgres.query(
|
|
495
|
+
`UPDATE users
|
|
496
|
+
SET full_name = COALESCE($1, full_name),
|
|
497
|
+
updated_at = CURRENT_TIMESTAMP
|
|
498
|
+
WHERE id = $2
|
|
499
|
+
RETURNING id, email, username, full_name, role`,
|
|
500
|
+
[updates.fullName, userId]
|
|
501
|
+
);
|
|
502
|
+
|
|
503
|
+
const user = result.rows[0];
|
|
504
|
+
|
|
505
|
+
// 2. Invalidate cache
|
|
506
|
+
await redis.del(`user:${userId}`);
|
|
507
|
+
|
|
508
|
+
return user;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
module.exports = new UserService();
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
---
|
|
516
|
+
|
|
517
|
+
### Course Search Service (Pinecone + PostgreSQL + Redis)
|
|
518
|
+
|
|
519
|
+
**File: `services/course-search-service.js`**
|
|
520
|
+
|
|
521
|
+
```javascript
|
|
522
|
+
const dbManager = require('../database/connection-manager');
|
|
523
|
+
const { OpenAI } = require('openai');
|
|
524
|
+
const crypto = require('crypto');
|
|
525
|
+
|
|
526
|
+
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
|
|
527
|
+
|
|
528
|
+
class CourseSearchService {
|
|
529
|
+
async searchCourses(query, options = {}) {
|
|
530
|
+
const redis = dbManager.getRedis();
|
|
531
|
+
const pinecone = dbManager.getPinecone();
|
|
532
|
+
const postgres = dbManager.getPostgres();
|
|
533
|
+
const mongodb = dbManager.getMongoDB();
|
|
534
|
+
|
|
535
|
+
const { topK = 10, category = null, userId = null } = options;
|
|
536
|
+
|
|
537
|
+
// 1. Generate cache key
|
|
538
|
+
const cacheKey = `search:${this.hashQuery(query, options)}`;
|
|
539
|
+
|
|
540
|
+
// 2. Check cache
|
|
541
|
+
const cached = await redis.get(cacheKey);
|
|
542
|
+
if (cached) {
|
|
543
|
+
console.log('Cache HIT for search:', query);
|
|
544
|
+
const results = JSON.parse(cached);
|
|
545
|
+
|
|
546
|
+
// Log cache hit
|
|
547
|
+
if (userId) {
|
|
548
|
+
await this.logSearch(userId, query, results.length, 'cache_hit');
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
return results;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
console.log('Cache MISS for search:', query);
|
|
555
|
+
|
|
556
|
+
// 3. Generate query embedding
|
|
557
|
+
const embedding = await this.generateEmbedding(query);
|
|
558
|
+
|
|
559
|
+
// 4. Search Pinecone
|
|
560
|
+
const index = pinecone.Index('course-search');
|
|
561
|
+
const filter = category ? { category: { $eq: category } } : {};
|
|
562
|
+
|
|
563
|
+
const searchResults = await index.query({
|
|
564
|
+
vector: embedding,
|
|
565
|
+
topK: topK,
|
|
566
|
+
includeMetadata: true,
|
|
567
|
+
filter: filter
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
// 5. Extract course IDs
|
|
571
|
+
const courseIds = searchResults.matches.map(match =>
|
|
572
|
+
parseInt(match.metadata.course_id)
|
|
573
|
+
);
|
|
574
|
+
|
|
575
|
+
if (courseIds.length === 0) {
|
|
576
|
+
return [];
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// 6. Fetch full course details from PostgreSQL
|
|
580
|
+
const placeholders = courseIds.map((_, i) => `$${i + 1}`).join(',');
|
|
581
|
+
const result = await postgres.query(
|
|
582
|
+
`SELECT id, title, description, category, difficulty_level,
|
|
583
|
+
price, current_enrollment, instructor_id
|
|
584
|
+
FROM courses
|
|
585
|
+
WHERE id IN (${placeholders}) AND is_published = true
|
|
586
|
+
ORDER BY ARRAY_POSITION($${courseIds.length + 1}::int[], id)`,
|
|
587
|
+
[...courseIds, courseIds]
|
|
588
|
+
);
|
|
589
|
+
|
|
590
|
+
const courses = result.rows.map((course, index) => ({
|
|
591
|
+
...course,
|
|
592
|
+
similarity_score: searchResults.matches[index].score
|
|
593
|
+
}));
|
|
594
|
+
|
|
595
|
+
// 7. Cache results (TTL: 15 minutes)
|
|
596
|
+
await redis.setEx(cacheKey, 900, JSON.stringify(courses));
|
|
597
|
+
|
|
598
|
+
// 8. Log search in MongoDB
|
|
599
|
+
if (userId) {
|
|
600
|
+
await this.logSearch(userId, query, courses.length, 'vector_search');
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
return courses;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
async generateEmbedding(text) {
|
|
607
|
+
const response = await openai.embeddings.create({
|
|
608
|
+
model: 'text-embedding-3-small',
|
|
609
|
+
input: text
|
|
610
|
+
});
|
|
611
|
+
return response.data[0].embedding;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
hashQuery(query, options) {
|
|
615
|
+
const str = JSON.stringify({ query, ...options });
|
|
616
|
+
return crypto.createHash('md5').update(str).digest('hex');
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
async logSearch(userId, query, resultsCount, searchType) {
|
|
620
|
+
const mongodb = dbManager.getMongoDB();
|
|
621
|
+
await mongodb.collection('search_logs').insertOne({
|
|
622
|
+
user_id: userId,
|
|
623
|
+
query: query,
|
|
624
|
+
results_count: resultsCount,
|
|
625
|
+
search_type: searchType,
|
|
626
|
+
timestamp: new Date()
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
async indexCourse(courseId) {
|
|
631
|
+
const postgres = dbManager.getPostgres();
|
|
632
|
+
const pinecone = dbManager.getPinecone();
|
|
633
|
+
|
|
634
|
+
// 1. Fetch course from PostgreSQL
|
|
635
|
+
const result = await postgres.query(
|
|
636
|
+
`SELECT id, title, description, category, difficulty_level,
|
|
637
|
+
price, current_enrollment, instructor_id
|
|
638
|
+
FROM courses WHERE id = $1`,
|
|
639
|
+
[courseId]
|
|
640
|
+
);
|
|
641
|
+
|
|
642
|
+
if (result.rows.length === 0) {
|
|
643
|
+
throw new Error(`Course ${courseId} not found`);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
const course = result.rows[0];
|
|
647
|
+
|
|
648
|
+
// 2. Generate embedding from title + description
|
|
649
|
+
const text = `${course.title}. ${course.description}`;
|
|
650
|
+
const embedding = await this.generateEmbedding(text);
|
|
651
|
+
|
|
652
|
+
// 3. Upsert to Pinecone
|
|
653
|
+
const index = pinecone.Index('course-search');
|
|
654
|
+
await index.upsert([{
|
|
655
|
+
id: `course_${course.id}`,
|
|
656
|
+
values: embedding,
|
|
657
|
+
metadata: {
|
|
658
|
+
course_id: course.id,
|
|
659
|
+
title: course.title,
|
|
660
|
+
category: course.category,
|
|
661
|
+
difficulty: course.difficulty_level,
|
|
662
|
+
instructor_id: course.instructor_id,
|
|
663
|
+
price: parseFloat(course.price),
|
|
664
|
+
enrollment_count: course.current_enrollment
|
|
665
|
+
}
|
|
666
|
+
}]);
|
|
667
|
+
|
|
668
|
+
console.log(`Indexed course ${courseId} in Pinecone`);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
module.exports = new CourseSearchService();
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
---
|
|
676
|
+
|
|
677
|
+
### Enrollment Service (PostgreSQL + Redis + MongoDB)
|
|
678
|
+
|
|
679
|
+
**File: `services/enrollment-service.js`**
|
|
680
|
+
|
|
681
|
+
```javascript
|
|
682
|
+
const dbManager = require('../database/connection-manager');
|
|
683
|
+
|
|
684
|
+
class EnrollmentService {
|
|
685
|
+
async enrollUser(userId, courseId, paymentData) {
|
|
686
|
+
const postgres = dbManager.getPostgres();
|
|
687
|
+
const redis = dbManager.getRedis();
|
|
688
|
+
const mongodb = dbManager.getMongoDB();
|
|
689
|
+
|
|
690
|
+
// Use PostgreSQL transaction for ACID guarantees
|
|
691
|
+
const client = await postgres.connect();
|
|
692
|
+
|
|
693
|
+
try {
|
|
694
|
+
await client.query('BEGIN');
|
|
695
|
+
|
|
696
|
+
// 1. Check course capacity
|
|
697
|
+
const courseResult = await client.query(
|
|
698
|
+
`SELECT id, max_students, current_enrollment, price
|
|
699
|
+
FROM courses
|
|
700
|
+
WHERE id = $1 AND is_published = true
|
|
701
|
+
FOR UPDATE`, // Lock row for update
|
|
702
|
+
[courseId]
|
|
703
|
+
);
|
|
704
|
+
|
|
705
|
+
if (courseResult.rows.length === 0) {
|
|
706
|
+
throw new Error('Course not found or not published');
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
const course = courseResult.rows[0];
|
|
710
|
+
|
|
711
|
+
if (course.current_enrollment >= course.max_students) {
|
|
712
|
+
throw new Error('Course is full');
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// 2. Check if already enrolled
|
|
716
|
+
const existingEnrollment = await client.query(
|
|
717
|
+
'SELECT id FROM enrollments WHERE user_id = $1 AND course_id = $2',
|
|
718
|
+
[userId, courseId]
|
|
719
|
+
);
|
|
720
|
+
|
|
721
|
+
if (existingEnrollment.rows.length > 0) {
|
|
722
|
+
throw new Error('User already enrolled in this course');
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// 3. Create enrollment
|
|
726
|
+
const enrollmentResult = await client.query(
|
|
727
|
+
`INSERT INTO enrollments (user_id, course_id, progress_percentage)
|
|
728
|
+
VALUES ($1, $2, 0)
|
|
729
|
+
RETURNING id, enrolled_at`,
|
|
730
|
+
[userId, courseId]
|
|
731
|
+
);
|
|
732
|
+
|
|
733
|
+
const enrollment = enrollmentResult.rows[0];
|
|
734
|
+
|
|
735
|
+
// 4. Update course enrollment count
|
|
736
|
+
await client.query(
|
|
737
|
+
`UPDATE courses
|
|
738
|
+
SET current_enrollment = current_enrollment + 1,
|
|
739
|
+
updated_at = CURRENT_TIMESTAMP
|
|
740
|
+
WHERE id = $1`,
|
|
741
|
+
[courseId]
|
|
742
|
+
);
|
|
743
|
+
|
|
744
|
+
await client.query('COMMIT');
|
|
745
|
+
|
|
746
|
+
// 5. Invalidate caches
|
|
747
|
+
await redis.del(`user:${userId}:enrollments`);
|
|
748
|
+
await redis.del(`course:${courseId}`);
|
|
749
|
+
await redis.del(`course:${courseId}:students`);
|
|
750
|
+
|
|
751
|
+
// 6. Log enrollment event in MongoDB
|
|
752
|
+
await mongodb.collection('enrollment_events').insertOne({
|
|
753
|
+
user_id: userId,
|
|
754
|
+
course_id: courseId,
|
|
755
|
+
event_type: 'enrollment_created',
|
|
756
|
+
event_data: {
|
|
757
|
+
payment_method: paymentData.method,
|
|
758
|
+
price_paid: parseFloat(course.price),
|
|
759
|
+
discount_applied: paymentData.discount || 0
|
|
760
|
+
},
|
|
761
|
+
timestamp: new Date()
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
return {
|
|
765
|
+
enrollment_id: enrollment.id,
|
|
766
|
+
enrolled_at: enrollment.enrolled_at,
|
|
767
|
+
course_id: courseId,
|
|
768
|
+
user_id: userId
|
|
769
|
+
};
|
|
770
|
+
|
|
771
|
+
} catch (error) {
|
|
772
|
+
await client.query('ROLLBACK');
|
|
773
|
+
throw error;
|
|
774
|
+
} finally {
|
|
775
|
+
client.release();
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
async getUserEnrollments(userId) {
|
|
780
|
+
const postgres = dbManager.getPostgres();
|
|
781
|
+
const redis = dbManager.getRedis();
|
|
782
|
+
|
|
783
|
+
// 1. Check cache
|
|
784
|
+
const cacheKey = `user:${userId}:enrollments`;
|
|
785
|
+
const cached = await redis.get(cacheKey);
|
|
786
|
+
|
|
787
|
+
if (cached) {
|
|
788
|
+
console.log('Cache HIT for user enrollments:', userId);
|
|
789
|
+
return JSON.parse(cached);
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// 2. Query PostgreSQL
|
|
793
|
+
console.log('Cache MISS for user enrollments:', userId);
|
|
794
|
+
const result = await postgres.query(
|
|
795
|
+
`SELECT e.id, e.course_id, e.enrolled_at, e.progress_percentage,
|
|
796
|
+
c.title, c.description, c.category, c.difficulty_level
|
|
797
|
+
FROM enrollments e
|
|
798
|
+
JOIN courses c ON c.id = e.course_id
|
|
799
|
+
WHERE e.user_id = $1
|
|
800
|
+
ORDER BY e.enrolled_at DESC`,
|
|
801
|
+
[userId]
|
|
802
|
+
);
|
|
803
|
+
|
|
804
|
+
const enrollments = result.rows;
|
|
805
|
+
|
|
806
|
+
// 3. Cache results (TTL: 5 minutes)
|
|
807
|
+
await redis.setEx(cacheKey, 300, JSON.stringify(enrollments));
|
|
808
|
+
|
|
809
|
+
return enrollments;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
module.exports = new EnrollmentService();
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
---
|
|
817
|
+
|
|
818
|
+
## Consistency Strategies
|
|
819
|
+
|
|
820
|
+
### 1. Cache Invalidation Strategy
|
|
821
|
+
|
|
822
|
+
**Pattern**: Write-through with TTL
|
|
823
|
+
|
|
824
|
+
```javascript
|
|
825
|
+
async function updateCourse(courseId, updates) {
|
|
826
|
+
const postgres = dbManager.getPostgres();
|
|
827
|
+
const redis = dbManager.getRedis();
|
|
828
|
+
|
|
829
|
+
// 1. Update PostgreSQL (source of truth)
|
|
830
|
+
await postgres.query(
|
|
831
|
+
'UPDATE courses SET title = $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2',
|
|
832
|
+
[updates.title, courseId]
|
|
833
|
+
);
|
|
834
|
+
|
|
835
|
+
// 2. Invalidate all related caches
|
|
836
|
+
await redis.del(`course:${courseId}`);
|
|
837
|
+
await redis.del(`course:${courseId}:students`);
|
|
838
|
+
|
|
839
|
+
// 3. Invalidate search cache (pattern delete)
|
|
840
|
+
const keys = await redis.keys('search:*');
|
|
841
|
+
if (keys.length > 0) {
|
|
842
|
+
await redis.del(keys);
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// 4. Re-index in Pinecone
|
|
846
|
+
await courseSearchService.indexCourse(courseId);
|
|
847
|
+
}
|
|
848
|
+
```
|
|
849
|
+
|
|
850
|
+
**Key Principles**:
|
|
851
|
+
- ✅ PostgreSQL is the source of truth
|
|
852
|
+
- ✅ Cache invalidation happens immediately after write
|
|
853
|
+
- ✅ TTL provides automatic cleanup for stale data
|
|
854
|
+
- ✅ Pattern-based invalidation for search caches
|
|
855
|
+
|
|
856
|
+
---
|
|
857
|
+
|
|
858
|
+
### 2. Eventual Consistency for Analytics
|
|
859
|
+
|
|
860
|
+
**Pattern**: Asynchronous logging to MongoDB
|
|
861
|
+
|
|
862
|
+
```javascript
|
|
863
|
+
async function logActivity(userId, eventType, eventData) {
|
|
864
|
+
const mongodb = dbManager.getMongoDB();
|
|
865
|
+
|
|
866
|
+
// Fire-and-forget: Don't wait for MongoDB write
|
|
867
|
+
mongodb.collection('user_activity').insertOne({
|
|
868
|
+
user_id: userId,
|
|
869
|
+
event_type: eventType,
|
|
870
|
+
event_data: eventData,
|
|
871
|
+
timestamp: new Date()
|
|
872
|
+
}).catch(err => {
|
|
873
|
+
console.error('Failed to log activity:', err);
|
|
874
|
+
// Don't fail the main operation if logging fails
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
```
|
|
878
|
+
|
|
879
|
+
**Key Principles**:
|
|
880
|
+
- ✅ Analytics don't block user operations
|
|
881
|
+
- ✅ Eventual consistency is acceptable for logs
|
|
882
|
+
- ✅ Failures in logging don't affect user experience
|
|
883
|
+
|
|
884
|
+
---
|
|
885
|
+
|
|
886
|
+
### 3. Transactional Consistency
|
|
887
|
+
|
|
888
|
+
**Pattern**: PostgreSQL transactions for critical operations
|
|
889
|
+
|
|
890
|
+
```javascript
|
|
891
|
+
async function transferEnrollment(fromUserId, toUserId, courseId) {
|
|
892
|
+
const client = await postgres.connect();
|
|
893
|
+
|
|
894
|
+
try {
|
|
895
|
+
await client.query('BEGIN');
|
|
896
|
+
|
|
897
|
+
// 1. Remove enrollment from first user
|
|
898
|
+
await client.query(
|
|
899
|
+
'DELETE FROM enrollments WHERE user_id = $1 AND course_id = $2',
|
|
900
|
+
[fromUserId, courseId]
|
|
901
|
+
);
|
|
902
|
+
|
|
903
|
+
// 2. Add enrollment to second user
|
|
904
|
+
await client.query(
|
|
905
|
+
'INSERT INTO enrollments (user_id, course_id) VALUES ($1, $2)',
|
|
906
|
+
[toUserId, courseId]
|
|
907
|
+
);
|
|
908
|
+
|
|
909
|
+
// 3. Commit transaction (all-or-nothing)
|
|
910
|
+
await client.query('COMMIT');
|
|
911
|
+
|
|
912
|
+
// 4. Invalidate caches after successful commit
|
|
913
|
+
await redis.del(`user:${fromUserId}:enrollments`);
|
|
914
|
+
await redis.del(`user:${toUserId}:enrollments`);
|
|
915
|
+
|
|
916
|
+
} catch (error) {
|
|
917
|
+
await client.query('ROLLBACK');
|
|
918
|
+
throw error;
|
|
919
|
+
} finally {
|
|
920
|
+
client.release();
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
```
|
|
924
|
+
|
|
925
|
+
**Key Principles**:
|
|
926
|
+
- ✅ Use transactions for operations requiring ACID guarantees
|
|
927
|
+
- ✅ Invalidate caches only after successful commit
|
|
928
|
+
- ✅ Always release database connections
|
|
929
|
+
|
|
930
|
+
---
|
|
931
|
+
|
|
932
|
+
### 4. Vector Search Consistency
|
|
933
|
+
|
|
934
|
+
**Pattern**: Async indexing with eventual consistency
|
|
935
|
+
|
|
936
|
+
```javascript
|
|
937
|
+
async function publishCourse(courseId) {
|
|
938
|
+
const postgres = dbManager.getPostgres();
|
|
939
|
+
|
|
940
|
+
// 1. Update PostgreSQL immediately
|
|
941
|
+
await postgres.query(
|
|
942
|
+
'UPDATE courses SET is_published = true WHERE id = $1',
|
|
943
|
+
[courseId]
|
|
944
|
+
);
|
|
945
|
+
|
|
946
|
+
// 2. Index in Pinecone asynchronously
|
|
947
|
+
// Use job queue (e.g., Bull, BullMQ) for production
|
|
948
|
+
setTimeout(async () => {
|
|
949
|
+
try {
|
|
950
|
+
await courseSearchService.indexCourse(courseId);
|
|
951
|
+
console.log(`Course ${courseId} indexed in Pinecone`);
|
|
952
|
+
} catch (error) {
|
|
953
|
+
console.error(`Failed to index course ${courseId}:`, error);
|
|
954
|
+
// Retry logic here
|
|
955
|
+
}
|
|
956
|
+
}, 0);
|
|
957
|
+
|
|
958
|
+
// 3. Invalidate caches
|
|
959
|
+
await redis.del(`course:${courseId}`);
|
|
960
|
+
}
|
|
961
|
+
```
|
|
962
|
+
|
|
963
|
+
**Key Principles**:
|
|
964
|
+
- ✅ PostgreSQL updated immediately (source of truth)
|
|
965
|
+
- ✅ Vector indexing happens asynchronously
|
|
966
|
+
- ✅ Short delay in search results is acceptable
|
|
967
|
+
- ✅ Use job queues for production reliability
|
|
968
|
+
|
|
969
|
+
---
|
|
970
|
+
|
|
971
|
+
## Best Practices
|
|
972
|
+
|
|
973
|
+
### 1. Database Selection Guidelines
|
|
974
|
+
|
|
975
|
+
| Data Type | Database | Reason |
|
|
976
|
+
|-----------|----------|--------|
|
|
977
|
+
| User accounts, enrollments | PostgreSQL | ACID transactions, strong consistency |
|
|
978
|
+
| Session data, rate limiting | Redis | Fast access, automatic expiration |
|
|
979
|
+
| Activity logs, analytics | MongoDB | Flexible schema, high write throughput |
|
|
980
|
+
| Course search | Pinecone | Semantic similarity search |
|
|
981
|
+
|
|
982
|
+
---
|
|
983
|
+
|
|
984
|
+
### 2. Caching Strategy
|
|
985
|
+
|
|
986
|
+
**Cache Hierarchy**:
|
|
987
|
+
|
|
988
|
+
```
|
|
989
|
+
Request
|
|
990
|
+
↓
|
|
991
|
+
Redis Cache (L1) ─── TTL: 5-60 minutes
|
|
992
|
+
↓ (miss)
|
|
993
|
+
PostgreSQL (Source of Truth)
|
|
994
|
+
↓
|
|
995
|
+
Update Redis Cache
|
|
996
|
+
↓
|
|
997
|
+
Return Response
|
|
998
|
+
```
|
|
999
|
+
|
|
1000
|
+
**TTL Guidelines**:
|
|
1001
|
+
- User profiles: 1 hour (changes infrequently)
|
|
1002
|
+
- Course details: 30 minutes (moderate changes)
|
|
1003
|
+
- Search results: 15 minutes (needs freshness)
|
|
1004
|
+
- User enrollments: 5 minutes (changes frequently)
|
|
1005
|
+
- Session data: 24 hours (explicit expiration)
|
|
1006
|
+
|
|
1007
|
+
---
|
|
1008
|
+
|
|
1009
|
+
### 3. Error Handling
|
|
1010
|
+
|
|
1011
|
+
```javascript
|
|
1012
|
+
async function robustGetUser(userId) {
|
|
1013
|
+
try {
|
|
1014
|
+
// Try cache first
|
|
1015
|
+
const cached = await redis.get(`user:${userId}`);
|
|
1016
|
+
if (cached) return JSON.parse(cached);
|
|
1017
|
+
} catch (redisError) {
|
|
1018
|
+
console.error('Redis error:', redisError);
|
|
1019
|
+
// Continue to PostgreSQL if Redis fails
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
try {
|
|
1023
|
+
// Fallback to PostgreSQL
|
|
1024
|
+
const result = await postgres.query(
|
|
1025
|
+
'SELECT * FROM users WHERE id = $1',
|
|
1026
|
+
[userId]
|
|
1027
|
+
);
|
|
1028
|
+
return result.rows[0];
|
|
1029
|
+
} catch (pgError) {
|
|
1030
|
+
console.error('PostgreSQL error:', pgError);
|
|
1031
|
+
throw new Error('Failed to fetch user');
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
```
|
|
1035
|
+
|
|
1036
|
+
**Key Principles**:
|
|
1037
|
+
- ✅ Graceful degradation (cache failure doesn't break app)
|
|
1038
|
+
- ✅ Always have fallback to source of truth
|
|
1039
|
+
- ✅ Log errors for monitoring
|
|
1040
|
+
|
|
1041
|
+
---
|
|
1042
|
+
|
|
1043
|
+
### 4. Monitoring and Observability
|
|
1044
|
+
|
|
1045
|
+
**Metrics to Track**:
|
|
1046
|
+
|
|
1047
|
+
```javascript
|
|
1048
|
+
// Cache hit rate
|
|
1049
|
+
const cacheHitRate = cacheHits / (cacheHits + cacheMisses);
|
|
1050
|
+
|
|
1051
|
+
// Database query performance
|
|
1052
|
+
const avgQueryTime = totalQueryTime / queryCount;
|
|
1053
|
+
|
|
1054
|
+
// Vector search latency
|
|
1055
|
+
const avgSearchLatency = totalSearchTime / searchCount;
|
|
1056
|
+
|
|
1057
|
+
// MongoDB write throughput
|
|
1058
|
+
const logsPerSecond = logCount / timeWindow;
|
|
1059
|
+
```
|
|
1060
|
+
|
|
1061
|
+
**Alerts**:
|
|
1062
|
+
- Cache hit rate < 80%
|
|
1063
|
+
- PostgreSQL query time > 100ms
|
|
1064
|
+
- Redis connection failures
|
|
1065
|
+
- MongoDB write failures
|
|
1066
|
+
- Pinecone search latency > 500ms
|
|
1067
|
+
|
|
1068
|
+
---
|
|
1069
|
+
|
|
1070
|
+
## Summary
|
|
1071
|
+
|
|
1072
|
+
### Architecture Benefits
|
|
1073
|
+
|
|
1074
|
+
✅ **Performance**: Redis caching reduces database load by 80%+
|
|
1075
|
+
✅ **Scalability**: Each database scales independently
|
|
1076
|
+
✅ **Flexibility**: Right tool for each data type
|
|
1077
|
+
✅ **Reliability**: Graceful degradation when components fail
|
|
1078
|
+
✅ **Analytics**: MongoDB handles high-volume event logging
|
|
1079
|
+
✅ **Search Quality**: Pinecone enables semantic search
|
|
1080
|
+
|
|
1081
|
+
### Key Design Decisions
|
|
1082
|
+
|
|
1083
|
+
| Decision | Rationale |
|
|
1084
|
+
|----------|-----------|
|
|
1085
|
+
| PostgreSQL for core data | ACID transactions, strong consistency for critical data |
|
|
1086
|
+
| Redis for caching | Sub-millisecond latency, automatic expiration |
|
|
1087
|
+
| MongoDB for logs | Flexible schema, high write throughput, time-series data |
|
|
1088
|
+
| Pinecone for search | Managed vector database, semantic similarity |
|
|
1089
|
+
| Cache-aside pattern | Simple, effective, graceful degradation |
|
|
1090
|
+
| Async logging | Don't block user operations for analytics |
|
|
1091
|
+
| TTL-based expiration | Automatic cleanup, eventual consistency |
|
|
1092
|
+
|
|
1093
|
+
### Trade-offs
|
|
1094
|
+
|
|
1095
|
+
**Complexity vs Performance**:
|
|
1096
|
+
- ❌ More databases = more operational complexity
|
|
1097
|
+
- ✅ Each database optimized for its use case
|
|
1098
|
+
- ✅ Better performance than single-database solution
|
|
1099
|
+
|
|
1100
|
+
**Consistency vs Availability**:
|
|
1101
|
+
- ✅ Strong consistency for transactions (PostgreSQL)
|
|
1102
|
+
- ✅ Eventual consistency for analytics (MongoDB)
|
|
1103
|
+
- ✅ Cache invalidation ensures freshness
|
|
1104
|
+
|
|
1105
|
+
**Cost vs Scale**:
|
|
1106
|
+
- ❌ Multiple databases = higher infrastructure cost
|
|
1107
|
+
- ✅ Each database can scale independently
|
|
1108
|
+
- ✅ Managed services (Pinecone, Redis Cloud) reduce ops burden
|
|
1109
|
+
|
|
1110
|
+
---
|
|
1111
|
+
|
|
1112
|
+
## Next Steps
|
|
1113
|
+
|
|
1114
|
+
1. **Implement monitoring**: Set up metrics and alerts for all databases
|
|
1115
|
+
2. **Add job queues**: Use Bull/BullMQ for async tasks (indexing, email)
|
|
1116
|
+
3. **Optimize queries**: Add indexes based on query patterns
|
|
1117
|
+
4. **Load testing**: Test system under realistic load
|
|
1118
|
+
5. **Disaster recovery**: Set up backups and replication for all databases
|
|
1119
|
+
6. **Security**: Implement encryption at rest and in transit
|
|
1120
|
+
|
|
1121
|
+
---
|
|
1122
|
+
|
|
1123
|
+
## Related Documentation
|
|
1124
|
+
|
|
1125
|
+
- **PostgreSQL**: See `../rules/relational-databases.md`
|
|
1126
|
+
- **Redis**: See `../rules/nosql-key-value-stores.md`
|
|
1127
|
+
- **MongoDB**: See `../rules/nosql-document-stores.md`
|
|
1128
|
+
- **Pinecone**: See `../rules/vector-databases.md`
|
|
1129
|
+
- **Caching**: See `../rules/performance-optimization.md`
|
|
1130
|
+
- **Transactions**: See `../rules/relational-transactions.md`
|
|
1131
|
+
|
|
1132
|
+
|