# Prism - Specification v1.3

> **"One spec, full spectrum."**

Prism is a code generation framework that enables defining data models once in Python/Pydantic and generating consistent CRUD operations, list endpoints (with filtering, sorting, pagination), and UI components across the entire stack—without losing your customizations on regeneration.

---

## 1. Overview

### 1.1 Design Principles

- **Spec as Code**: All specifications are defined in Python using Pydantic models, not YAML/JSON
- **Code-First**: Models are the source of truth; everything else is generated
- **Selective Exposure**: Fine-grained control over what gets exposed where (REST, GraphQL, MCP, Frontend)
- **Build-Time Generation**: Code is generated at build time, producing static, inspectable output
- **Type Safety**: End-to-end type safety from database to frontend
- **Testing Built-In**: Test scaffolding generated alongside implementation code
- **Extend, Don't Overwrite**: Downstream customizations are preserved across regenerations
- **Pluggable UI**: Default widgets with dependency injection for customization

### 1.2 Technology Stack

| Layer | Technology |
|-------|------------|
| Database | PostgreSQL / SQLite |
| ORM | SQLAlchemy |
| Migrations | Alembic |
| Validation | Pydantic |
| REST API | FastAPI |
| GraphQL | Strawberry GraphQL |
| MCP | FastMCP |
| Frontend | Vite + React + TypeScript + Tailwind |
| GraphQL Client | urql / Apollo Client |
| Testing | pytest (backend) / Vitest (frontend) |

---

## 2. Extensibility Architecture

Prism generates code that can be extended downstream without losing customizations when you regenerate. This is achieved through a layered architecture with clear extension points.

### 2.1 File Generation Strategies

```python
class FileStrategy(str, Enum):
    """How Prism handles each generated file."""

    ALWAYS_OVERWRITE = "always_overwrite"
    # Always regenerate. Use for pure boilerplate.
    # Examples: types/generated.ts, graphql operations

    GENERATE_ONCE = "generate_once"
    # Only create if doesn't exist. Never overwrite.
    # Examples: custom hooks, page components

    GENERATE_BASE = "generate_base"
    # Generate into _base or .generated files, user extends.
    # Examples: services, forms, tables

    MERGE = "merge"
    # Smart merge with conflict markers.
    # Examples: schema.py aggregation files
```

### 2.2 Base + Extension Pattern

Generated code uses inheritance and composition, allowing customization without modifying generated files.

```
src/
├── backend/
│   ├── services/
│   │   ├── _generated/              # ⚠️ ALWAYS OVERWRITTEN
│   │   │   ├── __init__.py
│   │   │   ├── customer_base.py     # Generated base class
│   │   │   └── order_base.py
│   │   │
│   │   ├── customer.py              # ✅ YOUR CODE - extends base
│   │   ├── order.py                 # ✅ YOUR CODE - extends base
│   │   └── __init__.py
│   │
│   └── api/
│       └── graphql/
│           ├── _generated/          # ⚠️ ALWAYS OVERWRITTEN
│           │   ├── types/
│           │   ├── queries/
│           │   └── mutations/
│           │
│           ├── types/               # ✅ YOUR EXTENSIONS
│           │   └── customer.py      # Extend generated types
│           ├── queries/             # ✅ YOUR EXTENSIONS
│           │   └── custom.py        # Add custom queries
│           └── schema.py            # Auto-imports both
│
└── frontend/
    ├── components/
    │   ├── _generated/              # ⚠️ ALWAYS OVERWRITTEN
    │   │   ├── CustomerFormBase.tsx
    │   │   └── CustomerTableBase.tsx
    │   │
    │   └── customer/
    │       ├── CustomerForm.tsx     # ✅ YOUR CODE - extends base
    │       └── CustomerTable.tsx    # ✅ YOUR CODE - extends base
```

### 2.3 Backend Extension Pattern

```python
# src/backend/services/_generated/customer_base.py
# ⚠️ AUTO-GENERATED BY PRISM - DO NOT EDIT
from __future__ import annotations
from ...models.customer import Customer
from ...schemas.customer import CustomerCreate, CustomerUpdate
from .base import BaseService


class CustomerServiceBase(BaseService[Customer, CustomerCreate, CustomerUpdate]):
    """
    Generated base service for Customer.
    Extend this class in services/customer.py to add custom logic.
    """
    model = Customer

    async def list(self, db, *, filters=None, pagination=None, sort=None):
        # Generated implementation
        ...

    async def create(self, db, *, data):
        # Generated implementation
        ...
```

```python
# src/backend/services/customer.py
# ✅ YOUR CODE - Safe to modify, will not be overwritten
from ._generated.customer_base import CustomerServiceBase


class CustomerService(CustomerServiceBase):
    """
    Custom service logic for Customer.
    This file is generated once and never overwritten.
    """

    async def create(self, db, *, data):
        # Custom: normalize email before creating
        data.email = data.email.lower()
        return await super().create(db, data=data)

    async def calculate_lifetime_value(self, db, customer_id: int) -> float:
        """Custom method not in generated base."""
        # Your custom business logic
        ...
```

### 2.4 GraphQL Extension Pattern

```python
# src/backend/api/graphql/_generated/queries/customer.py
# ⚠️ AUTO-GENERATED BY PRISM - DO NOT EDIT
import strawberry
from ....services.customer import CustomerService


@strawberry.type
class CustomerQueriesBase:
    @strawberry.field
    async def customer(self, info, id: str) -> CustomerType | None:
        ...

    @strawberry.field
    async def customers(self, info, filter: CustomerFilterInput | None = None):
        ...
```

```python
# src/backend/api/graphql/queries/customer.py
# ✅ YOUR CODE - Extend generated queries
import strawberry
from .._generated.queries.customer import CustomerQueriesBase


@strawberry.type
class CustomerQueries(CustomerQueriesBase):
    """Extended customer queries with custom fields."""

    @strawberry.field
    async def customer_stats(self, info) -> CustomerStats:
        """Custom query not in generated code."""
        ...

    @strawberry.field
    async def top_customers(self, info, limit: int = 10) -> list[CustomerType]:
        """Another custom query."""
        ...
```

### 2.5 Extension Configuration

```python
class ExtensionConfig(BaseModel):
    """Configure how Prism handles extensions."""

    # Backend
    services_strategy: FileStrategy = FileStrategy.GENERATE_BASE
    graphql_types_strategy: FileStrategy = FileStrategy.GENERATE_BASE
    graphql_queries_strategy: FileStrategy = FileStrategy.GENERATE_BASE
    graphql_mutations_strategy: FileStrategy = FileStrategy.GENERATE_BASE
    rest_endpoints_strategy: FileStrategy = FileStrategy.GENERATE_BASE

    # Frontend
    components_strategy: FileStrategy = FileStrategy.GENERATE_BASE
    hooks_strategy: FileStrategy = FileStrategy.GENERATE_ONCE
    pages_strategy: FileStrategy = FileStrategy.GENERATE_ONCE

    # Always overwritten (pure generated)
    # - types/generated.ts
    # - graphql operations (.graphql files)
    # - _generated/ directories

    # Merge locations (combined generated + custom)
    schema_assembly: FileStrategy = FileStrategy.MERGE
    router_assembly: FileStrategy = FileStrategy.MERGE


class StackSpec(BaseModel):
    # ... existing fields ...
    extensions: ExtensionConfig = Field(default_factory=ExtensionConfig)
```

### 2.6 Protected Regions (Alternative Pattern)

