Move desktop and e2e into examples/ directory

- desktop/ → examples/django-react-desktop-app/
- e2e/ → examples/django-react-site/
- example/ → examples/django-react-site/backend/
- Update Dockerfile.test, Makefile, playwright config, and
  django.config.mjs path references

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-31 20:41:20 -04:00
parent c866142770
commit eee352d908
51 changed files with 5983 additions and 10 deletions

View File

@@ -12,7 +12,7 @@ COPY django/ /app/django/
RUN pip install --no-cache-dir /app/django[channels] daphne RUN pip install --no-cache-dir /app/django[channels] daphne
# Copy example app # Copy example app
COPY example/ /app/example/ COPY examples/django-react-site/backend/ /app/example/
WORKDIR /app/example WORKDIR /app/example

View File

@@ -43,4 +43,4 @@ clean:
docker compose -f docker-compose.test.yml down -v --remove-orphans 2>/dev/null || true docker compose -f docker-compose.test.yml down -v --remove-orphans 2>/dev/null || true
rm -rf django/src/mizan.egg-info django/dist django/build rm -rf django/src/mizan.egg-info django/dist django/build
rm -rf react/dist react/node_modules rm -rf react/dist react/node_modules
rm -f example/db.sqlite3 rm -f examples/django-react-site/backend/db.sqlite3

View File

@@ -276,7 +276,7 @@ cd react && npm test
# E2E integration tests (real browser, real backend) # E2E integration tests (real browser, real backend)
docker compose -f docker-compose.test.yml up -d docker compose -f docker-compose.test.yml up -d
cd e2e/harness && npm install && npx mizan-generate && npx vite --port 5174 & cd examples/django-react-site/harness && npm install && npx mizan-generate && npx vite --port 5174 &
npx playwright test npx playwright test
# All at once # All at once
@@ -289,8 +289,8 @@ make test-all
mizan/ mizan/
django/ Python package (mizan) django/ Python package (mizan)
react/ TypeScript package (@rythazhur/mizan) react/ TypeScript package (@rythazhur/mizan)
example/ Integration test backend (Docker) examples/
desktop/ PyWebView desktop test app django-react-site/ E2E tests, React harness, Django backend
e2e/ Playwright E2E tests + React harness django-react-desktop-app/ PyWebView desktop app
Makefile Test orchestration Makefile Test orchestration
``` ```

View File

