Files
postgres/docs/FEATURES_JSON_GUIDE.md
2026-01-08 14:18:24 +00:00

12 KiB

Complete Guide to features.json Configuration System

Overview

The features.json file is now a comprehensive configuration system that defines:

  • UI Component Trees - Declarative component hierarchies
  • SQL Query Templates - Parameterized database queries
  • Playwright Playbooks - E2E test scenarios
  • Storybook Stories - Component documentation
  • Feature Flags - Enable/disable features
  • Translations - Multi-language support
  • Form Schemas - Dynamic form generation
  • API Endpoints - REST API definitions
  • Permissions - Role-based access control

1. Component Trees

Define complete UI hierarchies in JSON without writing JSX.

Example: Simple Component Tree

{
  "componentTrees": {
    "MyPage": {
      "component": "Box",
      "props": {
        "sx": { "p": 3 }
      },
      "children": [
        {
          "component": "Typography",
          "props": {
            "variant": "h4",
            "text": "{{pageTitle}}"
          }
        },
        {
          "component": "Button",
          "condition": "canCreate",
          "props": {
            "variant": "contained",
            "startIcon": "Add",
            "onClick": "handleCreate",
            "text": "Create New"
          }
        }
      ]
    }
  }
}

Using Component Trees in Code

import { getComponentTree } from '@/utils/featureConfig';
import ComponentTreeRenderer from '@/utils/ComponentTreeRenderer';

function MyComponent() {
  const tree = getComponentTree('MyPage');
  const data = { pageTitle: 'Welcome', canCreate: true };
  const handlers = { handleCreate: () => console.log('Create') };
  
  return <ComponentTreeRenderer tree={tree} data={data} handlers={handlers} />;
}

Component Tree Features

Template Interpolation:

{
  "props": {
    "text": "Hello {{user.name}}!"
  }
}

Conditional Rendering:

{
  "condition": "isAdmin && hasPermission('create')",
  "component": "Button"
}

Loops (forEach):

{
  "component": "List",
  "children": [
    {
      "component": "ListItem",
      "forEach": "items",
      "children": [
        {
          "component": "Typography",
          "props": {
            "text": "{{item.name}}"
          }
        }
      ]
    }
  ]
}

2. SQL Templates

Parameterized SQL queries with template variables.

Example SQL Templates

{
  "sqlTemplates": {
    "tables": {
      "createTable": {
        "description": "Create a new table with columns",
        "query": "CREATE TABLE \"{{tableName}}\" ({{columnDefinitions}})",
        "returns": "command"
      },
      "listTables": {
        "description": "Get all tables",
        "query": "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'",
        "returns": "rows"
      }
    },
    "records": {
      "insert": {
        "description": "Insert a new record",
        "query": "INSERT INTO \"{{tableName}}\" ({{columns}}) VALUES ({{values}}) RETURNING *",
        "returns": "rows"
      }
    }
  }
}

Using SQL Templates

import { getSqlTemplate, interpolateSqlTemplate } from '@/utils/featureConfig';

// Get template
const template = getSqlTemplate('records', 'insert');

// Interpolate parameters
const query = interpolateSqlTemplate(template, {
  tableName: 'users',
  columns: 'name, email',
  values: '$1, $2'
});

// Result: INSERT INTO "users" (name, email) VALUES ($1, $2) RETURNING *

3. Playwright Playbooks

Define E2E test scenarios in JSON.

Example Playbook

{
  "playwrightPlaybooks": {
    "createTable": {
      "name": "Create Table Workflow",
      "description": "Test creating a new database table",
      "tags": ["admin", "table", "crud"],
      "steps": [
        {
          "action": "goto",
          "url": "/admin/dashboard"
        },
        {
          "action": "click",
          "selector": "button:has-text('Create Table')"
        },
        {
          "action": "fill",
          "selector": "input[label='Table Name']",
          "value": "{{tableName}}"
        },
        {
          "action": "expect",
          "selector": "text={{tableName}}",
          "text": "visible"
        }
      ],
      "cleanup": [
        {
          "action": "click",
          "selector": "button:has-text('Drop Table')"
        }
      ]
    }
  }
}