For files that need inline customization, Prism supports protected regions:

```python
# src/backend/services/customer.py
from ._generated.customer_base import CustomerServiceBase


class CustomerService(CustomerServiceBase):
    # PRISM:PROTECTED:START - Custom imports
    from ..utils.email import normalize_email
    # PRISM:PROTECTED:END

    async def create(self, db, *, data):
        # PRISM:PROTECTED:START - Pre-create hook
        data.email = normalize_email(data.email)
        # PRISM:PROTECTED:END

        return await super().create(db, data=data)

    # PRISM:PROTECTED:START - Custom methods
    async def send_welcome_email(self, customer_id: int):
        ...
    # PRISM:PROTECTED:END
```

Protected regions are preserved during regeneration.

---

## 3. Widget System (Frontend)

Prism provides a pluggable widget system with sensible defaults that can be overridden at any level.

### 3.1 Widget Architecture

```typescript
// src/frontend/prism/widgets/types.ts

/** Base props all widgets receive */
export interface WidgetBaseProps<T = unknown> {
  name: string;
  value: T;
  onChange: (value: T) => void;
  label?: string;
  description?: string;
  placeholder?: string;
  required?: boolean;
  disabled?: boolean;
  error?: string;
  className?: string;
}

/** Widget component type */
export type Widget<T = unknown> = React.ComponentType<WidgetBaseProps<T>>;

/** Widget registry key */
export type WidgetKey =
  | FieldType           // Field type default
  | `${string}.${string}` // Model.field specific
  | string;             // Custom key
```

### 3.2 Default Widget Mapping

```typescript
// src/frontend/prism/widgets/defaults.ts

import {
  TextInput,
  TextArea,
  NumberInput,
  DecimalInput,
  Checkbox,
  DatePicker,
  DateTimePicker,
  TimePicker,
  Select,
  MultiSelect,
  JsonEditor,
  RelationSelect,
  FileUpload,
} from "./components";

/** Default widgets for each field type */
export const DEFAULT_WIDGETS: Record<FieldType, Widget> = {
  string: TextInput,
  text: TextArea,
  integer: NumberInput,
  float: NumberInput,
  decimal: DecimalInput,
  boolean: Checkbox,
  date: DatePicker,
  datetime: DateTimePicker,
  time: TimePicker,
  uuid: TextInput,
  json: JsonEditor,
  enum: Select,
  foreign_key: RelationSelect,
};

/**
 * UI widget hint overrides.
 * These match the ui_widget field in FieldSpec.
 */
export const UI_WIDGET_MAP: Record<string, Widget> = {
  textarea: TextArea,
  richtext: RichTextEditor,
  markdown: MarkdownEditor,
  datepicker: DatePicker,
  datetimepicker: DateTimePicker,
  timepicker: TimePicker,
  select: Select,
  multiselect: MultiSelect,
  radio: RadioGroup,
  checkbox: Checkbox,
  switch: Switch,
  slider: Slider,
  rating: RatingInput,
  color: ColorPicker,
  file: FileUpload,
  image: ImageUpload,
  password: PasswordInput,
  email: EmailInput,
  url: UrlInput,
  phone: PhoneInput,
  currency: CurrencyInput,
  percentage: PercentageInput,
  tags: TagInput,
  json: JsonEditor,
  code: CodeEditor,
};
```

### 3.3 Widget Registry (Dependency Injection)

```typescript
// src/frontend/prism/widgets/registry.ts

import { DEFAULT_WIDGETS, UI_WIDGET_MAP } from "./defaults";

class WidgetRegistry {
  private widgets: Map<string, Widget> = new Map();
  private defaults = DEFAULT_WIDGETS;
  private uiWidgets = UI_WIDGET_MAP;

  /**
   * Register a custom widget.
   *
   * @example
   * // Override all string fields
   * registry.register("string", MyCustomTextInput);
   *
   * // Override specific model.field
   * registry.register("Customer.email", EmailWithValidation);
   *
   * // Register new ui_widget type
   * registry.register("ui:address", AddressAutocomplete);
   */
  register(key: WidgetKey, widget: Widget): void {
    this.widgets.set(key, widget);
  }

  /**
   * Bulk register widgets.
   */
  registerAll(widgets: Record<WidgetKey, Widget>): void {
    Object.entries(widgets).forEach(([key, widget]) => {
      this.register(key, widget);
    });
  }

  /**
   * Get widget for a field.
   * Resolution order:
   * 1. Model.field specific (e.g., "Customer.email")
   * 2. ui_widget hint (e.g., "ui:richtext")
   * 3. Field type default (e.g., "string")
   * 4. Fallback to TextInput
   */
  resolve(
    modelName: string,
    fieldName: string,
    fieldType: FieldType,
    uiWidget?: string
  ): Widget {
    // 1. Check model.field specific override
    const specificKey = `${modelName}.${fieldName}`;
    if (this.widgets.has(specificKey)) {
      return this.widgets.get(specificKey)!;
    }

    // 2. Check ui_widget hint
    if (uiWidget) {
      const uiKey = `ui:${uiWidget}`;
      if (this.widgets.has(uiKey)) {
        return this.widgets.get(uiKey)!;
      }
      if (this.uiWidgets[uiWidget]) {
        return this.uiWidgets[uiWidget];
      }
    }

    // 3. Check field type override
    if (this.widgets.has(fieldType)) {
      return this.widgets.get(fieldType)!;
    }

    // 4. Return default for field type
    return this.defaults[fieldType] ?? TextInput;
  }

  /**
   * Create a scoped registry for a specific context.
   */
  scoped(): WidgetRegistry {
    const scoped = new WidgetRegistry();
    scoped.widgets = new Map(this.widgets);
    return scoped;
  }
}

export const widgetRegistry = new WidgetRegistry();
```

### 3.4 Widget Provider (React Context)

```typescript
// src/frontend/prism/widgets/context.tsx

import React, { createContext, useContext, useMemo } from "react";
import { widgetRegistry, WidgetRegistry } from "./registry";

const WidgetContext = createContext<WidgetRegistry>(widgetRegistry);

export interface WidgetProviderProps {
  children: React.ReactNode;
  /** Additional widgets to register in this scope */
  widgets?: Record<WidgetKey, Widget>;
  /** Replace the entire registry (not recommended) */
  registry?: WidgetRegistry;
}

export function WidgetProvider({
  children,
  widgets,
  registry
}: WidgetProviderProps) {
  const parentRegistry = useContext(WidgetContext);

  const scopedRegistry = useMemo(() => {
    const base = registry ?? parentRegistry.scoped();
    if (widgets) {
      base.registerAll(widgets);
    }
    return base;
  }, [registry, widgets, parentRegistry]);

  return (
    <WidgetContext.Provider value={scopedRegistry}>
      {children}
    </WidgetContext.Provider>
  );
}

export function useWidgetRegistry(): WidgetRegistry {
  return useContext(WidgetContext);
}

export function useWidget(
  modelName: string,
  fieldName: string,
  fieldType: FieldType,
  uiWidget?: string
): Widget {
  const registry = useWidgetRegistry();
  return registry.resolve(modelName, fieldName, fieldType, uiWidget);
}
```

### 3.5 Using Widgets in Generated Forms