@@ -2,17 +2,17 @@ import path from 'path'
import { fileURLToPath } from 'url' import { fileURLToPath } from 'url'
const __dirname = path.dirname(fileURLToPath(import.meta.url)) const __dirname = path.dirname(fileURLToPath(import.meta.url))
const root = path.resolve(__dirname, '../..') const root = path.resolve(__dirname, '../../..')
export default { export default {
projectId: 'e2e-harness', projectId: 'e2e-harness',
source: { source: {
django: { django: {
managePath: path.join(root, 'example/manage.py'), managePath: path.join(root, 'examples/django-react-site/backend/manage.py'),
command: [path.join(root, 'django/.venv/bin/python')], command: [path.join(root, 'django/.venv/bin/python')],
env: { env: {
PYTHONPATH: `${path.join(root, 'django/src')}:${path.join(root, 'example')}`, PYTHONPATH: `${path.join(root, 'django/src')}:${path.join(root, 'examples/django-react-site/backend')}`,
DJANGO_SETTINGS_MODULE: 'testapp.settings', DJANGO_SETTINGS_MODULE: 'testapp.settings',
}, },
}, },

View File

@@ -0,0 +1,40 @@
'use client'
// AUTO-GENERATED by mizan - do not edit manually
// Regenerate with: npm run schemas
import { useChannel, type ChannelSubscription } from 'mizan/channels'
import type { ChatParams, ChatReactMessage, ChatDjangoMessage, NotificationsDjangoMessage, PresenceDjangoMessage, PrivateDjangoMessage } from './generated.channels'
// ============================================================================
// Channel Hooks
// ============================================================================
/**
* Hook for the chat channel.
*/
export function useChatChannel(params: ChatParams): ChannelSubscription<ChatParams, ChatDjangoMessage, ChatReactMessage> {
return useChannel('chat', params)
}
/**
* Hook for the notifications channel.
*/
export function useNotificationsChannel(): ChannelSubscription<Record<string, never>, NotificationsDjangoMessage, never> {
return useChannel('notifications', {})
}
/**
* Hook for the presence channel.
*/
export function usePresenceChannel(): ChannelSubscription<Record<string, never>, PresenceDjangoMessage, never> {
return useChannel('presence', {})
}
/**
* Hook for the private channel.
*/
export function usePrivateChannel(): ChannelSubscription<Record<string, never>, PrivateDjangoMessage, never> {
return useChannel('private', {})
}

View File

@@ -0,0 +1,268 @@
{
"openapi": "3.1.0",
"info": {
"title": "mizan Channels",
"version": "1.0.0",
"description": "Auto-generated schema for mizan channels"
},
"paths": {
"/channels/chat/params": {
"post": {
"operationId": "chatParams",
"summary": "Chat channel params",
"parameters": [],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BaseModel"
}
}
}
}
},
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ChatParams"
}
}
},
"required": true
}
}
},
"/channels/chat/react": {
"post": {
"operationId": "chatReactMessage",
"summary": "Chat React→Django message",
"parameters": [],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BaseModel"
}
}
}
}
},
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ChatReactMessage"
}
}
},
"required": true
}
}
},
"/channels/chat/django": {
"post": {
"operationId": "chatDjangoMessage",
"summary": "Chat Django→React message",
"parameters": [],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ChatDjangoMessage"
}
}
}
}
}
}
},
"/channels/notifications/django": {
"post": {
"operationId": "notificationsDjangoMessage",
"summary": "Notifications Django→React message",
"parameters": [],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/NotificationsDjangoMessage"
}
}
}
}
}
}
},
"/channels/presence/django": {
"post": {
"operationId": "presenceDjangoMessage",
"summary": "Presence Django→React message",
"parameters": [],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PresenceDjangoMessage"
}
}
}
}
}
}
},
"/channels/private/django": {
"post": {
"operationId": "privateDjangoMessage",
"summary": "Private Django→React message",
"parameters": [],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrivateDjangoMessage"
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"BaseModel": {
"properties": {},
"title": "BaseModel",
"type": "object"
},
"ChatParams": {
"properties": {
"room": {
"title": "Room",
"type": "string"
}
},
"required": [
"room"
],
"title": "ChatParams",
"type": "object"
},
"ChatReactMessage": {
"properties": {
"text": {
"title": "Text",
"type": "string"
}
},
"required": [
"text"
],
"title": "ChatReactMessage",
"type": "object"
},
"ChatDjangoMessage": {
"properties": {
"text": {
"title": "Text",
"type": "string"
}
},
"required": [
"text"
],
"title": "ChatDjangoMessage",
"type": "object"
},
"NotificationsDjangoMessage": {
"properties": {
"text": {
"title": "Text",
"type": "string"
}
},
"required": [
"text"
],
"title": "NotificationsDjangoMessage",
"type": "object"
},
"PresenceDjangoMessage": {
"properties": {
"value": {
"title": "Value",
"type": "integer"
}
},
"required": [
"value"
],
"title": "PresenceDjangoMessage",
"type": "object"
},
"PrivateDjangoMessage": {
"properties": {
"text": {
"title": "Text",
"type": "string"
}
},
"required": [
"text"
],
"title": "PrivateDjangoMessage",
"type": "object"
}
}
},
"servers": [],
"x-mizan-channels": [
{
"name": "chat",
"pascalName": "Chat",
"hasParams": true,
"hasReactMessage": true,
"hasDjangoMessage": true,
"paramsType": "ChatParams",
"reactMessageType": "ChatReactMessage",
"djangoMessageType": "ChatDjangoMessage"
},
{
"name": "notifications",
"pascalName": "Notifications",
"hasParams": false,
"hasReactMessage": false,
"hasDjangoMessage": true,
"djangoMessageType": "NotificationsDjangoMessage"
},
{
"name": "presence",
"pascalName": "Presence",
"hasParams": false,
"hasReactMessage": false,
"hasDjangoMessage": true,
"djangoMessageType": "PresenceDjangoMessage"
},
{
"name": "private",
"pascalName": "Private",
"hasParams": false,
"hasReactMessage": false,
"hasDjangoMessage": true,
"djangoMessageType": "PrivateDjangoMessage"
}
]
}

