In the ground since Sat Nov 08 2025
Last watered inSun Nov 16 2025
Recap
Successfully completed Phase 0 (Prerequisites + Package Setup) for the Peek-a-boo React SDK. This phase accomplished two major goals:
-
Prerequisites: Implemented environment-aware feature flags with proper database schema, migrations, and SDK service endpoints. The critical blocker was lack of environment support in the database schema - feature flags were only scoped to organization/project without dev/staging/prod separation.
-
Package Setup: Created the @peek-a-boo/react-sdk package structure with TypeScript, Vite build configuration, and monorepo integration ready for Phase 1 implementation.
Key Achievement: Created a production-ready foundation where flags can be managed per environment, with proper API endpoints for the React SDK to consume, and a zero-dependency package structure ready for core client implementation.
The Problem
Initial assessment revealed the React SDK design assumed:
1GET /api/v1/flags?environment=production
But the database schema had NO environment concept:
Flags were only scoped to Organization + Project. This wouldn't support:
- Different flag states in dev vs production
- Safe testing before production rollout
- Environment-specific feature releases
The Solution
1. Schema Evolution
Added environment support with proper constraints:
Critical Design Decision: The unique constraint [key, projectId, environment] allows the same flag key (e.g., "new-checkout") to exist with different states across environments.
2. Migration Strategy
Lesson Learned: NEVER use workarounds for migrations. Always create proper migration files.
The correct flow:
1cd packages/core
2npx prisma migrate dev --name add_environment_and_key_to_feature_flags
This creates:
- Migration SQL file in prisma/migrations/
- Updates Prisma client with new types
- Applies changes to database
- Maintains migration history for production deployments
Why this matters:
- ✅ Reproducible across environments
- ✅ Can be committed to git
- ✅ Can be rolled back
- ✅ Safe for production with prisma migrate deploy
3. SDK Service Runtime API
Created a new domain module sdk-runtime (separate from admin feature-flags module):
1// apps/sdk-service/src/domains/sdk-runtime/
2
3GET /api/v1/flags?projectId={id}&environment={env}
4// Returns: { flags: [...], environment: "DEVELOPMENT" }
5
6GET /api/v1/flags/{key}?projectId={id}&environment={env}
7// Returns: { key, enabled, value, environment, ... }
Design Pattern: Runtime API is separate from admin API because:
- Different authentication (SDK keys vs user auth)
- Different data needs (runtime needs minimal, fast responses)
- Different caching strategies
- Different rate limiting requirements
4. Environment Variable Architecture
Problem: Hardcoded organization IDs scattered across Dashboard and seed file led to foreign key violations after database resets.
Solution: Single source of truth via environment variables:
1# packages/core/.env
2SEED_ORGANIZATION_ID="cmhqxjwpw000e2ikeps545uou"
3
4# apps/dashboard/.env.local
5ORGANIZATION_ID="cmhqxjwpw000e2ikeps545uou"
Seed file uses upsert to ensure consistent org ID:
1const SEED_ORG_ID = process.env.SEED_ORGANIZATION_ID || 'default-id';
2
3await prisma.organization.upsert({
4 where: { id: SEED_ORG_ID },
5 update: { name: organization.name },
6 create: { id: SEED_ORG_ID, name: organization.name },
7});
Benefits:
- Seed always creates same org ID
- Dashboard always uses same org ID
- Easy to change in one place
- No foreign key violations
Anatomy of a Complete System Fix
Phase 1: Schema Changes
- Update Prisma schema
- Run migration (interactive required)
- Verify Prisma client regenerated
Phase 2: Cascade Updates
Think through the entire system:
- Seed file - Must use new required fields
- Service DTOs - Must include new fields
- Dashboard actions - Must send new fields
- API endpoints - Must handle new fields
Critical Thinking Required: When changing schema, don't just fix one file. Trace through:
- Where is data created? (Dashboard form → action → API)
- Where is data read? (API endpoints → SDK)
- Where is data seeded? (Seed file)
Each layer needs updating, not just the schema.
Phase 3: Validation
Test the entire flow:
1# 1. Seed database
2pnpm run prisma:seed
3
4# 2. Create flag via Dashboard
5# (Opens form, submits, checks for errors)
6
7# 3. Query via API
8curl "http://localhost:6001/api/v1/flags?projectId=xxx&environment=development"
9
10# 4. Verify different environments work
11curl "http://localhost:6001/api/v1/flags?projectId=xxx&environment=production"
Package Setup & Monorepo Integration
With the database foundation and API endpoints complete, Phase 0 continues with creating the React SDK package structure and integrating it into the Turborepo monorepo.
Package Structure Created
1packages/react-sdk/
2├── src/
3│ ├── client/ # Vanilla JS client (no React)
4│ ├── react/ # React-specific code
5│ │ └── hooks/ # useFeatureFlag, useFeatureFlags
6│ └── test/ # Test utilities and setup
7├── package.json
8├── tsconfig.json
9└── vite.config.ts
Design Decision: Separate client/ from react/ to enable future framework adapters (Vue, Svelte) to reuse the core client logic.
Package Configuration
packages/react-sdk/package.json:
1{
2 "name": "@peek-a-boo/react-sdk",
3 "version": "0.0.1",
4 "type": "module",
5 "main": "dist/index.js",
6 "module": "dist/index.esm.js",
7 "types": "dist/index.d.ts",
8
9 "peerDependencies": {
10 "react": ">=16.8.0" // Hooks support
11 },
12
13 "devDependencies": {
14 "@types/react": "^18.2.0",
15 "@testing-library/react": "^14.0.0",
16 "@testing-library/react-hooks": "^8.0.1",
17 "@vitest/ui": "^1.0.0",
18 "jsdom": "^23.0.0",
19 "msw": "^2.0.0",
20 "typescript": "^5.0.0",
21 "vite": "^5.0.0",
22 "vitest": "^1.0.0"
23 }
24}
Key Characteristics:
- Zero runtime dependencies - Only React as peer dependency
- ESM + CJS outputs - Supports both module systems
- Testing stack: Vitest + Testing Library + MSW (for API mocking)
- Target bundle size: less than 10KB (goal from PDR)
TypeScript Configuration
packages/react-sdk/tsconfig.json:
1{
2 "compilerOptions": {
3 "target": "ES2020",
4 "module": "ESNext",
5 "lib": ["ES2020", "DOM"],
6 "jsx": "react",
7 "declaration": true, // Generate .d.ts files
8 "outDir": "dist",
9 "strict": true, // Full type safety
10 "moduleResolution": "bundler",
11 "esModuleInterop": true,
12 "skipLibCheck": true,
13 "forceConsistentCasingInFileNames": true,
14 "resolveJsonModule": true,
15 "isolatedModules": true,
16 "noEmit": true
17 },
18 "include": ["src"],
19 "exclude": ["node_modules", "dist"]
20}
Design Choices:
- strict: true - Catch errors at compile time
- declaration: true - TypeScript users get full autocomplete
- moduleResolution: "bundler" - Modern resolution for Vite
- noEmit: true - Vite handles building, TSC only type-checks
Vite Build Configuration
packages/react-sdk/vite.config.ts:
1import { defineConfig } from 'vitest/config';
2import { resolve } from 'path';
3
4export default defineConfig({
5 build: {
6 lib: {
7 entry: resolve(__dirname, 'src/index.ts'),
8 name: 'PeekabooReactSDK',
9 formats: ['es', 'cjs'],
10 fileName: (format) => `index.${format === 'es' ? 'esm' : format}.js`
11 },
12 rollupOptions: {
13 external: ['react'],
14 output: {
15 globals: { react: 'React' }
16 }
17 }
18 },
19 test: {
20 globals: true,
21 environment: 'jsdom',
22 setupFiles: './src/test/setup.ts'
23 }
24});
Build Strategy:
- Library mode - Not building an app, building a distributable package
- External React - Don't bundle React, expect consumer to provide it
- Two formats:
- dist/index.esm.js - For modern bundlers (tree-shakeable)
- dist/index.cjs.js - For legacy Node/CommonJS
Test Strategy:
- jsdom environment (simulates browser DOM)
- Global test utilities (describe, it, expect available everywhere)
- MSW setup file for mocking API requests
Critical Fix: Vitest Types
Initial version used import { defineConfig } from 'vite' which caused TypeScript error:
1Object literal may only specify known properties, and 'test' does not exist
Solution: Import from vitest/config instead:
1import { defineConfig } from 'vitest/config'; // ✅ Includes test types
This provides proper typing for the test configuration block.
Monorepo Integration
The React SDK automatically integrates with the existing Turborepo configuration:
Root turbo.json:
1{
2 "tasks": {
3 "build": {
4 "dependsOn": ["^build"],
5 "outputs": ["dist/**", ".next/**"]
6 }
7 }
8}
Why it works:
- "^build" dependency means build dependencies first
- "dist/**" output pattern matches React SDK output
- No package-specific configuration needed
Build command:
1# Build React SDK (builds @peek-a-boo/core first if needed)
2pnpm build --filter=@peek-a-boo/react-sdk
3
4# Or use the root-level script
5pnpm build:react-sdk
Installation & Verification
1# Install all dependencies
2pnpm install
3
4# Verify TypeScript setup (will error until source files created)
5cd packages/react-sdk
6pnpm run type:check
7
8# Expected: "No inputs were found" - normal, no .ts files yet
Phase 0 Completion Status
Prerequisites: ✅
- Schema supports environments
- Migration applied
- API endpoints created
- Environment variables configured
Package Setup: ✅
- Directory structure created
- package.json configured
- TypeScript configured
- Vite build configured
- Monorepo integration verified
- Dependencies installed
Ready for Phase 1: ✅ All foundational work complete
Used in Projects
- Peek-a-boo Feature Flag Platform - Foundation for React SDK development
- Pattern applicable to any feature flag system requiring environment separation
Key Lessons
1. Schema Design First
Don't build APIs before validating the database schema supports your use case. The React SDK design assumed environment support - checking the schema FIRST saved potential rework.
2. Think Systemically
Changing a Prisma schema isn't done until:
- Migration created ✅
- Seed updated ✅
- DTOs updated ✅
- API endpoints updated ✅
- Dashboard updated ✅
- All tested ✅
Missing ANY step breaks the system.
3. Environment Variables > Hardcoding
A single hardcoded ID becomes technical debt the moment you need to reset the database. Use env vars from day 1.
4. Separation of Concerns
Runtime API (/api/v1/flags) separate from Admin API (/feature-flags) because they serve different purposes, different clients, different security models.
5. Documentation Matters
The .env.example files with comments explain WHY each variable exists and HOW they relate:
1# Development organization ID (must match SEED_ORGANIZATION_ID in packages/core/.env)
2ORGANIZATION_ID=cmhqxjwpw000e2ikeps545uou
Next Steps
With Phase 0 complete, the React SDK can now proceed:
Phase 1: Core Client Implementation
- TypeScript types matching API response
- HTTP client with retry logic
- FeatureFlagClient (vanilla JS)
The foundation is solid:
- ✅ Schema supports environments
- ✅ API endpoints ready
- ✅ Data seeded and testable
- ✅ Environment variables configured
Technical Debt Identified
-
Hardcoded Organization Concept - Dashboard still requires organization context. Future: Multi-tenant support needed.
-
No API Key Authentication - Runtime endpoints currently accept any projectId. Future: Implement SDK key validation.
-
No Rate Limiting - Production deployment needs rate limiting on runtime endpoints.
-
Manual Param Awaiting - Next.js 15 warnings about params.id needing await. Minor but should be addressed.
Commands Reference
1# Database Migration
2cd packages/core
3npx prisma migrate dev --name your_migration_name
4
5# Seed Database
6pnpm run prisma:seed
7
8# Test Runtime API
9curl "http://localhost:6001/api/v1/flags?projectId=ID&environment=development"
10
11# Check Migration Status
12cd packages/core
13npx prisma migrate status
Debugging Tips
Foreign Key Violations:
- Check organization/project IDs exist in database
- Verify env vars match between seed and dashboard
- Use npx prisma studio to inspect actual IDs
Migration Issues:
- Always run from packages/core directory
- Ensure .env file exists in packages/core
- Use prisma migrate reset --force to start fresh (dev only!)
Type Errors After Schema Changes:
- Ensure Prisma client regenerated: npx prisma generate
- Restart TypeScript server in IDE
- Check all imports use Environment enum type, not strings