```typescript
// src/frontend/components/_generated/CustomerFormBase.tsx
// ⚠️ AUTO-GENERATED BY PRISM - DO NOT EDIT

import { useForm } from "react-hook-form";
import { useWidgetRegistry } from "../../prism/widgets/context";
import { CustomerCreateInput } from "../../types/generated";
import { CUSTOMER_FIELD_SPECS } from "../../prism/specs/customer";

export interface CustomerFormBaseProps {
  onSubmit: (data: CustomerCreateInput) => void;
  defaultValues?: Partial<CustomerCreateInput>;
  loading?: boolean;
}

export function CustomerFormBase({
  onSubmit,
  defaultValues,
  loading,
}: CustomerFormBaseProps) {
  const registry = useWidgetRegistry();
  const form = useForm<CustomerCreateInput>({ defaultValues });

  return (
    <form onSubmit={form.handleSubmit(onSubmit)}>
      {CUSTOMER_FIELD_SPECS.map((field) => {
        const Widget = registry.resolve(
          "Customer",
          field.name,
          field.type,
          field.uiWidget
        );

        return (
          <Widget
            key={field.name}
            name={field.name}
            label={field.displayName ?? field.name}
            description={field.description}
            placeholder={field.placeholder}
            required={field.required}
            disabled={loading}
            error={form.formState.errors[field.name]?.message}
            value={form.watch(field.name)}
            onChange={(value) => form.setValue(field.name, value)}
            {...field.widgetProps}
          />
        );
      })}

      <button type="submit" disabled={loading}>
        {loading ? "Saving..." : "Save"}
      </button>
    </form>
  );
}
```

### 3.6 Extending Forms

```typescript
// src/frontend/components/customer/CustomerForm.tsx
// ✅ YOUR CODE - Extends generated base

import { CustomerFormBase, CustomerFormBaseProps } from "../_generated/CustomerFormBase";
import { WidgetProvider } from "../../prism/widgets/context";
import { MyCustomEmailWidget } from "../widgets/MyCustomEmailWidget";

interface CustomerFormProps extends CustomerFormBaseProps {
  showNotes?: boolean;
}

export function CustomerForm({ showNotes = true, ...props }: CustomerFormProps) {
  return (
    <WidgetProvider
      widgets={{
        // Override email widget just for this form
        "Customer.email": MyCustomEmailWidget,
      }}
    >
      <div className="customer-form">
        <CustomerFormBase {...props} />

        {showNotes && (
          <div className="form-notes">
            <p>Custom content that won't be overwritten</p>
          </div>
        )}
      </div>
    </WidgetProvider>
  );
}
```

### 3.7 Global Widget Customization

```typescript
// src/frontend/prism/setup.ts
// ✅ YOUR CODE - Configure widgets globally

import { widgetRegistry } from "./widgets/registry";

// Custom widgets
import { CustomTextInput } from "../components/widgets/CustomTextInput";
import { CustomDatePicker } from "../components/widgets/CustomDatePicker";
import { AddressAutocomplete } from "../components/widgets/AddressAutocomplete";
import { CurrencyInput } from "../components/widgets/CurrencyInput";
import { CustomerEmailWidget } from "../components/widgets/CustomerEmailWidget";

// Override defaults
widgetRegistry.registerAll({
  // Override all text inputs globally
  "string": CustomTextInput,

  // Override all date pickers
  "date": CustomDatePicker,

  // Register custom ui_widget types
  "ui:address": AddressAutocomplete,
  "ui:currency": CurrencyInput,

  // Override specific model.field combinations
  "Customer.email": CustomerEmailWidget,
  "Order.total": CurrencyInput,
});

export { widgetRegistry };
```

```typescript
// src/frontend/main.tsx

import { WidgetProvider } from "./prism/widgets/context";
import { widgetRegistry } from "./prism/setup";  // Import configured registry

function App() {
  return (
    <WidgetProvider registry={widgetRegistry}>
      <Router>
        {/* Your app */}
      </Router>
    </WidgetProvider>
  );
}
```

### 3.8 Widget Specification in Model

```python
# In your Prism spec
FieldSpec(
    name="address",
    type=FieldType.STRING,
    ui_widget="address",  # Uses AddressAutocomplete
    ui_widget_props={      # Passed to widget as props
        "country": "US",
        "enableMap": True,
    },
)

FieldSpec(
    name="price",
    type=FieldType.DECIMAL,
    ui_widget="currency",
    ui_widget_props={
        "currency": "USD",
        "min": 0,
    },
)
```

---

## 4. REST vs GraphQL: When to Use Each

### 4.1 Executive Summary

**GraphQL does NOT make REST obsolete.** They serve different purposes and can coexist in the same application. Prism supports both as first-class citizens, allowing you to choose per-model which interfaces to expose.

### 4.2 Comparison Matrix

| Aspect | REST (FastAPI) | GraphQL (Strawberry) | MCP (FastMCP) |
|--------|----------------|---------------------|---------------|
| **Best for** | Simple CRUD, external APIs | Complex queries, flexible UIs | AI/LLM tooling |
| **Caching** | Excellent (HTTP caching, CDN) | Requires custom solutions | N/A |
| **Discoverability** | OpenAPI/Swagger | Schema introspection | Tool descriptions |
| **N+1 Problem** | Manual optimization | DataLoaders built-in | N/A |
| **Real-time** | Webhooks, SSE | Subscriptions | N/A |
| **File uploads** | Native support | Requires workarounds | N/A |
| **Mobile/bandwidth** | Over-fetching common | Precise data fetching | N/A |
| **Learning curve** | Low | Medium | Low |
| **Client generation** | OpenAPI codegen | GraphQL codegen | N/A |

### 4.3 Recommended Strategy

```python
# Example: Hybrid approach per model
ModelSpec(
    name="Customer",

    # REST: For external integrations, webhooks, simple operations
    rest=RESTExposure(enabled=True),

    # GraphQL: For frontend queries with flexible field selection
    graphql=GraphQLExposure(enabled=True),

    # MCP: For AI assistant tooling
    mcp=MCPExposure(enabled=True),
)

ModelSpec(
    name="WebhookEvent",

    # REST only - external systems expect REST
    rest=RESTExposure(enabled=True),
    graphql=GraphQLExposure(enabled=False),
    mcp=MCPExposure(enabled=False),
)

ModelSpec(
    name="Dashboard",

    # GraphQL only - complex nested queries for UI
    rest=RESTExposure(enabled=False),
    graphql=GraphQLExposure(enabled=True),
    mcp=MCPExposure(enabled=False),
)
```

---

## 5. Specification Schema (Python/Pydantic)

### 5.1 Core Model Definition