View File

@@ -0,0 +1,337 @@
// AUTO-GENERATED by mizan - do not edit manually
// Regenerate with: npm run schemas
// ============================================================================
// OpenAPI Types (generated by openapi-typescript)
// ============================================================================
export interface paths {
"/channels/chat/params": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/** Chat channel params */
post: operations["chatParams"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/channels/chat/react": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/** Chat React→Django message */
post: operations["chatReactMessage"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/channels/chat/django": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/** Chat Django→React message */
post: operations["chatDjangoMessage"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/channels/notifications/django": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/** Notifications Django→React message */
post: operations["notificationsDjangoMessage"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/channels/presence/django": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/** Presence Django→React message */
post: operations["presenceDjangoMessage"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/channels/private/django": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/** Private Django→React message */
post: operations["privateDjangoMessage"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
}
export type webhooks = Record<string, never>;
export interface components {
schemas: {
/** BaseModel */
BaseModel: Record<string, never>;
/** ChatParams */
ChatParams: {
/** Room */
room: string;
};
/** ChatReactMessage */
ChatReactMessage: {
/** Text */
text: string;
};
/** ChatDjangoMessage */
ChatDjangoMessage: {
/** Text */
text: string;
};
/** NotificationsDjangoMessage */
NotificationsDjangoMessage: {
/** Text */
text: string;
};
/** PresenceDjangoMessage */
PresenceDjangoMessage: {
/** Value */
value: number;
};
/** PrivateDjangoMessage */
PrivateDjangoMessage: {
/** Text */
text: string;
};
};
responses: never;
parameters: never;
requestBodies: never;
headers: never;
pathItems: never;
}
export type $defs = Record<string, never>;
export interface operations {
chatParams: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["ChatParams"];
};
};
responses: {
/** @description OK */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["BaseModel"];
};
};
};
};
chatReactMessage: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["ChatReactMessage"];
};
};
responses: {
/** @description OK */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["BaseModel"];
};
};
};
};
chatDjangoMessage: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description OK */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ChatDjangoMessage"];
};
};
};
};
notificationsDjangoMessage: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description OK */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["NotificationsDjangoMessage"];
};
};
};
};
presenceDjangoMessage: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description OK */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["PresenceDjangoMessage"];
};
};
};
};
privateDjangoMessage: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description OK */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["PrivateDjangoMessage"];
};
};
};
};
}
// ============================================================================
// Convenience Type Exports
// ============================================================================
export type ChatParams = components["schemas"]["ChatParams"]
export type ChatReactMessage = components["schemas"]["ChatReactMessage"]
export type ChatDjangoMessage = components["schemas"]["ChatDjangoMessage"]
export type NotificationsDjangoMessage = components["schemas"]["NotificationsDjangoMessage"]
export type PresenceDjangoMessage = components["schemas"]["PresenceDjangoMessage"]
export type PrivateDjangoMessage = components["schemas"]["PrivateDjangoMessage"]
// ============================================================================
// Channel Registry
// ============================================================================
export const CHANNELS = {
chat: {
name: 'chat',
pascalName: 'Chat',
hasParams: true,
hasReactMessage: true,
hasDjangoMessage: true,
paramsType: 'ChatParams',
reactMessageType: 'ChatReactMessage',
djangoMessageType: 'ChatDjangoMessage',
},
notifications: {
name: 'notifications',
pascalName: 'Notifications',
hasParams: false,
hasReactMessage: false,
hasDjangoMessage: true,
djangoMessageType: 'NotificationsDjangoMessage',
},
presence: {
name: 'presence',
pascalName: 'Presence',
hasParams: false,
hasReactMessage: false,
hasDjangoMessage: true,
djangoMessageType: 'PresenceDjangoMessage',
},
private: {
name: 'private',
pascalName: 'Private',
hasParams: false,
hasReactMessage: false,
hasDjangoMessage: true,
djangoMessageType: 'PrivateDjangoMessage',
},
} as const

