diff --git a/docs/deploystack/development/backend/api-pagination.mdx b/docs/deploystack/development/backend/api-pagination.mdx index 8352d32..1b5bb19 100644 --- a/docs/deploystack/development/backend/api-pagination.mdx +++ b/docs/deploystack/development/backend/api-pagination.mdx @@ -121,7 +121,7 @@ const paginatedResponseSchema = z.object({ ```typescript import { z } from 'zod'; -import { zodToJsonSchema } from 'zod-to-json-schema'; +import { createSchema } from 'zod-openapi'; // Query parameters (including pagination) const querySchema = z.object({ @@ -168,15 +168,9 @@ export default async function listItems(server: FastifyInstance) { tags: ['Items'], summary: 'List items with pagination', description: 'Retrieve items with pagination support. Supports filtering and sorting.', - querystring: zodToJsonSchema(querySchema, { - $refStrategy: 'none', - target: 'openApi3' - }), + querystring: createSchema(querySchema), response: { - 200: zodToJsonSchema(responseSchema, { - $refStrategy: 'none', - target: 'openApi3' - }) + 200: createSchema(responseSchema) } } }, async (request, reply) => { @@ -559,15 +553,9 @@ export default async function listServers(server: FastifyInstance) { tags: ['MCP Servers'], summary: 'List MCP servers', description: 'Retrieve MCP servers with pagination support...', - querystring: zodToJsonSchema(querySchema, { - $refStrategy: 'none', - target: 'openApi3' - }), + querystring: createSchema(querySchema), response: { - 200: zodToJsonSchema(listServersResponseSchema, { - $refStrategy: 'none', - target: 'openApi3' - }) + 200: createSchema(listServersResponseSchema) } } }, async (request, reply) => { diff --git a/docs/deploystack/development/backend/api-security.mdx b/docs/deploystack/development/backend/api-security.mdx index a9312ac..e82edd2 100644 --- a/docs/deploystack/development/backend/api-security.mdx +++ b/docs/deploystack/development/backend/api-security.mdx @@ -65,30 +65,15 @@ export default async function secureRoute(fastify: FastifyInstance) { summary: 'Protected endpoint', description: 'Requires admin permissions', security: [{ cookieAuth: [] }], - body: zodToJsonSchema(RequestSchema, { - $refStrategy: 'none', - target: 'openApi3' - }), + body: createSchema(RequestSchema), response: { - 200: zodToJsonSchema(SuccessResponseSchema, { - $refStrategy: 'none', - target: 'openApi3' - }), - 401: zodToJsonSchema(ErrorResponseSchema.describe('Unauthorized'), { - $refStrategy: 'none', - target: 'openApi3' - }), - 403: zodToJsonSchema(ErrorResponseSchema.describe('Forbidden'), { - $refStrategy: 'none', - target: 'openApi3' - }), - 400: zodToJsonSchema(ErrorResponseSchema.describe('Bad Request'), { - $refStrategy: 'none', - target: 'openApi3' - }) + 200: createSchema(SuccessResponseSchema.describe('Success')), + 401: createSchema(ErrorResponseSchema.describe('Unauthorized')), + 403: createSchema(ErrorResponseSchema.describe('Forbidden')), + 400: createSchema(ErrorResponseSchema.describe('Bad Request')) } }, - preValidation: requireGlobalAdmin(), // ✅ CORRECT: Runs before validation + preValidation: requireGlobalAdmin(), // ✅ CORRECT: Runs before validation, }, async (request, reply) => { // If we reach here, user is authorized AND input is validated const validatedData = request.body; diff --git a/docs/deploystack/development/backend/api.mdx b/docs/deploystack/development/backend/api.mdx index 38efc0f..ee5bf1a 100644 --- a/docs/deploystack/development/backend/api.mdx +++ b/docs/deploystack/development/backend/api.mdx @@ -9,7 +9,7 @@ This document explains how to generate and use the OpenAPI specification for the ## Overview -The DeployStack Backend uses Fastify with Swagger plugins to automatically generate OpenAPI 3.0 specifications. Route schemas are defined using [Zod](https://zod.dev/) for type safety and expressiveness, and then converted to JSON Schema using the [zod-to-json-schema](https://www.npmjs.com/package/zod-to-json-schema) library. This provides: +The DeployStack Backend uses Fastify with Swagger plugins to automatically generate OpenAPI 3.0 specifications. Route schemas are defined using [Zod](https://zod.dev/) for type safety and expressiveness, and then converted to JSON Schema using the [zod-openapi](https://www.npmjs.com/package/zod-openapi) library. This provides: - **Interactive Documentation**: Swagger UI interface for testing APIs - **Postman Integration**: JSON/YAML specs that can be imported into Postman @@ -171,7 +171,7 @@ Each route file should follow this pattern: ```typescript import { type FastifyInstance } from 'fastify' import { z } from 'zod' -import { zodToJsonSchema } from 'zod-to-json-schema' +import { createSchema } from 'zod-openapi' // Define your schemas const responseSchema = z.object({ @@ -185,10 +185,7 @@ export default async function yourRoute(server: FastifyInstance) { summary: 'Brief description', description: 'Detailed description', response: { - 200: zodToJsonSchema(responseSchema, { - $refStrategy: 'none', - target: 'openApi3' - }) + 200: createSchema(responseSchema) } } }, async () => { @@ -311,7 +308,7 @@ const routeSchema = { required: true, content: { 'application/json': { - schema: zodToJsonSchema(requestSchema, { $refStrategy: 'none', target: 'openApi3' }) + schema: createSchema(requestSchema) } } }, @@ -321,17 +318,17 @@ const routeSchema = { ## Adding Documentation to Routes -To add OpenAPI documentation to your routes, define your request body and response schemas using Zod. Then, use the `zodToJsonSchema` utility to convert these Zod schemas into the JSON Schema format expected by Fastify. +To add OpenAPI documentation to your routes, define your request body and response schemas using Zod. Then, use the `createSchema` utility to convert these Zod schemas into the JSON Schema format expected by Fastify. -Make sure you have `zod` and `zod-to-json-schema` installed in your backend service. +Make sure you have `zod` and `zod-openapi` installed in your backend service. -### Recommended Approach: Automatic Validation with Zod +### Recommended Approach: Dual-Schema Pattern for Validation + Documentation -The power of Zod lies in providing **automatic validation** through Fastify's schema system. This approach eliminates manual validation and leverages Zod's full validation capabilities. +**IMPORTANT**: After the Zod v4 migration, we use a **dual-schema approach** to ensure both proper Fastify validation and accurate OpenAPI documentation. ```typescript import { z } from 'zod'; -import { zodToJsonSchema } from 'zod-to-json-schema'; +import { createSchema } from 'zod-openapi'; // 1. Define your Zod schemas for request body, responses, etc. const myRequestBodySchema = z.object({ @@ -351,36 +348,37 @@ const myErrorResponseSchema = z.object({ error: z.string().describe("Error message detailing what went wrong") }); -// 2. Construct the Fastify route schema using zodToJsonSchema +// 2. Construct the Fastify route schema using DUAL-SCHEMA PATTERN const routeSchema = { tags: ['Category'], // Your API category summary: 'Brief description of your endpoint', description: 'Detailed description of what this endpoint does, its parameters, and expected outcomes. Requires Content-Type: application/json header when sending request body.', security: [{ cookieAuth: [] }], // Include if authentication is required - body: zodToJsonSchema(myRequestBodySchema, { - $refStrategy: 'none', // Keeps definitions inline, often simpler for Fastify - target: 'openApi3' // Ensures compatibility with OpenAPI 3.0 - }), + + // ✅ CRITICAL: Use plain JSON Schema for Fastify validation + body: { + type: 'object', + properties: { + name: { type: 'string', minLength: 3 }, + count: { type: 'number', minimum: 1 }, + type: { type: 'string', enum: ['mysql', 'sqlite'] } + }, + required: ['name', 'count', 'type'], + additionalProperties: false + }, + + // ✅ Use createSchema() for OpenAPI documentation requestBody: { required: true, content: { 'application/json': { - schema: zodToJsonSchema(myRequestBodySchema, { - $refStrategy: 'none', - target: 'openApi3' - }) + schema: createSchema(myRequestBodySchema) } } }, response: { - 200: zodToJsonSchema(mySuccessResponseSchema.describe("Successful operation"), { - $refStrategy: 'none', - target: 'openApi3' - }), - 400: zodToJsonSchema(myErrorResponseSchema.describe("Bad Request - Invalid input"), { - $refStrategy: 'none', - target: 'openApi3' - }), + 200: createSchema(mySuccessResponseSchema.describe("Successful operation")), + 400: createSchema(myErrorResponseSchema.describe("Bad Request - Invalid input")), // Define other responses (e.g., 401, 403, 404, 500) similarly } }; @@ -396,18 +394,20 @@ fastify.post<{ Body: RequestBody }>( '/your-route', { schema: routeSchema }, async (request, reply) => { - // ✅ Fastify has already validated request.body using our Zod schema + // ✅ Fastify has already validated request.body using the JSON schema // ✅ If we reach here, request.body is guaranteed to be valid // ✅ No manual validation needed! const { name, count, type } = request.body; // Fully typed and validated // Your route handler logic here - return reply.status(200).send({ + const successResponse = { success: true, itemId: 'some-uuid-v4-here', message: `Item ${name} processed successfully with ${count} items using ${type}.` - }); + }; + const jsonString = JSON.stringify(successResponse); + return reply.status(200).type('application/json').send(jsonString); } ); ``` @@ -421,6 +421,78 @@ fastify.post<{ Body: RequestBody }>( 5. **Type Safety**: Handlers receive properly typed, validated data 6. **Cleaner Code**: No redundant validation logic in handlers +## JSON Response Serialization Pattern + +**CRITICAL**: After the Zod v4 migration, all API responses must use manual JSON serialization to prevent `"[object Object]"` serialization issues. + +### Required Response Pattern + +```typescript +// ✅ CORRECT: Manual JSON serialization +const successResponse = { + success: true, + message: 'Operation completed successfully', + data: { /* your data */ } +}; +const jsonString = JSON.stringify(successResponse); +return reply.status(200).type('application/json').send(jsonString); +``` + +### What NOT to Do + +```typescript +// ❌ WRONG: Direct object response (causes serialization issues) +return reply.status(200).send({ + success: true, + message: 'This will become "[object Object]"' +}); + +// ❌ WRONG: Using reply.send() without JSON.stringify() +const response = { success: true, message: 'Test' }; +return reply.status(200).send(response); +``` + +### Error Response Pattern + +All error responses must also use manual JSON serialization: + +```typescript +// ✅ CORRECT: Error response with manual serialization +const errorResponse = { + success: false, + error: 'Detailed error message' +}; +const jsonString = JSON.stringify(errorResponse); +return reply.status(400).type('application/json').send(jsonString); +``` + +### Authentication Middleware Pattern + +Authentication middleware and hooks must also use this pattern: + +```typescript +// ✅ CORRECT: Authentication error with manual serialization +const errorResponse = { + success: false, + error: 'Unauthorized: Authentication required.' +}; +const jsonString = JSON.stringify(errorResponse); +return reply.status(401).type('application/json').send(jsonString); +``` + +### Why This Pattern is Required + +After the Zod v4 migration, Fastify's automatic JSON serialization can fail with complex objects, resulting in: +- Response bodies showing `"[object Object]"` instead of actual data +- Client applications receiving unparseable responses +- Test failures due to missing `success` and `error` properties + +The manual JSON serialization pattern ensures: +- ✅ Consistent, parseable JSON responses +- ✅ Proper `success`/`error` properties in all responses +- ✅ Reliable client-server communication +- ✅ Passing e2e tests + ### Why Both `body` and `requestBody` Properties? **Important**: You need BOTH properties for complete functionality: @@ -466,7 +538,7 @@ The validation chain works as follows: #### Zod Schema → JSON Schema → Fastify Validation → Handler 1. **Zod Schema**: Define validation rules using Zod -2. **JSON Schema**: Convert to OpenAPI format using `zodToJsonSchema()` +2. **JSON Schema**: Convert to OpenAPI format using `createSchema()` 3. **Fastify Validation**: Fastify automatically validates incoming requests 4. **Handler**: Receives validated, typed data @@ -520,7 +592,35 @@ const logoutSchema = { ## Configuration -The Swagger configuration is in `src/server.ts`: +### Fastify Server Configuration + +The Fastify server is configured with custom AJV options to ensure compatibility with `zod-openapi` schema generation. This configuration is in `src/server.ts`: + +```typescript +const server = fastify({ + logger: loggerConfig, + disableRequestLogging: true, + ajv: { + customOptions: { + strict: false, // Allows unknown keywords in schemas + strictTypes: false, // Disables strict type checking + strictTuples: false // Disables strict tuple checking + } + } +}) +``` + +**Why these AJV options are required:** + +- **`strict: false`**: AJV v8+ runs in strict mode by default, which rejects schemas containing unknown keywords. The `zod-openapi` library generates schemas that may include keywords AJV doesn't recognize in strict mode. +- **`strictTypes: false`**: Prevents strict type validation errors that can occur with complex Zod schemas. +- **`strictTuples: false`**: Allows more flexible tuple handling for array schemas. + +**Important**: These settings don't affect validation behavior - they only allow the schema compilation to succeed. All validation rules defined in your Zod schemas still work exactly as expected. + +### Swagger Configuration + +The Swagger documentation configuration is also in `src/server.ts`: ```typescript await server.register(fastifySwagger, { diff --git a/docs/deploystack/development/backend/database-sqlite.mdx b/docs/deploystack/development/backend/database-sqlite.mdx index 1bc0324..df58a4f 100644 --- a/docs/deploystack/development/backend/database-sqlite.mdx +++ b/docs/deploystack/development/backend/database-sqlite.mdx @@ -444,5 +444,3 @@ Consider hybrid approaches for scaling: For general database concepts and cross-database functionality, see the [Database Development Guide](/deploystack/development/backend/database). For initial setup and configuration, see the [Database Setup Guide](/deploystack/self-hosted/database-setup). - -For comparison with other databases, see the [Cloudflare D1 Development Guide](/deploystack/development/backend/database-d1). diff --git a/docs/deploystack/development/backend/oauth.mdx b/docs/deploystack/development/backend/oauth.mdx index d328ac2..ecc5ea6 100644 --- a/docs/deploystack/development/backend/oauth.mdx +++ b/docs/deploystack/development/backend/oauth.mdx @@ -149,7 +149,7 @@ Create the OAuth routes file: // services/backend/src/routes/auth/google.ts import type { FastifyInstance, FastifyReply } from 'fastify'; import { z } from 'zod'; -import { zodToJsonSchema } from 'zod-to-json-schema'; +import { createSchema } from 'zod-openapi'; import { getLucia } from '../../lib/lucia'; import { getDb, getSchema } from '../../db'; import { eq } from 'drizzle-orm'; @@ -379,7 +379,7 @@ Create a status endpoint for the provider: // services/backend/src/routes/auth/googleStatus.ts import type { FastifyInstance } from 'fastify'; import { z } from 'zod'; -import { zodToJsonSchema } from 'zod-to-json-schema'; +import { createSchema } from 'zod-openapi'; import { GlobalSettingsInitService } from '../../global-settings'; const GoogleStatusResponseSchema = z.object({ @@ -395,10 +395,7 @@ export default async function googleStatusRoutes(fastify: FastifyInstance) { summary: 'Get Google OAuth status', description: 'Returns the current status and configuration of Google OAuth', response: { - 200: zodToJsonSchema(GoogleStatusResponseSchema, { - $refStrategy: 'none', - target: 'openApi3' - }) + 200: createSchema(GoogleStatusResponseSchema) } } }, async (_request, reply) => { diff --git a/docs/deploystack/development/frontend/custom-ui-components.mdx b/docs/deploystack/development/frontend/custom-ui-components.mdx new file mode 100644 index 0000000..39ec1bd --- /dev/null +++ b/docs/deploystack/development/frontend/custom-ui-components.mdx @@ -0,0 +1,595 @@ +--- +title: Custom UI Components +description: Complete guide for creating, managing, and extending custom UI components in the DeployStack frontend, including best practices for integrating with shadcn/vue. +sidebar: Custom Components +--- + +# Custom UI Components + +This guide covers creating and managing custom UI components in DeployStack, including extending shadcn/vue components, building new components from scratch, and maintaining consistency across your design system. + +## Philosophy and Approach + +DeployStack follows the **shadcn/vue philosophy**: components are copied into your codebase and become **yours to own and customize**. This approach provides several key advantages: + +- **Full Control**: Direct access to component source code for unlimited customization +- **No Breaking Changes**: Updates don't break your customizations since you own the code +- **AI-Friendly**: Open code structure allows AI tools to understand and improve components +- **Design System Integration**: Components naturally fit together using shared design tokens + +### When to Create Custom Components + +Create custom components when: + +1. **Missing Components**: The component doesn't exist in shadcn/vue +2. **Complex Compositions**: Combining multiple shadcn components into reusable patterns +3. **Business Logic**: Components specific to DeployStack's domain (e.g., ServerStatusCard, DeploymentProgress) + +## Component Structure and Organization + +### Directory Structure + +All custom components follow the same structure as shadcn/vue components in `src/components/ui/`: + +```bash +src/components/ui/ +├── button-group/ # Custom component +│ ├── ButtonGroup.vue +│ ├── ButtonGroupItem.vue +│ └── index.ts +├── file-uploader/ # Custom component +│ ├── FileUploader.vue +│ ├── FileUploaderDropzone.vue +│ ├── FileUploaderList.vue +│ └── index.ts +├── button/ # Existing shadcn component +├── switch/ # Customized shadcn component +└── ... +``` + +### Export Pattern + +Always follow the shadcn/vue export pattern: + +```typescript +// src/components/ui/button-group/index.ts +export { default as ButtonGroup } from './ButtonGroup.vue' +export { default as ButtonGroupItem } from './ButtonGroupItem.vue' + +// Usage in components +import { ButtonGroup, ButtonGroupItem } from '@/components/ui/button-group' +``` + +## Component Development Guidelines + +### 1. Design Token Integration + +**✅ Use shadcn/vue design tokens for consistency:** + +```vue + + + + + +``` + +**❌ Avoid hard-coded colors:** + +```vue + + + + Content + + +``` + +### 2. Prop Interface Standards + +Follow consistent prop patterns across all components: + +```vue + +``` + +### 3. Class Variance Authority (CVA) + +Use CVA for complex styling variants: + +```vue + +``` + +### 4. Accessibility Requirements + +Ensure all custom components meet accessibility standards: + +```vue + + + + + {{ t('components.buttonGroup.label') }} + {{ t('components.buttonGroup.description') }} + + + + +``` + +## Component Types and Examples + +### 1. Composite Components + +Components that combine multiple shadcn components: + +```vue + + + + + + + {{ server.name }} + + + + + + + + + {{ t('actions.manage') }} + + + {{ t('actions.deploy') }} + + + + + + + {{ server.status }} + {{ server.category }} + + + {{ server.description }} + + + + +``` + +### 2. Utility Components + +Reusable patterns with business logic: + +```vue + + + + + + + + + {{ t(`deployment.status.${status}`) }} + + + + + + + {{ message }} + + + +``` + +### 3. Form Components + +Specialized form inputs that extend basic components: + +```vue + + + + + + + + + {{ selectedIcon.value }} + {{ selectedIcon.label }} + + {{ placeholder }} + + + + + + + + {{ t('components.iconSelector.noResults') }} + + + + {{ icon.value }} + {{ icon.label }} + + + + + + + +``` + +## Customizing Existing shadcn Components + +### Replacing Components + +To customize an existing shadcn component, simply modify the files in `src/components/ui/[component-name]/`: + +```vue + + + + + + + + +``` + +## Documentation Standards + +### Component Documentation + +Each custom component should include comprehensive documentation: + +```vue + + +``` + +### README Documentation + +Create a `README.md` for complex component groups: + +```markdown +# Button Group Component + +A composable button group component for related actions. + +## Features + +- Single or multiple selection modes +- Horizontal and vertical orientations +- Full keyboard accessibility +- Consistent with shadcn/vue design system + +## Usage + +```vue + + + + + Bold + Italic + Underline + + +``` + +## API Reference + +### ButtonGroup Props + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `modelValue` | `string \| string[]` | `undefined` | Selected value(s) | +| `variant` | `'default' \| 'outline' \| 'ghost'` | `'default'` | Visual style | +| `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | Layout direction | + +``` + +## Maintenance and Updates + +### Migration Strategy + +When shadcn/vue adds a component you've built custom: + +1. **Evaluate**: Compare features and API design +2. **Migrate**: If shadcn version is better, plan migration +3. **Extend**: If your version has unique features, consider contributing back +4. **Document**: Update migration guides for the team + +### Version Control + +- Tag component versions in commit messages: `feat(ui): add ButtonGroup component v1.0` +- Document breaking changes in component README +- Maintain changelog for significant UI component updates + +--- + +For questions about custom components, refer to the [UI Design System](/deploystack/development/frontend/ui-design-system) documentation or reach out to the frontend team.
+ {{ server.description }} +
+ {{ message }} +