```python
from __future__ import annotations
from enum import Enum
from typing import Any, Callable
from pydantic import BaseModel, Field


class FieldType(str, Enum):
    STRING = "string"
    TEXT = "text"
    INTEGER = "integer"
    FLOAT = "float"
    DECIMAL = "decimal"
    BOOLEAN = "boolean"
    DATETIME = "datetime"
    DATE = "date"
    TIME = "time"
    UUID = "uuid"
    JSON = "json"
    ENUM = "enum"
    FOREIGN_KEY = "foreign_key"


class FilterOperator(str, Enum):
    EQ = "eq"
    NE = "ne"
    GT = "gt"
    GTE = "gte"
    LT = "lt"
    LTE = "lte"
    LIKE = "like"
    ILIKE = "ilike"
    IN = "in"
    NOT_IN = "not_in"
    IS_NULL = "is_null"
    BETWEEN = "between"
    CONTAINS = "contains"
    STARTS_WITH = "starts_with"
    ENDS_WITH = "ends_with"


class FieldSpec(BaseModel):
    """Specification for a single model field."""

    name: str
    type: FieldType
    required: bool = True
    unique: bool = False
    indexed: bool = False
    default: Any = None
    default_factory: Callable[[], Any] | None = None

    # For enum fields
    enum_values: list[str] | None = None

    # For foreign keys
    references: str | None = None
    on_delete: str = "CASCADE"

    # For decimal fields
    precision: int | None = None
    scale: int | None = None

    # Validation
    min_length: int | None = None
    max_length: int | None = None
    min_value: float | None = None
    max_value: float | None = None
    pattern: str | None = None

    # List operations
    filterable: bool = True
    filter_operators: list[FilterOperator] = Field(
        default_factory=lambda: [FilterOperator.EQ]
    )
    sortable: bool = True
    searchable: bool = False

    # Display
    display_name: str | None = None
    description: str | None = None
    hidden: bool = False

    # Frontend widget configuration
    ui_widget: str | None = None  # Widget type from registry
    ui_placeholder: str | None = None
    ui_widget_props: dict[str, Any] = Field(default_factory=dict)

    # GraphQL specific
    graphql_type: str | None = None
    graphql_deprecation: str | None = None
```

### 5.2 Exposure Configuration

```python
class CRUDOperations(BaseModel):
    """Configure which CRUD operations to expose."""

    create: bool = True
    read: bool = True
    update: bool = True
    delete: bool = True
    list: bool = True


class PaginationStyle(str, Enum):
    OFFSET = "offset"
    CURSOR = "cursor"
    LIMIT_OFFSET = "limit_offset"


class PaginationConfig(BaseModel):
    """Pagination settings for list operations."""

    style: PaginationStyle = PaginationStyle.OFFSET
    default_page_size: int = 20
    max_page_size: int = 100


class RESTExposure(BaseModel):
    """FastAPI REST exposure configuration."""

    enabled: bool = True
    operations: CRUDOperations = Field(default_factory=CRUDOperations)
    prefix: str | None = None
    tags: list[str] = Field(default_factory=list)
    pagination: PaginationConfig = Field(default_factory=PaginationConfig)

    create_fields: list[str] | None = None
    update_fields: list[str] | None = None
    read_fields: list[str] | None = None
    list_fields: list[str] | None = None

    auth_required: bool = True
    permissions: dict[str, list[str]] = Field(default_factory=dict)
    operation_ids: dict[str, str] = Field(default_factory=dict)


class GraphQLExposure(BaseModel):
    """Strawberry GraphQL exposure configuration."""

    enabled: bool = True
    operations: CRUDOperations = Field(default_factory=CRUDOperations)

    type_name: str | None = None
    input_type_name: str | None = None
    query_name: str | None = None
    query_list_name: str | None = None

    use_connection: bool = True
    connection_name: str | None = None
    mutation_prefix: str | None = None

    enable_subscriptions: bool = False
    subscription_events: list[str] = Field(
        default_factory=lambda: ["created", "updated", "deleted"]
    )

    query_fields: list[str] | None = None
    mutation_fields: list[str] | None = None

    use_dataloader: bool = True
    max_depth: int | None = None
    max_complexity: int | None = None

    auth_required: bool = True
    permissions: dict[str, list[str]] = Field(default_factory=dict)
    nested_queries: bool = True

    type_description: str | None = None
    field_descriptions: dict[str, str] = Field(default_factory=dict)


class MCPExposure(BaseModel):
    """FastMCP exposure configuration."""

    enabled: bool = True
    operations: CRUDOperations = Field(default_factory=CRUDOperations)

    tool_prefix: str | None = None
    tool_descriptions: dict[str, str] = Field(default_factory=dict)
    field_descriptions: dict[str, str] = Field(default_factory=dict)

    expose_as_resource: bool = False
    resource_uri_template: str | None = None


class FrontendExposure(BaseModel):
    """React/TypeScript exposure configuration."""

    enabled: bool = True
    operations: CRUDOperations = Field(default_factory=CRUDOperations)

    api_style: str = "graphql"  # "graphql", "rest", "both"
    graphql_client: str = "urql"  # "urql", "apollo"

    generate_form: bool = True
    generate_table: bool = True
    generate_detail_view: bool = True

    form_layout: str = "vertical"
    table_columns: list[str] | None = None

    include_in_nav: bool = True
    nav_label: str | None = None
    nav_icon: str | None = None

    # Widget overrides for this model
    widget_overrides: dict[str, str] = Field(default_factory=dict)
    # e.g., {"email": "custom_email_widget"}
```

### 5.3 Model Specification

```python
class RelationshipSpec(BaseModel):
    """Specification for model relationships."""

    name: str
    target_model: str
    type: str  # "one_to_many", "many_to_one", "many_to_many", "one_to_one"
    back_populates: str | None = None
    lazy: str = "select"
    cascade: str = "all, delete-orphan"
    association_table: str | None = None
    graphql_field_name: str | None = None
    use_dataloader: bool = True


class ModelSpec(BaseModel):
    """Complete specification for a data model."""

    name: str
    table_name: str | None = None
    description: str | None = None

    fields: list[FieldSpec]
    relationships: list[RelationshipSpec] = Field(default_factory=list)

    rest: RESTExposure = Field(default_factory=RESTExposure)
    graphql: GraphQLExposure = Field(default_factory=GraphQLExposure)
    mcp: MCPExposure = Field(default_factory=MCPExposure)
    frontend: FrontendExposure = Field(default_factory=FrontendExposure)

    soft_delete: bool = False
    timestamps: bool = True

    before_create: str | None = None
    after_create: str | None = None
    before_update: str | None = None
    after_update: str | None = None
    before_delete: str | None = None
    after_delete: str | None = None

    test_factory: str | None = None
```

### 5.4 Stack Configuration