View File

@@ -0,0 +1,62 @@
// AUTO-GENERATED by mizan - do not edit manually
// Regenerate with: npm run schemas
//
// Server-side functions for SSR hydration.
// These run in Next.js server components/layouts.
import type { currentUserOutput, greetOutput } from './generated.mizan'
// ============================================================================
// Hydration Types
// ============================================================================
/** Typed hydration data for SSR */
export interface DjangoHydration {
currentUser?: currentUserOutput
greet?: greetOutput
}
// ============================================================================
// SSR Hydration Helper
// ============================================================================
/**
* Fetch hydration data for SSR.
*
* Call this in your server component:
* const hydration = await getDjangoHydration(client)
* return <DjangoContext hydration={hydration}>...</DjangoContext>
*/
export async function getDjangoHydration(
client: { request: (method: string, url: string, body?: unknown) => Promise<Response> }
): Promise<DjangoHydration> {
const hydration: DjangoHydration = {}
const results = await Promise.allSettled([
client.request('POST', '/api/mizan/call/', { fn: 'current_user', args: {} }),
client.request('POST', '/api/mizan/call/', { fn: 'greet', args: {} }),
])
if (results[0].status === 'fulfilled') {
const data = await (results[0] as PromiseFulfilledResult<Response>).value.json()
if (data.error) {
console.error('[getDjangoHydration] current_user failed:', data.code, data.message)
} else {
hydration.currentUser = data.data
}
} else {
console.error('[getDjangoHydration] current_user request failed:', (results[0] as PromiseRejectedResult).reason)
}
if (results[1].status === 'fulfilled') {
const data = await (results[1] as PromiseFulfilledResult<Response>).value.json()
if (data.error) {
console.error('[getDjangoHydration] greet failed:', data.code, data.message)
} else {
hydration.greet = data.data
}
} else {
console.error('[getDjangoHydration] greet request failed:', (results[1] as PromiseRejectedResult).reason)
}
return hydration
}

View File