Using Playbooks

import { getPlaywrightPlaybook } from '@/utils/featureConfig';

const playbook = getPlaywrightPlaybook('createTable');

// Execute playbook steps
for (const step of playbook.steps) {
  switch (step.action) {
    case 'goto':
      await page.goto(step.url);
      break;
    case 'click':
      await page.click(step.selector);
      break;
    // ... handle other actions
  }
}

4. Storybook Stories

Define component stories in JSON.

Example Stories

{
  "storybookStories": {
    "Button": {
      "primary": {
        "name": "Primary Button",
        "description": "Primary action button",
        "args": {
          "variant": "contained",
          "color": "primary",
          "text": "Click Me"
        }
      },
      "withIcon": {
        "name": "With Icon",
        "args": {
          "variant": "contained",
          "startIcon": "Add",
          "text": "Add Item"
        },
        "play": [
          "await userEvent.click(screen.getByText('Add Item'))",
          "await expect(args.onClick).toHaveBeenCalled()"
        ]
      }
    }
  }
}

Using Stories

import { getStorybookStory } from '@/utils/featureConfig';

const story = getStorybookStory('Button', 'primary');

export const Primary = {
  name: story.name,
  args: story.args,
};

5. Helper Functions

Component Trees

import {
  getComponentTree,
  getAllComponentTrees,
} from '@/utils/featureConfig';

const tree = getComponentTree('TableManagerTab');
const allTrees = getAllComponentTrees();

SQL Templates

import {
  getSqlTemplate,
  getAllSqlTemplates,
  getSqlTemplatesByCategory,
  interpolateSqlTemplate,
} from '@/utils/featureConfig';

const template = getSqlTemplate('records', 'insert');
const allTemplates = getAllSqlTemplates();
const recordTemplates = getSqlTemplatesByCategory('records');
const query = interpolateSqlTemplate(template, { tableName: 'users' });

Playwright Playbooks

import {
  getPlaywrightPlaybook,
  getAllPlaywrightPlaybooks,
  getPlaywrightPlaybooksByTag,
} from '@/utils/featureConfig';

const playbook = getPlaywrightPlaybook('createTable');
const allPlaybooks = getAllPlaywrightPlaybooks();
const adminPlaybooks = getPlaywrightPlaybooksByTag('admin');

Storybook Stories

import {
  getStorybookStory,
  getAllStorybookStories,
  getStorybookStoriesForComponent,
} from '@/utils/featureConfig';

const story = getStorybookStory('Button', 'primary');
const allStories = getAllStorybookStories();
const buttonStories = getStorybookStoriesForComponent('Button');

6. Feature Flags

Enable or disable features dynamically.

{
  "features": [
    {
      "id": "table-management",
      "name": "Table Management",
      "enabled": true,
      "priority": "high",
      "ui": {
        "showInNav": true,
        "icon": "TableChart",
        "actions": ["create", "delete"]
      }
    }
  ]
}

Using Features

import { getFeatureById, getFeatures } from '@/utils/featureConfig';

const feature = getFeatureById('table-management');
const canCreate = feature?.ui.actions.includes('create');
const allFeatures = getFeatures(); // Only enabled features

7. Form Schemas

Dynamic form generation from JSON.

{
  "formSchemas": {
    "users": {
      "fields": [
        {
          "name": "name",
          "type": "text",
          "label": "Name",
          "required": true,
          "minLength": 2,
          "maxLength": 100
        },
        {
          "name": "email",
          "type": "email",
          "label": "Email",
          "required": true,
          "validation": "email"
        }
      ],
      "submitLabel": "Save User",
      "cancelLabel": "Cancel"
    }
  }
}

Using Form Schemas

import { getFormSchema } from '@/utils/featureConfig';