```python
class FileStrategy(str, Enum):
    ALWAYS_OVERWRITE = "always_overwrite"
    GENERATE_ONCE = "generate_once"
    GENERATE_BASE = "generate_base"
    MERGE = "merge"


class ExtensionConfig(BaseModel):
    """Configure how Prism handles extensions."""

    # Backend strategies
    services_strategy: FileStrategy = FileStrategy.GENERATE_BASE
    graphql_types_strategy: FileStrategy = FileStrategy.GENERATE_BASE
    graphql_queries_strategy: FileStrategy = FileStrategy.GENERATE_BASE
    graphql_mutations_strategy: FileStrategy = FileStrategy.GENERATE_BASE
    rest_endpoints_strategy: FileStrategy = FileStrategy.GENERATE_BASE

    # Frontend strategies
    components_strategy: FileStrategy = FileStrategy.GENERATE_BASE
    hooks_strategy: FileStrategy = FileStrategy.GENERATE_ONCE
    pages_strategy: FileStrategy = FileStrategy.GENERATE_ONCE

    # Assembly strategies
    schema_assembly: FileStrategy = FileStrategy.MERGE
    router_assembly: FileStrategy = FileStrategy.MERGE

    # Protected regions
    use_protected_regions: bool = True
    protected_region_marker: str = "PRISM:PROTECTED"


class DatabaseConfig(BaseModel):
    """Database configuration."""

    dialect: str = "postgresql"
    async_driver: bool = True
    naming_convention: dict[str, str] = Field(default_factory=lambda: {
        "ix": "ix_%(column_0_label)s",
        "uq": "uq_%(table_name)s_%(column_0_name)s",
        "ck": "ck_%(table_name)s_%(constraint_name)s",
        "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
        "pk": "pk_%(table_name)s",
    })


class GraphQLConfig(BaseModel):
    """GraphQL server configuration."""

    enabled: bool = True
    path: str = "/graphql"
    graphiql: bool = True

    subscriptions_enabled: bool = True
    subscription_path: str = "/graphql/ws"

    query_depth_limit: int = 10
    query_complexity_limit: int = 100

    enable_tracing: bool = False
    enable_apollo_federation: bool = False
    dataloader_cache_per_request: bool = True


class WidgetConfig(BaseModel):
    """Frontend widget configuration."""

    # Custom widget mappings (applied globally)
    type_widgets: dict[str, str] = Field(default_factory=dict)
    # e.g., {"string": "MyTextInput", "date": "MyDatePicker"}

    ui_widgets: dict[str, str] = Field(default_factory=dict)
    # e.g., {"address": "AddressAutocomplete"}

    field_widgets: dict[str, str] = Field(default_factory=dict)
    # e.g., {"Customer.email": "CustomerEmailWidget"}


class GeneratorConfig(BaseModel):
    """Code generator configuration."""

    backend_output: str = "src/backend"
    frontend_output: str = "src/frontend"

    # Backend structure
    models_path: str = "models"
    schemas_path: str = "schemas"
    services_path: str = "services"
    services_generated_path: str = "services/_generated"
    rest_path: str = "api/rest"
    graphql_path: str = "api/graphql"
    graphql_generated_path: str = "api/graphql/_generated"
    mcp_path: str = "mcp"
    tests_path: str = "tests"

    # Frontend structure
    types_path: str = "types"
    graphql_operations_path: str = "graphql"
    api_client_path: str = "api"
    components_path: str = "components"
    components_generated_path: str = "components/_generated"
    hooks_path: str = "hooks"
    pages_path: str = "pages"
    prism_path: str = "prism"  # Widget system, specs
    frontend_tests_path: str = "__tests__"

    generate_migrations: bool = True
    overwrite_existing: bool = False
    dry_run: bool = False


class TestingConfig(BaseModel):
    """Testing configuration."""

    generate_unit_tests: bool = True
    generate_integration_tests: bool = True
    generate_factories: bool = True
    test_database: str = "sqlite"
    generate_graphql_tests: bool = True
    generate_component_tests: bool = True
    generate_hook_tests: bool = True
    generate_e2e_tests: bool = False


class StackSpec(BaseModel):
    """Complete stack specification."""

    name: str
    version: str = "1.0.0"
    description: str | None = None

    # Configuration
    database: DatabaseConfig = Field(default_factory=DatabaseConfig)
    graphql: GraphQLConfig = Field(default_factory=GraphQLConfig)
    generator: GeneratorConfig = Field(default_factory=GeneratorConfig)
    testing: TestingConfig = Field(default_factory=TestingConfig)
    extensions: ExtensionConfig = Field(default_factory=ExtensionConfig)
    widgets: WidgetConfig = Field(default_factory=WidgetConfig)

    # Models
    models: list[ModelSpec]

    # Global defaults
    default_rest_exposure: RESTExposure = Field(default_factory=RESTExposure)
    default_graphql_exposure: GraphQLExposure = Field(default_factory=GraphQLExposure)
    default_mcp_exposure: MCPExposure = Field(default_factory=MCPExposure)
    default_frontend_exposure: FrontendExposure = Field(default_factory=FrontendExposure)
```

---

## 6. Example Specification

```python
# specs/models.py
from prism import (
    StackSpec, ModelSpec, FieldSpec, FieldType, FilterOperator,
    RESTExposure, GraphQLExposure, MCPExposure, FrontendExposure,
    CRUDOperations, RelationshipSpec, DatabaseConfig, GraphQLConfig,
    GeneratorConfig, TestingConfig, ExtensionConfig, WidgetConfig,
    FileStrategy
)

stack = StackSpec(
    name="my-crm",
    version="1.0.0",
    description="Customer Relationship Management System",

    database=DatabaseConfig(
        dialect="postgresql",
        async_driver=True,
    ),

    graphql=GraphQLConfig(
        enabled=True,
        path="/graphql",
        graphiql=True,
        subscriptions_enabled=True,
    ),

    extensions=ExtensionConfig(
        services_strategy=FileStrategy.GENERATE_BASE,
        components_strategy=FileStrategy.GENERATE_BASE,
        pages_strategy=FileStrategy.GENERATE_ONCE,
        use_protected_regions=True,
    ),

    widgets=WidgetConfig(
        type_widgets={
            "string": "CustomTextInput",
        },
        ui_widgets={
            "currency": "CurrencyInput",
            "address": "AddressAutocomplete",
        },
        field_widgets={
            "Customer.email": "CustomerEmailWidget",
            "Order.total": "CurrencyInput",
        },
    ),

    generator=GeneratorConfig(
        backend_output="src/backend",
        frontend_output="src/frontend",
    ),

    testing=TestingConfig(
        generate_unit_tests=True,
        generate_integration_tests=True,
        generate_graphql_tests=True,
        generate_factories=True,
    ),

    models=[
        ModelSpec(
            name="Customer",
            description="Customer entity",
            soft_delete=True,
            timestamps=True,

            fields=[
                FieldSpec(
                    name="name",
                    type=FieldType.STRING,
                    max_length=255,
                    required=True,
                    searchable=True,
                    filter_operators=[FilterOperator.EQ, FilterOperator.ILIKE],
                    display_name="Customer Name",
                ),
                FieldSpec(
                    name="email",
                    type=FieldType.STRING,
                    max_length=255,
                    required=True,
                    unique=True,
                    indexed=True,
                    pattern=r"^[\w\.-]+@[\w\.-]+\.\w+$",
                    ui_widget="email",
                ),
                FieldSpec(
                    name="status",
                    type=FieldType.ENUM,
                    enum_values=["active", "inactive", "prospect"],
                    default="prospect",
                    ui_widget="select",
                ),
                FieldSpec(
                    name="notes",
                    type=FieldType.TEXT,
                    required=False,
                    searchable=True,
                    ui_widget="richtext",
                ),
                FieldSpec(
                    name="lifetime_value",
                    type=FieldType.DECIMAL,
                    precision=10,
                    scale=2,
                    default=0.0,
                    ui_widget="currency",
                    ui_widget_props={"currency": "USD"},
                ),
                FieldSpec(
                    name="address",
                    type=FieldType.STRING,
                    max_length=500,
                    required=False,
                    ui_widget="address",
                    ui_widget_props={"enableMap": True},
                ),
            ],

            relationships=[
                RelationshipSpec(
                    name="orders",
                    target_model="Order",
                    type="one_to_many",
                    back_populates="customer",
                    use_dataloader=True,
                ),
            ],

            rest=RESTExposure(
                enabled=True,
                tags=["customers"],
            ),

            graphql=GraphQLExposure(
                enabled=True,
                use_connection=True,
                enable_subscriptions=True,
            ),

            mcp=MCPExposure(
                enabled=True,
                tool_prefix="customer",
                tool_descriptions={
                    "list": "Search and list customers",
                    "read": "Get customer details by ID",
                },
            ),

            frontend=FrontendExposure(
                enabled=True,
                api_style="graphql",
                nav_label="Customers",
                nav_icon="users",
            ),
        ),
    ],
)
```

---

## 7. Generated Output Structure

