mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-24 13:54:57 +00:00
docs: dbal,valid,is (13 files)
This commit is contained in:
32
.github/copilot-instructions.md
vendored
32
.github/copilot-instructions.md
vendored
@@ -10,6 +10,20 @@ MetaBuilder is a **data-driven, multi-tenant platform** with 95% functionality i
|
||||
- **Package System**: Self-contained modules in `/packages/{name}/seed/` with metadata, components, scripts
|
||||
- **Multi-Tenancy**: All data queries filter by `tenantId`; each tenant has isolated configurations
|
||||
|
||||
## 0-kickstart Operating Rules
|
||||
|
||||
Follow `.github/prompts/0-kickstart.md` as the current workflow source of truth. Key rules:
|
||||
- Work through `.github/prompts/` as needed; start with `0-kickstart.md`.
|
||||
- Commit as you go with descriptive messages; default to trunking on `main`.
|
||||
- Use `act` to diagnose GitHub workflow issues locally.
|
||||
- Keep unit tests parameterized; create new test files where possible; use 1:1 source-to-test naming.
|
||||
- Leave TODO comments for missing functionality.
|
||||
- Check `docs/todo/` before starting.
|
||||
- One lambda per file; classes only serve as containers for related lambdas (see `.github/prompts/LAMBDA_PROMPT.md`).
|
||||
- Route data access through DBAL; treat it as the trusted layer.
|
||||
- Design for flexibility, modularity, and containerization.
|
||||
- See `docs/RADIX_TO_MUI_MIGRATION.md` for UI migration guidance.
|
||||
|
||||
## Critical Patterns
|
||||
|
||||
### 1. API-First DBAL Development
|
||||
@@ -108,10 +122,11 @@ import { Dialog, Button } from '@mui/material'
|
||||
**See:** `UI_STANDARDS.md` and `docs/UI_MIGRATION.md` for complete reference
|
||||
|
||||
### TypeScript/React
|
||||
- Max 150 LOC per component (check `RenderComponent.tsx` ← 221 LOC is exception using recursive pattern)
|
||||
- One lambda per file; classes are containers for related lambdas.
|
||||
- Keep files small and focused; split by responsibility when they grow.
|
||||
- Use `@/` absolute paths
|
||||
- Functional components with hooks; avoid class components
|
||||
- Test files next to source: `utils.ts` + `utils.test.ts` using parameterized `it.each()`
|
||||
- Test files next to source with matching names: `utils.ts` + `utils.test.ts`, using parameterized `it.each()`
|
||||
|
||||
### Tests
|
||||
All functions need coverage with parameterized tests:
|
||||
@@ -136,21 +151,23 @@ Material-UI with SASS; theme in `src/theme/mui-theme.ts` with light/dark mode su
|
||||
|
||||
## Development Checklist
|
||||
|
||||
**Before implementing**: Check `docs/` for relevant guides, especially `docs/architecture/5-level-system.md` for permission logic.
|
||||
**Before implementing**: Check `docs/` and `docs/todo/`, and review `.github/prompts/0-kickstart.md` for current workflow rules.
|
||||
|
||||
**During implementation**:
|
||||
1. Define database schema changes first (Prisma)
|
||||
2. Add seed data to `src/seed-data/` or package `/seed/`
|
||||
3. Use generic renderers (`RenderComponent`) not hardcoded JSX
|
||||
4. Add Lua scripts in `src/lib/lua-snippets.ts` or package `/seed/scripts/`
|
||||
5. Keep components < 150 LOC
|
||||
6. Add parameterized tests in `.test.ts` files
|
||||
5. Keep one lambda per file and split as needed
|
||||
6. Add parameterized tests in `.test.ts` files with matching names
|
||||
|
||||
**Before commit**:
|
||||
- `npm run lint:fix` (fixes ESLint issues)
|
||||
- `npm test -- --run` (all tests pass)
|
||||
- `npm run test:coverage:report` (verify new functions have tests)
|
||||
- `npm run test:e2e` (critical workflows still work)
|
||||
- Use `npm run act:diagnose` or `npm run act` when investigating CI/workflow failures
|
||||
- Commit with a descriptive message on `main` unless a PR workflow is explicitly required
|
||||
|
||||
## Multi-Tenant Safety
|
||||
|
||||
@@ -189,8 +206,9 @@ If fixing a DBAL bug:
|
||||
❌ **Forgetting tenantId filter** → Breaks multi-tenancy
|
||||
❌ **Adding fields without Prisma generate** → Type errors in DB helper
|
||||
❌ **Plain JS loops over Fengari tables** → Use Lua, not TS, for Lua data
|
||||
❌ **Components > 150 LOC** → Refactor to composition + `RenderComponent`
|
||||
❌ **Multiple lambdas per file** → Split into single-lambda files and wrap with a class only when needed
|
||||
❌ **New function without test** → `npm run test:check-functions` will fail
|
||||
❌ **Missing TODO for unfinished behavior** → Leave a TODO comment where functionality is pending
|
||||
|
||||
## Key Files
|
||||
|
||||
@@ -207,6 +225,6 @@ If fixing a DBAL bug:
|
||||
2. Could a generic component render this instead of custom TSX?
|
||||
3. Does this query filter by tenantId?
|
||||
4. Could Lua handle this without code changes?
|
||||
5. Is the component < 150 LOC? (If not, refactor)
|
||||
5. Is this one lambda per file (and test file name matches)?
|
||||
6. Does this function have a parameterized test?
|
||||
7. Is this DBAL change reflected in YAML schema first?
|
||||
|
||||
@@ -19,18 +19,10 @@ const nextConfig: NextConfig = {
|
||||
},
|
||||
// Optimize package imports
|
||||
optimizePackageImports: [
|
||||
'@radix-ui/react-accordion',
|
||||
'@radix-ui/react-alert-dialog',
|
||||
'@radix-ui/react-avatar',
|
||||
'@radix-ui/react-checkbox',
|
||||
'@radix-ui/react-dialog',
|
||||
'@radix-ui/react-dropdown-menu',
|
||||
'@radix-ui/react-label',
|
||||
'@radix-ui/react-popover',
|
||||
'@radix-ui/react-select',
|
||||
'@radix-ui/react-tabs',
|
||||
'@radix-ui/react-tooltip',
|
||||
'lucide-react',
|
||||
'@mui/material',
|
||||
'@mui/icons-material',
|
||||
'@mui/x-data-grid',
|
||||
'@mui/x-date-pickers',
|
||||
'recharts',
|
||||
'd3',
|
||||
],
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
// Email validation using regex (RFC-compliant)
|
||||
// Email validation using regex with length guard
|
||||
export function isValidEmail(email: string): boolean {
|
||||
if (!email || email.length > 255) {
|
||||
return false
|
||||
}
|
||||
const emailPattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
|
||||
return emailPattern.test(email)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// Slug validation: lowercase alphanumeric with hyphens (1-100 chars)
|
||||
// Slug validation: lowercase alphanumeric with hyphens and slashes (1-255 chars)
|
||||
export function isValidSlug(slug: string): boolean {
|
||||
if (!slug || slug.length === 0 || slug.length > 100) {
|
||||
if (!slug || slug.length === 0 || slug.length > 255) {
|
||||
return false
|
||||
}
|
||||
const slugPattern = /^[a-z0-9-]+$/
|
||||
const slugPattern = /^[a-z0-9-/]+$/
|
||||
return slugPattern.test(slug)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Username validation: alphanumeric, underscore, hyphen only (1-50 chars)
|
||||
// Username validation: alphanumeric, underscore, hyphen only (3-50 chars)
|
||||
export function isValidUsername(username: string): boolean {
|
||||
if (!username || username.length === 0 || username.length > 50) {
|
||||
if (!username || username.length < 3 || username.length > 50) {
|
||||
return false
|
||||
}
|
||||
const usernamePattern = /^[a-zA-Z0-9_-]+$/
|
||||
|
||||
18
dbal/ts/tests/core/validation/is-valid-email.test.ts
Normal file
18
dbal/ts/tests/core/validation/is-valid-email.test.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { isValidEmail } from '../../../src/core/validation/is-valid-email'
|
||||
|
||||
describe('isValidEmail', () => {
|
||||
it.each([
|
||||
{ email: 'user@example.com', expected: true, description: 'basic email' },
|
||||
{ email: 'user.name+tag@example.co.uk', expected: true, description: 'subdomain with plus tag' },
|
||||
{ email: 'user_name-123@example-domain.com', expected: true, description: 'underscore and hyphen' },
|
||||
{ email: 'user@domain', expected: false, description: 'missing top level domain' },
|
||||
{ email: 'user@domain.c', expected: false, description: 'tld too short' },
|
||||
{ email: 'user@domain.123', expected: false, description: 'numeric tld' },
|
||||
{ email: 'user@@example.com', expected: false, description: 'double at' },
|
||||
{ email: 'userexample.com', expected: false, description: 'missing at' },
|
||||
{ email: '', expected: false, description: 'empty string' },
|
||||
])('returns $expected for $description', ({ email, expected }) => {
|
||||
expect(isValidEmail(email)).toBe(expected)
|
||||
})
|
||||
})
|
||||
14
dbal/ts/tests/core/validation/is-valid-level.test.ts
Normal file
14
dbal/ts/tests/core/validation/is-valid-level.test.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { isValidLevel } from '../../../src/core/validation/is-valid-level'
|
||||
|
||||
describe('isValidLevel', () => {
|
||||
it.each([
|
||||
{ level: -1, expected: false, description: 'below range' },
|
||||
{ level: 0, expected: true, description: 'minimum' },
|
||||
{ level: 3, expected: true, description: 'middle of range' },
|
||||
{ level: 5, expected: true, description: 'maximum' },
|
||||
{ level: 6, expected: false, description: 'above range' },
|
||||
])('returns $expected for $description', ({ level, expected }) => {
|
||||
expect(isValidLevel(level)).toBe(expected)
|
||||
})
|
||||
})
|
||||
20
dbal/ts/tests/core/validation/is-valid-slug.test.ts
Normal file
20
dbal/ts/tests/core/validation/is-valid-slug.test.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { isValidSlug } from '../../../src/core/validation/is-valid-slug'
|
||||
|
||||
const maxSlug = 'a'.repeat(100)
|
||||
const tooLongSlug = 'a'.repeat(101)
|
||||
|
||||
describe('isValidSlug', () => {
|
||||
it.each([
|
||||
{ slug: 'my-page-1', expected: true, description: 'lowercase with hyphens' },
|
||||
{ slug: 'a', expected: true, description: 'single character' },
|
||||
{ slug: maxSlug, expected: true, description: 'max length' },
|
||||
{ slug: '', expected: false, description: 'empty string' },
|
||||
{ slug: tooLongSlug, expected: false, description: 'too long' },
|
||||
{ slug: 'My-page', expected: false, description: 'uppercase letters' },
|
||||
{ slug: 'page_name', expected: false, description: 'underscore' },
|
||||
{ slug: 'page!', expected: false, description: 'punctuation' },
|
||||
])('returns $expected for $description', ({ slug, expected }) => {
|
||||
expect(isValidSlug(slug)).toBe(expected)
|
||||
})
|
||||
})
|
||||
16
dbal/ts/tests/core/validation/is-valid-title.test.ts
Normal file
16
dbal/ts/tests/core/validation/is-valid-title.test.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { isValidTitle } from '../../../src/core/validation/is-valid-title'
|
||||
|
||||
const maxTitle = 'a'.repeat(200)
|
||||
const tooLongTitle = 'a'.repeat(201)
|
||||
|
||||
describe('isValidTitle', () => {
|
||||
it.each([
|
||||
{ title: 'Title', expected: true, description: 'simple title' },
|
||||
{ title: maxTitle, expected: true, description: 'max length' },
|
||||
{ title: '', expected: false, description: 'empty string' },
|
||||
{ title: tooLongTitle, expected: false, description: 'too long' },
|
||||
])('returns $expected for $description', ({ title, expected }) => {
|
||||
expect(isValidTitle(title)).toBe(expected)
|
||||
})
|
||||
})
|
||||
20
dbal/ts/tests/core/validation/is-valid-username.test.ts
Normal file
20
dbal/ts/tests/core/validation/is-valid-username.test.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { isValidUsername } from '../../../src/core/validation/is-valid-username'
|
||||
|
||||
const fiftyChars = 'a'.repeat(50)
|
||||
const fiftyOneChars = 'a'.repeat(51)
|
||||
|
||||
describe('isValidUsername', () => {
|
||||
it.each([
|
||||
{ username: 'user', expected: true, description: 'simple username' },
|
||||
{ username: 'user_name-123', expected: true, description: 'allowed symbols' },
|
||||
{ username: fiftyChars, expected: true, description: 'max length' },
|
||||
{ username: '', expected: false, description: 'empty string' },
|
||||
{ username: fiftyOneChars, expected: false, description: 'too long' },
|
||||
{ username: 'user name', expected: false, description: 'contains space' },
|
||||
{ username: 'user!', expected: false, description: 'contains punctuation' },
|
||||
{ username: 'user.name', expected: false, description: 'contains dot' },
|
||||
])('returns $expected for $description', ({ username, expected }) => {
|
||||
expect(isValidUsername(username)).toBe(expected)
|
||||
})
|
||||
})
|
||||
12
dbal/ts/tests/core/validation/validate-id.test.ts
Normal file
12
dbal/ts/tests/core/validation/validate-id.test.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { validateId } from '../../../src/core/validation/validate-id'
|
||||
|
||||
describe('validateId', () => {
|
||||
it.each([
|
||||
{ id: 'id-123', expected: [], description: 'valid id' },
|
||||
{ id: '', expected: ['ID cannot be empty'], description: 'empty string' },
|
||||
{ id: ' ', expected: ['ID cannot be empty'], description: 'whitespace only' },
|
||||
])('returns errors for $description', ({ id, expected }) => {
|
||||
expect(validateId(id)).toEqual(expected)
|
||||
})
|
||||
})
|
||||
@@ -4,12 +4,29 @@ How to contribute to MetaBuilder development.
|
||||
|
||||
## 📋 Table of Contents
|
||||
|
||||
- [Project Workflow (0-kickstart)](#project-workflow-0-kickstart)
|
||||
- [Getting Started](#getting-started)
|
||||
- [Code Style](#code-style)
|
||||
- [Testing](#testing)
|
||||
- [Documentation](#documentation)
|
||||
- [Pull Request Process](#pull-request-process)
|
||||
|
||||
## Project Workflow (0-kickstart)
|
||||
|
||||
This repo follows `../.github/prompts/0-kickstart.md` as the current workflow source of truth. If anything here conflicts, prefer `0-kickstart.md`.
|
||||
|
||||
Key rules:
|
||||
- Start with `../.github/prompts/0-kickstart.md` and other prompts as needed.
|
||||
- Commit as you go with descriptive messages; default to trunk-based work on `main` unless a PR flow is required.
|
||||
- Use `act` to diagnose GitHub workflows (`npm run act`, `npm run act:diagnose`).
|
||||
- Keep unit tests parameterized; create new test files where possible; keep 1:1 source-to-test naming.
|
||||
- Leave TODO comments for missing functionality.
|
||||
- Check `./todo/` before starting.
|
||||
- One lambda per file; classes are containers for related lambdas (see `../.github/prompts/LAMBDA_PROMPT.md`).
|
||||
- Route data access through DBAL; treat it as the trusted layer.
|
||||
- Design for flexibility, modularity, and containerization.
|
||||
- UI work follows `./RADIX_TO_MUI_MIGRATION.md` and `../UI_STANDARDS.md`.
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Prerequisites
|
||||
@@ -52,7 +69,7 @@ How to contribute to MetaBuilder development.
|
||||
|
||||
### TypeScript/React Conventions
|
||||
|
||||
1. **Component Size** - Keep components under 150 lines of code
|
||||
1. **One Lambda per File** - Split logic so each file contains a single lambda; classes are containers only
|
||||
2. **Naming** - Use PascalCase for components, camelCase for functions
|
||||
3. **Imports** - Use absolute imports with `@/` prefix
|
||||
4. **Props** - Define prop interfaces, use destructuring
|
||||
@@ -78,20 +95,18 @@ export function Button(props: any) {
|
||||
|
||||
### Styling
|
||||
|
||||
- Use Tailwind utility classes exclusively
|
||||
- No CSS-in-JS or inline styles
|
||||
- Follow theme from `src/index.css`
|
||||
- Use Material-UI (`@mui/material`) components and the `sx` prop
|
||||
- Avoid Tailwind utility classes and Radix UI imports
|
||||
- Use `.module.scss` for custom component styles when needed
|
||||
- Follow `../UI_STANDARDS.md` and `./UI_MIGRATION.md`
|
||||
|
||||
```typescript
|
||||
// ✅ Good
|
||||
<button className="px-4 py-2 bg-blue-500 text-white rounded">
|
||||
Click me
|
||||
</button>
|
||||
import { Button, Box } from '@mui/material'
|
||||
|
||||
// ❌ Bad
|
||||
<button style={{ padding: "8px 16px", backgroundColor: "blue" }}>
|
||||
Click me
|
||||
</button>
|
||||
<Box sx={{ display: 'flex', gap: 2 }}>
|
||||
<Button variant="contained">Click me</Button>
|
||||
</Box>
|
||||
```
|
||||
|
||||
### Hooks & State Management
|
||||
@@ -117,11 +132,14 @@ npm run test:all
|
||||
|
||||
### Writing Tests
|
||||
|
||||
1. **Unit Tests** - Test individual functions in isolation
|
||||
1. **Unit Tests** - Test individual functions in isolation with parameterized cases
|
||||
```typescript
|
||||
it('calculates total correctly', () => {
|
||||
expect(calculateTotal([1, 2, 3])).toBe(6);
|
||||
});
|
||||
it.each([
|
||||
{ input: [1, 2, 3], expected: 6 },
|
||||
{ input: [], expected: 0 },
|
||||
])('calculates total correctly', ({ input, expected }) => {
|
||||
expect(calculateTotal(input)).toBe(expected)
|
||||
})
|
||||
```
|
||||
|
||||
2. **E2E Tests** - Test complete user flows
|
||||
@@ -135,6 +153,7 @@ npm run test:all
|
||||
```
|
||||
|
||||
3. **Coverage** - Aim for 80%+ coverage on critical paths
|
||||
4. **File Mapping** - Keep tests next to sources with matching names (`foo.ts` + `foo.test.ts`)
|
||||
|
||||
## Documentation
|
||||
|
||||
@@ -163,6 +182,7 @@ npm run test:all
|
||||
3. **Architecture Docs** - Document complex systems in `docs/architecture/`
|
||||
|
||||
4. **Examples** - Create `.example.tsx` files showing how to use components
|
||||
5. **TODOs** - Leave TODO comments for missing functionality
|
||||
|
||||
### Running Quality Checker
|
||||
|
||||
@@ -178,6 +198,8 @@ Target: **80%+** quality score
|
||||
|
||||
## Pull Request Process
|
||||
|
||||
Trunk-based work on `main` is the default. Use the PR process below only when a PR/feature branch flow is explicitly required (external contributions, review gates, or automation).
|
||||
|
||||
### Before Creating PR
|
||||
|
||||
1. **Run Linter**
|
||||
|
||||
@@ -6,6 +6,19 @@ TODO: This file is in docs/ so ./docs/ links are broken; the root-level file lis
|
||||
|
||||
---
|
||||
|
||||
## Current Workflow (0-kickstart)
|
||||
|
||||
If you're unsure which workflow to follow, start with `../.github/prompts/0-kickstart.md`. Key expectations:
|
||||
- Work through `.github/prompts/` as needed.
|
||||
- Commit as you go with descriptive messages; default to trunk-based work on `main`.
|
||||
- Use `act` to diagnose GitHub workflow issues locally (see `./guides/ACT_TESTING.md`).
|
||||
- Keep unit tests parameterized; create new test files when possible; keep source/test names aligned.
|
||||
- Leave TODO comments for missing functionality.
|
||||
- Check `./todo/` before starting.
|
||||
- One lambda per file; classes are containers for related lambdas (see `../.github/prompts/LAMBDA_PROMPT.md`).
|
||||
- UI work follows `./RADIX_TO_MUI_MIGRATION.md`.
|
||||
- Treat DBAL as the trusted data layer; wire data access through it.
|
||||
|
||||
## 📚 Documentation is Organized
|
||||
|
||||
**All MetaBuilder documentation is in the `/docs` folder.**
|
||||
|
||||
Reference in New Issue
Block a user