Rename djarea to mizan and fix React casing conventions
Rename the package from djarea to mizan across the entire codebase — Python package, React library, generators, tests, and examples. Fix JSX/hook casing (MizanProvider, useMizan, etc.) that broke when the original PascalCase names were lowercased during the rename. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
# @rythazhur/djarea (TypeScript)
|
||||
# @rythazhur/mizan (TypeScript)
|
||||
|
||||
React client for the Djarea framework. See the [monorepo root](../README.md) for full documentation.
|
||||
React client for the mizan framework. See the [monorepo root](../README.md) for full documentation.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
npm install @rythazhur/djarea@git+https://git.impactsoundworks.com/isw/djarea.git#workspace=react
|
||||
npm install @rythazhur/mizan@git+https://git.impactsoundworks.com/isw/mizan.git#workspace=react
|
||||
```
|
||||
|
||||
## Usage
|
||||
@@ -30,8 +30,8 @@ export default {
|
||||
### 2. Generate
|
||||
|
||||
```bash
|
||||
npx djarea-generate # once
|
||||
npx djarea-generate --watch # dev mode
|
||||
npx mizan-generate # once
|
||||
npx mizan-generate --watch # dev mode
|
||||
```
|
||||
|
||||
### 3. Wrap your app
|
||||
@@ -74,7 +74,7 @@ chat.messages // typed, reactive
|
||||
| File | Contents |
|
||||
|------|----------|
|
||||
| `generated.django.tsx` | `DjangoContext` + typed hooks |
|
||||
| `generated.djarea.ts` | Pydantic types |
|
||||
| `generated.mizan.ts` | Pydantic types |
|
||||
| `generated.forms.ts` | Form hooks with Zod |
|
||||
| `generated.channels.hooks.tsx` | Channel hooks |
|
||||
| `index.ts` | Re-exports everything |
|
||||
@@ -83,11 +83,11 @@ chat.messages // typed, reactive
|
||||
|
||||
| Import | When to use |
|
||||
|--------|------------|
|
||||
| `@rythazhur/djarea` | Core: DjareaProvider, hooks, forms, errors |
|
||||
| `@rythazhur/djarea/channels` | WebSocket channels |
|
||||
| `@rythazhur/djarea/jwt` | JWT token management |
|
||||
| `@rythazhur/djarea/client` | HTTP clients (CSR/SSR) |
|
||||
| `@rythazhur/djarea/allauth` | Allauth UI components |
|
||||
| `@rythazhur/mizan` | Core: mizanProvider, hooks, forms, errors |
|
||||
| `@rythazhur/mizan/channels` | WebSocket channels |
|
||||
| `@rythazhur/mizan/jwt` | JWT token management |
|
||||
| `@rythazhur/mizan/client` | HTTP clients (CSR/SSR) |
|
||||
| `@rythazhur/mizan/allauth` | Allauth UI components |
|
||||
|
||||
These are **library internals** used by the generated code. You should import from `@/api` (your generated index), not from the library directly.
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@rythazhur/djarea",
|
||||
"name": "@rythazhur/mizan",
|
||||
"version": "0.1.1",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
@@ -39,7 +39,7 @@
|
||||
}
|
||||
},
|
||||
"bin": {
|
||||
"djarea-generate": "./dist/generator/cli.mjs"
|
||||
"mizan-generate": "./dist/generator/cli.mjs"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc -p tsconfig.build.json && node -e \"require('fs').cpSync('src/generator','dist/generator',{recursive:true})\"",
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
import React from 'react'
|
||||
import { render, screen, waitFor, act } from '@testing-library/react'
|
||||
import {
|
||||
DjareaProvider,
|
||||
useDjarea,
|
||||
useDjareaStatus,
|
||||
useDjareaCall,
|
||||
MizanProvider,
|
||||
useMizan,
|
||||
useMizanStatus,
|
||||
useMizanCall,
|
||||
// Legacy aliases for backwards compatibility tests
|
||||
DjangoContext,
|
||||
useDjango,
|
||||
@@ -27,18 +27,18 @@ import { describeIntegration, BACKEND_URL } from '../testing'
|
||||
// Unit Tests (no backend required)
|
||||
// ============================================================================
|
||||
|
||||
describe('Djarea Context (unit)', () => {
|
||||
describe('useDjarea hook', () => {
|
||||
describe('mizan Context (unit)', () => {
|
||||
describe('useMizan hook', () => {
|
||||
it('should throw when used outside provider', () => {
|
||||
function TestComponent() {
|
||||
useDjarea()
|
||||
useMizan()
|
||||
return <div>Test</div>
|
||||
}
|
||||
|
||||
const consoleSpy = jest.spyOn(console, 'error').mockImplementation()
|
||||
|
||||
expect(() => render(<TestComponent />)).toThrow(
|
||||
'useDjarea must be used within a DjareaProvider'
|
||||
'useMizan must be used within a MizanProvider'
|
||||
)
|
||||
|
||||
consoleSpy.mockRestore()
|
||||
@@ -48,14 +48,14 @@ describe('Djarea Context (unit)', () => {
|
||||
let contextValue: any = null
|
||||
|
||||
function TestComponent() {
|
||||
contextValue = useDjarea()
|
||||
contextValue = useMizan()
|
||||
return <div>Test</div>
|
||||
}
|
||||
|
||||
render(
|
||||
<DjareaProvider autoConnect={false}>
|
||||
<MizanProvider autoConnect={false}>
|
||||
<TestComponent />
|
||||
</DjareaProvider>
|
||||
</MizanProvider>
|
||||
)
|
||||
|
||||
expect(contextValue).not.toBeNull()
|
||||
@@ -63,17 +63,17 @@ describe('Djarea Context (unit)', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('useDjareaStatus hook', () => {
|
||||
describe('useMizanStatus hook', () => {
|
||||
it('should return disconnected when autoConnect is false', () => {
|
||||
function TestComponent() {
|
||||
const status = useDjareaStatus()
|
||||
const status = useMizanStatus()
|
||||
return <div data-testid="status">{status}</div>
|
||||
}
|
||||
|
||||
render(
|
||||
<DjareaProvider autoConnect={false}>
|
||||
<MizanProvider autoConnect={false}>
|
||||
<TestComponent />
|
||||
</DjareaProvider>
|
||||
</MizanProvider>
|
||||
)
|
||||
|
||||
expect(screen.getByTestId('status')).toHaveTextContent('disconnected')
|
||||
@@ -85,7 +85,7 @@ describe('Djarea Context (unit)', () => {
|
||||
let contextValue: any = null
|
||||
|
||||
function TestComponent() {
|
||||
contextValue = useDjarea()
|
||||
contextValue = useMizan()
|
||||
return <div>Test</div>
|
||||
}
|
||||
|
||||
@@ -95,9 +95,9 @@ describe('Djarea Context (unit)', () => {
|
||||
}
|
||||
|
||||
render(
|
||||
<DjareaProvider hydration={hydration} autoConnect={false}>
|
||||
<MizanProvider hydration={hydration} autoConnect={false}>
|
||||
<TestComponent />
|
||||
</DjareaProvider>
|
||||
</MizanProvider>
|
||||
)
|
||||
|
||||
expect(contextValue.getContext('auth_status')).toEqual({ is_authenticated: false })
|
||||
@@ -110,7 +110,7 @@ describe('Djarea Context (unit)', () => {
|
||||
// Integration Tests (require running backend)
|
||||
// ============================================================================
|
||||
|
||||
describeIntegration('Djarea Context (integration)', () => {
|
||||
describeIntegration('mizan Context (integration)', () => {
|
||||
describe('server function calls via HTTP', () => {
|
||||
it('should call echo function and get response', async () => {
|
||||
let result: any = null
|
||||
@@ -130,7 +130,7 @@ describeIntegration('Djarea Context (integration)', () => {
|
||||
}
|
||||
|
||||
render(
|
||||
<DjangoContext baseUrl={`${BACKEND_URL}/api/djarea`} autoConnect={false}>
|
||||
<DjangoContext baseUrl={`${BACKEND_URL}/api/mizan`} autoConnect={false}>
|
||||
<TestComponent />
|
||||
</DjangoContext>
|
||||
)
|
||||
@@ -161,7 +161,7 @@ describeIntegration('Djarea Context (integration)', () => {
|
||||
}
|
||||
|
||||
render(
|
||||
<DjangoContext baseUrl={`${BACKEND_URL}/api/djarea`} autoConnect={false}>
|
||||
<DjangoContext baseUrl={`${BACKEND_URL}/api/mizan`} autoConnect={false}>
|
||||
<TestComponent />
|
||||
</DjangoContext>
|
||||
)
|
||||
@@ -192,7 +192,7 @@ describeIntegration('Djarea Context (integration)', () => {
|
||||
}
|
||||
|
||||
render(
|
||||
<DjangoContext baseUrl={`${BACKEND_URL}/api/djarea`} autoConnect={false}>
|
||||
<DjangoContext baseUrl={`${BACKEND_URL}/api/mizan`} autoConnect={false}>
|
||||
<TestComponent />
|
||||
</DjangoContext>
|
||||
)
|
||||
@@ -227,7 +227,7 @@ describeIntegration('Djarea Context (integration)', () => {
|
||||
}
|
||||
|
||||
render(
|
||||
<DjangoContext baseUrl={`${BACKEND_URL}/api/djarea`} autoConnect={false}>
|
||||
<DjangoContext baseUrl={`${BACKEND_URL}/api/mizan`} autoConnect={false}>
|
||||
<TestComponent />
|
||||
</DjangoContext>
|
||||
)
|
||||
@@ -260,7 +260,7 @@ describeIntegration('Djarea Context (integration)', () => {
|
||||
}
|
||||
|
||||
render(
|
||||
<DjangoContext baseUrl={`${BACKEND_URL}/api/djarea`} autoConnect={false}>
|
||||
<DjangoContext baseUrl={`${BACKEND_URL}/api/mizan`} autoConnect={false}>
|
||||
<TestComponent />
|
||||
</DjangoContext>
|
||||
)
|
||||
@@ -296,7 +296,7 @@ describeIntegration('Djarea Context (integration)', () => {
|
||||
}
|
||||
|
||||
render(
|
||||
<DjangoContext baseUrl={`${BACKEND_URL}/api/djarea`} autoConnect={false}>
|
||||
<DjangoContext baseUrl={`${BACKEND_URL}/api/mizan`} autoConnect={false}>
|
||||
<TestComponent />
|
||||
</DjangoContext>
|
||||
)
|
||||
|
||||
@@ -28,7 +28,7 @@ function renderFormHook<TData extends Record<string, unknown>>(
|
||||
) {
|
||||
return renderHook(() => useDjangoFormCore<TData>(config), {
|
||||
wrapper: ({ children }) => (
|
||||
<DjangoContext baseUrl={`${BACKEND_URL}/api/djarea`} autoConnect={false}>
|
||||
<DjangoContext baseUrl={`${BACKEND_URL}/api/mizan`} autoConnect={false}>
|
||||
{children}
|
||||
</DjangoContext>
|
||||
),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Cross-cutting integration tests for djarea
|
||||
* Cross-cutting integration tests for mizan
|
||||
*
|
||||
* Tests error paths and protocol correctness across HTTP, Forms, and WebSocket.
|
||||
* Requires a running backend: docker-compose up
|
||||
@@ -10,22 +10,22 @@
|
||||
import { renderHook, act } from '@testing-library/react'
|
||||
import { ReactNode } from 'react'
|
||||
import { describeIntegration, BACKEND_URL, WS_URL } from '../testing'
|
||||
import { DjareaProvider, useDjarea } from '../context'
|
||||
import { MizanProvider, useMizan } from '../context'
|
||||
import { DjangoError } from '../errors'
|
||||
import { ChannelConnection } from '../channels/connection'
|
||||
import { RPCError } from '../channels/connection'
|
||||
|
||||
function Wrapper({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<DjareaProvider baseUrl={`${BACKEND_URL}/api/djarea`} autoConnect={false}>
|
||||
<MizanProvider baseUrl={`${BACKEND_URL}/api/mizan`} autoConnect={false}>
|
||||
{children}
|
||||
</DjareaProvider>
|
||||
</MizanProvider>
|
||||
)
|
||||
}
|
||||
|
||||
// Helper to get call function
|
||||
function useCall() {
|
||||
const { call } = useDjarea()
|
||||
const { call } = useMizan()
|
||||
return call
|
||||
}
|
||||
|
||||
@@ -503,7 +503,7 @@ describeIntegration('Error code coverage', () => {
|
||||
})
|
||||
|
||||
it('should return BAD_REQUEST for invalid JSON body', async () => {
|
||||
const response = await fetch(`${BACKEND_URL}/api/djarea/call/`, {
|
||||
const response = await fetch(`${BACKEND_URL}/api/mizan/call/`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
@@ -515,7 +515,7 @@ describeIntegration('Error code coverage', () => {
|
||||
})
|
||||
|
||||
it('should return BAD_REQUEST for missing fn field', async () => {
|
||||
const response = await fetch(`${BACKEND_URL}/api/djarea/call/`, {
|
||||
const response = await fetch(`${BACKEND_URL}/api/mizan/call/`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
@@ -528,11 +528,11 @@ describeIntegration('Error code coverage', () => {
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// Group 8: DjareaFormMixin integration
|
||||
// Group 8: mizanFormMixin integration
|
||||
// ============================================================================
|
||||
|
||||
describeIntegration('DjareaFormMixin integration', () => {
|
||||
it('should return schema with title, subtitle, and submit_label from DjareaFormMeta', async () => {
|
||||
describeIntegration('mizanFormMixin integration', () => {
|
||||
it('should return schema with title, subtitle, and submit_label from mizanFormMeta', async () => {
|
||||
const { result } = renderHook(() => useCall(), { wrapper: Wrapper })
|
||||
|
||||
let response: any = null
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/**
|
||||
* Re-export RouterAdapter from djarea/client.
|
||||
* Re-export RouterAdapter from mizan/client.
|
||||
*
|
||||
* Allauth extends this with a required getParam method.
|
||||
*/
|
||||
import type { RouterAdapter as BaseRouterAdapter } from 'djarea/client'
|
||||
import type { RouterAdapter as BaseRouterAdapter } from 'mizan/client'
|
||||
|
||||
export interface RouterAdapter extends BaseRouterAdapter {
|
||||
/** Get a specific route param (e.g., from /auth/[...path]) - required for allauth */
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
type DjangoFormState,
|
||||
type FormOptions,
|
||||
type FormErrors,
|
||||
} from 'djarea'
|
||||
} from 'mizan'
|
||||
import { useAuthContext } from '../contexts/AuthContext'
|
||||
import { useStyles } from '../contexts/StylesContext'
|
||||
import { getAuthDetails, AuthDetails } from '../api'
|
||||
@@ -41,7 +41,7 @@ interface AuthDjangoFormProps {
|
||||
}
|
||||
|
||||
/**
|
||||
* AuthDjangoForm renders a form from the Djarea server functions
|
||||
* AuthDjangoForm renders a form from the mizan server functions
|
||||
* with styling consistent with the auth UI.
|
||||
*
|
||||
* It fetches the form schema (including title, subtitle, fields, submit label)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useAllauthAPI } from '../../contexts/APIContext'
|
||||
import { useStyles } from '../../contexts/StylesContext'
|
||||
import { useDjangoFormCore } from 'djarea'
|
||||
import { useDjangoFormCore } from 'mizan'
|
||||
import { SettingsSection, SettingsItem, SettingsList, Badge, Button } from './SettingsComponents'
|
||||
|
||||
interface Email {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { useDjangoFormCore } from 'djarea'
|
||||
import { useDjangoFormCore } from 'mizan'
|
||||
import { useStyles } from '../../contexts/StylesContext'
|
||||
import { SettingsSection, Button } from './SettingsComponents'
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* 1. Define the base path for Django-initiated routes (must match HEADLESS_FRONTEND_URLS)
|
||||
* 2. Define where to navigate for various auth events (developer controls these)
|
||||
*
|
||||
* For JWT-based API calls, use djarea/jwt separately.
|
||||
* For JWT-based API calls, use mizan/jwt separately.
|
||||
*/
|
||||
|
||||
export interface AllauthConfig {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { useMemo } from 'react'
|
||||
import { useDjangoCSRClient, Auth } from 'djarea/client/react'
|
||||
import { useDjangoCSRClient, Auth } from 'mizan/client/react'
|
||||
import { useAuthContext } from './AuthContext'
|
||||
import { createAPI, AllauthAPI, BrowserFormAction } from '../api'
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { ReactNode, useEffect, useState } from 'react'
|
||||
import { useDjangoCSRClient, Auth } from 'djarea/client/react'
|
||||
import { useDjangoCSRClient, Auth } from 'mizan/client/react'
|
||||
import type { RouterAdapter } from '../adapters/router'
|
||||
import type { InitialAuth } from '../hydration'
|
||||
import { AuthContext } from './AuthContext'
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
'use client'
|
||||
|
||||
import { createContext, ReactNode, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useDjangoCSRClient, Auth } from 'djarea/client/react'
|
||||
import { useDjarea, useDjareaContext } from 'djarea'
|
||||
import { useDjangoCSRClient, Auth } from 'mizan/client/react'
|
||||
import { useMizan, useMizanContext } from 'mizan'
|
||||
import { getAuthDetails, createAPI } from '../api'
|
||||
import type { AllauthResponse } from '../types'
|
||||
import getAuthChangeEvent from '../events'
|
||||
@@ -30,7 +30,7 @@ export function AuthContext({
|
||||
auth: initialAuth,
|
||||
}: AuthContextProps) {
|
||||
const client = useDjangoCSRClient(Auth.SESSION)
|
||||
const { refreshAllContexts } = useDjarea()
|
||||
const { refreshAllContexts } = useMizan()
|
||||
const [auth, setAuth] = useState(initialAuth)
|
||||
const [event, setEvent] = useState('')
|
||||
const prevAuth = useRef(initialAuth)
|
||||
@@ -100,10 +100,10 @@ export interface AllauthUser {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current user from DjareaProvider.
|
||||
* Get the current user from MizanProvider.
|
||||
*
|
||||
* This uses the generic djarea hook to access the 'user' context.
|
||||
* The backend defines this context in lib/djarea/allauth/contexts.py:
|
||||
* This uses the generic mizan hook to access the 'user' context.
|
||||
* The backend defines this context in lib/mizan/allauth/contexts.py:
|
||||
*
|
||||
* @client(context='global')
|
||||
* def user(request) -> UserOutput | None:
|
||||
@@ -112,7 +112,7 @@ export interface AllauthUser {
|
||||
* @typeParam T - User type (defaults to AllauthUser, products can use more specific types)
|
||||
*/
|
||||
export function useUser<T extends AllauthUser = AllauthUser>(): T {
|
||||
const user = useDjareaContext<T>('user')
|
||||
const user = useMizanContext<T>('user')
|
||||
// Return empty object cast to T if user is undefined (not loaded)
|
||||
// This matches the previous behavior and allows optional chaining
|
||||
return (user ?? {}) as T
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { DjangoHTTPClient } from 'djarea/client'
|
||||
import type { DjangoHTTPClient } from 'mizan/client'
|
||||
import { createAPI } from './api'
|
||||
import type { AllauthResponse } from './types'
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* djarea/allauth
|
||||
* mizan/allauth
|
||||
*
|
||||
* React integration for django-allauth headless API.
|
||||
* Framework-agnostic - works with Next.js, Remix, React Router, etc.
|
||||
@@ -9,9 +9,9 @@
|
||||
* ```tsx
|
||||
* // layout.tsx
|
||||
* import { cookies } from 'next/headers'
|
||||
* import { createDjangoSSRClient } from 'djarea/client'
|
||||
* import { getInitialAuth } from 'djarea/allauth'
|
||||
* import { NextAllauthContext } from 'djarea/allauth/nextjs'
|
||||
* import { createDjangoSSRClient } from 'mizan/client'
|
||||
* import { getInitialAuth } from 'mizan/allauth'
|
||||
* import { NextAllauthContext } from 'mizan/allauth/nextjs'
|
||||
*
|
||||
* export default async function RootLayout({ children }) {
|
||||
* const ssrClient = createDjangoSSRClient({ cookies: await cookies() })
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
'use client'
|
||||
|
||||
/**
|
||||
* Next.js adapter for djarea/allauth.
|
||||
* Next.js adapter for mizan/allauth.
|
||||
*
|
||||
* Usage:
|
||||
* ```tsx
|
||||
* // In layout.tsx (server component)
|
||||
* import { createDjangoSSRClient } from 'djarea/client'
|
||||
* import { getInitialAuth } from 'djarea/allauth'
|
||||
* import { NextAllauthContext } from 'djarea/allauth/nextjs'
|
||||
* import { createDjangoSSRClient } from 'mizan/client'
|
||||
* import { getInitialAuth } from 'mizan/allauth'
|
||||
* import { NextAllauthContext } from 'mizan/allauth/nextjs'
|
||||
*
|
||||
* export default async function RootLayout({ children }) {
|
||||
* const ssrClient = createDjangoSSRClient({ cookies: await cookies() })
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* WebSocket connection manager for djarea/channels
|
||||
* WebSocket connection manager for mizan/channels
|
||||
*
|
||||
* Supports both pub/sub channels AND RPC calls over the same connection.
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
|
||||
/**
|
||||
* React context for djarea/channels
|
||||
* React context for mizan/channels
|
||||
*/
|
||||
|
||||
import { createContext, useContext, useEffect, useMemo, useRef, useState, type ReactNode } from 'react'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
|
||||
/**
|
||||
* React hooks for djarea/channels
|
||||
* React hooks for mizan/channels
|
||||
*
|
||||
* Includes pub/sub channel hooks AND RPC hooks.
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* djarea/channels
|
||||
* mizan/channels
|
||||
*
|
||||
* Real-time WebSocket communication with Django Channels.
|
||||
* Type-safe bidirectional messaging.
|
||||
@@ -8,7 +8,7 @@
|
||||
*
|
||||
* ```tsx
|
||||
* // layout.tsx
|
||||
* import { ChannelProvider } from 'djarea/channels'
|
||||
* import { ChannelProvider } from 'mizan/channels'
|
||||
*
|
||||
* export default function Layout({ children }) {
|
||||
* return (
|
||||
@@ -36,7 +36,7 @@
|
||||
*
|
||||
* ```tsx
|
||||
* // Using raw hook (for custom channels)
|
||||
* import { useChannel } from 'djarea/channels'
|
||||
* import { useChannel } from 'mizan/channels'
|
||||
*
|
||||
* function CustomChannel() {
|
||||
* const channel = useChannel<
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Types for djarea/channels
|
||||
* Types for mizan/channels
|
||||
*/
|
||||
|
||||
export type ConnectionStatus = 'connecting' | 'connected' | 'disconnected'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* djarea/client
|
||||
* mizan/client
|
||||
*
|
||||
* HTTP client factories for Django backends.
|
||||
* Framework-agnostic - works with vanilla JS, React, Vue, etc.
|
||||
@@ -8,7 +8,7 @@
|
||||
*
|
||||
* ### Client-Side (CSR)
|
||||
* ```ts
|
||||
* import { createDjangoCSRClient, Auth } from 'djarea/client'
|
||||
* import { createDjangoCSRClient, Auth } from 'mizan/client'
|
||||
*
|
||||
* // Session-based (cookies + CSRF)
|
||||
* const client = createDjangoCSRClient(Auth.SESSION)
|
||||
@@ -21,7 +21,7 @@
|
||||
*
|
||||
* ### Server-Side (SSR)
|
||||
* ```ts
|
||||
* import { createDjangoSSRClient } from 'djarea/client'
|
||||
* import { createDjangoSSRClient } from 'mizan/client'
|
||||
*
|
||||
* const client = createDjangoSSRClient({
|
||||
* cookies: await cookies() // Next.js cookies()
|
||||
@@ -34,7 +34,7 @@
|
||||
*
|
||||
* For React, import from `/react`:
|
||||
* ```tsx
|
||||
* import { useDjangoCSRClient, Auth } from 'djarea/client/react'
|
||||
* import { useDjangoCSRClient, Auth } from 'mizan/client/react'
|
||||
*
|
||||
* const client = useDjangoCSRClient(Auth.SESSION)
|
||||
* ```
|
||||
@@ -250,7 +250,7 @@ export function createDjangoCSRClient(
|
||||
): DjangoHTTPClient {
|
||||
if (!config?.baseUrl) {
|
||||
throw new Error(
|
||||
'baseUrl is required. Pass it via config or use DjareaProvider which provides it automatically.'
|
||||
'baseUrl is required. Pass it via config or use MizanProvider which provides it automatically.'
|
||||
)
|
||||
}
|
||||
|
||||
@@ -484,7 +484,7 @@ export async function ensureDjangoSession(config: SSRClientConfig): Promise<{
|
||||
}
|
||||
|
||||
// No CSRF token - need to initialize session
|
||||
const url = new URL('/api/djarea/session/', baseUrl)
|
||||
const url = new URL('/api/mizan/session/', baseUrl)
|
||||
const resp = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
@@ -496,7 +496,7 @@ export async function ensureDjangoSession(config: SSRClientConfig): Promise<{
|
||||
})
|
||||
|
||||
if (!resp.ok) {
|
||||
console.error('[djarea] Failed to initialize session:', resp.status, resp.statusText)
|
||||
console.error('[mizan] Failed to initialize session:', resp.status, resp.statusText)
|
||||
return { csrf: '', cookieHeader: existingCookies }
|
||||
}
|
||||
|
||||
@@ -553,7 +553,7 @@ function getCSRClient(): DjangoHTTPClient {
|
||||
*
|
||||
* Uses the standard CSR client with session-based auth.
|
||||
*
|
||||
* @param baseUrl - Base URL for the API (e.g., '/api/djarea')
|
||||
* @param baseUrl - Base URL for the API (e.g., '/api/mizan')
|
||||
* @param functionName - Name of the server function
|
||||
* @param input - Input data for the function
|
||||
* @returns Promise resolving to the function output
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
'use client'
|
||||
|
||||
/**
|
||||
* Next.js adapter for djarea/jwt.
|
||||
* Next.js adapter for mizan/jwt.
|
||||
*
|
||||
* Usage:
|
||||
* ```tsx
|
||||
* // In layout.tsx
|
||||
* import { NextAuthContext } from 'djarea/jwt/nextjs'
|
||||
* import { NextAuthContext } from 'mizan/jwt/nextjs'
|
||||
*
|
||||
* export default function RootLayout({ children }) {
|
||||
* return (
|
||||
|
||||
@@ -23,7 +23,7 @@ export type * from './types'
|
||||
* React hook that returns a client-side Django HTTP client.
|
||||
*
|
||||
* For SESSION auth, creates a session-based client with CSRF handling.
|
||||
* For JWT auth, automatically wires up the JWTContext from djarea/jwt.
|
||||
* For JWT auth, automatically wires up the JWTContext from mizan/jwt.
|
||||
*
|
||||
* @param auth - Authentication strategy (Auth.SESSION or Auth.JWT)
|
||||
* @param config - Optional client configuration
|
||||
@@ -35,7 +35,7 @@ export type * from './types'
|
||||
* const user = await client.json('GET', '/api/accounts/me/')
|
||||
*
|
||||
* @example
|
||||
* // JWT-based (requires JWTContext from djarea/jwt)
|
||||
* // JWT-based (requires JWTContext from mizan/jwt)
|
||||
* const client = useDjangoCSRClient(Auth.JWT)
|
||||
* const user = await client.json('GET', '/api/accounts/me/')
|
||||
*/
|
||||
@@ -48,7 +48,7 @@ export function useDjangoCSRClient(auth: Auth, config?: CSRClientConfig): Django
|
||||
if (auth === Auth.JWT) {
|
||||
if (!jwtContext?.getAccessToken) {
|
||||
throw new Error(
|
||||
'useDjangoCSRClient(Auth.JWT) requires JWTContext from djarea/jwt. ' +
|
||||
'useDjangoCSRClient(Auth.JWT) requires JWTContext from mizan/jwt. ' +
|
||||
'Wrap your component in JWTContext to use JWT authentication.'
|
||||
)
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ export interface JWTTokens {
|
||||
export interface JWTConfig {
|
||||
/** Base URL for API calls (default: '' - use relative URLs) */
|
||||
baseUrl?: string
|
||||
/** Djarea server function endpoint (default: /api/djarea/call/) */
|
||||
/** mizan server function endpoint (default: /api/mizan/call/) */
|
||||
endpoint?: string
|
||||
/** Seconds before expiry to trigger refresh (default: 30) */
|
||||
refreshBuffer?: number
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
|
||||
/**
|
||||
* Djarea React Context
|
||||
* mizan React Context
|
||||
*
|
||||
* Provides server function calls via HTTP (default) or WebSocket RPC (opt-in).
|
||||
* This is the core React integration for Django server functions.
|
||||
@@ -12,13 +12,13 @@
|
||||
* when connected, falling back to HTTP when disconnected
|
||||
*
|
||||
* Two layers:
|
||||
* 1. DjareaProvider (this file) - Generic provider with name-based API
|
||||
* - Libraries like Allauth use this: useDjarea(), useContext('current_user')
|
||||
* 1. MizanProvider (this file) - Generic provider with name-based API
|
||||
* - Libraries like Allauth use this: useMizan(), useContext('current_user')
|
||||
*
|
||||
* 2. Generated DjangoContext (in @/api) - Typed wrapper around DjareaProvider
|
||||
* 2. Generated DjangoContext (in @/api) - Typed wrapper around MizanProvider
|
||||
* - Product code uses this: useCurrentUser(), useUpdateProfile()
|
||||
*
|
||||
* The generated code wraps DjareaProvider and adds type-safe hooks.
|
||||
* The generated code wraps MizanProvider and adds type-safe hooks.
|
||||
*/
|
||||
|
||||
import {
|
||||
@@ -31,12 +31,12 @@ import {
|
||||
useCallback,
|
||||
type ReactNode,
|
||||
} from 'react'
|
||||
import { ChannelConnection, RPCError } from 'djarea/channels'
|
||||
import { ChannelConnection, RPCError } from 'mizan/channels'
|
||||
import {
|
||||
createDjangoCSRClient,
|
||||
Auth,
|
||||
type FunctionResponse,
|
||||
} from 'djarea/client'
|
||||
} from 'mizan/client'
|
||||
import { useJWT } from './jwt'
|
||||
import { DjangoError, type ErrorCode, type FunctionErrorResponse } from './errors'
|
||||
|
||||
@@ -69,17 +69,17 @@ export type PushListener<T = unknown> = (message: PushMessage<T>) => void
|
||||
export type ContextStore = Record<string, unknown>
|
||||
|
||||
/** Hydration data for SSR - maps context names to their initial data */
|
||||
export type DjareaHydration = Record<string, unknown>
|
||||
export type MizanHydration = Record<string, unknown>
|
||||
|
||||
/** Transport mode for server function calls */
|
||||
export type Transport = 'http' | 'websocket'
|
||||
|
||||
export interface DjareaContextValue {
|
||||
export interface MizanContextValue {
|
||||
/**
|
||||
* Call a server function by name.
|
||||
*
|
||||
* Transport behavior:
|
||||
* - 'http' (default): Always use HTTP POST /api/djarea/call/
|
||||
* - 'http' (default): Always use HTTP POST /api/mizan/call/
|
||||
* - 'websocket': Use WebSocket RPC when connected, HTTP fallback when not
|
||||
*
|
||||
* @param functionName - The server function name (e.g., 'echo', 'update_profile')
|
||||
@@ -139,14 +139,14 @@ export interface DjareaContextValue {
|
||||
whenReady: Promise<void>
|
||||
}
|
||||
|
||||
export interface DjareaProviderProps {
|
||||
export interface MizanProviderProps {
|
||||
children: ReactNode
|
||||
|
||||
/**
|
||||
* Initial hydration data for contexts (from SSR).
|
||||
* Keys are context names, values are the data.
|
||||
*/
|
||||
hydration?: DjareaHydration
|
||||
hydration?: MizanHydration
|
||||
|
||||
/**
|
||||
* List of context names to auto-fetch if not in hydration.
|
||||
@@ -156,7 +156,7 @@ export interface DjareaProviderProps {
|
||||
|
||||
/**
|
||||
* Base URL for HTTP fallback calls.
|
||||
* @default '/api/djarea'
|
||||
* @default '/api/mizan'
|
||||
*/
|
||||
baseUrl?: string
|
||||
|
||||
@@ -189,24 +189,24 @@ export interface DjareaProviderProps {
|
||||
// Context
|
||||
// ============================================================================
|
||||
|
||||
const DjareaContextInternal = createContext<DjareaContextValue | null>(null)
|
||||
const MizanContextInternal = createContext<MizanContextValue | null>(null)
|
||||
|
||||
// ============================================================================
|
||||
// Provider
|
||||
// ============================================================================
|
||||
|
||||
export function DjareaProvider({
|
||||
export function MizanProvider({
|
||||
children,
|
||||
hydration,
|
||||
contexts: contextNames = [],
|
||||
baseUrl = '/api/djarea',
|
||||
baseUrl = '/api/mizan',
|
||||
wsUrl = '/ws/',
|
||||
autoConnect = true,
|
||||
reconnect = true,
|
||||
reconnectDelay = 1000,
|
||||
maxReconnectAttempts = 10,
|
||||
connection: providedConnection,
|
||||
}: DjareaProviderProps) {
|
||||
}: MizanProviderProps) {
|
||||
const connectionRef = useRef<ChannelConnection | null>(null)
|
||||
|
||||
// Push listeners: Map<topic, Set<listener>>
|
||||
@@ -287,7 +287,7 @@ export function DjareaProvider({
|
||||
|
||||
// Connection error - fall through to HTTP
|
||||
console.warn(
|
||||
`[Djarea] WebSocket RPC failed for '${functionName}', falling back to HTTP:`,
|
||||
`[mizan] WebSocket RPC failed for '${functionName}', falling back to HTTP:`,
|
||||
e
|
||||
)
|
||||
}
|
||||
@@ -335,14 +335,14 @@ export function DjareaProvider({
|
||||
try {
|
||||
listener(data)
|
||||
} catch (e) {
|
||||
console.error(`[Djarea] Context listener error for '${name}':`, e)
|
||||
console.error(`[mizan] Context listener error for '${name}':`, e)
|
||||
}
|
||||
})
|
||||
}
|
||||
return next
|
||||
})
|
||||
} catch (e) {
|
||||
console.error(`[Djarea] Failed to refresh context '${name}':`, e)
|
||||
console.error(`[mizan] Failed to refresh context '${name}':`, e)
|
||||
throw e
|
||||
}
|
||||
},
|
||||
@@ -417,7 +417,7 @@ export function DjareaProvider({
|
||||
try {
|
||||
listener(message)
|
||||
} catch (e) {
|
||||
console.error('[Djarea] Push listener error:', e)
|
||||
console.error('[mizan] Push listener error:', e)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -443,7 +443,7 @@ export function DjareaProvider({
|
||||
return
|
||||
}
|
||||
fetch(`${baseUrl}/session/`, { credentials: 'include' })
|
||||
.catch(e => console.error('[DjareaProvider] Session init failed:', e))
|
||||
.catch(e => console.error('[MizanProvider] Session init failed:', e))
|
||||
.finally(() => {
|
||||
setSessionReady(true)
|
||||
sessionRef.current?.resolve()
|
||||
@@ -466,7 +466,7 @@ export function DjareaProvider({
|
||||
|
||||
const isRPCAvailable = status === 'connected'
|
||||
|
||||
const value = useMemo<DjareaContextValue>(
|
||||
const value = useMemo<MizanContextValue>(
|
||||
() => ({
|
||||
call,
|
||||
getContext,
|
||||
@@ -482,9 +482,9 @@ export function DjareaProvider({
|
||||
)
|
||||
|
||||
return (
|
||||
<DjareaContextInternal value={value}>
|
||||
<MizanContextInternal value={value}>
|
||||
{children}
|
||||
</DjareaContextInternal>
|
||||
</MizanContextInternal>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -493,7 +493,7 @@ export function DjareaProvider({
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Access the Djarea context.
|
||||
* Access the mizan context.
|
||||
*
|
||||
* Provides generic name-based API for server functions and contexts.
|
||||
* Libraries should use this hook, not the typed generated hooks.
|
||||
@@ -501,18 +501,18 @@ export function DjareaProvider({
|
||||
* @example
|
||||
* ```tsx
|
||||
* // Library code (e.g., Allauth)
|
||||
* import { useDjarea } from 'djarea'
|
||||
* import { useMizan } from 'mizan'
|
||||
*
|
||||
* function useUser() {
|
||||
* const { getContext } = useDjarea()
|
||||
* const { getContext } = useMizan()
|
||||
* return getContext('current_user')
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export function useDjarea(): DjareaContextValue {
|
||||
const context = useReactContext(DjareaContextInternal)
|
||||
export function useMizan(): MizanContextValue {
|
||||
const context = useReactContext(MizanContextInternal)
|
||||
if (!context) {
|
||||
throw new Error('useDjarea must be used within a DjareaProvider')
|
||||
throw new Error('useMizan must be used within a MizanProvider')
|
||||
}
|
||||
return context
|
||||
}
|
||||
@@ -526,12 +526,12 @@ export function useDjarea(): DjareaContextValue {
|
||||
* ```tsx
|
||||
* // In Allauth library
|
||||
* function useUser() {
|
||||
* return useDjareaContext('current_user')
|
||||
* return useMizanContext('current_user')
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export function useDjareaContext<T = unknown>(name: string): T | undefined {
|
||||
const { getContext } = useDjarea()
|
||||
export function useMizanContext<T = unknown>(name: string): T | undefined {
|
||||
const { getContext } = useMizan()
|
||||
return getContext<T>(name)
|
||||
}
|
||||
|
||||
@@ -548,20 +548,20 @@ export function useDjareaContext<T = unknown>(name: string): T | undefined {
|
||||
* ```tsx
|
||||
* // HTTP-only function (default)
|
||||
* function useUpdateProfile() {
|
||||
* return useDjareaCall('update_profile')
|
||||
* return useMizanCall('update_profile')
|
||||
* }
|
||||
*
|
||||
* // WebSocket-enabled function
|
||||
* function useSendMessage() {
|
||||
* return useDjareaCall('send_message', 'websocket')
|
||||
* return useMizanCall('send_message', 'websocket')
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export function useDjareaCall<TInput = unknown, TOutput = unknown>(
|
||||
export function useMizanCall<TInput = unknown, TOutput = unknown>(
|
||||
functionName: string,
|
||||
transport: Transport = 'http'
|
||||
): (input?: TInput) => Promise<TOutput> {
|
||||
const { call } = useDjarea()
|
||||
const { call } = useMizan()
|
||||
return useCallback(
|
||||
(input?: TInput) => call<TInput, TOutput>(functionName, input, transport),
|
||||
[call, functionName, transport]
|
||||
@@ -571,8 +571,8 @@ export function useDjareaCall<TInput = unknown, TOutput = unknown>(
|
||||
/**
|
||||
* Get the current WebSocket connection status.
|
||||
*/
|
||||
export function useDjareaStatus(): ConnectionStatus {
|
||||
const { status } = useDjarea()
|
||||
export function useMizanStatus(): ConnectionStatus {
|
||||
const { status } = useMizan()
|
||||
return status
|
||||
}
|
||||
|
||||
@@ -584,7 +584,7 @@ export function usePush<T = unknown>(
|
||||
topic: string,
|
||||
callback: PushListener<T>
|
||||
): void {
|
||||
const { onPush } = useDjarea()
|
||||
const { onPush } = useMizan()
|
||||
const callbackRef = useRef(callback)
|
||||
|
||||
useEffect(() => {
|
||||
@@ -604,20 +604,20 @@ export function usePush<T = unknown>(
|
||||
// Legacy Aliases (for backwards compatibility during migration)
|
||||
// ============================================================================
|
||||
|
||||
/** @deprecated Use DjareaProvider instead */
|
||||
export const DjangoContext = DjareaProvider
|
||||
/** @deprecated Use MizanProvider instead */
|
||||
export const DjangoContext = MizanProvider
|
||||
|
||||
/** @deprecated Use useDjarea instead */
|
||||
export const useDjango = useDjarea
|
||||
/** @deprecated Use useMizan instead */
|
||||
export const useDjango = useMizan
|
||||
|
||||
/** @deprecated Use useDjareaStatus instead */
|
||||
export const useDjangoStatus = useDjareaStatus
|
||||
/** @deprecated Use useMizanStatus instead */
|
||||
export const useDjangoStatus = useMizanStatus
|
||||
|
||||
/** @deprecated Use useDjareaCall instead */
|
||||
/** @deprecated Use useMizanCall instead */
|
||||
export function useServerFunction<TInput = unknown, TOutput = unknown>(
|
||||
functionName: string
|
||||
): (input: TInput) => Promise<TOutput> {
|
||||
const { call } = useDjarea()
|
||||
const { call } = useMizan()
|
||||
return useCallback(
|
||||
(input: TInput) => call<TInput, TOutput>(functionName, input),
|
||||
[call, functionName]
|
||||
@@ -625,5 +625,5 @@ export function useServerFunction<TInput = unknown, TOutput = unknown>(
|
||||
}
|
||||
|
||||
// Re-export types for the legacy API
|
||||
export type DjangoContextValue = DjareaContextValue
|
||||
export type DjangoContextProps = DjareaProviderProps
|
||||
export type DjangoContextValue = MizanContextValue
|
||||
export type DjangoContextProps = MizanProviderProps
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
'use client'
|
||||
|
||||
/**
|
||||
* Djarea Forms - Typed React Form Hooks for Django Server Functions
|
||||
* mizan Forms - Typed React Form Hooks for Django Server Functions
|
||||
*
|
||||
* This module provides the core form state management that generated
|
||||
* form hooks use. It integrates with Djarea server functions for
|
||||
* form hooks use. It integrates with mizan server functions for
|
||||
* schema fetching, validation, and submission.
|
||||
*
|
||||
* Users don't use this directly - they use generated typed hooks:
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import type { ZodObject, ZodRawShape, ZodError } from 'zod'
|
||||
import { useDjarea } from './context'
|
||||
import { useMizan } from './context'
|
||||
import { DjangoError } from './errors'
|
||||
|
||||
// Forms always use HTTP transport because Django Allauth and other auth
|
||||
@@ -421,7 +421,7 @@ export interface DjangoFormsetState<TData extends Record<string, unknown>> {
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Configuration for useDjareaFormCore.
|
||||
* Configuration for useMizanFormCore.
|
||||
* This is used by generated hooks - not directly by users.
|
||||
*/
|
||||
export interface FormCoreConfig<TData extends Record<string, unknown>> {
|
||||
@@ -447,7 +447,7 @@ export interface FormCoreConfig<TData extends Record<string, unknown>> {
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export function useDjareaFormCore<TData extends Record<string, unknown>>(
|
||||
export function useMizanFormCore<TData extends Record<string, unknown>>(
|
||||
config: FormCoreConfig<TData>
|
||||
): DjangoFormState<TData> {
|
||||
const { name, zodSchema, options = {} } = config
|
||||
@@ -458,7 +458,7 @@ export function useDjareaFormCore<TData extends Record<string, unknown>>(
|
||||
serverValidation: serverValidationMode = 'on-submit',
|
||||
} = options
|
||||
|
||||
const { call } = useDjarea()
|
||||
const { call } = useMizan()
|
||||
|
||||
// State
|
||||
const [data, setData] = useState<TData>({} as TData)
|
||||
@@ -865,7 +865,7 @@ function transformFormsetValidation<TData extends Record<string, unknown>>(
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration for useDjareaFormsetCore.
|
||||
* Configuration for useMizanFormsetCore.
|
||||
*/
|
||||
export interface FormsetCoreConfig<TData extends Record<string, unknown>> {
|
||||
/** Form name (used for server function calls) */
|
||||
@@ -892,7 +892,7 @@ export interface FormsetCoreConfig<TData extends Record<string, unknown>> {
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export function useDjareaFormsetCore<TData extends Record<string, unknown>>(
|
||||
export function useMizanFormsetCore<TData extends Record<string, unknown>>(
|
||||
config: FormsetCoreConfig<TData>
|
||||
): DjangoFormsetState<TData> {
|
||||
const {
|
||||
@@ -902,7 +902,7 @@ export function useDjareaFormsetCore<TData extends Record<string, unknown>>(
|
||||
debounceMs = 350,
|
||||
} = config
|
||||
|
||||
const { call } = useDjarea()
|
||||
const { call } = useMizan()
|
||||
|
||||
// State
|
||||
const [forms, setForms] = useState<TData[]>(
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Djarea Code Generator CLI
|
||||
* mizan Code Generator CLI
|
||||
*
|
||||
* Generate TypeScript types, React provider, and hooks from Django schemas.
|
||||
*
|
||||
* Usage:
|
||||
* npx djarea-generate # Run once
|
||||
* npx djarea-generate --watch # Watch mode
|
||||
* npx mizan-generate # Run once
|
||||
* npx mizan-generate --watch # Watch mode
|
||||
*/
|
||||
|
||||
import { promises as fs } from 'fs'
|
||||
import path from 'path'
|
||||
import { fetchChannelsSchema, fetchDjareaSchema } from './lib/fetch.mjs'
|
||||
import { generateDjareaFiles } from './lib/djarea.mjs'
|
||||
import { fetchChannelsSchema, fetchMizanSchema } from './lib/fetch.mjs'
|
||||
import { generateMizanFiles } from './lib/mizan.mjs'
|
||||
import { generateChannelsFiles } from './lib/channels.mjs'
|
||||
import { generateIndex } from './lib/index.mjs'
|
||||
|
||||
// Use cwd — the script runs via `npx djarea-generate` from the frontend root
|
||||
// Use cwd — the script runs via `npx mizan-generate` from the frontend root
|
||||
const frontendDir = process.cwd()
|
||||
|
||||
/**
|
||||
@@ -70,21 +70,21 @@ async function writeOutput(filePath, content) {
|
||||
async function generate(config, options = {}) {
|
||||
const { output } = options
|
||||
|
||||
console.log('[djarea] Starting schema generation...')
|
||||
console.log('[mizan] Starting schema generation...')
|
||||
|
||||
const outputPath = output || config.output || 'src/api/generated.ts'
|
||||
|
||||
let channelsSchema = null
|
||||
let djareaSchema = null
|
||||
let mizanSchema = null
|
||||
|
||||
// Fetch and generate channels if available
|
||||
try {
|
||||
console.log('[djarea] Fetching channels schema...')
|
||||
console.log('[mizan] Fetching channels schema...')
|
||||
channelsSchema = await fetchChannelsSchema(config.source, frontendDir)
|
||||
|
||||
const channelCount = channelsSchema['x-djarea-channels']?.length || 0
|
||||
const channelCount = channelsSchema['x-mizan-channels']?.length || 0
|
||||
if (channelCount > 0) {
|
||||
console.log(`[djarea] Found ${channelCount} channels`)
|
||||
console.log(`[mizan] Found ${channelCount} channels`)
|
||||
|
||||
const channelsTypesPath = outputPath.replace(/\.ts$/, '.channels.ts')
|
||||
const fullChannelsTypesPath = path.resolve(frontendDir, channelsTypesPath)
|
||||
@@ -95,85 +95,85 @@ async function generate(config, options = {}) {
|
||||
|
||||
const { types: channelsTypes, hooks: channelsHooks } = await generateChannelsFiles(channelsSchema)
|
||||
|
||||
console.log(`[djarea] Generating -> ${channelsTypesPath}`)
|
||||
console.log(`[mizan] Generating -> ${channelsTypesPath}`)
|
||||
await writeOutput(fullChannelsTypesPath, channelsTypes)
|
||||
|
||||
if (channelsHooks) {
|
||||
console.log(`[djarea] Generating -> ${channelsHooksPath}`)
|
||||
console.log(`[mizan] Generating -> ${channelsHooksPath}`)
|
||||
await writeOutput(fullChannelsHooksPath, channelsHooks)
|
||||
}
|
||||
|
||||
console.log(`[djarea] Generating -> ${channelsSchemaPath}`)
|
||||
console.log(`[mizan] Generating -> ${channelsSchemaPath}`)
|
||||
await writeOutput(fullChannelsSchemaPath, JSON.stringify(channelsSchema, null, 2))
|
||||
} else {
|
||||
console.log('[djarea] No channels registered, skipping channels generation')
|
||||
console.log('[mizan] No channels registered, skipping channels generation')
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(`[djarea] Channels schema not available: ${err.message}`)
|
||||
console.log(`[mizan] Channels schema not available: ${err.message}`)
|
||||
}
|
||||
|
||||
// Fetch and generate djarea files
|
||||
// Fetch and generate mizan files
|
||||
try {
|
||||
console.log('[djarea] Fetching djarea schema...')
|
||||
djareaSchema = await fetchDjareaSchema(config.source, frontendDir)
|
||||
console.log('[mizan] Fetching mizan schema...')
|
||||
mizanSchema = await fetchMizanSchema(config.source, frontendDir)
|
||||
|
||||
const functionCount = djareaSchema['x-djarea-functions']?.length || 0
|
||||
const functionCount = mizanSchema['x-mizan-functions']?.length || 0
|
||||
if (functionCount > 0) {
|
||||
console.log(`[djarea] Found ${functionCount} djarea functions`)
|
||||
console.log(`[mizan] Found ${functionCount} mizan functions`)
|
||||
|
||||
const djareaTypesPath = outputPath.replace(/\.ts$/, '.djarea.ts')
|
||||
const fullDjareaTypesPath = path.resolve(frontendDir, djareaTypesPath)
|
||||
const djareaProviderPath = outputPath.replace(/\.ts$/, '.django.tsx')
|
||||
const fullDjareaProviderPath = path.resolve(frontendDir, djareaProviderPath)
|
||||
const djareaServerPath = outputPath.replace(/\.ts$/, '.django.server.ts')
|
||||
const fullDjareaServerPath = path.resolve(frontendDir, djareaServerPath)
|
||||
const djareaFormsPath = outputPath.replace(/\.ts$/, '.forms.ts')
|
||||
const fullDjareaFormsPath = path.resolve(frontendDir, djareaFormsPath)
|
||||
const djareaSchemaPath = outputPath.replace(/\.ts$/, '.djarea.schema.json')
|
||||
const fullDjareaSchemaPath = path.resolve(frontendDir, djareaSchemaPath)
|
||||
const mizanTypesPath = outputPath.replace(/\.ts$/, '.mizan.ts')
|
||||
const fullMizanTypesPath = path.resolve(frontendDir, mizanTypesPath)
|
||||
const mizanProviderPath = outputPath.replace(/\.ts$/, '.django.tsx')
|
||||
const fullMizanProviderPath = path.resolve(frontendDir, mizanProviderPath)
|
||||
const mizanServerPath = outputPath.replace(/\.ts$/, '.django.server.ts')
|
||||
const fullMizanServerPath = path.resolve(frontendDir, mizanServerPath)
|
||||
const mizanFormsPath = outputPath.replace(/\.ts$/, '.forms.ts')
|
||||
const fullMizanFormsPath = path.resolve(frontendDir, mizanFormsPath)
|
||||
const mizanSchemaPath = outputPath.replace(/\.ts$/, '.mizan.schema.json')
|
||||
const fullMizanSchemaPath = path.resolve(frontendDir, mizanSchemaPath)
|
||||
|
||||
const hasChannels = (channelsSchema?.['x-djarea-channels']?.length || 0) > 0
|
||||
const { types: djareaTypes, provider: djareaProvider, server: djareaServer, forms: djareaForms } = await generateDjareaFiles(djareaSchema, { hasChannels })
|
||||
const hasChannels = (channelsSchema?.['x-mizan-channels']?.length || 0) > 0
|
||||
const { types: mizanTypes, provider: mizanProvider, server: mizanServer, forms: mizanForms } = await generateMizanFiles(mizanSchema, { hasChannels })
|
||||
|
||||
console.log(`[djarea] Generating -> ${djareaTypesPath}`)
|
||||
await writeOutput(fullDjareaTypesPath, djareaTypes)
|
||||
console.log(`[mizan] Generating -> ${mizanTypesPath}`)
|
||||
await writeOutput(fullMizanTypesPath, mizanTypes)
|
||||
|
||||
if (djareaProvider) {
|
||||
console.log(`[djarea] Generating -> ${djareaProviderPath}`)
|
||||
await writeOutput(fullDjareaProviderPath, djareaProvider)
|
||||
if (mizanProvider) {
|
||||
console.log(`[mizan] Generating -> ${mizanProviderPath}`)
|
||||
await writeOutput(fullMizanProviderPath, mizanProvider)
|
||||
}
|
||||
|
||||
if (djareaServer) {
|
||||
console.log(`[djarea] Generating -> ${djareaServerPath}`)
|
||||
await writeOutput(fullDjareaServerPath, djareaServer)
|
||||
if (mizanServer) {
|
||||
console.log(`[mizan] Generating -> ${mizanServerPath}`)
|
||||
await writeOutput(fullMizanServerPath, mizanServer)
|
||||
}
|
||||
|
||||
if (djareaForms) {
|
||||
console.log(`[djarea] Generating -> ${djareaFormsPath}`)
|
||||
await writeOutput(fullDjareaFormsPath, djareaForms)
|
||||
if (mizanForms) {
|
||||
console.log(`[mizan] Generating -> ${mizanFormsPath}`)
|
||||
await writeOutput(fullMizanFormsPath, mizanForms)
|
||||
}
|
||||
|
||||
console.log(`[djarea] Generating -> ${djareaSchemaPath}`)
|
||||
await writeOutput(fullDjareaSchemaPath, JSON.stringify(djareaSchema, null, 2))
|
||||
console.log(`[mizan] Generating -> ${mizanSchemaPath}`)
|
||||
await writeOutput(fullMizanSchemaPath, JSON.stringify(mizanSchema, null, 2))
|
||||
} else {
|
||||
console.log('[djarea] No djarea functions registered, skipping djarea generation')
|
||||
console.log('[mizan] No mizan functions registered, skipping mizan generation')
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(`[djarea] Djarea schema not available: ${err.message}`)
|
||||
console.log(`[mizan] mizan schema not available: ${err.message}`)
|
||||
}
|
||||
|
||||
// Generate consolidated index.ts
|
||||
const indexPath = path.dirname(outputPath) + '/index.ts'
|
||||
const fullIndexPath = path.resolve(frontendDir, indexPath)
|
||||
|
||||
console.log(`[djarea] Generating -> ${indexPath}`)
|
||||
console.log(`[mizan] Generating -> ${indexPath}`)
|
||||
const indexContent = generateIndex({
|
||||
channelsSchema,
|
||||
djareaSchema,
|
||||
mizanSchema,
|
||||
})
|
||||
await writeOutput(fullIndexPath, indexContent)
|
||||
|
||||
console.log('[djarea] Generation complete!')
|
||||
console.log('[mizan] Generation complete!')
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -194,7 +194,7 @@ async function watch(config, options) {
|
||||
try {
|
||||
await generate(config, options)
|
||||
} catch (err) {
|
||||
console.error('[djarea] Generation failed:', err.message)
|
||||
console.error('[mizan] Generation failed:', err.message)
|
||||
} finally {
|
||||
running = false
|
||||
}
|
||||
@@ -202,7 +202,7 @@ async function watch(config, options) {
|
||||
|
||||
await runGenerate()
|
||||
|
||||
console.log('[djarea] Watching for changes (press Ctrl+C to stop)...')
|
||||
console.log('[mizan] Watching for changes (press Ctrl+C to stop)...')
|
||||
|
||||
if (config.source.django) {
|
||||
const { watch: chokidarWatch } = await import('chokidar')
|
||||
@@ -221,14 +221,14 @@ async function watch(config, options) {
|
||||
})
|
||||
|
||||
watcher.on('change', (filePath) => {
|
||||
console.log(`[djarea] Detected change: ${path.relative(djangoDir, filePath)}`)
|
||||
console.log(`[mizan] Detected change: ${path.relative(djangoDir, filePath)}`)
|
||||
if (timeout) clearTimeout(timeout)
|
||||
timeout = setTimeout(runGenerate, debounce)
|
||||
})
|
||||
}
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
console.log('\n[djarea] Stopping watch mode...')
|
||||
console.log('\n[mizan] Stopping watch mode...')
|
||||
process.exit(0)
|
||||
})
|
||||
}
|
||||
@@ -252,10 +252,10 @@ async function main() {
|
||||
output = args[++i]
|
||||
} else if (args[i] === '--help' || args[i] === '-h') {
|
||||
console.log(`
|
||||
Djarea Code Generator - Generate TypeScript from Django schemas
|
||||
mizan Code Generator - Generate TypeScript from Django schemas
|
||||
|
||||
Usage:
|
||||
npx djarea-generate [options]
|
||||
npx mizan-generate [options]
|
||||
|
||||
Options:
|
||||
-c, --config <path> Config file path (default: django.config.mjs)
|
||||
@@ -278,6 +278,6 @@ Options:
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error('[djarea] Error:', err.message)
|
||||
console.error('[mizan] Error:', err.message)
|
||||
process.exit(1)
|
||||
})
|
||||
|
||||
@@ -16,7 +16,7 @@ export async function generateChannelsTypes(schema) {
|
||||
const typesCode = astToString(ast)
|
||||
|
||||
const lines = [
|
||||
'// AUTO-GENERATED by djarea - do not edit manually',
|
||||
'// AUTO-GENERATED by mizan - do not edit manually',
|
||||
'// Regenerate with: npm run schemas',
|
||||
'',
|
||||
'// ============================================================================',
|
||||
@@ -27,8 +27,8 @@ export async function generateChannelsTypes(schema) {
|
||||
'',
|
||||
]
|
||||
|
||||
// Extract channel metadata from x-djarea-channels extension
|
||||
const channels = schema['x-djarea-channels'] || []
|
||||
// Extract channel metadata from x-mizan-channels extension
|
||||
const channels = schema['x-mizan-channels'] || []
|
||||
|
||||
if (channels.length > 0) {
|
||||
lines.push('// ============================================================================')
|
||||
@@ -86,7 +86,7 @@ export async function generateChannelsTypes(schema) {
|
||||
* Generate channel hooks from metadata.
|
||||
*/
|
||||
export function generateChannelsHooks(schema) {
|
||||
const channels = schema['x-djarea-channels'] || []
|
||||
const channels = schema['x-mizan-channels'] || []
|
||||
|
||||
if (channels.length === 0) {
|
||||
return null
|
||||
@@ -95,10 +95,10 @@ export function generateChannelsHooks(schema) {
|
||||
const lines = [
|
||||
"'use client'",
|
||||
'',
|
||||
'// AUTO-GENERATED by djarea - do not edit manually',
|
||||
'// AUTO-GENERATED by mizan - do not edit manually',
|
||||
'// Regenerate with: npm run schemas',
|
||||
'',
|
||||
"import { useChannel, type ChannelSubscription } from 'djarea/channels'",
|
||||
"import { useChannel, type ChannelSubscription } from 'mizan/channels'",
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
/**
|
||||
* Djarea Code Generator
|
||||
* mizan Code Generator
|
||||
*
|
||||
* Generates TypeScript types and React provider from Djarea OpenAPI schema.
|
||||
* Generates TypeScript types and React provider from mizan OpenAPI schema.
|
||||
* Uses openapi-typescript for robust type generation.
|
||||
*
|
||||
* Output structure:
|
||||
* - generated.djarea.ts - Types only (from OpenAPI)
|
||||
* - generated.provider.tsx - Typed provider wrapping DjareaProvider + hooks
|
||||
* - generated.mizan.ts - Types only (from OpenAPI)
|
||||
* - generated.provider.tsx - Typed provider wrapping MizanProvider + hooks
|
||||
* - generated.forms.ts - Typed form hooks with Zod schemas
|
||||
*/
|
||||
|
||||
@@ -74,14 +74,14 @@ function buildSchemaExports(schemaNames) {
|
||||
/**
|
||||
* Generate the types file using openapi-typescript.
|
||||
*/
|
||||
export async function generateDjareaTypes(schema) {
|
||||
export async function generateMizanTypes(schema) {
|
||||
// Generate types using openapi-typescript
|
||||
const ast = await openapiTS(schema)
|
||||
const schemaNames = getSchemaNamesFromAst(ast)
|
||||
const typesCode = astToString(ast)
|
||||
|
||||
const lines = [
|
||||
'// AUTO-GENERATED by djarea - do not edit manually',
|
||||
'// AUTO-GENERATED by mizan - do not edit manually',
|
||||
'// Regenerate with: npm run schemas',
|
||||
'',
|
||||
'// ============================================================================',
|
||||
@@ -104,8 +104,8 @@ export async function generateDjareaTypes(schema) {
|
||||
'',
|
||||
]
|
||||
|
||||
// Extract function metadata from x-djarea-functions extension
|
||||
const functions = schema['x-djarea-functions'] || []
|
||||
// Extract function metadata from x-mizan-functions extension
|
||||
const functions = schema['x-mizan-functions'] || []
|
||||
|
||||
if (functions.length > 0) {
|
||||
lines.push('export const DJANGO_FUNCTIONS = {')
|
||||
@@ -128,16 +128,16 @@ export async function generateDjareaTypes(schema) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the React provider that wraps DjareaProvider with typed hooks.
|
||||
* Generate the React provider that wraps MizanProvider with typed hooks.
|
||||
*
|
||||
* The generated provider:
|
||||
* - Wraps DjareaProvider (from djarea library)
|
||||
* - Wraps MizanProvider (from mizan library)
|
||||
* - Passes context names for auto-fetch
|
||||
* - Provides typed hooks for contexts and functions
|
||||
*/
|
||||
export function generateDjareaProvider(schema, options = {}) {
|
||||
export function generateMizanProvider(schema, options = {}) {
|
||||
const { hasChannels = false } = options
|
||||
const functions = schema['x-djarea-functions'] || []
|
||||
const functions = schema['x-mizan-functions'] || []
|
||||
|
||||
if (functions.length === 0) {
|
||||
return null
|
||||
@@ -162,31 +162,31 @@ export function generateDjareaProvider(schema, options = {}) {
|
||||
const lines = [
|
||||
"'use client'",
|
||||
'',
|
||||
'// AUTO-GENERATED by djarea - do not edit manually',
|
||||
'// AUTO-GENERATED by mizan - do not edit manually',
|
||||
'// Regenerate with: npm run schemas',
|
||||
'',
|
||||
'// This file provides typed wrappers around the djarea library.',
|
||||
'// - DjangoContext: Typed provider wrapping DjareaProvider',
|
||||
'// 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 {",
|
||||
" DjareaProvider,",
|
||||
" useDjarea,",
|
||||
" useDjareaContext,",
|
||||
" useDjareaCall,",
|
||||
" type DjareaHydration,",
|
||||
" MizanProvider,",
|
||||
" useMizan,",
|
||||
" useMizanContext,",
|
||||
" useMizanCall,",
|
||||
" type MizanHydration,",
|
||||
" type Transport,",
|
||||
"} from 'djarea'",
|
||||
"} from 'mizan'",
|
||||
...(hasChannels ? [
|
||||
"import { ChannelProvider, ChannelConnection } from 'djarea/channels'",
|
||||
"import { ChannelProvider, ChannelConnection } from 'mizan/channels'",
|
||||
"import { useRef } from 'react'",
|
||||
] : []),
|
||||
'',
|
||||
]
|
||||
|
||||
if (uniqueTypeImports.length > 0) {
|
||||
lines.push(`import type { ${uniqueTypeImports.join(', ')} } from './generated.djarea'`)
|
||||
lines.push(`import type { ${uniqueTypeImports.join(', ')} } from './generated.mizan'`)
|
||||
lines.push('')
|
||||
}
|
||||
|
||||
@@ -208,10 +208,10 @@ export function generateDjareaProvider(schema, options = {}) {
|
||||
lines.push('}')
|
||||
lines.push('')
|
||||
|
||||
lines.push('/** Convert typed hydration to djarea format */')
|
||||
lines.push('function toDjareaHydration(hydration?: DjangoHydration): DjareaHydration | undefined {')
|
||||
lines.push('/** Convert typed hydration to mizan format */')
|
||||
lines.push('function toMizanHydration(hydration?: DjangoHydration): MizanHydration | undefined {')
|
||||
lines.push(' if (!hydration) return undefined')
|
||||
lines.push(' const result: DjareaHydration = {}')
|
||||
lines.push(' const result: MizanHydration = {}')
|
||||
for (const ctx of contexts) {
|
||||
lines.push(` if (hydration.${ctx.camelName} !== undefined) result['${ctx.name}'] = hydration.${ctx.camelName}`)
|
||||
}
|
||||
@@ -237,18 +237,18 @@ export function generateDjareaProvider(schema, options = {}) {
|
||||
}
|
||||
lines.push(' /** WebSocket URL for RPC calls (default: /ws/) */')
|
||||
lines.push(' wsUrl?: string')
|
||||
lines.push(' /** Base URL for HTTP fallback (default: /api/djarea) */')
|
||||
lines.push(' /** Base URL for HTTP fallback (default: /api/mizan) */')
|
||||
lines.push(' baseUrl?: string')
|
||||
lines.push('}')
|
||||
lines.push('')
|
||||
|
||||
// Context names array for DjareaProvider
|
||||
// Context names array for MizanProvider
|
||||
const contextNames = contexts.map(ctx => `'${ctx.name}'`).join(', ')
|
||||
|
||||
lines.push('/**')
|
||||
lines.push(' * Typed Django context provider.')
|
||||
lines.push(' *')
|
||||
lines.push(' * Wraps DjareaProvider with:')
|
||||
lines.push(' * Wraps MizanProvider with:')
|
||||
lines.push(' * - Typed hydration')
|
||||
lines.push(' * - Auto-fetch for registered contexts')
|
||||
lines.push(' *')
|
||||
@@ -275,9 +275,9 @@ export function generateDjareaProvider(schema, options = {}) {
|
||||
}
|
||||
|
||||
lines.push(' return (')
|
||||
lines.push(' <DjareaProvider')
|
||||
lines.push(' <MizanProvider')
|
||||
if (contexts.length > 0) {
|
||||
lines.push(' hydration={toDjareaHydration(hydration)}')
|
||||
lines.push(' hydration={toMizanHydration(hydration)}')
|
||||
lines.push(` contexts={[${contextNames}]}`)
|
||||
}
|
||||
lines.push(' wsUrl={wsUrl}')
|
||||
@@ -295,7 +295,7 @@ export function generateDjareaProvider(schema, options = {}) {
|
||||
lines.push(' {children}')
|
||||
}
|
||||
|
||||
lines.push(' </DjareaProvider>')
|
||||
lines.push(' </MizanProvider>')
|
||||
lines.push(' )')
|
||||
lines.push('}')
|
||||
lines.push('')
|
||||
@@ -317,7 +317,7 @@ export function generateDjareaProvider(schema, options = {}) {
|
||||
lines.push(` * @throws if context not loaded yet`)
|
||||
lines.push(` */`)
|
||||
lines.push(`export function use${pascal}(): ${ctx.outputType} {`)
|
||||
lines.push(` const data = useDjareaContext<${ctx.outputType}>('${ctx.name}')`)
|
||||
lines.push(` const data = useMizanContext<${ctx.outputType}>('${ctx.name}')`)
|
||||
lines.push(` if (data === undefined) {`)
|
||||
lines.push(` throw new Error('use${pascal}: context not loaded yet')`)
|
||||
lines.push(` }`)
|
||||
@@ -332,7 +332,7 @@ export function generateDjareaProvider(schema, options = {}) {
|
||||
lines.push(' * Use this in components that only need to trigger refreshes.')
|
||||
lines.push(' */')
|
||||
lines.push('export function useDjangoRefresh() {')
|
||||
lines.push(' const { refreshContext, refreshAllContexts } = useDjarea()')
|
||||
lines.push(' const { refreshContext, refreshAllContexts } = useMizan()')
|
||||
lines.push(' return {')
|
||||
for (const ctx of contexts) {
|
||||
const pascal = pascalCase(ctx.camelName)
|
||||
@@ -365,7 +365,7 @@ export function generateDjareaProvider(schema, options = {}) {
|
||||
lines.push(` * Transport: ${transport}`)
|
||||
lines.push(` */`)
|
||||
lines.push(`export function use${pascal}() {`)
|
||||
lines.push(` return useDjareaCall<${fn.inputType}, ${fn.outputType}>('${fn.name}', '${transport}')`)
|
||||
lines.push(` return useMizanCall<${fn.inputType}, ${fn.outputType}>('${fn.name}', '${transport}')`)
|
||||
lines.push(`}`)
|
||||
} else {
|
||||
lines.push(`/**`)
|
||||
@@ -373,7 +373,7 @@ export function generateDjareaProvider(schema, options = {}) {
|
||||
lines.push(` * Transport: ${transport}`)
|
||||
lines.push(` */`)
|
||||
lines.push(`export function use${pascal}() {`)
|
||||
lines.push(` return useDjareaCall<void, ${fn.outputType}>('${fn.name}', '${transport}')`)
|
||||
lines.push(` return useMizanCall<void, ${fn.outputType}>('${fn.name}', '${transport}')`)
|
||||
lines.push(`}`)
|
||||
}
|
||||
lines.push('')
|
||||
@@ -385,11 +385,11 @@ export function generateDjareaProvider(schema, options = {}) {
|
||||
// ============================================================================
|
||||
|
||||
lines.push('// ============================================================================')
|
||||
lines.push('// Re-exports from djarea library')
|
||||
lines.push('// Re-exports from mizan library')
|
||||
lines.push('// ============================================================================')
|
||||
lines.push('')
|
||||
lines.push("export { useDjarea, useDjareaStatus, usePush, DjangoError } from 'djarea'")
|
||||
lines.push("export type { ConnectionStatus, PushMessage, PushListener } from 'djarea'")
|
||||
lines.push("export { useMizan, useMizanStatus, usePush, DjangoError } from 'mizan'")
|
||||
lines.push("export type { ConnectionStatus, PushMessage, PushListener } from 'mizan'")
|
||||
lines.push('')
|
||||
|
||||
return lines.join('\n')
|
||||
@@ -399,8 +399,8 @@ export function generateDjareaProvider(schema, options = {}) {
|
||||
* Generate server-side hydration helper (runs in Next.js server components).
|
||||
* This is separate from the client file because it needs to run on the server.
|
||||
*/
|
||||
export function generateDjareaServer(schema) {
|
||||
const functions = schema['x-djarea-functions'] || []
|
||||
export function generateMizanServer(schema) {
|
||||
const functions = schema['x-mizan-functions'] || []
|
||||
const contexts = functions.filter(fn => fn.isContext)
|
||||
|
||||
if (contexts.length === 0) {
|
||||
@@ -412,7 +412,7 @@ export function generateDjareaServer(schema) {
|
||||
const uniqueTypeImports = [...new Set(typeImports)].sort()
|
||||
|
||||
const lines = [
|
||||
'// AUTO-GENERATED by djarea - do not edit manually',
|
||||
'// AUTO-GENERATED by mizan - do not edit manually',
|
||||
'// Regenerate with: npm run schemas',
|
||||
'//',
|
||||
'// Server-side functions for SSR hydration.',
|
||||
@@ -421,7 +421,7 @@ export function generateDjareaServer(schema) {
|
||||
]
|
||||
|
||||
if (uniqueTypeImports.length > 0) {
|
||||
lines.push(`import type { ${uniqueTypeImports.join(', ')} } from './generated.djarea'`)
|
||||
lines.push(`import type { ${uniqueTypeImports.join(', ')} } from './generated.mizan'`)
|
||||
lines.push('')
|
||||
}
|
||||
|
||||
@@ -457,7 +457,7 @@ export function generateDjareaServer(schema) {
|
||||
lines.push('')
|
||||
lines.push(' const results = await Promise.allSettled([')
|
||||
for (const ctx of contexts) {
|
||||
lines.push(` client.request('POST', '/api/djarea/call/', { fn: '${ctx.name}', args: {} }),`)
|
||||
lines.push(` client.request('POST', '/api/mizan/call/', { fn: '${ctx.name}', args: {} }),`)
|
||||
}
|
||||
lines.push(' ])')
|
||||
lines.push('')
|
||||
@@ -484,13 +484,13 @@ export function generateDjareaServer(schema) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate all djarea files.
|
||||
* Generate all mizan files.
|
||||
*/
|
||||
export async function generateDjareaFiles(schema, options = {}) {
|
||||
const types = await generateDjareaTypes(schema)
|
||||
const provider = generateDjareaProvider(schema, options)
|
||||
const server = generateDjareaServer(schema)
|
||||
const forms = generateDjareaForms(schema)
|
||||
export async function generateMizanFiles(schema, options = {}) {
|
||||
const types = await generateMizanTypes(schema)
|
||||
const provider = generateMizanProvider(schema, options)
|
||||
const server = generateMizanServer(schema)
|
||||
const forms = generateMizanForms(schema)
|
||||
|
||||
return { types, provider, server, forms }
|
||||
}
|
||||
@@ -498,8 +498,8 @@ export async function generateDjareaFiles(schema, options = {}) {
|
||||
/**
|
||||
* Generate typed form hooks with Zod schemas.
|
||||
*/
|
||||
export function generateDjareaForms(schema) {
|
||||
const functions = schema['x-djarea-functions'] || []
|
||||
export function generateMizanForms(schema) {
|
||||
const functions = schema['x-mizan-functions'] || []
|
||||
|
||||
// Group form functions by form name
|
||||
const formFunctions = functions.filter(fn => fn.isForm)
|
||||
@@ -535,7 +535,7 @@ export function generateDjareaForms(schema) {
|
||||
const lines = [
|
||||
"'use client'",
|
||||
'',
|
||||
'// AUTO-GENERATED by djarea - do not edit manually',
|
||||
'// AUTO-GENERATED by mizan - do not edit manually',
|
||||
'// Regenerate with: npm run schemas',
|
||||
'',
|
||||
'// Typed form hooks with Zod validation.',
|
||||
@@ -549,7 +549,7 @@ export function generateDjareaForms(schema) {
|
||||
" type DjangoFormState,",
|
||||
" type DjangoFormsetState,",
|
||||
" type FormOptions,",
|
||||
"} from 'djarea'",
|
||||
"} from 'mizan'",
|
||||
'',
|
||||
'// ============================================================================',
|
||||
'// Zod Schemas',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Schema Fetching
|
||||
*
|
||||
* Fetches djarea and channels schemas from Django management commands.
|
||||
* Fetches mizan and channels schemas from Django management commands.
|
||||
*/
|
||||
|
||||
import { spawn } from 'child_process'
|
||||
@@ -78,11 +78,11 @@ export async function fetchChannelsSchema(source, cwd) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch djarea schema from Django.
|
||||
* Fetch mizan schema from Django.
|
||||
*/
|
||||
export async function fetchDjareaSchema(source, cwd) {
|
||||
export async function fetchMizanSchema(source, cwd) {
|
||||
if (!source.django) {
|
||||
throw new Error('Djarea schema export requires django source configuration')
|
||||
throw new Error('mizan schema export requires django source configuration')
|
||||
}
|
||||
return runDjangoCommand(source, cwd, 'export_djarea_schema')
|
||||
return runDjangoCommand(source, cwd, 'export_mizan_schema')
|
||||
}
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* Extract context hooks from djarea schema.
|
||||
* Extract context hooks from mizan schema.
|
||||
* Returns hook names in PascalCase (e.g., useAuthStatus, useUser).
|
||||
*/
|
||||
function extractContextHooks(djareaSchema) {
|
||||
const functions = djareaSchema?.['x-djarea-functions'] || []
|
||||
function extractContextHooks(mizanSchema) {
|
||||
const functions = mizanSchema?.['x-mizan-functions'] || []
|
||||
const contexts = functions.filter(fn => fn.isContext)
|
||||
|
||||
return contexts.map(ctx => {
|
||||
@@ -24,13 +24,13 @@ function extractContextHooks(djareaSchema) {
|
||||
*
|
||||
* @param {Object} options - Generation options
|
||||
* @param {Object} options.channelsSchema - Channels schema (optional)
|
||||
* @param {Object} options.djareaSchema - Djarea schema (optional)
|
||||
* @param {Object} options.mizanSchema - mizan schema (optional)
|
||||
* @returns {string} Generated index.ts content
|
||||
*/
|
||||
export function generateIndex({ channelsSchema, djareaSchema }) {
|
||||
export function generateIndex({ channelsSchema, mizanSchema }) {
|
||||
const lines = [
|
||||
'/**',
|
||||
' * Djarea API - Consolidated Exports',
|
||||
' * mizan API - Consolidated Exports',
|
||||
' *',
|
||||
' * Import everything from here:',
|
||||
' *',
|
||||
@@ -46,25 +46,25 @@ export function generateIndex({ channelsSchema, djareaSchema }) {
|
||||
' * ```',
|
||||
' */',
|
||||
'',
|
||||
'// AUTO-GENERATED by djarea - do not edit manually',
|
||||
'// AUTO-GENERATED by mizan - do not edit manually',
|
||||
'// Regenerate with: npm run schemas',
|
||||
'',
|
||||
]
|
||||
|
||||
// ==========================================================================
|
||||
// Djarea Provider & Hooks (from generated.django.tsx)
|
||||
// mizan Provider & Hooks (from generated.django.tsx)
|
||||
// ==========================================================================
|
||||
|
||||
const functions = djareaSchema?.['x-djarea-functions'] || []
|
||||
const hasDjarea = functions.length > 0
|
||||
const functions = mizanSchema?.['x-mizan-functions'] || []
|
||||
const hasMizan = functions.length > 0
|
||||
|
||||
if (hasDjarea) {
|
||||
const contextHooks = extractContextHooks(djareaSchema)
|
||||
if (hasMizan) {
|
||||
const contextHooks = extractContextHooks(mizanSchema)
|
||||
const contexts = functions.filter(fn => fn.isContext)
|
||||
const regularFunctions = functions.filter(fn => !fn.isContext && !fn.isForm)
|
||||
|
||||
lines.push('// =============================================================================')
|
||||
lines.push('// Djarea Provider & Hooks')
|
||||
lines.push('// mizan Provider & Hooks')
|
||||
lines.push('// =============================================================================')
|
||||
lines.push('')
|
||||
|
||||
@@ -104,9 +104,9 @@ export function generateIndex({ channelsSchema, djareaSchema }) {
|
||||
}
|
||||
|
||||
lines.push('')
|
||||
lines.push(' // Re-exports from djarea library')
|
||||
lines.push(' useDjarea,')
|
||||
lines.push(' useDjareaStatus,')
|
||||
lines.push(' // Re-exports from mizan library')
|
||||
lines.push(' useMizan,')
|
||||
lines.push(' useMizanStatus,')
|
||||
lines.push(' usePush,')
|
||||
lines.push(' DjangoError,')
|
||||
lines.push(' type ConnectionStatus,')
|
||||
@@ -120,7 +120,7 @@ export function generateIndex({ channelsSchema, djareaSchema }) {
|
||||
// Channel Hooks (from generated.channels.hooks.tsx)
|
||||
// ==========================================================================
|
||||
|
||||
const channels = channelsSchema?.['x-djarea-channels'] || []
|
||||
const channels = channelsSchema?.['x-mizan-channels'] || []
|
||||
|
||||
if (channels.length > 0) {
|
||||
lines.push('// =============================================================================')
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Djarea - Django Server Functions Client
|
||||
* mizan - Django Server Functions Client
|
||||
*
|
||||
* Frontend client for Django server functions.
|
||||
* Server functions are the core primitive - accessed via React hooks.
|
||||
@@ -9,9 +9,9 @@
|
||||
* 1. Library layer (this package) - Generic name-based API
|
||||
* Used by libraries like Allauth that need to call functions by name.
|
||||
*
|
||||
* import { useDjarea, useDjareaContext, useDjareaCall } from 'djarea'
|
||||
* const user = useDjareaContext('current_user')
|
||||
* const call = useDjareaCall('update_profile')
|
||||
* import { useMizan, useMizanContext, useMizanCall } from 'mizan'
|
||||
* const user = useMizanContext('current_user')
|
||||
* const call = useMizanCall('update_profile')
|
||||
*
|
||||
* 2. Generated layer (@/api) - Typed project-specific API
|
||||
* Used by product code for type-safe hooks.
|
||||
@@ -20,7 +20,7 @@
|
||||
* const user = useCurrentUser()
|
||||
* const updateProfile = useUpdateProfile()
|
||||
*
|
||||
* The generated code wraps DjareaProvider and adds type-safe hooks.
|
||||
* The generated code wraps MizanProvider and adds type-safe hooks.
|
||||
*/
|
||||
|
||||
// ============================================================================
|
||||
@@ -29,19 +29,19 @@
|
||||
|
||||
export {
|
||||
// Provider
|
||||
DjareaProvider,
|
||||
type DjareaProviderProps,
|
||||
type DjareaHydration,
|
||||
MizanProvider,
|
||||
type MizanProviderProps,
|
||||
type MizanHydration,
|
||||
|
||||
// Hooks (generic name-based API for libraries)
|
||||
useDjarea,
|
||||
useDjareaContext,
|
||||
useDjareaCall,
|
||||
useDjareaStatus,
|
||||
useMizan,
|
||||
useMizanContext,
|
||||
useMizanCall,
|
||||
useMizanStatus,
|
||||
usePush,
|
||||
|
||||
// Types
|
||||
type DjareaContextValue,
|
||||
type MizanContextValue,
|
||||
type ConnectionStatus,
|
||||
type PushMessage,
|
||||
type PushListener,
|
||||
@@ -89,9 +89,9 @@ export {
|
||||
|
||||
export {
|
||||
// Single form
|
||||
useDjareaFormCore,
|
||||
useMizanFormCore,
|
||||
// Legacy alias
|
||||
useDjareaFormCore as useDjangoFormCore,
|
||||
useMizanFormCore as useDjangoFormCore,
|
||||
type DjangoFormState,
|
||||
type FormSchema,
|
||||
type FormErrors,
|
||||
@@ -99,9 +99,9 @@ export {
|
||||
type FormSubmitResult,
|
||||
type FormCoreConfig,
|
||||
// Formset
|
||||
useDjareaFormsetCore,
|
||||
useMizanFormsetCore,
|
||||
// Legacy alias
|
||||
useDjareaFormsetCore as useDjangoFormsetCore,
|
||||
useMizanFormsetCore as useDjangoFormsetCore,
|
||||
type DjangoFormsetState,
|
||||
type FormsetSchema,
|
||||
type FormsetErrors,
|
||||
|
||||
@@ -21,7 +21,7 @@ const Context = createContext<JWTState | null>(null)
|
||||
|
||||
const DEFAULT_CONFIG: Required<JWTConfig> = {
|
||||
baseUrl: '',
|
||||
endpoint: '/api/djarea/call/',
|
||||
endpoint: '/api/mizan/call/',
|
||||
refreshBuffer: 30,
|
||||
autoObtain: true,
|
||||
autoRefresh: true,
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
/**
|
||||
* Contract Tests for Djarea JWT Server Functions
|
||||
* Contract Tests for mizan JWT Server Functions
|
||||
*
|
||||
* Validates that the backend schema exports the expected JWT functions.
|
||||
* These tests catch frontend/backend contract mismatches early.
|
||||
*/
|
||||
|
||||
import djareaSchema from '@/api/generated.djarea.schema.json'
|
||||
import mizanSchema from '@/api/generated.mizan.schema.json'
|
||||
|
||||
type DjareaFunction = {
|
||||
type mizanFunction = {
|
||||
name: string
|
||||
camelName: string
|
||||
hasInput: boolean
|
||||
@@ -16,11 +16,11 @@ type DjareaFunction = {
|
||||
transport: string
|
||||
}
|
||||
|
||||
function getFunctions(): DjareaFunction[] {
|
||||
return (djareaSchema as any)['x-djarea-functions'] ?? []
|
||||
function getFunctions(): mizanFunction[] {
|
||||
return (mizanSchema as any)['x-mizan-functions'] ?? []
|
||||
}
|
||||
|
||||
function findFunction(name: string): DjareaFunction | undefined {
|
||||
function findFunction(name: string): mizanFunction | undefined {
|
||||
return getFunctions().find(fn => fn.name === name)
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ describe('JWT Server Functions Contract', () => {
|
||||
})
|
||||
|
||||
it('should return token pair with expected fields', () => {
|
||||
const schemas = (djareaSchema as any).components?.schemas
|
||||
const schemas = (mizanSchema as any).components?.schemas
|
||||
const output = schemas?.jwtObtainOutput
|
||||
|
||||
expect(output).toBeDefined()
|
||||
@@ -59,7 +59,7 @@ describe('JWT Server Functions Contract', () => {
|
||||
const fn = findFunction('jwt_refresh')
|
||||
expect(fn?.hasInput).toBe(true)
|
||||
|
||||
const schemas = (djareaSchema as any).components?.schemas
|
||||
const schemas = (mizanSchema as any).components?.schemas
|
||||
const input = schemas?.jwtRefreshInput
|
||||
|
||||
expect(input).toBeDefined()
|
||||
@@ -67,7 +67,7 @@ describe('JWT Server Functions Contract', () => {
|
||||
})
|
||||
|
||||
it('should return token pair with expected fields', () => {
|
||||
const schemas = (djareaSchema as any).components?.schemas
|
||||
const schemas = (mizanSchema as any).components?.schemas
|
||||
const output = schemas?.jwtRefreshOutput
|
||||
|
||||
expect(output).toBeDefined()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* djarea/jwt
|
||||
* mizan/jwt
|
||||
*
|
||||
* JWT token management via djarea server functions.
|
||||
* JWT token management via mizan server functions.
|
||||
* Handles token lifecycle: obtain, refresh, clear.
|
||||
*
|
||||
* ## Quick Start
|
||||
@@ -9,8 +9,8 @@
|
||||
* Use JWTContext in authenticated areas (e.g., inside UserRoute):
|
||||
*
|
||||
* ```tsx
|
||||
* import { JWTContext } from 'djarea/jwt'
|
||||
* import { UserRoute } from 'djarea/allauth'
|
||||
* import { JWTContext } from 'mizan/jwt'
|
||||
* import { UserRoute } from 'mizan/allauth'
|
||||
*
|
||||
* function ProtectedPage() {
|
||||
* return (
|
||||
@@ -26,7 +26,7 @@
|
||||
* Then use JWT-authenticated requests:
|
||||
*
|
||||
* ```tsx
|
||||
* import { useDjangoCSRClient, Auth } from 'djarea/client/react'
|
||||
* import { useDjangoCSRClient, Auth } from 'mizan/client/react'
|
||||
*
|
||||
* function MyProtectedContent() {
|
||||
* const client = useDjangoCSRClient(Auth.JWT)
|
||||
@@ -40,7 +40,7 @@
|
||||
*
|
||||
* ## How It Works
|
||||
*
|
||||
* 1. JWTContext calls jwt_obtain server function (via /api/djarea/call/)
|
||||
* 1. JWTContext calls jwt_obtain server function (via /api/mizan/call/)
|
||||
* 2. If not authenticated, returns FORBIDDEN (tokens stay null)
|
||||
* 3. Client uses getAccessToken() for Bearer token injection
|
||||
* 4. Tokens auto-refresh via jwt_refresh server function
|
||||
@@ -51,7 +51,7 @@
|
||||
* ```tsx
|
||||
* <JWTContext
|
||||
* config={{
|
||||
* endpoint: '/api/djarea/call/', // default
|
||||
* endpoint: '/api/mizan/call/', // default
|
||||
* refreshBuffer: 30, // refresh 30s before expiry
|
||||
* autoObtain: true, // obtain on mount
|
||||
* autoRefresh: true, // auto-refresh before expiry
|
||||
@@ -62,7 +62,7 @@
|
||||
* ## Manual Token Management
|
||||
*
|
||||
* ```tsx
|
||||
* import { useJWT } from 'djarea/jwt'
|
||||
* import { useJWT } from 'mizan/jwt'
|
||||
*
|
||||
* function LogoutButton() {
|
||||
* const jwt = useJWT()
|
||||
|
||||
@@ -31,7 +31,7 @@ export const describeIntegration = runIntegrationTests ? describe : describe.ski
|
||||
*/
|
||||
export const BACKEND_URL = (() => {
|
||||
if (!process.env.NEXT_PUBLIC_HOST_URL) {
|
||||
console.warn('[djarea/testing] NEXT_PUBLIC_HOST_URL not set, falling back to http://localhost')
|
||||
console.warn('[mizan/testing] NEXT_PUBLIC_HOST_URL not set, falling back to http://localhost')
|
||||
}
|
||||
return process.env.NEXT_PUBLIC_HOST_URL || 'http://localhost'
|
||||
})()
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
"skipLibCheck": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"djarea": ["./src/index.ts"],
|
||||
"djarea/*": ["./src/*"]
|
||||
"mizan": ["./src/index.ts"],
|
||||
"mizan/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src"],
|
||||
|
||||
@@ -4,14 +4,14 @@ import path from 'path'
|
||||
export default defineConfig({
|
||||
resolve: {
|
||||
alias: {
|
||||
'djarea/channels': path.resolve(__dirname, 'src/channels/index.ts'),
|
||||
'djarea/client/react': path.resolve(__dirname, 'src/client/react.ts'),
|
||||
'djarea/client/nextjs': path.resolve(__dirname, 'src/client/nextjs.tsx'),
|
||||
'djarea/client': path.resolve(__dirname, 'src/client/index.ts'),
|
||||
'djarea/jwt': path.resolve(__dirname, 'src/jwt/index.ts'),
|
||||
'djarea/allauth/nextjs': path.resolve(__dirname, 'src/allauth/nextjs.tsx'),
|
||||
'djarea/allauth': path.resolve(__dirname, 'src/allauth/index.ts'),
|
||||
'djarea': path.resolve(__dirname, 'src/index.ts'),
|
||||
'mizan/channels': path.resolve(__dirname, 'src/channels/index.ts'),
|
||||
'mizan/client/react': path.resolve(__dirname, 'src/client/react.ts'),
|
||||
'mizan/client/nextjs': path.resolve(__dirname, 'src/client/nextjs.tsx'),
|
||||
'mizan/client': path.resolve(__dirname, 'src/client/index.ts'),
|
||||
'mizan/jwt': path.resolve(__dirname, 'src/jwt/index.ts'),
|
||||
'mizan/allauth/nextjs': path.resolve(__dirname, 'src/allauth/nextjs.tsx'),
|
||||
'mizan/allauth': path.resolve(__dirname, 'src/allauth/index.ts'),
|
||||
'mizan': path.resolve(__dirname, 'src/index.ts'),
|
||||
},
|
||||
},
|
||||
test: {
|
||||
@@ -20,7 +20,7 @@ export default defineConfig({
|
||||
setupFiles: ['./vitest.setup.ts'],
|
||||
include: ['src/**/*.test.{ts,tsx}'],
|
||||
exclude: [
|
||||
// Requires @/api/generated.djarea.schema.json from consuming project
|
||||
// Requires @/api/generated.mizan.schema.json from consuming project
|
||||
'src/jwt/__tests__/contract.test.ts',
|
||||
],
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user