```
project/
├── src/
│   ├── backend/
│   │   ├── models/
│   │   │   ├── __init__.py
│   │   │   ├── base.py
│   │   │   └── customer.py          # Always overwritten
│   │   │
│   │   ├── schemas/
│   │   │   ├── __init__.py
│   │   │   ├── base.py
│   │   │   └── customer.py          # Always overwritten
│   │   │
│   │   ├── services/
│   │   │   ├── _generated/          # ⚠️ Always overwritten
│   │   │   │   ├── __init__.py
│   │   │   │   ├── base.py
│   │   │   │   └── customer_base.py
│   │   │   │
│   │   │   ├── __init__.py
│   │   │   └── customer.py          # ✅ Your extensions
│   │   │
│   │   ├── api/
│   │   │   ├── rest/
│   │   │   │   ├── _generated/      # ⚠️ Always overwritten
│   │   │   │   └── customer.py      # ✅ Your extensions
│   │   │   │
│   │   │   └── graphql/
│   │   │       ├── _generated/      # ⚠️ Always overwritten
│   │   │       │   ├── types/
│   │   │       │   ├── queries/
│   │   │       │   ├── mutations/
│   │   │       │   └── subscriptions/
│   │   │       │
│   │   │       ├── types/           # ✅ Your extensions
│   │   │       ├── queries/         # ✅ Your extensions
│   │   │       ├── mutations/       # ✅ Your extensions
│   │   │       └── schema.py        # Auto-merges both
│   │   │
│   │   ├── mcp/
│   │   │   └── ...
│   │   │
│   │   └── tests/
│   │       └── ...
│   │
│   └── frontend/
│       ├── prism/                   # Widget system
│       │   ├── widgets/
│       │   │   ├── types.ts
│       │   │   ├── defaults.ts      # Always overwritten
│       │   │   ├── registry.ts
│       │   │   ├── context.tsx
│       │   │   └── components/      # Default widget components
│       │   │
│       │   ├── specs/               # Always overwritten
│       │   │   └── customer.ts      # Field specs for forms
│       │   │
│       │   └── setup.ts             # ✅ Your widget config
│       │
│       ├── types/
│       │   └── generated.ts         # Always overwritten
│       │
│       ├── graphql/                 # Always overwritten
│       │   └── ...
│       │
│       ├── hooks/
│       │   └── useCustomer.ts       # Generated once
│       │
│       ├── components/
│       │   ├── _generated/          # ⚠️ Always overwritten
│       │   │   ├── CustomerFormBase.tsx
│       │   │   └── CustomerTableBase.tsx
│       │   │
│       │   └── customer/            # ✅ Your extensions
│       │       ├── CustomerForm.tsx
│       │       └── CustomerTable.tsx
│       │
│       └── pages/
│           └── customers/           # Generated once
│               ├── index.tsx
│               └── [id].tsx
│
└── specs/
    └── models.py
```

---

## 8. Project Bootstrapping

Prism can scaffold a complete monorepo project from your initial data model spec, similar to `create-react-app` but for the full stack.

### 8.1 Quick Start

```bash
# Interactive project creation
prism create my-app

# Or with a spec file
prism create my-app --spec ./my-models.py

# Or with a template
prism create my-app --template saas
```

### 8.2 The `prism create` Command

```bash
prism create <project-name> [options]

Options:
  --spec <path>         Path to initial StackSpec file (optional)
  --template <name>     Project template: minimal, full, saas, api-only (default: full)
  --package-manager     npm, pnpm, yarn, bun (default: pnpm)
  --python-manager      pip, poetry, uv, pdm (default: uv)
  --database            postgresql, sqlite (default: postgresql)
  --no-install          Skip dependency installation
  --no-git              Skip git initialization
  --yes, -y             Skip interactive prompts, use defaults
```

### 8.3 Interactive Mode

When run without `--yes`, Prism guides you through setup:

```
$ prism create my-crm

🔮 Prism - Create New Project

? Project name: my-crm
? Description: Customer Relationship Management System
? Package manager: pnpm
? Python manager: uv
? Database: postgresql
? Include MCP server? Yes
? GraphQL client: urql
? Authentication: JWT
? Project template: full

? Define your initial models interactively? Yes

📦 Model: Customer
  ? Add field: name (string, required)
  ? Add field: email (string, required, unique)
  ? Add field: status (enum: active, inactive, prospect)
  ? Add field: (done)

  ? Expose via REST API? Yes
  ? Expose via GraphQL? Yes
  ? Expose via MCP? Yes

? Add another model? Yes

📦 Model: Order
  ? Add field: customer_id (foreign_key -> Customer)
  ? Add field: total (decimal, required)
  ? Add field: status (enum: pending, paid, shipped, delivered)
  ? Add field: (done)

? Add another model? No

Creating project structure...
Installing dependencies...
Generating code from spec...
Initializing database...
Running initial migration...

✅ Project created successfully!

Next steps:
  cd my-crm
  prism dev          # Start development servers
  prism studio       # Open Prism Studio (visual editor)
```

### 8.4 Initial Spec File

You can provide your models upfront in a spec file:

```python
# my-models.py - Pass this to: prism create my-app --spec my-models.py
from prism import StackSpec, ModelSpec, FieldSpec, FieldType, FilterOperator

spec = StackSpec(
    name="my-crm",
    description="Customer Relationship Management System",

    models=[
        ModelSpec(
            name="Customer",
            description="Customer entity",
            fields=[
                FieldSpec(name="name", type=FieldType.STRING, max_length=255, required=True),
                FieldSpec(name="email", type=FieldType.STRING, unique=True, required=True),
                FieldSpec(
                    name="status",
                    type=FieldType.ENUM,
                    enum_values=["active", "inactive", "prospect"],
                    default="prospect",
                ),
                FieldSpec(name="notes", type=FieldType.TEXT, required=False),
            ],
        ),
        ModelSpec(
            name="Order",
            description="Customer order",
            fields=[
                FieldSpec(name="customer_id", type=FieldType.FOREIGN_KEY, references="Customer"),
                FieldSpec(name="order_number", type=FieldType.STRING, unique=True, required=True),
                FieldSpec(name="total", type=FieldType.DECIMAL, precision=10, scale=2, required=True),
                FieldSpec(
                    name="status",
                    type=FieldType.ENUM,
                    enum_values=["pending", "paid", "shipped", "delivered", "cancelled"],
                    default="pending",
                ),
            ],
        ),
    ],
)
```

### 8.5 Generated Project Structure

