cfsa-antigravity 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/bin/cli.mjs +324 -0
- package/package.json +34 -0
- package/template/.agent/instructions/commands.md +48 -0
- package/template/.agent/instructions/patterns.md +61 -0
- package/template/.agent/instructions/structure.md +29 -0
- package/template/.agent/instructions/tech-stack.md +43 -0
- package/template/.agent/instructions/workflow.md +41 -0
- package/template/.agent/kit-sync.md +15 -0
- package/template/.agent/rules/boundary-not-placeholder.md +146 -0
- package/template/.agent/rules/completion-checklist.md +48 -0
- package/template/.agent/rules/decision-classification.md +103 -0
- package/template/.agent/rules/extensibility.md +47 -0
- package/template/.agent/rules/question-vs-command.md +81 -0
- package/template/.agent/rules/security-first.md +43 -0
- package/template/.agent/rules/specificity-standards.md +54 -0
- package/template/.agent/rules/tdd-contract-first.md +57 -0
- package/template/.agent/rules/vertical-slices.md +42 -0
- package/template/.agent/skill-library/MANIFEST.md +480 -0
- package/template/.agent/skill-library/README.md +38 -0
- package/template/.agent/skill-library/meta/brand-guidelines/SKILL.md +73 -0
- package/template/.agent/skill-library/meta/claude-code/README.md +9 -0
- package/template/.agent/skill-library/meta/claude-code/agent-development/SKILL.md +415 -0
- package/template/.agent/skill-library/meta/claude-code/hook-development/SKILL.md +712 -0
- package/template/.agent/skill-library/meta/claude-code/plugin-structure/SKILL.md +476 -0
- package/template/.agent/skill-library/meta/git-advanced/SKILL.md +972 -0
- package/template/.agent/skill-library/meta/mcp-builder/SKILL.md +236 -0
- package/template/.agent/skill-library/meta/product-marketing-context/SKILL.md +241 -0
- package/template/.agent/skill-library/meta/regex-patterns/SKILL.md +751 -0
- package/template/.agent/skill-library/meta/tmux-processes/SKILL.md +210 -0
- package/template/.agent/skill-library/meta/using-tmux-for-interactive-commands/SKILL.md +178 -0
- package/template/.agent/skill-library/stack/3d/threejs-pro/SKILL.md +300 -0
- package/template/.agent/skill-library/stack/ai/ai-sdk/SKILL.md +77 -0
- package/template/.agent/skill-library/stack/ai/langchain/SKILL.md +530 -0
- package/template/.agent/skill-library/stack/ai/ollama/SKILL.md +321 -0
- package/template/.agent/skill-library/stack/ai/openai-sdk/SKILL.md +549 -0
- package/template/.agent/skill-library/stack/analytics/google-analytics/SKILL.md +153 -0
- package/template/.agent/skill-library/stack/api/graphql/SKILL.md +1061 -0
- package/template/.agent/skill-library/stack/api/trpc/SKILL.md +576 -0
- package/template/.agent/skill-library/stack/auth/authjs/SKILL.md +569 -0
- package/template/.agent/skill-library/stack/auth/clerk/SKILL.md +590 -0
- package/template/.agent/skill-library/stack/auth/firebase-auth/SKILL.md +734 -0
- package/template/.agent/skill-library/stack/cms/payload-cms/SKILL.md +573 -0
- package/template/.agent/skill-library/stack/cms/shopify/SKILL.md +1193 -0
- package/template/.agent/skill-library/stack/cms/wordpress/SKILL.md +1104 -0
- package/template/.agent/skill-library/stack/css/sass-scss/SKILL.md +1121 -0
- package/template/.agent/skill-library/stack/css/tailwind-css-patterns/SKILL.md +863 -0
- package/template/.agent/skill-library/stack/css/tailwind-design-system/SKILL.md +490 -0
- package/template/.agent/skill-library/stack/css/vanilla-css/SKILL.md +1078 -0
- package/template/.agent/skill-library/stack/databases/clickhouse/SKILL.md +311 -0
- package/template/.agent/skill-library/stack/databases/influxdb/SKILL.md +280 -0
- package/template/.agent/skill-library/stack/databases/lancedb/SKILL.md +415 -0
- package/template/.agent/skill-library/stack/databases/mongodb/SKILL.md +1169 -0
- package/template/.agent/skill-library/stack/databases/neo4j/SKILL.md +839 -0
- package/template/.agent/skill-library/stack/databases/pgvector/SKILL.md +241 -0
- package/template/.agent/skill-library/stack/databases/pinecone/SKILL.md +212 -0
- package/template/.agent/skill-library/stack/databases/postgresql/SKILL.md +658 -0
- package/template/.agent/skill-library/stack/databases/qdrant/SKILL.md +312 -0
- package/template/.agent/skill-library/stack/databases/redis/SKILL.md +1079 -0
- package/template/.agent/skill-library/stack/databases/spacetimedb/SKILL.md +532 -0
- package/template/.agent/skill-library/stack/databases/sqlite/SKILL.md +1132 -0
- package/template/.agent/skill-library/stack/databases/supabase/SKILL.md +640 -0
- package/template/.agent/skill-library/stack/databases/surrealdb-expert/SKILL.md +945 -0
- package/template/.agent/skill-library/stack/databases/timescaledb/SKILL.md +745 -0
- package/template/.agent/skill-library/stack/databases/weaviate/SKILL.md +218 -0
- package/template/.agent/skill-library/stack/devops/github-actions/SKILL.md +554 -0
- package/template/.agent/skill-library/stack/devops/kubernetes/SKILL.md +950 -0
- package/template/.agent/skill-library/stack/devops/nginx/SKILL.md +841 -0
- package/template/.agent/skill-library/stack/devops/terraform/SKILL.md +860 -0
- package/template/.agent/skill-library/stack/email/resend/SKILL.md +391 -0
- package/template/.agent/skill-library/stack/engines/godot/SKILL.md +488 -0
- package/template/.agent/skill-library/stack/extensions/chrome-extension/SKILL.md +375 -0
- package/template/.agent/skill-library/stack/extensions/vscode-extension/SKILL.md +453 -0
- package/template/.agent/skill-library/stack/frameworks/astro-framework/SKILL.md +162 -0
- package/template/.agent/skill-library/stack/frameworks/electron/SKILL.md +1286 -0
- package/template/.agent/skill-library/stack/frameworks/fastapi/SKILL.md +650 -0
- package/template/.agent/skill-library/stack/frameworks/hono/SKILL.md +90 -0
- package/template/.agent/skill-library/stack/frameworks/nestjs/SKILL.md +878 -0
- package/template/.agent/skill-library/stack/frameworks/nextjs/SKILL.md +635 -0
- package/template/.agent/skill-library/stack/frameworks/nuxt/SKILL.md +564 -0
- package/template/.agent/skill-library/stack/frameworks/sveltekit/SKILL.md +614 -0
- package/template/.agent/skill-library/stack/frameworks/tauri/SKILL.md +920 -0
- package/template/.agent/skill-library/stack/gamedev/godot/SKILL.md +1032 -0
- package/template/.agent/skill-library/stack/gamedev/unity/SKILL.md +1175 -0
- package/template/.agent/skill-library/stack/hosting/aws/SKILL.md +467 -0
- package/template/.agent/skill-library/stack/hosting/cloudflare/SKILL.md +201 -0
- package/template/.agent/skill-library/stack/hosting/docker-expert/SKILL.md +409 -0
- package/template/.agent/skill-library/stack/hosting/vercel/SKILL.md +484 -0
- package/template/.agent/skill-library/stack/languages/bash-scripting/SKILL.md +773 -0
- package/template/.agent/skill-library/stack/languages/c-cpp/SKILL.md +712 -0
- package/template/.agent/skill-library/stack/languages/gdscript/SKILL.md +789 -0
- package/template/.agent/skill-library/stack/languages/go/SKILL.md +664 -0
- package/template/.agent/skill-library/stack/languages/java/SKILL.md +778 -0
- package/template/.agent/skill-library/stack/languages/kotlin/SKILL.md +665 -0
- package/template/.agent/skill-library/stack/languages/python/SKILL.md +678 -0
- package/template/.agent/skill-library/stack/languages/rust/SKILL.md +673 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/SKILL.md +141 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/advanced-generics.md +90 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/branded-types.md +57 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/builder-pattern.md +71 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/common-pitfalls.md +135 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/conditional-types.md +27 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/decorators.md +98 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/discriminated-unions.md +62 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/mapped-types.md +53 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/performance-best-practices.md +104 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/template-literal-types.md +49 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/testing-types.md +112 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/type-guards.md +70 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/type-inference.md +101 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/utility-types.md +98 -0
- package/template/.agent/skill-library/stack/languages/vanilla-javascript/SKILL.md +803 -0
- package/template/.agent/skill-library/stack/messaging/kafka/SKILL.md +235 -0
- package/template/.agent/skill-library/stack/mobile/expo-react-native/SKILL.md +665 -0
- package/template/.agent/skill-library/stack/mobile/flutter/SKILL.md +316 -0
- package/template/.agent/skill-library/stack/mobile/react-native/SKILL.md +337 -0
- package/template/.agent/skill-library/stack/monitoring/posthog/SKILL.md +396 -0
- package/template/.agent/skill-library/stack/monitoring/sentry/SKILL.md +509 -0
- package/template/.agent/skill-library/stack/observability/datadog/SKILL.md +179 -0
- package/template/.agent/skill-library/stack/observability/distributed-tracing/SKILL.md +140 -0
- package/template/.agent/skill-library/stack/observability/logging-best-practices/SKILL.md +168 -0
- package/template/.agent/skill-library/stack/observability/opentelemetry/SKILL.md +164 -0
- package/template/.agent/skill-library/stack/observability/prometheus-grafana/SKILL.md +246 -0
- package/template/.agent/skill-library/stack/observability/python-observability/SKILL.md +158 -0
- package/template/.agent/skill-library/stack/orm/drizzle-orm/SKILL.md +613 -0
- package/template/.agent/skill-library/stack/orm/prisma/SKILL.md +744 -0
- package/template/.agent/skill-library/stack/payments/lemonsqueezy/SKILL.md +393 -0
- package/template/.agent/skill-library/stack/payments/stripe-integration/SKILL.md +457 -0
- package/template/.agent/skill-library/stack/queue/bullmq/SKILL.md +385 -0
- package/template/.agent/skill-library/stack/queue/inngest/SKILL.md +438 -0
- package/template/.agent/skill-library/stack/realtime/socketio/SKILL.md +595 -0
- package/template/.agent/skill-library/stack/search/elasticsearch/SKILL.md +248 -0
- package/template/.agent/skill-library/stack/search/meilisearch/SKILL.md +385 -0
- package/template/.agent/skill-library/stack/security/crypto-patterns/SKILL.md +437 -0
- package/template/.agent/skill-library/stack/security/csp-cors-headers/SKILL.md +588 -0
- package/template/.agent/skill-library/stack/security/dependency-auditing/SKILL.md +560 -0
- package/template/.agent/skill-library/stack/security/input-sanitization/SKILL.md +430 -0
- package/template/.agent/skill-library/stack/security/owasp-web-security/SKILL.md +421 -0
- package/template/.agent/skill-library/stack/state/tanstack-query/SKILL.md +637 -0
- package/template/.agent/skill-library/stack/state/zustand/SKILL.md +483 -0
- package/template/.agent/skill-library/stack/storage/aws-s3/SKILL.md +415 -0
- package/template/.agent/skill-library/stack/testing/playwright/SKILL.md +641 -0
- package/template/.agent/skill-library/stack/testing/storybook/SKILL.md +923 -0
- package/template/.agent/skill-library/stack/testing/testing-library/SKILL.md +872 -0
- package/template/.agent/skill-library/stack/testing/vitest/SKILL.md +714 -0
- package/template/.agent/skill-library/stack/ui/react-best-practices/SKILL.md +877 -0
- package/template/.agent/skill-library/stack/ui/react-composition-patterns/SKILL.md +1107 -0
- package/template/.agent/skill-library/stack/ui/react-flow/SKILL.md +425 -0
- package/template/.agent/skill-library/stack/ui/shadcn-ui/SKILL.md +703 -0
- package/template/.agent/skill-library/surface/api/api-caching/SKILL.md +458 -0
- package/template/.agent/skill-library/surface/api/api-documentation-openapi/SKILL.md +697 -0
- package/template/.agent/skill-library/surface/api/api-error-handling/SKILL.md +478 -0
- package/template/.agent/skill-library/surface/api/api-security-checklist/SKILL.md +147 -0
- package/template/.agent/skill-library/surface/api/api-versioning/SKILL.md +420 -0
- package/template/.agent/skill-library/surface/api/email-best-practices/SKILL.md +59 -0
- package/template/.agent/skill-library/surface/api/rate-limiting-abuse-protection/SKILL.md +147 -0
- package/template/.agent/skill-library/surface/api/rest-api-design/SKILL.md +478 -0
- package/template/.agent/skill-library/surface/api/webhook-design/SKILL.md +752 -0
- package/template/.agent/skill-library/surface/cli/cli-configuration-management/SKILL.md +445 -0
- package/template/.agent/skill-library/surface/cli/cli-error-diagnostics/SKILL.md +515 -0
- package/template/.agent/skill-library/surface/cli/cli-shell-integration/SKILL.md +479 -0
- package/template/.agent/skill-library/surface/cli/cli-ux-design/SKILL.md +477 -0
- package/template/.agent/skill-library/surface/desktop/desktop-app-distribution/SKILL.md +416 -0
- package/template/.agent/skill-library/surface/desktop/desktop-security-sandboxing/SKILL.md +407 -0
- package/template/.agent/skill-library/surface/desktop/desktop-ux-conventions/SKILL.md +361 -0
- package/template/.agent/skill-library/surface/desktop/native-os-integration/SKILL.md +563 -0
- package/template/.agent/skill-library/surface/extension/browser-extension-patterns/SKILL.md +482 -0
- package/template/.agent/skill-library/surface/extension/plugin-architecture-design/SKILL.md +632 -0
- package/template/.agent/skill-library/surface/extension/vscode-extension-development/SKILL.md +728 -0
- package/template/.agent/skill-library/surface/mobile/app-store-submission/SKILL.md +304 -0
- package/template/.agent/skill-library/surface/mobile/mobile-offline-sync/SKILL.md +443 -0
- package/template/.agent/skill-library/surface/mobile/mobile-responsive-patterns/SKILL.md +432 -0
- package/template/.agent/skill-library/surface/mobile/push-notifications/SKILL.md +495 -0
- package/template/.agent/skill-library/surface/web/accessibility-compliance/SKILL.md +827 -0
- package/template/.agent/skill-library/surface/web/ai-seo/SKILL.md +398 -0
- package/template/.agent/skill-library/surface/web/ai-seo/references/content-patterns.md +285 -0
- package/template/.agent/skill-library/surface/web/ai-seo/references/platform-ranking-factors.md +152 -0
- package/template/.agent/skill-library/surface/web/analytics-tracking/SKILL.md +309 -0
- package/template/.agent/skill-library/surface/web/analytics-tracking/references/event-library.md +260 -0
- package/template/.agent/skill-library/surface/web/analytics-tracking/references/ga4-implementation.md +300 -0
- package/template/.agent/skill-library/surface/web/analytics-tracking/references/gtm-implementation.md +390 -0
- package/template/.agent/skill-library/surface/web/authentication-ui-flows/SKILL.md +530 -0
- package/template/.agent/skill-library/surface/web/dark-mode-theming/SKILL.md +516 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/SKILL.md +105 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/data/charts.csv +26 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/data/colors.csv +97 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/data/landing.csv +31 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/data/styles.csv +59 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/data/typography.csv +58 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/data/ux-guidelines.csv +100 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/scripts/core.py +258 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/scripts/design_system.py +1067 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/scripts/search.py +106 -0
- package/template/.agent/skill-library/surface/web/form-handling-validation/SKILL.md +675 -0
- package/template/.agent/skill-library/surface/web/frontend-design/SKILL.md +1393 -0
- package/template/.agent/skill-library/surface/web/frontend-design/templates/cppn-hero.tsx +299 -0
- package/template/.agent/skill-library/surface/web/frontend-design/templates/wave-hero.tsx +875 -0
- package/template/.agent/skill-library/surface/web/frontend-verification/SKILL.md +111 -0
- package/template/.agent/skill-library/surface/web/frontend-verification/scripts/ux_audit.py +739 -0
- package/template/.agent/skill-library/surface/web/i18n-localization/SKILL.md +154 -0
- package/template/.agent/skill-library/surface/web/offline-first-pwa/SKILL.md +657 -0
- package/template/.agent/skill-library/surface/web/page-cro/SKILL.md +182 -0
- package/template/.agent/skill-library/surface/web/page-cro/references/experiments.md +248 -0
- package/template/.agent/skill-library/surface/web/programmatic-seo/SKILL.md +238 -0
- package/template/.agent/skill-library/surface/web/programmatic-seo/references/playbooks.md +308 -0
- package/template/.agent/skill-library/surface/web/schema-markup/SKILL.md +179 -0
- package/template/.agent/skill-library/surface/web/schema-markup/references/schema-examples.md +398 -0
- package/template/.agent/skill-library/surface/web/seo-audit/SKILL.md +394 -0
- package/template/.agent/skill-library/surface/web/seo-audit/references/ai-writing-detection.md +200 -0
- package/template/.agent/skill-library/surface/web/web-performance-optimization/SKILL.md +646 -0
- package/template/.agent/skill-library/surface/web/web-scraping/SKILL.md +58 -0
- package/template/.agent/skills/accessibility/SKILL.md +522 -0
- package/template/.agent/skills/accessibility/references/WCAG.md +162 -0
- package/template/.agent/skills/adversarial-review/SKILL.md +90 -0
- package/template/.agent/skills/antigravity-workflows/SKILL.md +81 -0
- package/template/.agent/skills/antigravity-workflows/resources/implementation-playbook.md +36 -0
- package/template/.agent/skills/api-design-principles/SKILL.md +37 -0
- package/template/.agent/skills/api-design-principles/assets/api-design-checklist.md +155 -0
- package/template/.agent/skills/api-design-principles/assets/rest-api-template.py +182 -0
- package/template/.agent/skills/api-design-principles/references/graphql-schema-design.md +583 -0
- package/template/.agent/skills/api-design-principles/references/rest-best-practices.md +408 -0
- package/template/.agent/skills/api-design-principles/resources/implementation-playbook.md +513 -0
- package/template/.agent/skills/api-versioning/SKILL.md +420 -0
- package/template/.agent/skills/architecture-mapping/SKILL.md +219 -0
- package/template/.agent/skills/bootstrap-agents/SKILL.md +259 -0
- package/template/.agent/skills/brainstorming/SKILL.md +236 -0
- package/template/.agent/skills/brand-guidelines/SKILL.md +44 -0
- package/template/.agent/skills/clean-code/SKILL.md +94 -0
- package/template/.agent/skills/code-review-pro/SKILL.md +152 -0
- package/template/.agent/skills/concise-planning/SKILL.md +68 -0
- package/template/.agent/skills/cross-layer-consistency/SKILL.md +117 -0
- package/template/.agent/skills/database-schema-design/SKILL.md +429 -0
- package/template/.agent/skills/deployment-procedures/SKILL.md +241 -0
- package/template/.agent/skills/design-anti-cliche/SKILL.md +159 -0
- package/template/.agent/skills/design-direction/SKILL.md +45 -0
- package/template/.agent/skills/error-handling-patterns/SKILL.md +721 -0
- package/template/.agent/skills/find-skills/SKILL.md +145 -0
- package/template/.agent/skills/git-advanced/SKILL.md +972 -0
- package/template/.agent/skills/git-workflow/SKILL.md +420 -0
- package/template/.agent/skills/idea-extraction/SKILL.md +271 -0
- package/template/.agent/skills/logging-best-practices/SKILL.md +851 -0
- package/template/.agent/skills/migration-management/SKILL.md +384 -0
- package/template/.agent/skills/minimalist-surgical-development/SKILL.md +69 -0
- package/template/.agent/skills/parallel-agents/SKILL.md +165 -0
- package/template/.agent/skills/parallel-debugging/SKILL.md +135 -0
- package/template/.agent/skills/parallel-feature-development/SKILL.md +166 -0
- package/template/.agent/skills/performance-budgeting/SKILL.md +144 -0
- package/template/.agent/skills/pipeline-rubrics/SKILL.md +51 -0
- package/template/.agent/skills/pipeline-rubrics/references/architecture-rubric.md +19 -0
- package/template/.agent/skills/pipeline-rubrics/references/be-rubric.md +21 -0
- package/template/.agent/skills/pipeline-rubrics/references/fe-rubric.md +20 -0
- package/template/.agent/skills/pipeline-rubrics/references/ia-rubric.md +19 -0
- package/template/.agent/skills/pipeline-rubrics/references/scoring.md +28 -0
- package/template/.agent/skills/pipeline-rubrics/references/vision-rubric.md +11 -0
- package/template/.agent/skills/prd-templates/SKILL.md +88 -0
- package/template/.agent/skills/prd-templates/references/architecture-design-template.md +88 -0
- package/template/.agent/skills/prd-templates/references/be-spec-template.md +101 -0
- package/template/.agent/skills/prd-templates/references/data-placement-template.md +74 -0
- package/template/.agent/skills/prd-templates/references/decomposition-templates.md +211 -0
- package/template/.agent/skills/prd-templates/references/design-system-decisions.md +198 -0
- package/template/.agent/skills/prd-templates/references/engineering-standards-template.md +124 -0
- package/template/.agent/skills/prd-templates/references/fe-classification-procedures.md +47 -0
- package/template/.agent/skills/prd-templates/references/fe-spec-template.md +84 -0
- package/template/.agent/skills/prd-templates/references/infrastructure-report-template.md +71 -0
- package/template/.agent/skills/prd-templates/references/operational-templates.md +116 -0
- package/template/.agent/skills/prd-templates/references/placeholder-guard-template.md +21 -0
- package/template/.agent/skills/prd-templates/references/surface-model.md +61 -0
- package/template/.agent/skills/prd-templates/references/vision-template.md +66 -0
- package/template/.agent/skills/prompt-engineer/README.md +659 -0
- package/template/.agent/skills/prompt-engineer/SKILL.md +249 -0
- package/template/.agent/skills/regex-patterns/SKILL.md +751 -0
- package/template/.agent/skills/resolve-ambiguity/SKILL.md +278 -0
- package/template/.agent/skills/rest-api-design/SKILL.md +478 -0
- package/template/.agent/skills/security-scanning-security-hardening/SKILL.md +231 -0
- package/template/.agent/skills/session-continuity/SKILL.md +730 -0
- package/template/.agent/skills/session-continuity/protocols/01-session-resumption.md +38 -0
- package/template/.agent/skills/session-continuity/protocols/02-progress-generation.md +85 -0
- package/template/.agent/skills/session-continuity/protocols/03-progress-update.md +70 -0
- package/template/.agent/skills/session-continuity/protocols/04-pattern-extraction.md +60 -0
- package/template/.agent/skills/session-continuity/protocols/05-session-close.md +37 -0
- package/template/.agent/skills/session-continuity/protocols/06-decision-analysis.md +84 -0
- package/template/.agent/skills/session-continuity/protocols/07-spec-pipeline-generation.md +48 -0
- package/template/.agent/skills/session-continuity/protocols/08-spec-pipeline-update.md +43 -0
- package/template/.agent/skills/session-continuity/protocols/09-parallel-claim.md +122 -0
- package/template/.agent/skills/session-continuity/protocols/10-placeholder-verification-gate.md +104 -0
- package/template/.agent/skills/session-continuity/protocols/ambiguity-gates.md +48 -0
- package/template/.agent/skills/skill-creator/LICENSE.txt +202 -0
- package/template/.agent/skills/skill-creator/README.md +270 -0
- package/template/.agent/skills/skill-creator/SKILL.md +590 -0
- package/template/.agent/skills/skill-creator/references/output-patterns.md +82 -0
- package/template/.agent/skills/skill-creator/references/workflows.md +28 -0
- package/template/.agent/skills/skill-creator/scripts/init_skill.py +303 -0
- package/template/.agent/skills/skill-creator/scripts/package_skill.py +110 -0
- package/template/.agent/skills/skill-creator/scripts/quick_validate.py +95 -0
- package/template/.agent/skills/spec-writing/SKILL.md +110 -0
- package/template/.agent/skills/systematic-debugging/CREATION-LOG.md +119 -0
- package/template/.agent/skills/systematic-debugging/SKILL.md +297 -0
- package/template/.agent/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
- package/template/.agent/skills/systematic-debugging/condition-based-waiting.md +115 -0
- package/template/.agent/skills/systematic-debugging/defense-in-depth.md +122 -0
- package/template/.agent/skills/systematic-debugging/find-polluter.sh +63 -0
- package/template/.agent/skills/systematic-debugging/root-cause-tracing.md +169 -0
- package/template/.agent/skills/systematic-debugging/test-academic.md +14 -0
- package/template/.agent/skills/systematic-debugging/test-pressure-1.md +58 -0
- package/template/.agent/skills/systematic-debugging/test-pressure-2.md +68 -0
- package/template/.agent/skills/systematic-debugging/test-pressure-3.md +69 -0
- package/template/.agent/skills/tdd-workflow/SKILL.md +409 -0
- package/template/.agent/skills/tech-stack-catalog/SKILL.md +49 -0
- package/template/.agent/skills/tech-stack-catalog/references/constraint-questions.md +21 -0
- package/template/.agent/skills/tech-stack-catalog/references/dev-tooling-decisions.md +37 -0
- package/template/.agent/skills/tech-stack-catalog/references/surface-decision-tables.md +69 -0
- package/template/.agent/skills/technical-writer/SKILL.md +242 -0
- package/template/.agent/skills/testing-strategist/SKILL.md +932 -0
- package/template/.agent/skills/verification-before-completion/SKILL.md +145 -0
- package/template/.agent/skills/workflow-automation/SKILL.md +73 -0
- package/template/.agent/workflows/audit-ambiguity-execute.md +165 -0
- package/template/.agent/workflows/audit-ambiguity-rubrics.md +83 -0
- package/template/.agent/workflows/audit-ambiguity.md +64 -0
- package/template/.agent/workflows/bootstrap-agents-fill.md +201 -0
- package/template/.agent/workflows/bootstrap-agents-provision.md +197 -0
- package/template/.agent/workflows/bootstrap-agents.md +66 -0
- package/template/.agent/workflows/create-prd-architecture.md +119 -0
- package/template/.agent/workflows/create-prd-compile.md +138 -0
- package/template/.agent/workflows/create-prd-design-system.md +135 -0
- package/template/.agent/workflows/create-prd-security.md +113 -0
- package/template/.agent/workflows/create-prd-stack.md +91 -0
- package/template/.agent/workflows/create-prd.md +168 -0
- package/template/.agent/workflows/decompose-architecture-structure.md +82 -0
- package/template/.agent/workflows/decompose-architecture-validate.md +119 -0
- package/template/.agent/workflows/decompose-architecture.md +111 -0
- package/template/.agent/workflows/evolve-contract.md +98 -0
- package/template/.agent/workflows/evolve-feature-cascade.md +140 -0
- package/template/.agent/workflows/evolve-feature-classify.md +116 -0
- package/template/.agent/workflows/evolve-feature.md +56 -0
- package/template/.agent/workflows/ideate-discover.md +144 -0
- package/template/.agent/workflows/ideate-extract.md +129 -0
- package/template/.agent/workflows/ideate-validate.md +117 -0
- package/template/.agent/workflows/ideate.md +113 -0
- package/template/.agent/workflows/implement-slice-setup.md +113 -0
- package/template/.agent/workflows/implement-slice-tdd.md +198 -0
- package/template/.agent/workflows/implement-slice.md +50 -0
- package/template/.agent/workflows/plan-phase.md +202 -0
- package/template/.agent/workflows/propagate-decision-apply.md +135 -0
- package/template/.agent/workflows/propagate-decision-scan.md +147 -0
- package/template/.agent/workflows/propagate-decision.md +56 -0
- package/template/.agent/workflows/remediate-pipeline-assess.md +138 -0
- package/template/.agent/workflows/remediate-pipeline-execute.md +135 -0
- package/template/.agent/workflows/remediate-pipeline.md +55 -0
- package/template/.agent/workflows/resolve-ambiguity.md +82 -0
- package/template/.agent/workflows/sync-kit.md +209 -0
- package/template/.agent/workflows/update-architecture-map.md +74 -0
- package/template/.agent/workflows/validate-phase.md +219 -0
- package/template/.agent/workflows/verify-infrastructure.md +207 -0
- package/template/.agent/workflows/write-architecture-spec-deepen.md +139 -0
- package/template/.agent/workflows/write-architecture-spec-design.md +202 -0
- package/template/.agent/workflows/write-architecture-spec.md +63 -0
- package/template/.agent/workflows/write-be-spec-classify.md +165 -0
- package/template/.agent/workflows/write-be-spec-write.md +98 -0
- package/template/.agent/workflows/write-be-spec.md +76 -0
- package/template/.agent/workflows/write-fe-spec-classify.md +170 -0
- package/template/.agent/workflows/write-fe-spec-write.md +94 -0
- package/template/.agent/workflows/write-fe-spec.md +71 -0
- package/template/AGENTS.md +176 -0
- package/template/GEMINI.md +177 -0
- package/template/docs/README.md +187 -0
- package/template/docs/audits/.gitkeep +0 -0
- package/template/docs/audits/README.md +10 -0
- package/template/docs/plans/.gitkeep +0 -0
- package/template/docs/plans/README.md +21 -0
- package/template/docs/plans/be/.gitkeep +0 -0
- package/template/docs/plans/be/README.md +11 -0
- package/template/docs/plans/fe/.gitkeep +0 -0
- package/template/docs/plans/fe/README.md +11 -0
- package/template/docs/plans/ia/.gitkeep +0 -0
- package/template/docs/plans/ia/README.md +17 -0
- package/template/docs/plans/ia/deep-dives/.gitkeep +0 -0
- package/template/docs/plans/ia/deep-dives/README.md +5 -0
- package/template/docs/plans/phases/.gitkeep +0 -0
- package/template/docs/plans/phases/README.md +11 -0
|
@@ -0,0 +1,1132 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sqlite
|
|
3
|
+
description: "Expert SQLite development guide covering when to use SQLite, WAL mode, PRAGMA tuning, full-text search (FTS5), JSON support, window functions, CTEs, better-sqlite3 (Node.js), LMDB comparison, Turso/libSQL for distributed SQLite, Litestream for replication, database-per-tenant patterns, testing with in-memory SQLite, file locking, backup strategies, and migration patterns."
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# SQLite Expert
|
|
8
|
+
|
|
9
|
+
## 1. When to Use SQLite
|
|
10
|
+
|
|
11
|
+
### Ideal Use Cases
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
Edge computing -- Embedded in IoT devices, Cloudflare Workers (D1), edge functions
|
|
15
|
+
Embedded apps -- Desktop apps (Electron), mobile apps (iOS, Android)
|
|
16
|
+
CLI tools -- Local data storage for command-line applications
|
|
17
|
+
Testing -- In-memory databases as test doubles for PostgreSQL/MySQL
|
|
18
|
+
Single-server apps -- Web apps with moderate traffic on a single server
|
|
19
|
+
Prototyping -- Rapid development before migrating to a client-server DB
|
|
20
|
+
Data analysis -- Ad-hoc queries on local datasets, CSV/JSON imports
|
|
21
|
+
Configuration stores -- Application settings, feature flags, local state
|
|
22
|
+
Personal projects -- Blogs, small SaaS, internal tools with < 100K daily requests
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### When NOT to Use SQLite
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
High write concurrency -- Many concurrent writers (SQLite serializes writes)
|
|
29
|
+
Multi-server deployments -- Multiple app servers writing to the same database
|
|
30
|
+
Very large datasets -- Databases approaching 281 TB limit (practical limit is lower)
|
|
31
|
+
Client-server needs -- When multiple applications must access the same database over a network
|
|
32
|
+
Real-time replication -- Built-in replication does not exist (use Turso/Litestream as workarounds)
|
|
33
|
+
Heavy analytics -- OLAP workloads with full table scans on 100M+ rows
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Capacity Guidelines
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
Max database size: 281 TB (theoretical), practical limit depends on filesystem
|
|
40
|
+
Max row size: 1 GB (default page size limits practical row size)
|
|
41
|
+
Max columns per table: 2000
|
|
42
|
+
Concurrent readers: Unlimited (with WAL mode)
|
|
43
|
+
Concurrent writers: 1 at a time (writes are serialized)
|
|
44
|
+
Typical sweet spot: < 1 TB database, < 100K writes/day, unlimited reads
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 2. WAL Mode
|
|
50
|
+
|
|
51
|
+
Write-Ahead Logging mode is essential for production SQLite. It allows concurrent reads during writes and improves performance significantly.
|
|
52
|
+
|
|
53
|
+
```sql
|
|
54
|
+
-- Enable WAL mode (do this once, it persists across connections)
|
|
55
|
+
PRAGMA journal_mode = WAL;
|
|
56
|
+
|
|
57
|
+
-- Check current journal mode
|
|
58
|
+
PRAGMA journal_mode;
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### How WAL Works
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
Default (DELETE) mode:
|
|
65
|
+
- Writes lock the entire database
|
|
66
|
+
- Readers blocked during writes
|
|
67
|
+
- Safe but slow
|
|
68
|
+
|
|
69
|
+
WAL mode:
|
|
70
|
+
- Writes go to a separate WAL file
|
|
71
|
+
- Readers see the last committed state (snapshot isolation)
|
|
72
|
+
- Multiple readers can run concurrently with one writer
|
|
73
|
+
- WAL file is periodically checkpointed (merged back into main DB)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### WAL Configuration
|
|
77
|
+
|
|
78
|
+
```sql
|
|
79
|
+
-- Auto-checkpoint threshold (pages, default 1000)
|
|
80
|
+
-- Set to 0 to disable auto-checkpoint (manual control)
|
|
81
|
+
PRAGMA wal_autocheckpoint = 1000;
|
|
82
|
+
|
|
83
|
+
-- Manual checkpoint
|
|
84
|
+
PRAGMA wal_checkpoint(TRUNCATE); -- Checkpoint and truncate WAL file
|
|
85
|
+
PRAGMA wal_checkpoint(PASSIVE); -- Checkpoint without blocking
|
|
86
|
+
PRAGMA wal_checkpoint(FULL); -- Checkpoint, block new writers until done
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### WAL Caveats
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
- WAL file can grow large if checkpoints are infrequent or long transactions hold a snapshot
|
|
93
|
+
- WAL mode does not work over network filesystems (NFS, SMB)
|
|
94
|
+
- WAL mode creates two extra files: .db-wal and .db-shm
|
|
95
|
+
- All three files (.db, .db-wal, .db-shm) must be backed up together
|
|
96
|
+
- WAL mode is slightly slower for write-heavy workloads with no concurrent reads
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## 3. PRAGMA Settings for Performance
|
|
102
|
+
|
|
103
|
+
```sql
|
|
104
|
+
-- Essential production PRAGMAs (run at connection open)
|
|
105
|
+
PRAGMA journal_mode = WAL; -- Write-ahead logging
|
|
106
|
+
PRAGMA synchronous = NORMAL; -- Balance between safety and speed
|
|
107
|
+
PRAGMA cache_size = -64000; -- 64 MB page cache (negative = KB)
|
|
108
|
+
PRAGMA foreign_keys = ON; -- Enforce foreign key constraints
|
|
109
|
+
PRAGMA busy_timeout = 5000; -- Wait 5 seconds for locks instead of failing immediately
|
|
110
|
+
PRAGMA temp_store = MEMORY; -- Store temp tables in memory
|
|
111
|
+
PRAGMA mmap_io = 268435456; -- Memory-map up to 256 MB of the database file
|
|
112
|
+
|
|
113
|
+
-- Analysis and optimization
|
|
114
|
+
PRAGMA optimize; -- Run on connection close (SQLite 3.18+)
|
|
115
|
+
ANALYZE; -- Update query planner statistics
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### PRAGMA Reference
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
journal_mode:
|
|
122
|
+
DELETE -- Default. Delete journal after commit. Safest, slowest.
|
|
123
|
+
WAL -- Write-ahead log. Best for concurrent reads. Recommended.
|
|
124
|
+
MEMORY -- Journal in memory. Fast but not crash-safe.
|
|
125
|
+
OFF -- No journal. Fastest, no crash recovery. Testing only.
|
|
126
|
+
|
|
127
|
+
synchronous:
|
|
128
|
+
FULL -- Sync after every write. Slowest, safest.
|
|
129
|
+
NORMAL -- Sync at critical moments. Good balance. Recommended with WAL.
|
|
130
|
+
OFF -- No syncing. Fastest. Data loss on power failure.
|
|
131
|
+
|
|
132
|
+
cache_size:
|
|
133
|
+
Positive -- Number of pages (default page size 4096 bytes)
|
|
134
|
+
Negative -- Size in KB (e.g., -64000 = 64 MB)
|
|
135
|
+
|
|
136
|
+
busy_timeout:
|
|
137
|
+
0 -- Return SQLITE_BUSY immediately (default)
|
|
138
|
+
N -- Wait N milliseconds for the lock before returning SQLITE_BUSY
|
|
139
|
+
|
|
140
|
+
temp_store:
|
|
141
|
+
DEFAULT -- Use compile-time default
|
|
142
|
+
FILE -- Temp data on disk
|
|
143
|
+
MEMORY -- Temp data in memory (faster for temp tables and sorting)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## 4. Full-Text Search (FTS5)
|
|
149
|
+
|
|
150
|
+
```sql
|
|
151
|
+
-- Create an FTS5 virtual table
|
|
152
|
+
CREATE VIRTUAL TABLE articles_fts USING fts5(
|
|
153
|
+
title,
|
|
154
|
+
body,
|
|
155
|
+
tags,
|
|
156
|
+
content='articles', -- Content table (external content FTS)
|
|
157
|
+
content_rowid='id', -- Map to the real table's rowid
|
|
158
|
+
tokenize='porter unicode61' -- Porter stemmer + Unicode tokenizer
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
-- Populate FTS index from existing data
|
|
162
|
+
INSERT INTO articles_fts(articles_fts) VALUES('rebuild');
|
|
163
|
+
|
|
164
|
+
-- Keep FTS in sync with triggers
|
|
165
|
+
CREATE TRIGGER articles_ai AFTER INSERT ON articles BEGIN
|
|
166
|
+
INSERT INTO articles_fts(rowid, title, body, tags)
|
|
167
|
+
VALUES (new.id, new.title, new.body, new.tags);
|
|
168
|
+
END;
|
|
169
|
+
|
|
170
|
+
CREATE TRIGGER articles_ad AFTER DELETE ON articles BEGIN
|
|
171
|
+
INSERT INTO articles_fts(articles_fts, rowid, title, body, tags)
|
|
172
|
+
VALUES ('delete', old.id, old.title, old.body, old.tags);
|
|
173
|
+
END;
|
|
174
|
+
|
|
175
|
+
CREATE TRIGGER articles_au AFTER UPDATE ON articles BEGIN
|
|
176
|
+
INSERT INTO articles_fts(articles_fts, rowid, title, body, tags)
|
|
177
|
+
VALUES ('delete', old.id, old.title, old.body, old.tags);
|
|
178
|
+
INSERT INTO articles_fts(rowid, title, body, tags)
|
|
179
|
+
VALUES (new.id, new.title, new.body, new.tags);
|
|
180
|
+
END;
|
|
181
|
+
|
|
182
|
+
-- Search queries
|
|
183
|
+
SELECT a.*, rank
|
|
184
|
+
FROM articles_fts
|
|
185
|
+
JOIN articles a ON a.id = articles_fts.rowid
|
|
186
|
+
WHERE articles_fts MATCH 'sqlite AND performance'
|
|
187
|
+
ORDER BY rank;
|
|
188
|
+
|
|
189
|
+
-- Phrase search
|
|
190
|
+
SELECT * FROM articles_fts WHERE articles_fts MATCH '"full text search"';
|
|
191
|
+
|
|
192
|
+
-- Column-specific search
|
|
193
|
+
SELECT * FROM articles_fts WHERE articles_fts MATCH 'title:sqlite OR body:optimization';
|
|
194
|
+
|
|
195
|
+
-- Prefix search
|
|
196
|
+
SELECT * FROM articles_fts WHERE articles_fts MATCH 'optim*';
|
|
197
|
+
|
|
198
|
+
-- NEAR operator
|
|
199
|
+
SELECT * FROM articles_fts WHERE articles_fts MATCH 'NEAR(sqlite performance, 5)';
|
|
200
|
+
|
|
201
|
+
-- BM25 ranking (built into FTS5)
|
|
202
|
+
SELECT *, bm25(articles_fts, 10.0, 1.0, 5.0) AS score
|
|
203
|
+
FROM articles_fts
|
|
204
|
+
WHERE articles_fts MATCH 'database optimization'
|
|
205
|
+
ORDER BY score;
|
|
206
|
+
-- Weights: title=10, body=1, tags=5
|
|
207
|
+
|
|
208
|
+
-- Highlight snippets
|
|
209
|
+
SELECT highlight(articles_fts, 1, '<mark>', '</mark>') AS snippet
|
|
210
|
+
FROM articles_fts
|
|
211
|
+
WHERE articles_fts MATCH 'sqlite';
|
|
212
|
+
|
|
213
|
+
-- Snippet with context
|
|
214
|
+
SELECT snippet(articles_fts, 1, '<mark>', '</mark>', '...', 20) AS snippet
|
|
215
|
+
FROM articles_fts
|
|
216
|
+
WHERE articles_fts MATCH 'sqlite';
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## 5. JSON Support
|
|
222
|
+
|
|
223
|
+
```sql
|
|
224
|
+
-- SQLite has built-in JSON functions (3.9+) and JSON operators (3.38+)
|
|
225
|
+
|
|
226
|
+
-- Create a table with JSON data
|
|
227
|
+
CREATE TABLE products (
|
|
228
|
+
id INTEGER PRIMARY KEY,
|
|
229
|
+
name TEXT NOT NULL,
|
|
230
|
+
data JSON NOT NULL DEFAULT '{}'
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
-- Insert JSON data
|
|
234
|
+
INSERT INTO products (name, data) VALUES (
|
|
235
|
+
'Widget',
|
|
236
|
+
'{"price": 29.99, "specs": {"weight": 1.5, "color": "blue"}, "tags": ["sale", "featured"]}'
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
-- Extract values with json_extract()
|
|
240
|
+
SELECT
|
|
241
|
+
name,
|
|
242
|
+
json_extract(data, '$.price') AS price,
|
|
243
|
+
json_extract(data, '$.specs.weight') AS weight,
|
|
244
|
+
json_extract(data, '$.specs.color') AS color
|
|
245
|
+
FROM products;
|
|
246
|
+
|
|
247
|
+
-- Arrow operators (3.38+)
|
|
248
|
+
SELECT
|
|
249
|
+
name,
|
|
250
|
+
data ->> '$.price' AS price, -- Extract as SQL value (text, int, float)
|
|
251
|
+
data -> '$.specs' AS specs -- Extract as JSON string
|
|
252
|
+
FROM products;
|
|
253
|
+
|
|
254
|
+
-- Query JSON arrays
|
|
255
|
+
SELECT * FROM products
|
|
256
|
+
WHERE EXISTS (
|
|
257
|
+
SELECT 1 FROM json_each(json_extract(data, '$.tags'))
|
|
258
|
+
WHERE value = 'sale'
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
-- json_each: expand JSON array to rows
|
|
262
|
+
SELECT p.name, tag.value AS tag
|
|
263
|
+
FROM products p, json_each(json_extract(p.data, '$.tags')) tag;
|
|
264
|
+
|
|
265
|
+
-- json_tree: recursively expand JSON
|
|
266
|
+
SELECT * FROM json_tree('{"a": {"b": [1, 2, 3]}}');
|
|
267
|
+
|
|
268
|
+
-- Modify JSON
|
|
269
|
+
UPDATE products
|
|
270
|
+
SET data = json_set(data, '$.price', 24.99)
|
|
271
|
+
WHERE id = 1;
|
|
272
|
+
|
|
273
|
+
UPDATE products
|
|
274
|
+
SET data = json_insert(data, '$.inStock', true)
|
|
275
|
+
WHERE id = 1;
|
|
276
|
+
|
|
277
|
+
UPDATE products
|
|
278
|
+
SET data = json_remove(data, '$.tags[0]')
|
|
279
|
+
WHERE id = 1;
|
|
280
|
+
|
|
281
|
+
-- JSON aggregation
|
|
282
|
+
SELECT json_group_array(name) AS names FROM products;
|
|
283
|
+
SELECT json_group_object(name, json_extract(data, '$.price')) AS price_map FROM products;
|
|
284
|
+
|
|
285
|
+
-- Index on JSON value (generated column approach)
|
|
286
|
+
ALTER TABLE products ADD COLUMN price_indexed REAL
|
|
287
|
+
GENERATED ALWAYS AS (json_extract(data, '$.price')) STORED;
|
|
288
|
+
CREATE INDEX idx_products_price ON products (price_indexed);
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## 6. Window Functions
|
|
294
|
+
|
|
295
|
+
```sql
|
|
296
|
+
-- ROW_NUMBER, RANK, DENSE_RANK
|
|
297
|
+
SELECT
|
|
298
|
+
name,
|
|
299
|
+
category,
|
|
300
|
+
price,
|
|
301
|
+
ROW_NUMBER() OVER (ORDER BY price DESC) AS row_num,
|
|
302
|
+
RANK() OVER (ORDER BY price DESC) AS rank,
|
|
303
|
+
DENSE_RANK() OVER (ORDER BY price DESC) AS dense_rank
|
|
304
|
+
FROM products;
|
|
305
|
+
|
|
306
|
+
-- Partitioned ranking
|
|
307
|
+
SELECT
|
|
308
|
+
name,
|
|
309
|
+
category,
|
|
310
|
+
price,
|
|
311
|
+
ROW_NUMBER() OVER (PARTITION BY category ORDER BY price DESC) AS rank_in_category
|
|
312
|
+
FROM products;
|
|
313
|
+
|
|
314
|
+
-- Running totals
|
|
315
|
+
SELECT
|
|
316
|
+
date,
|
|
317
|
+
revenue,
|
|
318
|
+
SUM(revenue) OVER (ORDER BY date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running_total
|
|
319
|
+
FROM daily_revenue;
|
|
320
|
+
|
|
321
|
+
-- Moving average
|
|
322
|
+
SELECT
|
|
323
|
+
date,
|
|
324
|
+
revenue,
|
|
325
|
+
AVG(revenue) OVER (ORDER BY date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS avg_7day
|
|
326
|
+
FROM daily_revenue;
|
|
327
|
+
|
|
328
|
+
-- LAG and LEAD (previous/next row values)
|
|
329
|
+
SELECT
|
|
330
|
+
date,
|
|
331
|
+
revenue,
|
|
332
|
+
LAG(revenue, 1) OVER (ORDER BY date) AS prev_day_revenue,
|
|
333
|
+
LEAD(revenue, 1) OVER (ORDER BY date) AS next_day_revenue,
|
|
334
|
+
revenue - LAG(revenue, 1) OVER (ORDER BY date) AS day_over_day_change
|
|
335
|
+
FROM daily_revenue;
|
|
336
|
+
|
|
337
|
+
-- FIRST_VALUE and LAST_VALUE
|
|
338
|
+
SELECT
|
|
339
|
+
name,
|
|
340
|
+
category,
|
|
341
|
+
price,
|
|
342
|
+
FIRST_VALUE(name) OVER (PARTITION BY category ORDER BY price DESC) AS most_expensive,
|
|
343
|
+
price * 1.0 / FIRST_VALUE(price) OVER (PARTITION BY category ORDER BY price DESC) AS pct_of_max
|
|
344
|
+
FROM products;
|
|
345
|
+
|
|
346
|
+
-- NTILE (divide into buckets)
|
|
347
|
+
SELECT
|
|
348
|
+
name,
|
|
349
|
+
price,
|
|
350
|
+
NTILE(4) OVER (ORDER BY price) AS price_quartile
|
|
351
|
+
FROM products;
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
---
|
|
355
|
+
|
|
356
|
+
## 7. CTEs (Common Table Expressions)
|
|
357
|
+
|
|
358
|
+
```sql
|
|
359
|
+
-- Basic CTE for readability
|
|
360
|
+
WITH active_users AS (
|
|
361
|
+
SELECT id, name, email
|
|
362
|
+
FROM users
|
|
363
|
+
WHERE active = 1
|
|
364
|
+
),
|
|
365
|
+
recent_orders AS (
|
|
366
|
+
SELECT user_id, COUNT(*) AS order_count, SUM(total) AS total_spent
|
|
367
|
+
FROM orders
|
|
368
|
+
WHERE created_at > datetime('now', '-30 days')
|
|
369
|
+
GROUP BY user_id
|
|
370
|
+
)
|
|
371
|
+
SELECT
|
|
372
|
+
au.name,
|
|
373
|
+
au.email,
|
|
374
|
+
COALESCE(ro.order_count, 0) AS orders,
|
|
375
|
+
COALESCE(ro.total_spent, 0) AS spent
|
|
376
|
+
FROM active_users au
|
|
377
|
+
LEFT JOIN recent_orders ro ON ro.user_id = au.id
|
|
378
|
+
ORDER BY spent DESC;
|
|
379
|
+
|
|
380
|
+
-- Recursive CTE: hierarchical data (org chart, categories, threads)
|
|
381
|
+
WITH RECURSIVE category_tree AS (
|
|
382
|
+
-- Base case: root categories
|
|
383
|
+
SELECT id, name, parent_id, 0 AS depth, name AS path
|
|
384
|
+
FROM categories
|
|
385
|
+
WHERE parent_id IS NULL
|
|
386
|
+
|
|
387
|
+
UNION ALL
|
|
388
|
+
|
|
389
|
+
-- Recursive case: child categories
|
|
390
|
+
SELECT c.id, c.name, c.parent_id, ct.depth + 1, ct.path || ' > ' || c.name
|
|
391
|
+
FROM categories c
|
|
392
|
+
JOIN category_tree ct ON c.parent_id = ct.id
|
|
393
|
+
)
|
|
394
|
+
SELECT * FROM category_tree ORDER BY path;
|
|
395
|
+
|
|
396
|
+
-- Recursive CTE: generate a date series
|
|
397
|
+
WITH RECURSIVE dates AS (
|
|
398
|
+
SELECT date('2025-01-01') AS d
|
|
399
|
+
UNION ALL
|
|
400
|
+
SELECT date(d, '+1 day')
|
|
401
|
+
FROM dates
|
|
402
|
+
WHERE d < date('2025-01-31')
|
|
403
|
+
)
|
|
404
|
+
SELECT d AS date, COALESCE(r.revenue, 0) AS revenue
|
|
405
|
+
FROM dates
|
|
406
|
+
LEFT JOIN daily_revenue r ON r.date = dates.d;
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
---
|
|
410
|
+
|
|
411
|
+
## 8. better-sqlite3 (Node.js)
|
|
412
|
+
|
|
413
|
+
### Setup and Configuration
|
|
414
|
+
|
|
415
|
+
```typescript
|
|
416
|
+
import Database from "better-sqlite3";
|
|
417
|
+
import path from "path";
|
|
418
|
+
|
|
419
|
+
const DB_PATH = path.resolve(process.env.DB_PATH || "./data/app.db");
|
|
420
|
+
|
|
421
|
+
// Open database with recommended options
|
|
422
|
+
const db = new Database(DB_PATH, {
|
|
423
|
+
verbose: process.env.NODE_ENV === "development" ? console.log : undefined,
|
|
424
|
+
fileMustExist: false, // Create if not exists
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
// Apply production PRAGMAs
|
|
428
|
+
db.pragma("journal_mode = WAL");
|
|
429
|
+
db.pragma("synchronous = NORMAL");
|
|
430
|
+
db.pragma("cache_size = -64000");
|
|
431
|
+
db.pragma("foreign_keys = ON");
|
|
432
|
+
db.pragma("busy_timeout = 5000");
|
|
433
|
+
db.pragma("temp_store = MEMORY");
|
|
434
|
+
|
|
435
|
+
// Enable optimize on close
|
|
436
|
+
process.on("exit", () => {
|
|
437
|
+
db.pragma("optimize");
|
|
438
|
+
db.close();
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
// Graceful shutdown
|
|
442
|
+
process.on("SIGINT", () => process.exit(0));
|
|
443
|
+
process.on("SIGTERM", () => process.exit(0));
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
### CRUD Operations
|
|
447
|
+
|
|
448
|
+
```typescript
|
|
449
|
+
// Prepared statements (compiled once, executed many times -- much faster)
|
|
450
|
+
const insertUser = db.prepare(`
|
|
451
|
+
INSERT INTO users (name, email, created_at)
|
|
452
|
+
VALUES (@name, @email, datetime('now'))
|
|
453
|
+
`);
|
|
454
|
+
|
|
455
|
+
const getUserById = db.prepare(`
|
|
456
|
+
SELECT * FROM users WHERE id = ?
|
|
457
|
+
`);
|
|
458
|
+
|
|
459
|
+
const getUsersByStatus = db.prepare(`
|
|
460
|
+
SELECT * FROM users WHERE active = ? ORDER BY created_at DESC LIMIT ?
|
|
461
|
+
`);
|
|
462
|
+
|
|
463
|
+
const updateUser = db.prepare(`
|
|
464
|
+
UPDATE users SET name = @name, email = @email WHERE id = @id
|
|
465
|
+
`);
|
|
466
|
+
|
|
467
|
+
const deleteUser = db.prepare(`
|
|
468
|
+
DELETE FROM users WHERE id = ?
|
|
469
|
+
`);
|
|
470
|
+
|
|
471
|
+
// Execute
|
|
472
|
+
const result = insertUser.run({ name: "Alice", email: "alice@example.com" });
|
|
473
|
+
console.log("Inserted ID:", result.lastInsertRowid);
|
|
474
|
+
console.log("Changes:", result.changes);
|
|
475
|
+
|
|
476
|
+
const user = getUserById.get(1); // Returns a single row or undefined
|
|
477
|
+
const users = getUsersByStatus.all(1, 20); // Returns array of rows
|
|
478
|
+
|
|
479
|
+
updateUser.run({ id: 1, name: "Alice Smith", email: "alice@example.com" });
|
|
480
|
+
deleteUser.run(1);
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
### Transactions
|
|
484
|
+
|
|
485
|
+
```typescript
|
|
486
|
+
// Transactions in better-sqlite3 are synchronous and fast
|
|
487
|
+
const transferFunds = db.transaction((fromId: number, toId: number, amount: number) => {
|
|
488
|
+
const from = db.prepare("SELECT balance FROM accounts WHERE id = ?").get(fromId);
|
|
489
|
+
if (!from || from.balance < amount) {
|
|
490
|
+
throw new Error("Insufficient funds");
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
db.prepare("UPDATE accounts SET balance = balance - ? WHERE id = ?").run(amount, fromId);
|
|
494
|
+
db.prepare("UPDATE accounts SET balance = balance + ? WHERE id = ?").run(amount, toId);
|
|
495
|
+
|
|
496
|
+
return { fromId, toId, amount };
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
// Transactions automatically rollback on error
|
|
500
|
+
try {
|
|
501
|
+
const result = transferFunds(1, 2, 100);
|
|
502
|
+
console.log("Transfer complete:", result);
|
|
503
|
+
} catch (err) {
|
|
504
|
+
console.error("Transfer failed:", err.message);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Bulk insert (wrap in transaction for massive speedup)
|
|
508
|
+
const insertMany = db.transaction((items: { name: string; value: number }[]) => {
|
|
509
|
+
const stmt = db.prepare("INSERT INTO items (name, value) VALUES (@name, @value)");
|
|
510
|
+
for (const item of items) {
|
|
511
|
+
stmt.run(item);
|
|
512
|
+
}
|
|
513
|
+
return items.length;
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
// Without transaction: ~50 inserts/second
|
|
517
|
+
// With transaction: ~50,000 inserts/second
|
|
518
|
+
const count = insertMany(largeArray);
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
### Custom Functions
|
|
522
|
+
|
|
523
|
+
```typescript
|
|
524
|
+
// Register custom SQL functions
|
|
525
|
+
db.function("generate_slug", (text: string) => {
|
|
526
|
+
return text
|
|
527
|
+
.toLowerCase()
|
|
528
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
529
|
+
.replace(/(^-|-$)/g, "");
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
// Use in queries
|
|
533
|
+
db.prepare("INSERT INTO posts (title, slug) VALUES (?, generate_slug(?))").run(title, title);
|
|
534
|
+
|
|
535
|
+
// Aggregate function
|
|
536
|
+
db.aggregate("median", {
|
|
537
|
+
start: () => [] as number[],
|
|
538
|
+
step: (acc: number[], value: number) => {
|
|
539
|
+
acc.push(value);
|
|
540
|
+
},
|
|
541
|
+
result: (acc: number[]) => {
|
|
542
|
+
acc.sort((a, b) => a - b);
|
|
543
|
+
const mid = Math.floor(acc.length / 2);
|
|
544
|
+
return acc.length % 2 === 0 ? (acc[mid - 1] + acc[mid]) / 2 : acc[mid];
|
|
545
|
+
},
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
const result = db.prepare("SELECT median(price) AS median_price FROM products").get();
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
---
|
|
552
|
+
|
|
553
|
+
## 9. Turso / libSQL (Distributed SQLite)
|
|
554
|
+
|
|
555
|
+
### What It Is
|
|
556
|
+
|
|
557
|
+
```
|
|
558
|
+
Turso is a hosted SQLite-compatible database built on libSQL (a fork of SQLite):
|
|
559
|
+
- Edge deployment: database replicas close to users worldwide
|
|
560
|
+
- Embedded replicas: sync a local SQLite copy for zero-latency reads
|
|
561
|
+
- HTTP API: access from serverless environments (no persistent connections needed)
|
|
562
|
+
- SQLite compatibility: use standard SQLite syntax and tools
|
|
563
|
+
- Multi-tenancy: one database per tenant, or shared database with RLS
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
### Client Usage
|
|
567
|
+
|
|
568
|
+
```typescript
|
|
569
|
+
import { createClient } from "@libsql/client";
|
|
570
|
+
|
|
571
|
+
// Remote connection (Turso hosted)
|
|
572
|
+
const db = createClient({
|
|
573
|
+
url: process.env.TURSO_DATABASE_URL, // libsql://mydb-myorg.turso.io
|
|
574
|
+
authToken: process.env.TURSO_AUTH_TOKEN,
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
// Embedded replica (local copy with remote sync)
|
|
578
|
+
const db = createClient({
|
|
579
|
+
url: "file:./local-replica.db",
|
|
580
|
+
syncUrl: process.env.TURSO_DATABASE_URL,
|
|
581
|
+
authToken: process.env.TURSO_AUTH_TOKEN,
|
|
582
|
+
syncInterval: 60, // Sync every 60 seconds
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
// Queries
|
|
586
|
+
const result = await db.execute("SELECT * FROM users WHERE active = ?", [true]);
|
|
587
|
+
console.log(result.rows);
|
|
588
|
+
|
|
589
|
+
// Batch (transaction)
|
|
590
|
+
await db.batch([
|
|
591
|
+
{ sql: "INSERT INTO users (name, email) VALUES (?, ?)", args: ["Alice", "alice@example.com"] },
|
|
592
|
+
{ sql: "INSERT INTO audit_log (action, user_email) VALUES (?, ?)", args: ["user_created", "alice@example.com"] },
|
|
593
|
+
], "write");
|
|
594
|
+
|
|
595
|
+
// Interactive transaction
|
|
596
|
+
const tx = await db.transaction("write");
|
|
597
|
+
try {
|
|
598
|
+
await tx.execute("UPDATE accounts SET balance = balance - ? WHERE id = ?", [100, fromId]);
|
|
599
|
+
await tx.execute("UPDATE accounts SET balance = balance + ? WHERE id = ?", [100, toId]);
|
|
600
|
+
await tx.commit();
|
|
601
|
+
} catch {
|
|
602
|
+
await tx.rollback();
|
|
603
|
+
}
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
---
|
|
607
|
+
|
|
608
|
+
## 10. Litestream (Continuous Replication)
|
|
609
|
+
|
|
610
|
+
### What It Is
|
|
611
|
+
|
|
612
|
+
```
|
|
613
|
+
Litestream continuously replicates a SQLite database to S3-compatible storage:
|
|
614
|
+
- Streaming WAL replication (near real-time, sub-second lag)
|
|
615
|
+
- Point-in-time recovery
|
|
616
|
+
- No changes to application code required
|
|
617
|
+
- Works with any SQLite database
|
|
618
|
+
- Restore to any point in time within retention period
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
### Configuration
|
|
622
|
+
|
|
623
|
+
```yaml
|
|
624
|
+
# litestream.yml
|
|
625
|
+
dbs:
|
|
626
|
+
- path: /data/app.db
|
|
627
|
+
replicas:
|
|
628
|
+
- type: s3
|
|
629
|
+
bucket: my-backup-bucket
|
|
630
|
+
path: app-db
|
|
631
|
+
region: us-west-2
|
|
632
|
+
access-key-id: ${AWS_ACCESS_KEY_ID}
|
|
633
|
+
secret-access-key: ${AWS_SECRET_ACCESS_KEY}
|
|
634
|
+
retention: 720h # Keep 30 days of WAL segments
|
|
635
|
+
retention-check-interval: 1h
|
|
636
|
+
sync-interval: 1s # Replicate every second
|
|
637
|
+
|
|
638
|
+
# Multiple replicas supported
|
|
639
|
+
- type: abs # Azure Blob Storage
|
|
640
|
+
bucket: my-container
|
|
641
|
+
path: app-db
|
|
642
|
+
account-name: ${AZURE_ACCOUNT_NAME}
|
|
643
|
+
account-key: ${AZURE_ACCOUNT_KEY}
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
### Commands
|
|
647
|
+
|
|
648
|
+
```bash
|
|
649
|
+
# Start replication (runs as a daemon alongside your app)
|
|
650
|
+
litestream replicate -config litestream.yml
|
|
651
|
+
|
|
652
|
+
# Restore from replica
|
|
653
|
+
litestream restore -config litestream.yml -o /data/restored.db /data/app.db
|
|
654
|
+
|
|
655
|
+
# Restore to a specific point in time
|
|
656
|
+
litestream restore -config litestream.yml -timestamp "2025-02-15T10:30:00Z" -o /data/restored.db /data/app.db
|
|
657
|
+
|
|
658
|
+
# List available snapshots and WAL segments
|
|
659
|
+
litestream snapshots -config litestream.yml /data/app.db
|
|
660
|
+
litestream wal -config litestream.yml /data/app.db
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
### Dockerfile Pattern
|
|
664
|
+
|
|
665
|
+
```dockerfile
|
|
666
|
+
FROM node:20-slim
|
|
667
|
+
|
|
668
|
+
# Install Litestream
|
|
669
|
+
RUN apt-get update && apt-get install -y wget && \
|
|
670
|
+
wget https://github.com/benbjohnson/litestream/releases/download/v0.3.13/litestream-v0.3.13-linux-amd64.deb && \
|
|
671
|
+
dpkg -i litestream-v0.3.13-linux-amd64.deb && \
|
|
672
|
+
rm litestream-v0.3.13-linux-amd64.deb
|
|
673
|
+
|
|
674
|
+
COPY litestream.yml /etc/litestream.yml
|
|
675
|
+
COPY . /app
|
|
676
|
+
WORKDIR /app
|
|
677
|
+
|
|
678
|
+
# Restore database on startup, then run app with replication
|
|
679
|
+
CMD ["sh", "-c", "litestream restore -if-db-not-exists -config /etc/litestream.yml /data/app.db && litestream replicate -config /etc/litestream.yml -exec 'node server.js'"]
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
---
|
|
683
|
+
|
|
684
|
+
## 11. Database-per-Tenant Pattern
|
|
685
|
+
|
|
686
|
+
```typescript
|
|
687
|
+
import Database from "better-sqlite3";
|
|
688
|
+
import path from "path";
|
|
689
|
+
import fs from "fs";
|
|
690
|
+
|
|
691
|
+
const TENANT_DB_DIR = path.resolve("./data/tenants");
|
|
692
|
+
|
|
693
|
+
// Cache open database connections
|
|
694
|
+
const dbPool = new Map<string, Database.Database>();
|
|
695
|
+
|
|
696
|
+
function getTenantDB(tenantId: string): Database.Database {
|
|
697
|
+
// Validate tenant ID (prevent path traversal)
|
|
698
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(tenantId)) {
|
|
699
|
+
throw new Error("Invalid tenant ID");
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
if (dbPool.has(tenantId)) {
|
|
703
|
+
return dbPool.get(tenantId)!;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
const dbPath = path.join(TENANT_DB_DIR, `${tenantId}.db`);
|
|
707
|
+
const isNew = !fs.existsSync(dbPath);
|
|
708
|
+
|
|
709
|
+
const db = new Database(dbPath);
|
|
710
|
+
db.pragma("journal_mode = WAL");
|
|
711
|
+
db.pragma("synchronous = NORMAL");
|
|
712
|
+
db.pragma("foreign_keys = ON");
|
|
713
|
+
db.pragma("busy_timeout = 5000");
|
|
714
|
+
|
|
715
|
+
if (isNew) {
|
|
716
|
+
// Run migrations for new tenant
|
|
717
|
+
applyMigrations(db);
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
dbPool.set(tenantId, db);
|
|
721
|
+
return db;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
function closeTenantDB(tenantId: string): void {
|
|
725
|
+
const db = dbPool.get(tenantId);
|
|
726
|
+
if (db) {
|
|
727
|
+
db.pragma("optimize");
|
|
728
|
+
db.close();
|
|
729
|
+
dbPool.delete(tenantId);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// Close all on shutdown
|
|
734
|
+
function closeAllTenantDBs(): void {
|
|
735
|
+
for (const [tenantId] of dbPool) {
|
|
736
|
+
closeTenantDB(tenantId);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
process.on("exit", closeAllTenantDBs);
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
### Benefits and Trade-offs
|
|
744
|
+
|
|
745
|
+
```
|
|
746
|
+
Benefits:
|
|
747
|
+
- Complete data isolation between tenants
|
|
748
|
+
- Easy to backup, restore, or delete a single tenant's data
|
|
749
|
+
- Easy to move a tenant to a different server
|
|
750
|
+
- No noisy neighbor problems (one tenant's queries do not affect others)
|
|
751
|
+
- Different tenants can run different schema versions during migrations
|
|
752
|
+
- Simple per-tenant data export (just copy the .db file)
|
|
753
|
+
|
|
754
|
+
Trade-offs:
|
|
755
|
+
- More files to manage (one .db + .db-wal + .db-shm per tenant)
|
|
756
|
+
- Cross-tenant queries require opening multiple databases
|
|
757
|
+
- Connection pool management (open file descriptor limits)
|
|
758
|
+
- Schema migrations must be applied to every tenant database
|
|
759
|
+
- Monitoring and alerting is more complex
|
|
760
|
+
```
|
|
761
|
+
|
|
762
|
+
---
|
|
763
|
+
|
|
764
|
+
## 12. Testing with In-Memory SQLite
|
|
765
|
+
|
|
766
|
+
```typescript
|
|
767
|
+
import Database from "better-sqlite3";
|
|
768
|
+
|
|
769
|
+
function createTestDB(): Database.Database {
|
|
770
|
+
const db = new Database(":memory:"); // In-memory, no disk I/O
|
|
771
|
+
db.pragma("foreign_keys = ON");
|
|
772
|
+
|
|
773
|
+
// Apply schema
|
|
774
|
+
db.exec(`
|
|
775
|
+
CREATE TABLE users (
|
|
776
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
777
|
+
name TEXT NOT NULL,
|
|
778
|
+
email TEXT NOT NULL UNIQUE,
|
|
779
|
+
active INTEGER NOT NULL DEFAULT 1,
|
|
780
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
781
|
+
);
|
|
782
|
+
|
|
783
|
+
CREATE TABLE orders (
|
|
784
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
785
|
+
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
786
|
+
total REAL NOT NULL CHECK (total >= 0),
|
|
787
|
+
status TEXT NOT NULL DEFAULT 'pending'
|
|
788
|
+
CHECK (status IN ('pending', 'confirmed', 'shipped', 'delivered', 'cancelled')),
|
|
789
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
790
|
+
);
|
|
791
|
+
|
|
792
|
+
CREATE INDEX idx_orders_user_id ON orders (user_id);
|
|
793
|
+
CREATE INDEX idx_orders_status ON orders (status);
|
|
794
|
+
`);
|
|
795
|
+
|
|
796
|
+
return db;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// Vitest example
|
|
800
|
+
import { describe, it, expect, beforeEach } from "vitest";
|
|
801
|
+
|
|
802
|
+
describe("User Repository", () => {
|
|
803
|
+
let db: Database.Database;
|
|
804
|
+
|
|
805
|
+
beforeEach(() => {
|
|
806
|
+
db = createTestDB(); // Fresh database per test (fast, isolated)
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
it("should create a user", () => {
|
|
810
|
+
const stmt = db.prepare("INSERT INTO users (name, email) VALUES (?, ?)");
|
|
811
|
+
const result = stmt.run("Alice", "alice@example.com");
|
|
812
|
+
expect(result.lastInsertRowid).toBe(1);
|
|
813
|
+
expect(result.changes).toBe(1);
|
|
814
|
+
});
|
|
815
|
+
|
|
816
|
+
it("should enforce unique email", () => {
|
|
817
|
+
const stmt = db.prepare("INSERT INTO users (name, email) VALUES (?, ?)");
|
|
818
|
+
stmt.run("Alice", "alice@example.com");
|
|
819
|
+
expect(() => stmt.run("Bob", "alice@example.com")).toThrow(/UNIQUE constraint failed/);
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
it("should cascade delete orders when user is deleted", () => {
|
|
823
|
+
db.prepare("INSERT INTO users (name, email) VALUES (?, ?)").run("Alice", "alice@example.com");
|
|
824
|
+
db.prepare("INSERT INTO orders (user_id, total) VALUES (?, ?)").run(1, 49.99);
|
|
825
|
+
db.prepare("INSERT INTO orders (user_id, total) VALUES (?, ?)").run(1, 29.99);
|
|
826
|
+
|
|
827
|
+
db.prepare("DELETE FROM users WHERE id = ?").run(1);
|
|
828
|
+
|
|
829
|
+
const orders = db.prepare("SELECT * FROM orders WHERE user_id = ?").all(1);
|
|
830
|
+
expect(orders).toHaveLength(0);
|
|
831
|
+
});
|
|
832
|
+
});
|
|
833
|
+
```
|
|
834
|
+
|
|
835
|
+
### Using SQLite as a PostgreSQL Test Double
|
|
836
|
+
|
|
837
|
+
```typescript
|
|
838
|
+
// If your production DB is PostgreSQL, you can use SQLite for fast unit tests.
|
|
839
|
+
// Key differences to account for:
|
|
840
|
+
//
|
|
841
|
+
// 1. Type system: SQLite is dynamically typed, PostgreSQL is strict
|
|
842
|
+
// 2. Date functions: datetime('now') vs now(), date arithmetic differs
|
|
843
|
+
// 3. String functions: Some PostgreSQL functions do not exist in SQLite
|
|
844
|
+
// 4. RETURNING clause: SQLite 3.35+ supports RETURNING
|
|
845
|
+
// 5. UPSERT: SQLite supports ON CONFLICT, syntax is slightly different
|
|
846
|
+
// 6. JSON: SQLite uses json_extract(), PostgreSQL uses -> and ->>
|
|
847
|
+
//
|
|
848
|
+
// Strategy: Write a thin adapter layer that translates between the two.
|
|
849
|
+
// Use SQLite for fast unit tests, PostgreSQL for integration tests.
|
|
850
|
+
```
|
|
851
|
+
|
|
852
|
+
---
|
|
853
|
+
|
|
854
|
+
## 13. File Locking
|
|
855
|
+
|
|
856
|
+
```
|
|
857
|
+
SQLite uses file-level locking to manage concurrent access:
|
|
858
|
+
|
|
859
|
+
Lock levels (in order of escalation):
|
|
860
|
+
UNLOCKED -- No lock held
|
|
861
|
+
SHARED -- Reading, multiple readers allowed
|
|
862
|
+
RESERVED -- Planning to write, one writer allowed, readers continue
|
|
863
|
+
PENDING -- Waiting for readers to finish before writing
|
|
864
|
+
EXCLUSIVE -- Writing, no other access allowed
|
|
865
|
+
|
|
866
|
+
WAL mode changes this significantly:
|
|
867
|
+
- Readers never block writers
|
|
868
|
+
- Writers never block readers
|
|
869
|
+
- Only one writer at a time (other writers wait based on busy_timeout)
|
|
870
|
+
|
|
871
|
+
Common SQLITE_BUSY errors:
|
|
872
|
+
- Multiple processes writing simultaneously
|
|
873
|
+
- Long-running read transactions holding a snapshot
|
|
874
|
+
- WAL file growing because a reader holds an old snapshot
|
|
875
|
+
|
|
876
|
+
Solutions:
|
|
877
|
+
- Set PRAGMA busy_timeout = 5000 (wait before failing)
|
|
878
|
+
- Keep write transactions short
|
|
879
|
+
- Use a single write connection and multiple read connections
|
|
880
|
+
- In WAL mode, close long-running read transactions promptly
|
|
881
|
+
```
|
|
882
|
+
|
|
883
|
+
### Connection Pool Pattern
|
|
884
|
+
|
|
885
|
+
```typescript
|
|
886
|
+
import Database from "better-sqlite3";
|
|
887
|
+
|
|
888
|
+
class SQLitePool {
|
|
889
|
+
private writer: Database.Database;
|
|
890
|
+
private readers: Database.Database[];
|
|
891
|
+
private readerIndex = 0;
|
|
892
|
+
|
|
893
|
+
constructor(dbPath: string, readerCount = 4) {
|
|
894
|
+
// Single writer connection
|
|
895
|
+
this.writer = new Database(dbPath);
|
|
896
|
+
this.writer.pragma("journal_mode = WAL");
|
|
897
|
+
this.writer.pragma("synchronous = NORMAL");
|
|
898
|
+
this.writer.pragma("foreign_keys = ON");
|
|
899
|
+
this.writer.pragma("busy_timeout = 5000");
|
|
900
|
+
|
|
901
|
+
// Multiple reader connections
|
|
902
|
+
this.readers = Array.from({ length: readerCount }, () => {
|
|
903
|
+
const reader = new Database(dbPath, { readonly: true });
|
|
904
|
+
reader.pragma("cache_size = -32000");
|
|
905
|
+
return reader;
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
getWriter(): Database.Database {
|
|
910
|
+
return this.writer;
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
getReader(): Database.Database {
|
|
914
|
+
const reader = this.readers[this.readerIndex % this.readers.length];
|
|
915
|
+
this.readerIndex++;
|
|
916
|
+
return reader;
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
close(): void {
|
|
920
|
+
this.writer.pragma("optimize");
|
|
921
|
+
this.writer.close();
|
|
922
|
+
for (const reader of this.readers) {
|
|
923
|
+
reader.close();
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
```
|
|
928
|
+
|
|
929
|
+
---
|
|
930
|
+
|
|
931
|
+
## 14. Backup Strategies
|
|
932
|
+
|
|
933
|
+
```bash
|
|
934
|
+
# Online backup using SQLite's backup API (safe with concurrent access)
|
|
935
|
+
sqlite3 /data/app.db ".backup /backups/app-$(date +%Y%m%d-%H%M%S).db"
|
|
936
|
+
|
|
937
|
+
# With better-sqlite3 in Node.js:
|
|
938
|
+
```
|
|
939
|
+
|
|
940
|
+
```typescript
|
|
941
|
+
const db = new Database("/data/app.db");
|
|
942
|
+
|
|
943
|
+
// Backup to a file
|
|
944
|
+
db.backup("/backups/app-backup.db")
|
|
945
|
+
.then(() => console.log("Backup complete"))
|
|
946
|
+
.catch((err) => console.error("Backup failed:", err));
|
|
947
|
+
|
|
948
|
+
// Backup with progress
|
|
949
|
+
await db.backup("/backups/app-backup.db", {
|
|
950
|
+
progress({ totalPages, remainingPages }) {
|
|
951
|
+
const pct = ((totalPages - remainingPages) / totalPages * 100).toFixed(1);
|
|
952
|
+
console.log(`Backup progress: ${pct}%`);
|
|
953
|
+
return 200; // Sleep 200ms between steps (reduce I/O pressure)
|
|
954
|
+
},
|
|
955
|
+
});
|
|
956
|
+
```
|
|
957
|
+
|
|
958
|
+
```bash
|
|
959
|
+
# NEVER just copy the .db file while the database is in use
|
|
960
|
+
# The .db-wal file may contain uncommitted data
|
|
961
|
+
|
|
962
|
+
# If you must copy files, checkpoint first:
|
|
963
|
+
sqlite3 /data/app.db "PRAGMA wal_checkpoint(TRUNCATE);"
|
|
964
|
+
# Then copy the .db file (WAL is now empty)
|
|
965
|
+
|
|
966
|
+
# For continuous backup, use Litestream (see section 10)
|
|
967
|
+
```
|
|
968
|
+
|
|
969
|
+
---
|
|
970
|
+
|
|
971
|
+
## 15. Migration Patterns
|
|
972
|
+
|
|
973
|
+
```typescript
|
|
974
|
+
interface Migration {
|
|
975
|
+
version: number;
|
|
976
|
+
description: string;
|
|
977
|
+
up: string;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
const migrations: Migration[] = [
|
|
981
|
+
{
|
|
982
|
+
version: 1,
|
|
983
|
+
description: "Create users table",
|
|
984
|
+
up: `
|
|
985
|
+
CREATE TABLE users (
|
|
986
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
987
|
+
name TEXT NOT NULL,
|
|
988
|
+
email TEXT NOT NULL UNIQUE,
|
|
989
|
+
active INTEGER NOT NULL DEFAULT 1,
|
|
990
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
991
|
+
);
|
|
992
|
+
`,
|
|
993
|
+
},
|
|
994
|
+
{
|
|
995
|
+
version: 2,
|
|
996
|
+
description: "Create orders table",
|
|
997
|
+
up: `
|
|
998
|
+
CREATE TABLE orders (
|
|
999
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1000
|
+
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
1001
|
+
total REAL NOT NULL CHECK (total >= 0),
|
|
1002
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
1003
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1004
|
+
);
|
|
1005
|
+
CREATE INDEX idx_orders_user_id ON orders (user_id);
|
|
1006
|
+
`,
|
|
1007
|
+
},
|
|
1008
|
+
{
|
|
1009
|
+
version: 3,
|
|
1010
|
+
description: "Add phone column to users",
|
|
1011
|
+
up: `
|
|
1012
|
+
ALTER TABLE users ADD COLUMN phone TEXT;
|
|
1013
|
+
`,
|
|
1014
|
+
},
|
|
1015
|
+
];
|
|
1016
|
+
|
|
1017
|
+
function applyMigrations(db: Database.Database): void {
|
|
1018
|
+
// Create migrations tracking table
|
|
1019
|
+
db.exec(`
|
|
1020
|
+
CREATE TABLE IF NOT EXISTS _migrations (
|
|
1021
|
+
version INTEGER PRIMARY KEY,
|
|
1022
|
+
description TEXT NOT NULL,
|
|
1023
|
+
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1024
|
+
);
|
|
1025
|
+
`);
|
|
1026
|
+
|
|
1027
|
+
const currentVersion = db.prepare(
|
|
1028
|
+
"SELECT COALESCE(MAX(version), 0) AS version FROM _migrations"
|
|
1029
|
+
).get() as { version: number };
|
|
1030
|
+
|
|
1031
|
+
const pending = migrations.filter((m) => m.version > currentVersion.version);
|
|
1032
|
+
|
|
1033
|
+
if (pending.length === 0) {
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
const applyAll = db.transaction(() => {
|
|
1038
|
+
for (const migration of pending) {
|
|
1039
|
+
db.exec(migration.up);
|
|
1040
|
+
db.prepare(
|
|
1041
|
+
"INSERT INTO _migrations (version, description) VALUES (?, ?)"
|
|
1042
|
+
).run(migration.version, migration.description);
|
|
1043
|
+
console.log(`Applied migration ${migration.version}: ${migration.description}`);
|
|
1044
|
+
}
|
|
1045
|
+
});
|
|
1046
|
+
|
|
1047
|
+
applyAll();
|
|
1048
|
+
}
|
|
1049
|
+
```
|
|
1050
|
+
|
|
1051
|
+
---
|
|
1052
|
+
|
|
1053
|
+
## 16. Common Anti-Patterns
|
|
1054
|
+
|
|
1055
|
+
### Missing WAL Mode
|
|
1056
|
+
|
|
1057
|
+
```typescript
|
|
1058
|
+
// BAD: Default journal mode (readers blocked during writes)
|
|
1059
|
+
const db = new Database("app.db");
|
|
1060
|
+
|
|
1061
|
+
// GOOD: Enable WAL for concurrent access
|
|
1062
|
+
const db = new Database("app.db");
|
|
1063
|
+
db.pragma("journal_mode = WAL");
|
|
1064
|
+
```
|
|
1065
|
+
|
|
1066
|
+
### Missing busy_timeout
|
|
1067
|
+
|
|
1068
|
+
```typescript
|
|
1069
|
+
// BAD: No busy timeout (SQLITE_BUSY errors on contention)
|
|
1070
|
+
const db = new Database("app.db");
|
|
1071
|
+
|
|
1072
|
+
// GOOD: Wait for locks instead of failing immediately
|
|
1073
|
+
const db = new Database("app.db");
|
|
1074
|
+
db.pragma("busy_timeout = 5000");
|
|
1075
|
+
```
|
|
1076
|
+
|
|
1077
|
+
### Unbatched Inserts
|
|
1078
|
+
|
|
1079
|
+
```typescript
|
|
1080
|
+
// BAD: 10,000 individual transactions (~50 inserts/second)
|
|
1081
|
+
for (const item of items) {
|
|
1082
|
+
db.prepare("INSERT INTO items (name) VALUES (?)").run(item.name);
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
// GOOD: Single transaction (~50,000 inserts/second)
|
|
1086
|
+
const insertAll = db.transaction((items: { name: string }[]) => {
|
|
1087
|
+
const stmt = db.prepare("INSERT INTO items (name) VALUES (?)");
|
|
1088
|
+
for (const item of items) {
|
|
1089
|
+
stmt.run(item.name);
|
|
1090
|
+
}
|
|
1091
|
+
});
|
|
1092
|
+
insertAll(items);
|
|
1093
|
+
```
|
|
1094
|
+
|
|
1095
|
+
### Using SQLite Over NFS
|
|
1096
|
+
|
|
1097
|
+
```
|
|
1098
|
+
# BAD: SQLite on a network filesystem
|
|
1099
|
+
# WAL mode does not work, file locking is unreliable, data corruption risk
|
|
1100
|
+
|
|
1101
|
+
# GOOD: Keep the database on a local filesystem
|
|
1102
|
+
# Use Turso or Litestream if you need remote access or replication
|
|
1103
|
+
```
|
|
1104
|
+
|
|
1105
|
+
---
|
|
1106
|
+
|
|
1107
|
+
## 17. Critical Reminders
|
|
1108
|
+
|
|
1109
|
+
### ALWAYS
|
|
1110
|
+
|
|
1111
|
+
- Enable WAL mode for any production use
|
|
1112
|
+
- Set `PRAGMA foreign_keys = ON` (it is off by default)
|
|
1113
|
+
- Set `PRAGMA busy_timeout` to a reasonable value (e.g., 5000 ms)
|
|
1114
|
+
- Use prepared statements (compiled once, faster execution)
|
|
1115
|
+
- Wrap bulk operations in transactions (orders of magnitude faster)
|
|
1116
|
+
- Use parameterized queries -- never interpolate user input into SQL
|
|
1117
|
+
- Run `PRAGMA optimize` periodically or on connection close
|
|
1118
|
+
- Back up using the SQLite backup API or Litestream, not file copy
|
|
1119
|
+
- Close database connections on application shutdown
|
|
1120
|
+
- Validate tenant IDs when using database-per-tenant pattern (prevent path traversal)
|
|
1121
|
+
|
|
1122
|
+
### NEVER
|
|
1123
|
+
|
|
1124
|
+
- Copy a `.db` file while the database is in use (use backup API instead)
|
|
1125
|
+
- Use SQLite over a network filesystem (NFS, SMB, CIFS)
|
|
1126
|
+
- Assume SQLite supports concurrent writes (it serializes them)
|
|
1127
|
+
- Skip foreign key PRAGMA (it is disabled by default, constraints will not be enforced)
|
|
1128
|
+
- Insert thousands of rows without a wrapping transaction
|
|
1129
|
+
- Store the database in a temporary or world-writable directory
|
|
1130
|
+
- Use `PRAGMA synchronous = OFF` in production (data loss on crash)
|
|
1131
|
+
- Ignore the `.db-wal` and `.db-shm` files during backup
|
|
1132
|
+
- Use SQLite as a message queue with high concurrency (use Redis or a proper queue)
|