claude-code-orchestrator-kit 1.4.1 → 1.4.16
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/.claude/agents/business/workers/lead-research-assistant.md +199 -0
- package/.claude/agents/database/workers/api-builder.md +8 -0
- package/.claude/agents/database/workers/database-architect.md +11 -3
- package/.claude/agents/database/workers/supabase-auditor.md +7 -7
- package/.claude/agents/database/workers/supabase-fixer.md +825 -0
- package/.claude/agents/database/workers/supabase-realtime-optimizer.md +1086 -0
- package/.claude/agents/database/workers/supabase-storage-optimizer.md +1187 -0
- package/.claude/agents/development/workers/code-reviewer.md +17 -2
- package/.claude/agents/development/workers/code-structure-refactorer.md +771 -0
- package/.claude/agents/development/workers/judge-specialist.md +3275 -0
- package/.claude/agents/development/workers/langgraph-specialist.md +1343 -0
- package/.claude/agents/development/workers/stage-pipeline-specialist.md +1173 -0
- package/.claude/agents/frontend/workers/fullstack-nextjs-specialist.md +10 -0
- package/.claude/agents/frontend/workers/nextjs-ui-designer.md +30 -0
- package/.claude/agents/health/workers/bug-fixer.md +31 -3
- package/.claude/agents/health/workers/bug-hunter.md +0 -1
- package/.claude/agents/health/workers/dead-code-hunter.md +167 -75
- package/.claude/agents/health/workers/dead-code-remover.md +217 -66
- package/.claude/agents/health/workers/dependency-auditor.md +83 -24
- package/.claude/agents/health/workers/dependency-updater.md +0 -1
- package/.claude/agents/health/workers/security-scanner.md +0 -1
- package/.claude/agents/infrastructure/workers/bullmq-worker-specialist.md +748 -0
- package/.claude/agents/infrastructure/workers/deployment-engineer.md +446 -0
- package/.claude/agents/infrastructure/workers/infrastructure-specialist.md +2 -2
- package/.claude/agents/infrastructure/workers/rag-specialist.md +799 -0
- package/.claude/agents/infrastructure/workers/server-hardening-specialist.md +1128 -0
- package/.claude/agents/integrations/workers/lms-integration-specialist.md +866 -0
- package/.claude/agents/meta/workers/meta-agent-v3.md +22 -0
- package/.claude/agents/testing/workers/integration-tester.md +1 -1
- package/.claude/agents/testing/workers/test-writer.md +16 -0
- package/.claude/commands/health-bugs.md +14 -281
- package/.claude/commands/health-cleanup.md +14 -281
- package/.claude/commands/health-deps.md +14 -281
- package/.claude/commands/health-metrics.md +51 -709
- package/.claude/commands/health-reuse.md +14 -311
- package/.claude/commands/health-security.md +14 -281
- package/.claude/commands/push.md +17 -3
- package/.claude/commands/speckit.implement.md +0 -11
- package/.claude/commands/supabase-performance-optimizer.md +73 -0
- package/.claude/commands/ultra-think.md +158 -0
- package/.claude/commands/worktree.md +150 -0
- package/.claude/scripts/gates/check-bundle-size.sh +0 -0
- package/.claude/scripts/gates/check-coverage.sh +0 -0
- package/.claude/scripts/gates/check-security.sh +0 -0
- package/.claude/scripts/release.sh +469 -94
- package/.claude/skills/algorithmic-art/LICENSE.txt +202 -0
- package/.claude/skills/algorithmic-art/SKILL.md +405 -0
- package/.claude/skills/algorithmic-art/templates/generator_template.js +223 -0
- package/.claude/skills/algorithmic-art/templates/viewer.html +599 -0
- package/.claude/skills/artifacts-builder/LICENSE.txt +202 -0
- package/.claude/skills/artifacts-builder/SKILL.md +74 -0
- package/.claude/skills/artifacts-builder/scripts/bundle-artifact.sh +54 -0
- package/.claude/skills/artifacts-builder/scripts/init-artifact.sh +322 -0
- package/.claude/skills/artifacts-builder/scripts/shadcn-components.tar.gz +0 -0
- package/.claude/skills/bug-health-inline/SKILL.md +221 -0
- package/.claude/skills/bug-health-inline/references/worker-prompts.md +182 -0
- package/.claude/skills/canvas-design/LICENSE.txt +202 -0
- package/.claude/skills/canvas-design/SKILL.md +130 -0
- package/.claude/skills/canvas-design/canvas-fonts/ArsenalSC-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/ArsenalSC-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/BigShoulders-Bold.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/BigShoulders-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/BigShoulders-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/Boldonse-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/Boldonse-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/BricolageGrotesque-Bold.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/BricolageGrotesque-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/BricolageGrotesque-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/CrimsonPro-Bold.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/CrimsonPro-Italic.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/CrimsonPro-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/CrimsonPro-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/DMMono-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/DMMono-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/EricaOne-OFL.txt +94 -0
- package/.claude/skills/canvas-design/canvas-fonts/EricaOne-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/GeistMono-Bold.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/GeistMono-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/GeistMono-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/Gloock-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/Gloock-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/IBMPlexMono-Bold.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/IBMPlexMono-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/IBMPlexMono-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/IBMPlexSerif-Bold.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/IBMPlexSerif-BoldItalic.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/IBMPlexSerif-Italic.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/IBMPlexSerif-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/InstrumentSans-Bold.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/InstrumentSans-BoldItalic.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/InstrumentSans-Italic.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/InstrumentSans-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/InstrumentSans-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/InstrumentSerif-Italic.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/InstrumentSerif-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/Italiana-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/Italiana-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/JetBrainsMono-Bold.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/JetBrainsMono-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/JetBrainsMono-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/Jura-Light.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/Jura-Medium.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/Jura-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/LibreBaskerville-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/LibreBaskerville-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/Lora-Bold.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/Lora-BoldItalic.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/Lora-Italic.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/Lora-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/Lora-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/NationalPark-Bold.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/NationalPark-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/NationalPark-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/NothingYouCouldDo-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/NothingYouCouldDo-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/Outfit-Bold.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/Outfit-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/Outfit-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/PixelifySans-Medium.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/PixelifySans-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/PoiretOne-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/PoiretOne-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/RedHatMono-Bold.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/RedHatMono-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/RedHatMono-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/Silkscreen-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/Silkscreen-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/SmoochSans-Medium.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/SmoochSans-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/Tektur-Medium.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/Tektur-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/Tektur-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/WorkSans-Bold.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/WorkSans-BoldItalic.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/WorkSans-Italic.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/WorkSans-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/WorkSans-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/YoungSerif-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/YoungSerif-Regular.ttf +0 -0
- package/.claude/skills/changelog-generator/SKILL.md +104 -0
- package/.claude/skills/cleanup-health-inline/SKILL.md +224 -0
- package/.claude/skills/code-reviewer/SKILL.md +209 -0
- package/.claude/skills/code-reviewer/references/code_review_checklist.md +103 -0
- package/.claude/skills/code-reviewer/references/coding_standards.md +103 -0
- package/.claude/skills/code-reviewer/references/common_antipatterns.md +103 -0
- package/.claude/skills/code-reviewer/scripts/code_quality_checker.py +114 -0
- package/.claude/skills/code-reviewer/scripts/pr_analyzer.py +114 -0
- package/.claude/skills/code-reviewer/scripts/review_report_generator.py +114 -0
- package/.claude/skills/content-research-writer/SKILL.md +538 -0
- package/.claude/skills/deps-health-inline/SKILL.md +227 -0
- package/.claude/skills/frontend-aesthetics/SKILL.md +51 -396
- package/.claude/skills/git-commit-helper/SKILL.md +203 -0
- package/.claude/skills/lead-research-assistant/SKILL.md +199 -0
- package/.claude/skills/reuse-health-inline/SKILL.md +248 -0
- package/.claude/skills/rollback-changes/SKILL.md +50 -524
- package/.claude/skills/run-quality-gate/SKILL.md +36 -346
- package/.claude/skills/security-health-inline/SKILL.md +224 -0
- package/.claude/skills/senior-architect/SKILL.md +209 -0
- package/.claude/skills/senior-architect/references/architecture_patterns.md +755 -0
- package/.claude/skills/senior-architect/references/system_design_workflows.md +749 -0
- package/.claude/skills/senior-architect/references/tech_decision_guide.md +612 -0
- package/.claude/skills/senior-architect/scripts/architecture_diagram_generator.py +114 -0
- package/.claude/skills/senior-architect/scripts/dependency_analyzer.py +114 -0
- package/.claude/skills/senior-architect/scripts/project_architect.py +114 -0
- package/.claude/skills/senior-devops/SKILL.md +209 -0
- package/.claude/skills/senior-devops/references/cicd_pipeline_guide.md +103 -0
- package/.claude/skills/senior-devops/references/deployment_strategies.md +103 -0
- package/.claude/skills/senior-devops/references/infrastructure_as_code.md +103 -0
- package/.claude/skills/senior-devops/scripts/deployment_manager.py +114 -0
- package/.claude/skills/senior-devops/scripts/pipeline_generator.py +114 -0
- package/.claude/skills/senior-devops/scripts/terraform_scaffolder.py +114 -0
- package/.claude/skills/senior-prompt-engineer/SKILL.md +226 -0
- package/.claude/skills/senior-prompt-engineer/references/agentic_system_design.md +80 -0
- package/.claude/skills/senior-prompt-engineer/references/llm_evaluation_frameworks.md +80 -0
- package/.claude/skills/senior-prompt-engineer/references/prompt_engineering_patterns.md +80 -0
- package/.claude/skills/senior-prompt-engineer/scripts/agent_orchestrator.py +100 -0
- package/.claude/skills/senior-prompt-engineer/scripts/prompt_optimizer.py +100 -0
- package/.claude/skills/senior-prompt-engineer/scripts/rag_evaluator.py +100 -0
- package/.claude/skills/setup-knip/SKILL.md +372 -0
- package/.claude/skills/systematic-debugging/CREATION-LOG.md +119 -0
- package/.claude/skills/systematic-debugging/SKILL.md +296 -0
- package/.claude/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
- package/.claude/skills/systematic-debugging/condition-based-waiting.md +115 -0
- package/.claude/skills/systematic-debugging/defense-in-depth.md +122 -0
- package/.claude/skills/systematic-debugging/find-polluter.sh +63 -0
- package/.claude/skills/systematic-debugging/root-cause-tracing.md +169 -0
- package/.claude/skills/systematic-debugging/test-academic.md +14 -0
- package/.claude/skills/systematic-debugging/test-pressure-1.md +58 -0
- package/.claude/skills/systematic-debugging/test-pressure-2.md +68 -0
- package/.claude/skills/systematic-debugging/test-pressure-3.md +69 -0
- package/.claude/skills/theme-factory/LICENSE.txt +202 -0
- package/.claude/skills/theme-factory/SKILL.md +59 -0
- package/.claude/skills/theme-factory/theme-showcase.pdf +0 -0
- package/.claude/skills/theme-factory/themes/arctic-frost.md +19 -0
- package/.claude/skills/theme-factory/themes/botanical-garden.md +19 -0
- package/.claude/skills/theme-factory/themes/desert-rose.md +19 -0
- package/.claude/skills/theme-factory/themes/forest-canopy.md +19 -0
- package/.claude/skills/theme-factory/themes/golden-hour.md +19 -0
- package/.claude/skills/theme-factory/themes/midnight-galaxy.md +19 -0
- package/.claude/skills/theme-factory/themes/modern-minimalist.md +19 -0
- package/.claude/skills/theme-factory/themes/ocean-depths.md +19 -0
- package/.claude/skills/theme-factory/themes/sunset-boulevard.md +19 -0
- package/.claude/skills/theme-factory/themes/tech-innovation.md +19 -0
- package/.claude/skills/ui-design-system/SKILL.md +32 -0
- package/.claude/skills/ui-design-system/scripts/design_token_generator.py +529 -0
- package/.claude/skills/ux-researcher-designer/SKILL.md +30 -0
- package/.claude/skills/ux-researcher-designer/scripts/persona_generator.py +508 -0
- package/.claude/skills/webapp-testing/LICENSE.txt +202 -0
- package/.claude/skills/webapp-testing/SKILL.md +96 -0
- package/.claude/skills/webapp-testing/examples/console_logging.py +35 -0
- package/.claude/skills/webapp-testing/examples/element_discovery.py +40 -0
- package/.claude/skills/webapp-testing/examples/static_html_automation.py +33 -0
- package/.claude/skills/webapp-testing/scripts/with_server.py +106 -0
- package/.gitignore +4 -0
- package/README.md +492 -1093
- package/README.ru.md +719 -0
- package/docs/Agents Ecosystem/AGENT-ORCHESTRATION.md +2 -2
- package/docs/COMMANDS-GUIDE.md +0 -15
- package/docs/reports/skills/new-skills-analysis-2025-12.md +331 -0
- package/package.json +11 -3
- package/.claude/agents/health/orchestrators/bug-orchestrator.md +0 -1084
- package/.claude/agents/health/orchestrators/dead-code-orchestrator.md +0 -1064
- package/.claude/agents/health/orchestrators/dependency-orchestrator.md +0 -1064
- package/.claude/agents/health/orchestrators/reuse-orchestrator.md +0 -1112
- package/.claude/agents/health/orchestrators/security-orchestrator.md +0 -1064
- package/.claude/commands/worktree-cleanup.md +0 -382
- package/.claude/commands/worktree-create.md +0 -287
- package/.claude/commands/worktree-list.md +0 -239
- package/.claude/commands/worktree-remove.md +0 -339
- package/.claude/project-index.md +0 -75
- package/.claude/skills/load-project-context/SKILL.md +0 -89
- package/.claude/skills/resume-session/SKILL.md +0 -164
- package/.claude/skills/save-session-context/SKILL.md +0 -123
- package/.claude/templates/project-index.template.md +0 -67
- package/.claude/templates/session/context.template.md +0 -40
- package/.claude/templates/session/log.template.md +0 -72
- package/.github/BRANCH_PROTECTION.md +0 -137
- package/.github/workflows/build.yml +0 -70
- package/.github/workflows/deploy-staging.yml +0 -90
- package/.github/workflows/test.yml +0 -104
|
@@ -0,0 +1,755 @@
|
|
|
1
|
+
# Architecture Patterns
|
|
2
|
+
|
|
3
|
+
Real-world patterns from the MegaCampusAI monorepo codebase.
|
|
4
|
+
|
|
5
|
+
## Database Patterns
|
|
6
|
+
|
|
7
|
+
### Pattern 1: Soft Delete with RLS (Row Level Security)
|
|
8
|
+
|
|
9
|
+
**Description:**
|
|
10
|
+
Implement soft deletes using `deleted_at` timestamp instead of hard deletes. Combined with Supabase RLS for multi-tenant security.
|
|
11
|
+
|
|
12
|
+
**When to Use:**
|
|
13
|
+
- Multi-tenant applications (organizations, workspaces)
|
|
14
|
+
- Audit trail requirements
|
|
15
|
+
- Data recovery needs
|
|
16
|
+
- Compliance (GDPR right to be forgotten with delayed purge)
|
|
17
|
+
|
|
18
|
+
**Implementation:**
|
|
19
|
+
```sql
|
|
20
|
+
-- Migration: Add deleted_at column
|
|
21
|
+
ALTER TABLE courses ADD COLUMN deleted_at timestamptz;
|
|
22
|
+
|
|
23
|
+
-- RLS policy: Hide deleted records
|
|
24
|
+
CREATE POLICY "Users see only non-deleted courses"
|
|
25
|
+
ON courses FOR SELECT
|
|
26
|
+
USING (deleted_at IS NULL AND organization_id IN (
|
|
27
|
+
SELECT organization_id FROM organization_members WHERE user_id = auth.uid()
|
|
28
|
+
));
|
|
29
|
+
|
|
30
|
+
-- Soft delete function
|
|
31
|
+
CREATE OR REPLACE FUNCTION soft_delete_course(course_id uuid)
|
|
32
|
+
RETURNS void AS $$
|
|
33
|
+
BEGIN
|
|
34
|
+
UPDATE courses
|
|
35
|
+
SET deleted_at = NOW()
|
|
36
|
+
WHERE id = course_id AND deleted_at IS NULL;
|
|
37
|
+
END;
|
|
38
|
+
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**TypeScript Usage:**
|
|
42
|
+
```typescript
|
|
43
|
+
// Soft delete
|
|
44
|
+
await supabase
|
|
45
|
+
.from('courses')
|
|
46
|
+
.update({ deleted_at: new Date().toISOString() })
|
|
47
|
+
.eq('id', courseId);
|
|
48
|
+
|
|
49
|
+
// Query non-deleted (RLS handles this automatically)
|
|
50
|
+
const { data } = await supabase
|
|
51
|
+
.from('courses')
|
|
52
|
+
.select('*')
|
|
53
|
+
.is('deleted_at', null); // Explicit filter for clarity
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Benefits:**
|
|
57
|
+
- Data recovery possible
|
|
58
|
+
- Audit trail maintained
|
|
59
|
+
- Foreign key integrity preserved
|
|
60
|
+
- Performance: no cascading deletes
|
|
61
|
+
|
|
62
|
+
**Trade-offs:**
|
|
63
|
+
- Storage overhead (deleted records remain)
|
|
64
|
+
- Query filters required (mitigated by RLS)
|
|
65
|
+
- Periodic cleanup jobs needed
|
|
66
|
+
|
|
67
|
+
### Pattern 2: Intentional Duplication for Runtime Isolation
|
|
68
|
+
|
|
69
|
+
**Description:**
|
|
70
|
+
Maintain separate admin clients for different runtime environments rather than forced unification.
|
|
71
|
+
|
|
72
|
+
**When to Use:**
|
|
73
|
+
- Different runtime environments (Node.js vs Next.js Server)
|
|
74
|
+
- Different configuration requirements
|
|
75
|
+
- Different type sources
|
|
76
|
+
- Historical technical debt with breaking changes
|
|
77
|
+
|
|
78
|
+
**Implementation (from codebase):**
|
|
79
|
+
```typescript
|
|
80
|
+
// packages/course-gen-platform/src/shared/supabase/admin.ts
|
|
81
|
+
// Node.js backend (tRPC, BullMQ)
|
|
82
|
+
let supabaseAdmin: SupabaseClient<Database> | null = null;
|
|
83
|
+
|
|
84
|
+
export function getSupabaseAdmin(): SupabaseClient<Database> {
|
|
85
|
+
if (!supabaseAdmin) {
|
|
86
|
+
const supabaseUrl = process.env.SUPABASE_URL; // Different env var
|
|
87
|
+
const supabaseServiceKey = process.env.SUPABASE_SERVICE_KEY; // Different env var
|
|
88
|
+
supabaseAdmin = createClient<Database>(supabaseUrl, supabaseServiceKey, {
|
|
89
|
+
auth: { autoRefreshToken: false, persistSession: false }
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
return supabaseAdmin;
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
// packages/web/lib/supabase-admin.ts
|
|
98
|
+
// Next.js Server (Components, Actions, API Routes)
|
|
99
|
+
export const supabaseAdmin = createClient<Database>(
|
|
100
|
+
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
|
101
|
+
process.env.SUPABASE_SERVICE_ROLE_KEY!, // Different env var
|
|
102
|
+
{ auth: { persistSession: false, autoRefreshToken: false } }
|
|
103
|
+
);
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Benefits:**
|
|
107
|
+
- Clear runtime separation
|
|
108
|
+
- Independent configuration
|
|
109
|
+
- No shared state issues
|
|
110
|
+
- Easier debugging
|
|
111
|
+
|
|
112
|
+
**Trade-offs:**
|
|
113
|
+
- Code duplication (documented as intentional)
|
|
114
|
+
- Synchronized updates required
|
|
115
|
+
- Must document reasons in CLAUDE.md
|
|
116
|
+
|
|
117
|
+
**Anti-Pattern to Avoid:**
|
|
118
|
+
```typescript
|
|
119
|
+
// DON'T: Force unification across runtime boundaries
|
|
120
|
+
// This creates complex environment detection and shared state issues
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Pattern 3: Single Source of Truth with Re-exports
|
|
124
|
+
|
|
125
|
+
**Description:**
|
|
126
|
+
Centralize type definitions in shared-types package, re-export from consuming packages.
|
|
127
|
+
|
|
128
|
+
**When to Use:**
|
|
129
|
+
- Monorepo with shared types
|
|
130
|
+
- Database schema changes
|
|
131
|
+
- Cross-package type consistency
|
|
132
|
+
|
|
133
|
+
**Implementation:**
|
|
134
|
+
```typescript
|
|
135
|
+
// packages/shared-types/src/database.types.ts (MAIN SOURCE)
|
|
136
|
+
export interface Course {
|
|
137
|
+
id: string;
|
|
138
|
+
title: string;
|
|
139
|
+
organization_id: string;
|
|
140
|
+
deleted_at?: string | null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// packages/web/types/database.ts (RE-EXPORT ONLY)
|
|
144
|
+
export * from '@megacampus/shared-types/database.types';
|
|
145
|
+
|
|
146
|
+
// packages/course-gen-platform/src/types/database.ts (RE-EXPORT ONLY)
|
|
147
|
+
export type { Database } from '@megacampus/shared-types';
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**Benefits:**
|
|
151
|
+
- Single update point
|
|
152
|
+
- Type consistency
|
|
153
|
+
- Easier refactoring
|
|
154
|
+
- Clear ownership
|
|
155
|
+
|
|
156
|
+
**Trade-offs:**
|
|
157
|
+
- Dependency coupling (shared-types changes affect all packages)
|
|
158
|
+
- Build order requirements
|
|
159
|
+
|
|
160
|
+
**Anti-Pattern to Avoid:**
|
|
161
|
+
```typescript
|
|
162
|
+
// DON'T: Duplicate type definitions
|
|
163
|
+
// packages/web/types/database.ts
|
|
164
|
+
export interface Course { ... } // BAD: Copy-paste from shared-types
|
|
165
|
+
|
|
166
|
+
// DON'T: Partial re-definitions
|
|
167
|
+
export interface Course extends Partial<SharedCourse> { ... } // BAD: Drift risk
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Frontend Patterns
|
|
171
|
+
|
|
172
|
+
### Pattern 4: Zustand + Immer for Complex State
|
|
173
|
+
|
|
174
|
+
**Description:**
|
|
175
|
+
Use Zustand with Immer middleware for nested state updates without spread operators.
|
|
176
|
+
|
|
177
|
+
**When to Use:**
|
|
178
|
+
- Complex nested state (navigation stacks, multi-level data)
|
|
179
|
+
- Performance-critical updates
|
|
180
|
+
- Multiple related state changes
|
|
181
|
+
|
|
182
|
+
**Implementation (from enrichment-inspector-store.ts):**
|
|
183
|
+
```typescript
|
|
184
|
+
import { create } from 'zustand';
|
|
185
|
+
import { immer } from 'zustand/middleware/immer';
|
|
186
|
+
|
|
187
|
+
interface NavigationEntry {
|
|
188
|
+
view: InspectorView;
|
|
189
|
+
enrichmentId?: string;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
interface State {
|
|
193
|
+
history: NavigationEntry[];
|
|
194
|
+
current: NavigationEntry | null;
|
|
195
|
+
dirty: boolean;
|
|
196
|
+
|
|
197
|
+
openDetail: (enrichmentId: string) => void;
|
|
198
|
+
goBack: () => void;
|
|
199
|
+
setDirty: (dirty: boolean) => void;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export const useStore = create<State>()(
|
|
203
|
+
immer((set) => ({
|
|
204
|
+
history: [],
|
|
205
|
+
current: null,
|
|
206
|
+
dirty: false,
|
|
207
|
+
|
|
208
|
+
openDetail: (enrichmentId) =>
|
|
209
|
+
set((state) => {
|
|
210
|
+
// Immer allows direct mutation syntax
|
|
211
|
+
if (state.current) {
|
|
212
|
+
state.history.push(state.current);
|
|
213
|
+
}
|
|
214
|
+
state.current = { view: 'detail', enrichmentId };
|
|
215
|
+
}),
|
|
216
|
+
|
|
217
|
+
goBack: () =>
|
|
218
|
+
set((state) => {
|
|
219
|
+
if (state.history.length === 0) return;
|
|
220
|
+
state.current = state.history.pop()!;
|
|
221
|
+
}),
|
|
222
|
+
|
|
223
|
+
setDirty: (dirty) => set((state) => { state.dirty = dirty; }),
|
|
224
|
+
}))
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
// Selector hooks for granular subscriptions
|
|
228
|
+
export const useCurrentView = () => useStore((s) => s.current?.view ?? 'root');
|
|
229
|
+
export const useCanGoBack = () => useStore((s) => s.history.length > 0);
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
**Benefits:**
|
|
233
|
+
- Readable nested updates (no spread hell)
|
|
234
|
+
- Immutability guaranteed
|
|
235
|
+
- Performance (structural sharing)
|
|
236
|
+
- Type safety
|
|
237
|
+
|
|
238
|
+
**Trade-offs:**
|
|
239
|
+
- Extra dependency (immer)
|
|
240
|
+
- Slight runtime overhead
|
|
241
|
+
- Learning curve (draft vs actual state)
|
|
242
|
+
|
|
243
|
+
**Anti-Pattern to Avoid:**
|
|
244
|
+
```typescript
|
|
245
|
+
// DON'T: Spread operator hell
|
|
246
|
+
set((state) => ({
|
|
247
|
+
...state,
|
|
248
|
+
history: [...state.history, state.current],
|
|
249
|
+
current: { ...state.current, view: 'detail' }
|
|
250
|
+
})); // Hard to read, error-prone
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Pattern 5: Stack Navigation Pattern
|
|
254
|
+
|
|
255
|
+
**Description:**
|
|
256
|
+
Browser-like navigation with history stack for panel-based UIs.
|
|
257
|
+
|
|
258
|
+
**When to Use:**
|
|
259
|
+
- Inspector panels
|
|
260
|
+
- Wizards/multi-step forms
|
|
261
|
+
- Mobile-like navigation in desktop apps
|
|
262
|
+
|
|
263
|
+
**Implementation:**
|
|
264
|
+
```typescript
|
|
265
|
+
// State
|
|
266
|
+
interface NavigationEntry {
|
|
267
|
+
view: 'root' | 'create' | 'detail';
|
|
268
|
+
enrichmentId?: string;
|
|
269
|
+
createType?: string;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
interface State {
|
|
273
|
+
history: NavigationEntry[];
|
|
274
|
+
current: NavigationEntry | null;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Actions
|
|
278
|
+
const openRoot = (id: string) =>
|
|
279
|
+
set((state) => {
|
|
280
|
+
state.history = []; // Clear history
|
|
281
|
+
state.current = { view: 'root' };
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
const openCreate = (type: string) =>
|
|
285
|
+
set((state) => {
|
|
286
|
+
if (state.current) {
|
|
287
|
+
state.history.push(state.current); // Save current
|
|
288
|
+
}
|
|
289
|
+
state.current = { view: 'create', createType: type };
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
const goBack = () =>
|
|
293
|
+
set((state) => {
|
|
294
|
+
if (state.history.length === 0) return;
|
|
295
|
+
state.current = state.history.pop()!; // Pop previous
|
|
296
|
+
});
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
**Benefits:**
|
|
300
|
+
- Familiar browser-like UX
|
|
301
|
+
- Back button support
|
|
302
|
+
- Clear state transitions
|
|
303
|
+
- Deep linking support
|
|
304
|
+
|
|
305
|
+
**Trade-offs:**
|
|
306
|
+
- Memory overhead (history stack)
|
|
307
|
+
- Complex state management
|
|
308
|
+
- Must handle edge cases (empty history)
|
|
309
|
+
|
|
310
|
+
### Pattern 6: Optimistic Updates with Rollback
|
|
311
|
+
|
|
312
|
+
**Description:**
|
|
313
|
+
Update UI immediately, rollback on server error.
|
|
314
|
+
|
|
315
|
+
**When to Use:**
|
|
316
|
+
- Real-time collaboration
|
|
317
|
+
- Perceived performance critical
|
|
318
|
+
- High-latency operations
|
|
319
|
+
|
|
320
|
+
**Implementation:**
|
|
321
|
+
```typescript
|
|
322
|
+
const deleteEnrichment = async (id: string) => {
|
|
323
|
+
// Save current state
|
|
324
|
+
const snapshot = store.getState().enrichments;
|
|
325
|
+
|
|
326
|
+
// Optimistic update
|
|
327
|
+
store.setState((state) => {
|
|
328
|
+
state.enrichments = state.enrichments.filter(e => e.id !== id);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
try {
|
|
332
|
+
await api.delete(`/enrichments/${id}`);
|
|
333
|
+
} catch (error) {
|
|
334
|
+
// Rollback on error
|
|
335
|
+
store.setState({ enrichments: snapshot });
|
|
336
|
+
toast.error('Failed to delete enrichment');
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
**Benefits:**
|
|
342
|
+
- Instant UI feedback
|
|
343
|
+
- Better perceived performance
|
|
344
|
+
- Improved UX
|
|
345
|
+
|
|
346
|
+
**Trade-offs:**
|
|
347
|
+
- Rollback complexity
|
|
348
|
+
- Race conditions possible
|
|
349
|
+
- Must handle conflicts
|
|
350
|
+
|
|
351
|
+
## API Patterns
|
|
352
|
+
|
|
353
|
+
### Pattern 7: tRPC Router Organization
|
|
354
|
+
|
|
355
|
+
**Description:**
|
|
356
|
+
Organize tRPC routers by domain with nested routers for admin vs user operations.
|
|
357
|
+
|
|
358
|
+
**When to Use:**
|
|
359
|
+
- Complex API with admin/user separation
|
|
360
|
+
- Multi-tenant applications
|
|
361
|
+
- Clear permission boundaries
|
|
362
|
+
|
|
363
|
+
**Implementation:**
|
|
364
|
+
```typescript
|
|
365
|
+
// Admin router (service role bypass RLS)
|
|
366
|
+
const adminOrgRouter = router({
|
|
367
|
+
list: publicProcedure.query(async () => {
|
|
368
|
+
const admin = getSupabaseAdmin();
|
|
369
|
+
return admin.from('organizations').select('*');
|
|
370
|
+
}),
|
|
371
|
+
|
|
372
|
+
create: publicProcedure
|
|
373
|
+
.input(z.object({ name: z.string() }))
|
|
374
|
+
.mutation(async ({ input }) => {
|
|
375
|
+
const admin = getSupabaseAdmin();
|
|
376
|
+
return admin.from('organizations').insert(input);
|
|
377
|
+
}),
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// Main router composition
|
|
381
|
+
export const appRouter = router({
|
|
382
|
+
admin: router({
|
|
383
|
+
organizations: adminOrgRouter,
|
|
384
|
+
courses: adminCourseRouter,
|
|
385
|
+
}),
|
|
386
|
+
courses: userCourseRouter,
|
|
387
|
+
});
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
**Benefits:**
|
|
391
|
+
- Clear permission separation
|
|
392
|
+
- Organized by domain
|
|
393
|
+
- Type-safe client
|
|
394
|
+
- Easy to test
|
|
395
|
+
|
|
396
|
+
**Trade-offs:**
|
|
397
|
+
- Router nesting complexity
|
|
398
|
+
- Type inference depth limits
|
|
399
|
+
|
|
400
|
+
### Pattern 8: Rate Limiting with Redis
|
|
401
|
+
|
|
402
|
+
**Description:**
|
|
403
|
+
Token bucket algorithm with Redis for API rate limiting.
|
|
404
|
+
|
|
405
|
+
**When to Use:**
|
|
406
|
+
- Public APIs
|
|
407
|
+
- AI generation endpoints (expensive operations)
|
|
408
|
+
- Abuse prevention
|
|
409
|
+
|
|
410
|
+
**Implementation:**
|
|
411
|
+
```typescript
|
|
412
|
+
import { Redis } from '@upstash/redis';
|
|
413
|
+
|
|
414
|
+
const redis = new Redis({
|
|
415
|
+
url: process.env.UPSTASH_REDIS_URL!,
|
|
416
|
+
token: process.env.UPSTASH_REDIS_TOKEN!,
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
export async function rateLimit(identifier: string, limit = 10, window = 60) {
|
|
420
|
+
const key = `rate_limit:${identifier}`;
|
|
421
|
+
|
|
422
|
+
const multi = redis.multi();
|
|
423
|
+
multi.incr(key);
|
|
424
|
+
multi.expire(key, window);
|
|
425
|
+
|
|
426
|
+
const [count] = await multi.exec<[number, number]>();
|
|
427
|
+
|
|
428
|
+
return {
|
|
429
|
+
success: count <= limit,
|
|
430
|
+
remaining: Math.max(0, limit - count),
|
|
431
|
+
reset: window,
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Usage in API route
|
|
436
|
+
export async function POST(req: Request) {
|
|
437
|
+
const ip = req.headers.get('x-forwarded-for') ?? 'unknown';
|
|
438
|
+
const { success, remaining } = await rateLimit(ip, 10, 60);
|
|
439
|
+
|
|
440
|
+
if (!success) {
|
|
441
|
+
return new Response('Rate limit exceeded', {
|
|
442
|
+
status: 429,
|
|
443
|
+
headers: { 'X-RateLimit-Remaining': remaining.toString() }
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Process request
|
|
448
|
+
}
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
**Benefits:**
|
|
452
|
+
- Abuse prevention
|
|
453
|
+
- Cost control (AI APIs)
|
|
454
|
+
- Fair usage enforcement
|
|
455
|
+
|
|
456
|
+
**Trade-offs:**
|
|
457
|
+
- Redis dependency
|
|
458
|
+
- Network latency
|
|
459
|
+
- Clock skew issues
|
|
460
|
+
|
|
461
|
+
## Monorepo Patterns
|
|
462
|
+
|
|
463
|
+
### Pattern 9: Package Structure by Domain
|
|
464
|
+
|
|
465
|
+
**Description:**
|
|
466
|
+
Organize packages by domain/purpose, not by type.
|
|
467
|
+
|
|
468
|
+
**Current Structure:**
|
|
469
|
+
```
|
|
470
|
+
packages/
|
|
471
|
+
├── course-gen-platform/ # Backend (Node.js, tRPC, BullMQ)
|
|
472
|
+
├── web/ # Frontend (Next.js 15)
|
|
473
|
+
├── shared-types/ # Shared TypeScript types
|
|
474
|
+
├── shared-logger/ # Shared logging utility
|
|
475
|
+
└── trpc-client-sdk/ # Generated tRPC client
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
**Benefits:**
|
|
479
|
+
- Clear boundaries
|
|
480
|
+
- Independent deployment
|
|
481
|
+
- Team ownership
|
|
482
|
+
- Easier onboarding
|
|
483
|
+
|
|
484
|
+
**Trade-offs:**
|
|
485
|
+
- Potential code duplication (intentional)
|
|
486
|
+
- Build order dependencies
|
|
487
|
+
|
|
488
|
+
### Pattern 10: Migration File Naming
|
|
489
|
+
|
|
490
|
+
**Description:**
|
|
491
|
+
Use timestamp-based migration files with descriptive names.
|
|
492
|
+
|
|
493
|
+
**Pattern:**
|
|
494
|
+
```
|
|
495
|
+
packages/course-gen-platform/supabase/migrations/
|
|
496
|
+
├── 20251229100000_add_course_visibility.sql
|
|
497
|
+
├── 20251229130000_add_share_token_index.sql
|
|
498
|
+
├── 20251230_01_organization_enums.sql
|
|
499
|
+
├── 20251230_02_organization_extensions.sql
|
|
500
|
+
├── 20251230_03_organization_members.sql
|
|
501
|
+
└── 20251230_04_organization_invitations.sql
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
**Benefits:**
|
|
505
|
+
- Chronological ordering
|
|
506
|
+
- Descriptive names
|
|
507
|
+
- Multi-step migrations (same day with sequence)
|
|
508
|
+
- Easy rollback identification
|
|
509
|
+
|
|
510
|
+
**Trade-offs:**
|
|
511
|
+
- Timestamp conflicts (rare)
|
|
512
|
+
- Must enforce naming convention
|
|
513
|
+
|
|
514
|
+
## Anti-Patterns to Avoid
|
|
515
|
+
|
|
516
|
+
### Anti-Pattern 1: God Components
|
|
517
|
+
|
|
518
|
+
**What:**
|
|
519
|
+
Single component with 1000+ lines handling multiple concerns.
|
|
520
|
+
|
|
521
|
+
**Why Bad:**
|
|
522
|
+
- Hard to test
|
|
523
|
+
- Poor reusability
|
|
524
|
+
- Difficult debugging
|
|
525
|
+
- Slow re-renders
|
|
526
|
+
|
|
527
|
+
**Solution:**
|
|
528
|
+
```typescript
|
|
529
|
+
// BAD: God component
|
|
530
|
+
function CourseEditor() {
|
|
531
|
+
// 1000+ lines: toolbar, canvas, sidebar, modals, state, API calls
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// GOOD: Composition
|
|
535
|
+
function CourseEditor() {
|
|
536
|
+
return (
|
|
537
|
+
<>
|
|
538
|
+
<Toolbar />
|
|
539
|
+
<Canvas />
|
|
540
|
+
<Sidebar />
|
|
541
|
+
<Modals />
|
|
542
|
+
</>
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
### Anti-Pattern 2: Prop Drilling
|
|
548
|
+
|
|
549
|
+
**What:**
|
|
550
|
+
Passing props through 5+ component levels.
|
|
551
|
+
|
|
552
|
+
**Why Bad:**
|
|
553
|
+
- Tight coupling
|
|
554
|
+
- Hard to refactor
|
|
555
|
+
- TypeScript noise
|
|
556
|
+
|
|
557
|
+
**Solution:**
|
|
558
|
+
```typescript
|
|
559
|
+
// BAD: Prop drilling
|
|
560
|
+
<Parent userId={userId}>
|
|
561
|
+
<Child userId={userId}>
|
|
562
|
+
<GrandChild userId={userId}>
|
|
563
|
+
<GreatGrandChild userId={userId} />
|
|
564
|
+
|
|
565
|
+
// GOOD: Context or Zustand
|
|
566
|
+
const useUser = () => useStore((s) => s.userId);
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
### Anti-Pattern 3: Untyped API Responses
|
|
570
|
+
|
|
571
|
+
**What:**
|
|
572
|
+
Using `any` or `unknown` for API responses.
|
|
573
|
+
|
|
574
|
+
**Why Bad:**
|
|
575
|
+
- Runtime errors
|
|
576
|
+
- No autocomplete
|
|
577
|
+
- Refactoring nightmares
|
|
578
|
+
|
|
579
|
+
**Solution:**
|
|
580
|
+
```typescript
|
|
581
|
+
// BAD
|
|
582
|
+
const data = await fetch('/api/courses') as any;
|
|
583
|
+
|
|
584
|
+
// GOOD: tRPC (type-safe by default)
|
|
585
|
+
const data = await trpc.courses.list.query();
|
|
586
|
+
|
|
587
|
+
// GOOD: Zod validation
|
|
588
|
+
const CourseSchema = z.object({ id: z.string(), title: z.string() });
|
|
589
|
+
const data = CourseSchema.parse(await response.json());
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
### Anti-Pattern 4: Massive Migrations
|
|
593
|
+
|
|
594
|
+
**What:**
|
|
595
|
+
Single migration file with 500+ lines changing multiple tables.
|
|
596
|
+
|
|
597
|
+
**Why Bad:**
|
|
598
|
+
- Hard to review
|
|
599
|
+
- Risky to rollback
|
|
600
|
+
- Difficult debugging
|
|
601
|
+
|
|
602
|
+
**Solution:**
|
|
603
|
+
```sql
|
|
604
|
+
-- BAD: Single massive migration
|
|
605
|
+
-- 20251230_big_refactor.sql (500 lines)
|
|
606
|
+
|
|
607
|
+
-- GOOD: Sequential migrations
|
|
608
|
+
-- 20251230_01_organization_enums.sql
|
|
609
|
+
-- 20251230_02_organization_extensions.sql
|
|
610
|
+
-- 20251230_03_organization_members.sql
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
## Security Best Practices
|
|
614
|
+
|
|
615
|
+
### Practice 1: RLS (Row Level Security) First
|
|
616
|
+
|
|
617
|
+
Always enable RLS on tables with multi-tenant data:
|
|
618
|
+
|
|
619
|
+
```sql
|
|
620
|
+
-- Enable RLS
|
|
621
|
+
ALTER TABLE courses ENABLE ROW LEVEL SECURITY;
|
|
622
|
+
|
|
623
|
+
-- Policy: Users see only their organization's courses
|
|
624
|
+
CREATE POLICY "org_isolation"
|
|
625
|
+
ON courses FOR ALL
|
|
626
|
+
USING (organization_id IN (
|
|
627
|
+
SELECT organization_id
|
|
628
|
+
FROM organization_members
|
|
629
|
+
WHERE user_id = auth.uid()
|
|
630
|
+
));
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
### Practice 2: Service Role Key Security
|
|
634
|
+
|
|
635
|
+
Never expose service role key to client:
|
|
636
|
+
|
|
637
|
+
```typescript
|
|
638
|
+
// BAD: Client-side
|
|
639
|
+
const supabase = createClient(url, SERVICE_ROLE_KEY); // Exposed!
|
|
640
|
+
|
|
641
|
+
// GOOD: Server-side only
|
|
642
|
+
// packages/web/lib/supabase-admin.ts (server)
|
|
643
|
+
export const supabaseAdmin = createClient(url, SERVICE_ROLE_KEY);
|
|
644
|
+
|
|
645
|
+
// packages/web/app/api/courses/route.ts (server)
|
|
646
|
+
import { supabaseAdmin } from '@/lib/supabase-admin';
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
### Practice 3: Input Validation
|
|
650
|
+
|
|
651
|
+
Use Zod schemas for all user inputs:
|
|
652
|
+
|
|
653
|
+
```typescript
|
|
654
|
+
// Define schema
|
|
655
|
+
const CreateCourseInput = z.object({
|
|
656
|
+
title: z.string().min(3).max(100),
|
|
657
|
+
organizationId: z.string().uuid(),
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
// Validate in API
|
|
661
|
+
export async function POST(req: Request) {
|
|
662
|
+
const body = await req.json();
|
|
663
|
+
const validated = CreateCourseInput.parse(body); // Throws on invalid
|
|
664
|
+
|
|
665
|
+
// Safe to use validated data
|
|
666
|
+
await createCourse(validated);
|
|
667
|
+
}
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
## Performance Considerations
|
|
671
|
+
|
|
672
|
+
### Optimization 1: React Query for Server State
|
|
673
|
+
|
|
674
|
+
```typescript
|
|
675
|
+
import { useQuery } from '@tanstack/react-query';
|
|
676
|
+
|
|
677
|
+
function CourseList() {
|
|
678
|
+
const { data, isLoading } = useQuery({
|
|
679
|
+
queryKey: ['courses'],
|
|
680
|
+
queryFn: () => trpc.courses.list.query(),
|
|
681
|
+
staleTime: 5 * 60 * 1000, // 5 minutes
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
**Benefits:**
|
|
687
|
+
- Automatic caching
|
|
688
|
+
- Background refetch
|
|
689
|
+
- Optimistic updates
|
|
690
|
+
- Request deduplication
|
|
691
|
+
|
|
692
|
+
### Optimization 2: Database Indexing
|
|
693
|
+
|
|
694
|
+
```sql
|
|
695
|
+
-- Index for foreign key lookups
|
|
696
|
+
CREATE INDEX idx_courses_organization_id ON courses(organization_id);
|
|
697
|
+
|
|
698
|
+
-- Composite index for filtered queries
|
|
699
|
+
CREATE INDEX idx_courses_org_deleted ON courses(organization_id, deleted_at)
|
|
700
|
+
WHERE deleted_at IS NULL;
|
|
701
|
+
|
|
702
|
+
-- Partial index for active records
|
|
703
|
+
CREATE INDEX idx_active_courses ON courses(created_at)
|
|
704
|
+
WHERE deleted_at IS NULL;
|
|
705
|
+
```
|
|
706
|
+
|
|
707
|
+
### Optimization 3: Lazy Loading
|
|
708
|
+
|
|
709
|
+
```typescript
|
|
710
|
+
// Code splitting
|
|
711
|
+
const CourseEditor = lazy(() => import('./CourseEditor'));
|
|
712
|
+
|
|
713
|
+
// Component lazy loading
|
|
714
|
+
<Suspense fallback={<Skeleton />}>
|
|
715
|
+
<CourseEditor />
|
|
716
|
+
</Suspense>
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
## Tools and Resources
|
|
720
|
+
|
|
721
|
+
### Recommended Tools
|
|
722
|
+
|
|
723
|
+
- **Database:** Supabase (PostgreSQL with RLS)
|
|
724
|
+
- **State Management:** Zustand + Immer
|
|
725
|
+
- **API:** tRPC (type-safe)
|
|
726
|
+
- **Validation:** Zod schemas
|
|
727
|
+
- **Package Manager:** pnpm (monorepo)
|
|
728
|
+
- **Rate Limiting:** Upstash Redis
|
|
729
|
+
|
|
730
|
+
### Migration Commands
|
|
731
|
+
|
|
732
|
+
```bash
|
|
733
|
+
# Create new migration
|
|
734
|
+
pnpm supabase migration new add_feature_name
|
|
735
|
+
|
|
736
|
+
# Apply migrations locally
|
|
737
|
+
pnpm supabase migration up
|
|
738
|
+
|
|
739
|
+
# Apply migrations to production
|
|
740
|
+
pnpm supabase db push
|
|
741
|
+
|
|
742
|
+
# Generate TypeScript types
|
|
743
|
+
pnpm supabase gen types typescript --project-id PROJECT_REF > types.ts
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
## Conclusion
|
|
747
|
+
|
|
748
|
+
These patterns are extracted from real production code in the MegaCampusAI monorepo. They represent battle-tested solutions to common architectural challenges in modern full-stack TypeScript applications.
|
|
749
|
+
|
|
750
|
+
Key takeaways:
|
|
751
|
+
1. Intentional duplication can be better than forced abstraction
|
|
752
|
+
2. Single source of truth for types prevents drift
|
|
753
|
+
3. RLS provides database-level security
|
|
754
|
+
4. Immer simplifies complex state updates
|
|
755
|
+
5. Migration files should be small and sequential
|