```
my-crm/
├── .github/
│   └── workflows/
│       ├── ci.yml                    # CI pipeline
│       └── deploy.yml                # Deployment workflow
│
├── packages/
│   ├── backend/                      # Python backend
│   │   ├── src/
│   │   │   └── my_crm/
│   │   │       ├── __init__.py
│   │   │       ├── main.py           # FastAPI app entry
│   │   │       ├── config.py         # Settings (pydantic-settings)
│   │   │       ├── database.py       # Database connection
│   │   │       │
│   │   │       ├── models/           # SQLAlchemy models
│   │   │       │   ├── __init__.py
│   │   │       │   ├── base.py
│   │   │       │   ├── customer.py
│   │   │       │   └── order.py
│   │   │       │
│   │   │       ├── schemas/          # Pydantic schemas
│   │   │       │   └── ...
│   │   │       │
│   │   │       ├── services/         # Business logic
│   │   │       │   ├── _generated/
│   │   │       │   └── ...
│   │   │       │
│   │   │       ├── api/
│   │   │       │   ├── rest/         # FastAPI routes
│   │   │       │   │   └── ...
│   │   │       │   └── graphql/      # Strawberry GraphQL
│   │   │       │       └── ...
│   │   │       │
│   │   │       └── mcp/              # MCP server
│   │   │           └── ...
│   │   │
│   │   ├── tests/
│   │   │   ├── conftest.py
│   │   │   ├── factories/
│   │   │   └── ...
│   │   │
│   │   ├── alembic/
│   │   │   ├── env.py
│   │   │   └── versions/
│   │   │       └── 0001_initial.py
│   │   │
│   │   ├── pyproject.toml
│   │   └── README.md
│   │
│   └── frontend/                     # React frontend
│       ├── src/
│       │   ├── main.tsx
│       │   ├── App.tsx
│       │   ├── vite-env.d.ts
│       │   │
│       │   ├── prism/                # Prism widget system
│       │   │   ├── widgets/
│       │   │   │   ├── types.ts
│       │   │   │   ├── defaults.ts
│       │   │   │   ├── registry.ts
│       │   │   │   ├── context.tsx
│       │   │   │   └── components/
│       │   │   │       ├── TextInput.tsx
│       │   │   │       ├── Select.tsx
│       │   │   │       ├── DatePicker.tsx
│       │   │   │       └── ...
│       │   │   └── setup.ts
│       │   │
│       │   ├── types/
│       │   │   └── generated.ts
│       │   │
│       │   ├── graphql/
│       │   │   ├── client.ts
│       │   │   ├── fragments/
│       │   │   ├── queries/
│       │   │   ├── mutations/
│       │   │   └── subscriptions/
│       │   │
│       │   ├── hooks/
│       │   │   ├── useCustomer.ts
│       │   │   └── useOrder.ts
│       │   │
│       │   ├── components/
│       │   │   ├── _generated/
│       │   │   ├── ui/               # Shadcn/ui components
│       │   │   ├── layout/
│       │   │   │   ├── Sidebar.tsx
│       │   │   │   ├── Header.tsx
│       │   │   │   └── Layout.tsx
│       │   │   ├── customer/
│       │   │   └── order/
│       │   │
│       │   ├── pages/
│       │   │   ├── index.tsx
│       │   │   ├── customers/
│       │   │   │   ├── index.tsx
│       │   │   │   ├── [id].tsx
│       │   │   │   └── new.tsx
│       │   │   └── orders/
│       │   │       └── ...
│       │   │
│       │   └── lib/
│       │       └── utils.ts
│       │
│       ├── public/
│       ├── index.html
│       ├── package.json
│       ├── tsconfig.json
│       ├── tailwind.config.js
│       ├── postcss.config.js
│       ├── vite.config.ts
│       ├── codegen.ts                # GraphQL codegen config
│       └── README.md
│
├── specs/
│   └── models.py                     # Your Prism spec (source of truth)
│
├── docker/
│   ├── Dockerfile.backend
│   ├── Dockerfile.frontend
│   └── docker-compose.yml
│
├── scripts/
│   ├── dev.sh                        # Start all dev servers
│   ├── test.sh                       # Run all tests
│   └── migrate.sh                    # Run migrations
│
├── .env.example
├── .gitignore
├── .editorconfig
├── .prettierrc
├── pnpm-workspace.yaml               # Monorepo workspace config
├── turbo.json                        # Turborepo config (optional)
├── prism.config.py                   # Prism configuration
├── README.md
└── LICENSE
```

### 8.6 Generated Configuration Files

#### `pnpm-workspace.yaml`
```yaml
packages:
  - 'packages/*'
```

#### `packages/backend/pyproject.toml`
```toml
[project]
name = "my-crm-backend"
version = "0.1.0"
description = "My CRM Backend"
requires-python = ">=3.11"
dependencies = [
    "fastapi>=0.109.0",
    "uvicorn[standard]>=0.27.0",
    "sqlalchemy[asyncio]>=2.0.25",
    "asyncpg>=0.29.0",           # PostgreSQL
    "alembic>=1.13.1",
    "pydantic>=2.6.0",
    "pydantic-settings>=2.1.0",
    "strawberry-graphql[fastapi]>=0.219.0",
    "fastmcp>=0.1.0",
    "python-jose[cryptography]>=3.3.0",  # JWT
    "passlib[bcrypt]>=1.7.4",
    "httpx>=0.26.0",
]

[project.optional-dependencies]
dev = [
    "pytest>=8.0.0",
    "pytest-asyncio>=0.23.0",
    "pytest-cov>=4.1.0",
    "factory-boy>=3.3.0",
    "ruff>=0.2.0",
    "mypy>=1.8.0",
    "pre-commit>=3.6.0",
]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.ruff]
line-length = 100
target-version = "py311"

[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B", "SIM"]

[tool.mypy]
python_version = "3.11"
strict = true

[tool.pytest.ini_options]
asyncio_mode = "auto"
testpaths = ["tests"]
```

#### `packages/frontend/package.json`
```json
{
  "name": "my-crm-frontend",
  "version": "0.1.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "preview": "vite preview",
    "test": "vitest",
    "test:coverage": "vitest run --coverage",
    "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
    "codegen": "graphql-codegen",
    "codegen:watch": "graphql-codegen --watch"
  },
  "dependencies": {
    "react": "^18.3.0",
    "react-dom": "^18.3.0",
    "react-router-dom": "^6.22.0",
    "@tanstack/react-query": "^5.20.0",
    "urql": "^4.0.0",
    "graphql": "^16.8.0",
    "graphql-ws": "^5.14.0",
    "react-hook-form": "^7.50.0",
    "@hookform/resolvers": "^3.3.0",
    "zod": "^3.22.0",
    "date-fns": "^3.3.0",
    "clsx": "^2.1.0",
    "tailwind-merge": "^2.2.0",
    "lucide-react": "^0.330.0",
    "@radix-ui/react-slot": "^1.0.0",
    "class-variance-authority": "^0.7.0"
  },
  "devDependencies": {
    "@types/react": "^18.2.0",
    "@types/react-dom": "^18.2.0",
    "@vitejs/plugin-react": "^4.2.0",
    "typescript": "^5.3.0",
    "vite": "^5.1.0",
    "vitest": "^1.3.0",
    "@vitest/coverage-v8": "^1.3.0",
    "@testing-library/react": "^14.2.0",
    "@testing-library/jest-dom": "^6.4.0",
    "tailwindcss": "^3.4.0",
    "postcss": "^8.4.0",
    "autoprefixer": "^10.4.0",
    "@graphql-codegen/cli": "^5.0.0",
    "@graphql-codegen/typescript": "^4.0.0",
    "@graphql-codegen/typescript-operations": "^4.0.0",
    "@graphql-codegen/typescript-urql": "^4.0.0",
    "eslint": "^8.56.0",
    "@typescript-eslint/eslint-plugin": "^7.0.0",
    "@typescript-eslint/parser": "^7.0.0",
    "eslint-plugin-react-hooks": "^4.6.0"
  }
}
```