@@ -0,0 +1,257 @@
'use client'
// AUTO-GENERATED by mizan - do not edit manually
// Regenerate with: npm run schemas
// This file provides typed wrappers around the mizan library.
// - DjangoContext: Typed provider wrapping mizanProvider
// - Typed hooks: useAuthStatus(), useUser(), etc.
import { type ReactNode, useCallback } from 'react'
import {
mizanProvider,
usemizan,
usemizanContext,
usemizanCall,
type mizanHydration,
type Transport,
} from 'mizan'
import { ChannelProvider, ChannelConnection } from 'mizan/channels'
import { useRef } from 'react'
import type { addEmailSchemaOutput, addEmailValidateInput, addEmailValidateOutput, addInput, addOutput, buggyFnOutput, contactSchemaInput, contactSchemaOutput, contactSubmitOutput, contactValidateInput, contactValidateOutput, currentUserOutput, echoInput, echoOutput, greetInput, greetOutput, httpOnlyEchoInput, httpOnlyEchoOutput, itemFormsetSchemaInput, itemFormsetSchemaOutput, itemFormsetSubmitInput, itemFormsetSubmitOutput, itemFormsetValidateInput, itemFormsetValidateOutput, itemSchemaInput, itemSchemaOutput, itemSubmitOutput, itemValidateInput, itemValidateOutput, jwtObtainOutput, jwtRefreshInput, jwtRefreshOutput, loginSchemaOutput, loginSubmitInput, loginSubmitOutput, loginValidateInput, loginValidateOutput, multiplyInput, multiplyOutput, notImplementedFnOutput, permissionCheckFnInput, permissionCheckFnOutput, signupSchemaOutput, signupSubmitInput, signupSubmitOutput, signupValidateInput, signupValidateOutput, staffOnlyOutput, superuserOnlyOutput, verifiedOnlyOutput, whoamiOutput, wsWhoamiOutput } from './generated.mizan'
// ============================================================================
// Hydration Types
// ============================================================================
/** Typed hydration data for SSR */
export interface DjangoHydration {
currentUser?: currentUserOutput
greet?: greetOutput
}
/** Convert typed hydration to mizan format */
function tomizanHydration(hydration?: DjangoHydration): mizanHydration | undefined {
if (!hydration) return undefined
const result: mizanHydration = {}
if (hydration.currentUser !== undefined) result['current_user'] = hydration.currentUser
if (hydration.greet !== undefined) result['greet'] = hydration.greet
return result
}
// ============================================================================
// Provider
// ============================================================================
export interface DjangoContextProps {
children: ReactNode
/** SSR hydration data */
hydration?: DjangoHydration
/** WebSocket URL for RPC calls (default: /ws/) */
wsUrl?: string
/** Base URL for HTTP fallback (default: /api/mizan) */
baseUrl?: string
}
/**
* Typed Django context provider.
*
* Wraps mizanProvider with:
* - Typed hydration
* - Auto-fetch for registered contexts
*
* Usage:
* <DjangoContext hydration={hydration}>
* <App />
* </DjangoContext>
*/
export function DjangoContext({
children,
hydration,
wsUrl,
baseUrl,
}: DjangoContextProps) {
const connectionRef = useRef<ChannelConnection | null>(null)
if (!connectionRef.current) {
connectionRef.current = new ChannelConnection({ url: wsUrl || '/ws/' })
}
return (
<mizanProvider
hydration={tomizanHydration(hydration)}
contexts={['current_user', 'greet']}
wsUrl={wsUrl}
baseUrl={baseUrl}
connection={connectionRef.current}
>
<ChannelProvider connection={connectionRef.current} autoConnect={true}>
{children}
</ChannelProvider>
</mizanProvider>
)
}
// ============================================================================
// Context Hooks (typed wrappers)
// ============================================================================
/**
* Get current_user context data.
* @throws if context not loaded yet
*/
export function useCurrentUser(): currentUserOutput {
const data = usemizanContext<currentUserOutput>('current_user')
if (data === undefined) {
throw new Error('useCurrentUser: context not loaded yet')
}
return data
}
/**
* Get greet context data.
* @throws if context not loaded yet
*/
export function useGreet(): greetOutput {
const data = usemizanContext<greetOutput>('greet')
if (data === undefined) {
throw new Error('useGreet: context not loaded yet')
}
return data
}
/**
* Get context refresh functions without subscribing to data changes.
* Use this in components that only need to trigger refreshes.
*/
export function useDjangoRefresh() {
const { refreshContext, refreshAllContexts } = usemizan()
return {
refreshCurrentUser: () => refreshContext('current_user'),
refreshGreet: () => refreshContext('greet'),
refreshAll: refreshAllContexts,
}
}
// ============================================================================
// Function Hooks (typed wrappers)
// ============================================================================
/**
* Call echo server function.
* Transport: websocket
*/
export function useEcho() {
return usemizanCall<echoInput, echoOutput>('echo', 'websocket')
}
/**
* Call add server function.
* Transport: websocket
*/
export function useAdd() {
return usemizanCall<addInput, addOutput>('add', 'websocket')
}
/**
* Call whoami server function.
* Transport: http
*/
export function useWhoami() {
return usemizanCall<void, whoamiOutput>('whoami', 'http')
}
/**
* Call http_only_echo server function.
* Transport: http
*/
export function useHttpOnlyEcho() {
return usemizanCall<httpOnlyEchoInput, httpOnlyEchoOutput>('http_only_echo', 'http')
}
/**
* Call staff_only server function.
* Transport: http
*/
export function useStaffOnly() {
return usemizanCall<void, staffOnlyOutput>('staff_only', 'http')
}
/**
* Call superuser_only server function.
* Transport: http
*/
export function useSuperuserOnly() {
return usemizanCall<void, superuserOnlyOutput>('superuser_only', 'http')
}
/**
* Call verified_only server function.
* Transport: http
*/
export function useVerifiedOnly() {
return usemizanCall<void, verifiedOnlyOutput>('verified_only', 'http')
}
/**
* Call multiply server function.
* Transport: http
*/
export function useMultiply() {
return usemizanCall<multiplyInput, multiplyOutput>('multiply', 'http')
}
/**
* Call not_implemented_fn server function.
* Transport: http
*/
export function useNotImplementedFn() {
return usemizanCall<void, notImplementedFnOutput>('not_implemented_fn', 'http')
}
/**
* Call buggy_fn server function.
* Transport: http
*/
export function useBuggyFn() {
return usemizanCall<void, buggyFnOutput>('buggy_fn', 'http')
}
/**
* Call permission_check_fn server function.
* Transport: http
*/
export function usePermissionCheckFn() {
return usemizanCall<permissionCheckFnInput, permissionCheckFnOutput>('permission_check_fn', 'http')
}
/**
* Call ws_whoami server function.
* Transport: websocket
*/
export function useWsWhoami() {
return usemizanCall<void, wsWhoamiOutput>('ws_whoami', 'websocket')
}
/**
* Call jwt_obtain server function.
* Transport: http
*/
export function useJwtObtain() {
return usemizanCall<void, jwtObtainOutput>('jwt_obtain', 'http')
}
/**
* Call jwt_refresh server function.
* Transport: http
*/
export function useJwtRefresh() {
return usemizanCall<jwtRefreshInput, jwtRefreshOutput>('jwt_refresh', 'http')
}
// ============================================================================
// Re-exports from mizan library
// ============================================================================
export { usemizan, usemizanStatus, usePush, DjangoError } from 'mizan'
export type { ConnectionStatus, PushMessage, PushListener } from 'mizan'

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,226 @@
'use client'
// AUTO-GENERATED by mizan - do not edit manually
// Regenerate with: npm run schemas
// Typed form hooks with Zod validation.
// Zod schemas are generated from Django form field definitions.
// Client-side validation matches Django constraints (required, max_length, email, etc.)
import { z } from 'zod'
import {
useDjangoFormCore,
useDjangoFormsetCore,
type DjangoFormState,
type DjangoFormsetState,
type FormOptions,
} from 'mizan'
// ============================================================================
// Zod Schemas
// ============================================================================
/**
* Zod schema for login form
* Generated from Django form field definitions
*/
export const LoginSchema = z.object({
})
/**
* Zod schema for signup form
* Generated from Django form field definitions
*/
export const SignupSchema = z.object({
})
/**
* Zod schema for add_email form
* Generated from Django form field definitions
*/
export const AddEmailSchema = z.object({
})
/**
* Zod schema for contact form
* Generated from Django form field definitions
*/
export const ContactSchema = z.object({
name: z.string().max(100),
email: z.string().email('Invalid email address').max(320),
message: z.string(),
})
/**
* Zod schema for item form
* Generated from Django form field definitions
*/
export const ItemSchema = z.object({
label: z.string().max(50),
quantity: z.number().int().min(1),
})
// ============================================================================
// Form Data Types (inferred from Zod schemas)
// ============================================================================
/** Form data type for login, inferred from Zod schema */
export type LoginFormData = z.infer<typeof LoginSchema>
/** Form data type for signup, inferred from Zod schema */
export type SignupFormData = z.infer<typeof SignupSchema>
/** Form data type for add_email, inferred from Zod schema */
export type AddEmailFormData = z.infer<typeof AddEmailSchema>
/** Form data type for contact, inferred from Zod schema */
export type ContactFormData = z.infer<typeof ContactSchema>
/** Form data type for item, inferred from Zod schema */
export type ItemFormData = z.infer<typeof ItemSchema>
// ============================================================================
// Form Hooks
// ============================================================================
/**
* Typed form hook for login
*
* Features:
* - Full TypeScript inference for form fields
* - Client-side Zod validation (instant feedback)
* - Server-side Django validation (authoritative)
*/
export function useLoginForm(
options?: FormOptions
): DjangoFormState<LoginFormData> {
return useDjangoFormCore<LoginFormData>({
name: 'login',
zodSchema: LoginSchema,
options,
})
}
/**
* Typed form hook for signup
*
* Features:
* - Full TypeScript inference for form fields
* - Client-side Zod validation (instant feedback)
* - Server-side Django validation (authoritative)
*/
export function useSignupForm(
options?: FormOptions
): DjangoFormState<SignupFormData> {
return useDjangoFormCore<SignupFormData>({
name: 'signup',
zodSchema: SignupSchema,
options,
})
}
/**
* Typed form hook for add_email
*
* Features:
* - Full TypeScript inference for form fields
* - Client-side Zod validation (instant feedback)
* - Server-side Django validation (authoritative)
*/
export function useAddEmailForm(
options?: FormOptions
): DjangoFormState<AddEmailFormData> {
return useDjangoFormCore<AddEmailFormData>({
name: 'add_email',
zodSchema: AddEmailSchema,
options,
})
}
/**
* Typed form hook for contact
*
* Features:
* - Full TypeScript inference for form fields
* - Client-side Zod validation (instant feedback)
* - Server-side Django validation (authoritative)
*/
export function useContactForm(
options?: FormOptions
): DjangoFormState<ContactFormData> {
return useDjangoFormCore<ContactFormData>({
name: 'contact',
zodSchema: ContactSchema,
options,
})
}
/**
* Typed form hook for item
*
* Features:
* - Full TypeScript inference for form fields
* - Client-side Zod validation (instant feedback)
* - Server-side Django validation (authoritative)
*/
export function useItemForm(
options?: FormOptions
): DjangoFormState<ItemFormData> {
return useDjangoFormCore<ItemFormData>({
name: 'item',
zodSchema: ItemSchema,
options,
})
}
/**
* Typed formset hook for item
*/
export function useItemFormset(
initialCount?: number,
liveValidation?: boolean
): DjangoFormsetState<ItemFormData> {
return useDjangoFormsetCore<ItemFormData>({
name: 'item',
zodSchema: ItemSchema,
initialCount,
liveValidation,
})
}
// ============================================================================
// Form Registry
// ============================================================================
export const DJANGO_FORMS = {
login: {
name: 'login',
schema: LoginSchema,
hook: 'useLoginForm',
hasFormset: false,
},
signup: {
name: 'signup',
schema: SignupSchema,
hook: 'useSignupForm',
hasFormset: false,
},
addEmail: {
name: 'add_email',
schema: AddEmailSchema,
hook: 'useAddEmailForm',
hasFormset: false,
},
contact: {
name: 'contact',
schema: ContactSchema,
hook: 'useContactForm',
hasFormset: false,
},
item: {
name: 'item',
schema: ItemSchema,
hook: 'useItemForm',
hasFormset: true,
},
} as const

View File

@@ -0,0 +1,4 @@
{
"status": "failed",
"failedTests": []
}

View File

@@ -1,7 +1,7 @@
import { defineConfig } from '@playwright/test' import { defineConfig } from '@playwright/test'
export default defineConfig({ export default defineConfig({
testDir: './e2e', testDir: './examples/django-react-site',
timeout: 15000, timeout: 15000,
retries: 0, retries: 0,
reporter: 'list', reporter: 'list',