Mario Brusarosco

peekaboo react sdk phase0 setup

In the ground since Sat Nov 08 2025

Last watered inSun Nov 16 2025

Related Topics

Referece Links

Recap

Successfully completed Phase 0 (Prerequisites + Package Setup) for the Peek-a-boo React SDK. This phase accomplished two major goals:

  1. 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.

  2. 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:

1model FeatureFlag {
2 id String
3 name String
4 value Json
5 projectId String
6 organizationId String
7 // ❌ NO environment field
8}

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:

1model FeatureFlag {
2 id String @id @default(cuid())
3 key String // Unique identifier (e.g., "new-checkout")
4 name String // Display name
5 description String? // Optional description
6 enabled Boolean @default(false)
7 value Json // For future variations
8 environment Environment @default(DEVELOPMENT)
9
10 projectId String
11 organizationId String
12
13 // ONE flag key per project per environment
14 @@unique([key, projectId, environment])
15}
16
17enum Environment {
18 DEVELOPMENT
19 STAGING
20 PRODUCTION
21}

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

  1. Update Prisma schema
  2. Run migration (interactive required)
  3. 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:

  1. Where is data created? (Dashboard form → action → API)
  2. Where is data read? (API endpoints → SDK)
  3. 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

  1. Hardcoded Organization Concept - Dashboard still requires organization context. Future: Multi-tenant support needed.

  2. No API Key Authentication - Runtime endpoints currently accept any projectId. Future: Implement SDK key validation.

  3. No Rate Limiting - Production deployment needs rate limiting on runtime endpoints.

  4. 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