#### `docker/docker-compose.yml`
```yaml
version: '3.8'

services:
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: prism
      POSTGRES_PASSWORD: prism
      POSTGRES_DB: my_crm
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U prism -d my_crm"]
      interval: 5s
      timeout: 5s
      retries: 5

  backend:
    build:
      context: ../packages/backend
      dockerfile: ../../docker/Dockerfile.backend
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=postgresql+asyncpg://prism:prism@db:5432/my_crm
      - SECRET_KEY=dev-secret-change-in-production
    depends_on:
      db:
        condition: service_healthy
    volumes:
      - ../packages/backend/src:/app/src
    command: uvicorn my_crm.main:app --host 0.0.0.0 --port 8000 --reload

  frontend:
    build:
      context: ../packages/frontend
      dockerfile: ../../docker/Dockerfile.frontend
    ports:
      - "5173:5173"
    environment:
      - VITE_API_URL=http://localhost:8000
      - VITE_GRAPHQL_URL=http://localhost:8000/graphql
      - VITE_GRAPHQL_WS_URL=ws://localhost:8000/graphql/ws
    volumes:
      - ../packages/frontend/src:/app/src
    command: pnpm dev --host

volumes:
  postgres_data:
```

#### `prism.config.py`
```python
"""Prism configuration for this project."""
from prism import PrismConfig

config = PrismConfig(
    # Spec location
    spec_path="specs/models.py",

    # Output paths
    backend_path="packages/backend/src/my_crm",
    frontend_path="packages/frontend/src",

    # Development
    backend_port=8000,
    frontend_port=5173,

    # Database
    database_url_env="DATABASE_URL",

    # Features
    enable_mcp=True,
    enable_graphql=True,
    enable_rest=True,
)
```

### 8.7 Development Commands

After bootstrapping, these commands are available:

```bash
# Start everything (database, backend, frontend)
prism dev

# Start individual services
prism dev --backend-only
prism dev --frontend-only

# Run in Docker
prism dev --docker

# Generate code after spec changes
prism generate

# Database operations
prism db migrate              # Create and run migrations
prism db migrate --message "Add new field"
prism db reset                # Reset database
prism db seed                 # Run seeders

# Testing
prism test                    # Run all tests
prism test --backend          # Backend only
prism test --frontend         # Frontend only
prism test --coverage         # With coverage

# Code quality
prism lint                    # Lint all code
prism format                  # Format all code
prism typecheck               # Type checking

# Production
prism build                   # Build for production
prism deploy                  # Deploy (if configured)
```

### 8.8 Project Templates

#### `minimal` - Bare essentials
- SQLite database
- REST API only
- No frontend
- Basic tests

#### `full` (default) - Complete stack
- PostgreSQL database
- REST + GraphQL + MCP
- React frontend with all widgets
- Full test suite
- Docker setup
- CI/CD workflows

#### `saas` - SaaS starter
- Everything in `full`, plus:
- Multi-tenancy support
- User authentication (JWT)
- Role-based permissions
- Subscription/billing models
- Admin dashboard

#### `api-only` - Headless API
- PostgreSQL database
- REST + GraphQL + MCP
- No frontend
- OpenAPI documentation
- Full test suite

### 8.9 Post-Bootstrap Workflow

```bash
# 1. Create project
prism create my-app --spec my-models.py

# 2. Navigate to project
cd my-app

# 3. Start development
prism dev

# 4. Open in browser
#    Frontend: http://localhost:5173
#    GraphQL:  http://localhost:8000/graphql
#    API Docs: http://localhost:8000/docs

# 5. Modify your spec
code specs/models.py

# 6. Regenerate code
prism generate

# 7. Create migration for model changes
prism db migrate --message "Updated customer fields"

# 8. Continue developing...
```

---

## 9. CLI Interface

```bash
# ============================================
# PROJECT CREATION
# ============================================

# Create new project (interactive)
prism create my-project

# Create with spec file
prism create my-project --spec ./models.py

# Create with template
prism create my-project --template saas

# Create with all options
prism create my-project \
  --spec ./models.py \
  --template full \
  --package-manager pnpm \
  --python-manager uv \
  --database postgresql \
  --yes

# ============================================
# CODE GENERATION
# ============================================

# Generate all code from specification
prism generate

# Generate from specific spec
prism generate specs/models.py

# Generate specific layers only
prism generate --only models,schemas,services
prism generate --only graphql
prism generate --only frontend

# Generate with options
prism generate --dry-run          # Preview changes
prism generate --force            # Force overwrite all
prism generate --diff             # Show diff of changes

# ============================================
# DEVELOPMENT
# ============================================

# Start all services
prism dev

# Start specific services
prism dev --backend-only
prism dev --frontend-only

# Start with Docker
prism dev --docker

# Watch mode (auto-regenerate on spec changes)
prism dev --watch

# ============================================
# DATABASE
# ============================================

# Create and apply migrations
prism db migrate

# Create migration with message
prism db migrate --message "Add customer status field"

# Show migration status
prism db status

# Reset database
prism db reset

# Seed database
prism db seed

# ============================================
# TESTING
# ============================================

# Run all tests
prism test

# Run specific tests
prism test --backend
prism test --frontend
prism test --e2e

# With coverage
prism test --coverage

# ============================================
# CODE QUALITY
# ============================================

prism lint                    # Lint all
prism format                  # Format all
prism typecheck               # Type check all

# ============================================
# UTILITIES
# ============================================

# Validate specification
prism validate specs/models.py

# Generate GraphQL schema SDL
prism schema --output schema.graphql

# Check what would be overwritten
prism status

# List extension points
prism extensions

# Open Prism Studio (visual spec editor)
prism studio

# ============================================
# PRODUCTION
# ============================================

# Build for production
prism build

# Build Docker images
prism build --docker

# Deploy (requires configuration)
prism deploy --env production
```

# Generate specific layers only
prism generate specs/models.py --only models,schemas,services
prism generate specs/models.py --only graphql
prism generate specs/models.py --only frontend

# Generate with options
prism generate specs/models.py --dry-run          # Preview changes
prism generate specs/models.py --force            # Force overwrite all
prism generate specs/models.py --diff             # Show diff of changes
prism generate specs/models.py --preserve-custom  # Default, keep extensions

# Validate specification
prism validate specs/models.py

# Generate migrations
prism migrate specs/models.py --message "Add customer model"

# Generate GraphQL schema SDL
prism schema specs/models.py --output schema.graphql

# Watch mode for development
prism watch specs/models.py

# Initialize new project
prism init my-project --template full
prism init my-project --template graphql-only

# Check what would be overwritten
prism status specs/models.py

# List extension points
prism extensions specs/models.py
```

---

## 10. Migration Strategy

For existing projects:

1. **Introspection Mode**: Generate spec from existing codebase
   ```bash
   prism introspect src/backend/models --output specs/models.py
   ```

2. **Gradual Adoption**: Add models to spec one at a time
   ```python
   stack = StackSpec(
       models=[
           ModelSpec(name="NewModel", ...),
       ],
   )
   ```

3. **Preserve Custom Code**: Prism detects existing files and generates base classes instead of overwriting.

---

## 11. Future Enhancements

- **Prism Studio**: Visual spec editor with drag-and-drop model builder
- **Prism Cloud**: Hosted spec management and team collaboration
- **Apollo Federation**: Support federated GraphQL schemas
- **Persisted Queries**: Generate and manage persisted query IDs
- **OpenAPI Import**: Generate specs from existing OpenAPI definitions
- **Database Introspection**: Generate specs from existing database schema
- **Admin UI Generation**: Auto-generate admin interfaces
- **Widget Marketplace**: Shareable widget packages
- **Event Sourcing**: Support for event-driven architectures
- **Multi-tenancy**: Built-in tenant isolation patterns
- **Caching Layer**: Redis/Memcached integration
- **Audit Logging**: Automatic audit trail generation
- **Plugin System**: Third-party generator plugins