const schema = getFormSchema('users');

<FormDialog
  open={open}
  title="Add User"
  fields={schema.fields}
  submitLabel={schema.submitLabel}
  onSubmit={handleSubmit}
/>

8. Translations

Multi-language support.

{
  "translations": {
    "en": {
      "features": {
        "database-crud": {
          "name": "Database CRUD Operations",
          "description": "Create, read, update, and delete records"
        }
      },
      "actions": {
        "create": "Create",
        "update": "Update"
      }
    },
    "fr": {
      "features": {
        "database-crud": {
          "name": "Opérations CRUD",
          "description": "Créer, lire, mettre à jour et supprimer"
        }
      },
      "actions": {
        "create": "Créer",
        "update": "Mettre à jour"
      }
    }
  }
}

Using Translations

import {
  getFeatureTranslation,
  getActionTranslation,
} from '@/utils/featureConfig';

const feature = getFeatureTranslation('database-crud', 'fr');
const createAction = getActionTranslation('create', 'fr');

9. API Endpoints

REST API documentation in JSON.

{
  "apiEndpoints": {
    "users": {
      "list": {
        "method": "GET",
        "path": "/api/admin/users",
        "description": "List all users"
      },
      "create": {
        "method": "POST",
        "path": "/api/admin/users",
        "description": "Create a new user"
      }
    }
  }
}

Using API Endpoints

import { getApiEndpoint, getApiEndpoints } from '@/utils/featureConfig';

const endpoint = getApiEndpoint('users', 'list');
// { method: 'GET', path: '/api/admin/users', description: '...' }

const allUserEndpoints = getApiEndpoints('users');

10. Permissions

Role-based access control.

{
  "permissions": {
    "users": {
      "create": ["admin"],
      "read": ["admin", "user"],
      "update": ["admin"],
      "delete": ["admin"]
    }
  }
}

Using Permissions

import { hasPermission, getPermissions } from '@/utils/featureConfig';

const canCreate = hasPermission('users', 'create', userRole);
const userPermissions = getPermissions('users');

Benefits

1. Configuration-Driven Development

  • Define UIs, queries, tests, and stories in JSON
  • No code changes needed for many modifications
  • Non-developers can contribute

2. Consistency

  • All features use the same structure
  • Standardized component usage
  • Enforced patterns

3. Rapid Development

  • Prototype new features quickly
  • Reuse existing patterns
  • Less boilerplate code

4. Maintainability

  • Single source of truth
  • Easy to find and update configuration
  • Clear separation of concerns

5. Testing

  • Playbooks define test scenarios
  • Storybook stories from JSON
  • Easy to add new test cases

6. Flexibility

  • Enable/disable features dynamically
  • A/B test different configurations
  • Multi-language support

Best Practices

1. Keep Trees Shallow

Avoid deeply nested component trees - they're hard to read and maintain.

2. Use Meaningful Names

Name component trees, playbooks, and templates descriptively:

  • UserListPage
  • Page1

3. Document with Comments

Use the comment property in component trees:

{
  "component": "Outlet",
  "comment": "Child routes render here"
}

4. Validate Configuration

Use TypeScript types to ensure correctness:

import type { ComponentTree, SqlTemplate } from '@/utils/featureConfig';

5. Test Generated UIs

Always test component trees after changes:

const tree = getComponentTree('MyPage');
expect(tree).toBeDefined();
expect(tree.component).toBe('Box');

6. Version Control

Track features.json changes carefully - it's critical infrastructure.

7. Modular Organization

Group related templates, playbooks, and stories together.

Conclusion

The features.json configuration system enables:

  • 50% less boilerplate code in components
  • Declarative UI definition without JSX
  • Configuration-driven E2E tests with Playwright
  • Automated Storybook stories from JSON
  • Parameterized SQL queries for safety
  • Complete feature configuration in one place

This architecture scales to hundreds of features while keeping the codebase maintainable and the development workflow efficient.