diff --git a/packages/mizan-react/src/__tests__/context.test.tsx b/packages/mizan-react/src/__tests__/context.test.tsx
index 964af48..759b5a9 100644
--- a/packages/mizan-react/src/__tests__/context.test.tsx
+++ b/packages/mizan-react/src/__tests__/context.test.tsx
@@ -15,12 +15,12 @@ import {
useMizanStatus,
useMizanCall,
// Legacy aliases for backwards compatibility tests
- DjangoContext,
+ MizanProvider,
useDjango,
- useDjangoStatus,
- useServerFunction,
+ useMizanStatus,
+ useMizanCall,
} from '../context'
-import { DjangoError } from '../errors'
+import { MizanError } from '../errors'
import { describeIntegration, BACKEND_URL } from '../testing'
// ============================================================================
@@ -117,7 +117,7 @@ describeIntegration('mizan Context (integration)', () => {
let error: any = null
function TestComponent() {
- const { call, status } = useDjango()
+ const { call, status } = useMizan()
React.useEffect(() => {
// Use HTTP fallback (status will be disconnected without WebSocket)
@@ -130,9 +130,9 @@ describeIntegration('mizan Context (integration)', () => {
}
render(
-
+
-
+
)
await waitFor(() => {
@@ -149,7 +149,7 @@ describeIntegration('mizan Context (integration)', () => {
let error: any = null
function TestComponent() {
- const { call } = useDjango()
+ const { call } = useMizan()
React.useEffect(() => {
call<{ a: number; b: number }, { result: number }>('add', { a: 10, b: 20 })
@@ -161,9 +161,9 @@ describeIntegration('mizan Context (integration)', () => {
}
render(
-
+
-
+
)
await waitFor(() => {
@@ -174,12 +174,12 @@ describeIntegration('mizan Context (integration)', () => {
expect(result).toEqual({ result: 30 })
})
- it('should throw DjangoError for validation errors', async () => {
+ it('should throw MizanError for validation errors', async () => {
let result: any = null
let error: any = null
function TestComponent() {
- const { call } = useDjango()
+ const { call } = useMizan()
React.useEffect(() => {
// Call without required field
@@ -192,9 +192,9 @@ describeIntegration('mizan Context (integration)', () => {
}
render(
-
+
-
+
)
await waitFor(() => {
@@ -202,11 +202,11 @@ describeIntegration('mizan Context (integration)', () => {
}, { timeout: 5000 })
expect(result).toBeNull()
- expect(error).toBeInstanceOf(DjangoError)
+ expect(error).toBeInstanceOf(MizanError)
})
})
- describe('useServerFunction hook', () => {
+ describe('useMizanCall hook', () => {
it('should create typed function that calls backend', async () => {
let result: any = null
let error: any = null
@@ -215,7 +215,7 @@ describeIntegration('mizan Context (integration)', () => {
interface EchoOutput { message: string }
function TestComponent() {
- const echo = useServerFunction('echo')
+ const echo = useMizanCall('echo')
React.useEffect(() => {
echo({ text: 'typed function test' })
@@ -227,9 +227,9 @@ describeIntegration('mizan Context (integration)', () => {
}
render(
-
+
-
+
)
await waitFor(() => {
@@ -248,7 +248,7 @@ describeIntegration('mizan Context (integration)', () => {
let error: any = null
function TestComponent() {
- const { call } = useDjango()
+ const { call } = useMizan()
React.useEffect(() => {
call('login.schema', { data: {} })
@@ -260,9 +260,9 @@ describeIntegration('mizan Context (integration)', () => {
}
render(
-
+
-
+
)
await waitFor(() => {
@@ -282,7 +282,7 @@ describeIntegration('mizan Context (integration)', () => {
let error: any = null
function TestComponent() {
- const { call } = useDjango()
+ const { call } = useMizan()
React.useEffect(() => {
call('login.validate', {
@@ -296,9 +296,9 @@ describeIntegration('mizan Context (integration)', () => {
}
render(
-
+
-
+
)
await waitFor(() => {
diff --git a/packages/mizan-react/src/__tests__/errors.test.ts b/packages/mizan-react/src/__tests__/errors.test.ts
index fdad8ad..dedfe9a 100644
--- a/packages/mizan-react/src/__tests__/errors.test.ts
+++ b/packages/mizan-react/src/__tests__/errors.test.ts
@@ -2,9 +2,9 @@
* Tests for Django Server Error
*/
-import { DjangoError, type FunctionErrorResponse } from '../errors'
+import { MizanError, type FunctionErrorResponse } from '../errors'
-describe('DjangoError', () => {
+describe('MizanError', () => {
it('should create error with message and code', () => {
const response: FunctionErrorResponse = {
error: true,
@@ -12,11 +12,11 @@ describe('DjangoError', () => {
message: 'Function not found',
}
- const error = new DjangoError(response)
+ const error = new MizanError(response)
expect(error.message).toBe('Function not found')
expect(error.code).toBe('NOT_FOUND')
- expect(error.name).toBe('DjangoError')
+ expect(error.name).toBe('MizanError')
})
it('should preserve details', () => {
@@ -32,7 +32,7 @@ describe('DjangoError', () => {
},
}
- const error = new DjangoError(response)
+ const error = new MizanError(response)
expect(error.details).toBeDefined()
expect(error.details?.fields?.name).toEqual(['Required', 'Too short'])
@@ -45,14 +45,14 @@ describe('DjangoError', () => {
message: 'Server error',
}
- const error = new DjangoError(response)
+ const error = new MizanError(response)
expect(error.response).toBe(response)
})
describe('isValidationError', () => {
it('should return true for validation errors', () => {
- const error = new DjangoError({
+ const error = new MizanError({
error: true,
code: 'VALIDATION_ERROR',
message: 'Invalid',
@@ -62,7 +62,7 @@ describe('DjangoError', () => {
})
it('should return false for other errors', () => {
- const error = new DjangoError({
+ const error = new MizanError({
error: true,
code: 'NOT_FOUND',
message: 'Not found',
@@ -74,7 +74,7 @@ describe('DjangoError', () => {
describe('isAuthError', () => {
it('should return true for unauthorized', () => {
- const error = new DjangoError({
+ const error = new MizanError({
error: true,
code: 'UNAUTHORIZED',
message: 'Not authenticated',
@@ -84,7 +84,7 @@ describe('DjangoError', () => {
})
it('should return true for forbidden', () => {
- const error = new DjangoError({
+ const error = new MizanError({
error: true,
code: 'FORBIDDEN',
message: 'Access denied',
@@ -94,7 +94,7 @@ describe('DjangoError', () => {
})
it('should return false for other errors', () => {
- const error = new DjangoError({
+ const error = new MizanError({
error: true,
code: 'NOT_FOUND',
message: 'Not found',
@@ -106,7 +106,7 @@ describe('DjangoError', () => {
describe('isNotFound', () => {
it('should return true for not found errors', () => {
- const error = new DjangoError({
+ const error = new MizanError({
error: true,
code: 'NOT_FOUND',
message: 'Not found',
@@ -116,7 +116,7 @@ describe('DjangoError', () => {
})
it('should return false for other errors', () => {
- const error = new DjangoError({
+ const error = new MizanError({
error: true,
code: 'VALIDATION_ERROR',
message: 'Invalid',
@@ -128,7 +128,7 @@ describe('DjangoError', () => {
describe('getFieldErrors', () => {
it('should return field errors for validation error', () => {
- const error = new DjangoError({
+ const error = new MizanError({
error: true,
code: 'VALIDATION_ERROR',
message: 'Invalid input',
@@ -149,7 +149,7 @@ describe('DjangoError', () => {
})
it('should return null for non-validation errors', () => {
- const error = new DjangoError({
+ const error = new MizanError({
error: true,
code: 'NOT_FOUND',
message: 'Not found',
@@ -159,7 +159,7 @@ describe('DjangoError', () => {
})
it('should return null if no fields in details', () => {
- const error = new DjangoError({
+ const error = new MizanError({
error: true,
code: 'VALIDATION_ERROR',
message: 'Invalid',
@@ -172,7 +172,7 @@ describe('DjangoError', () => {
describe('getFieldError', () => {
it('should return first error for a field', () => {
- const error = new DjangoError({
+ const error = new MizanError({
error: true,
code: 'VALIDATION_ERROR',
message: 'Invalid input',
@@ -187,7 +187,7 @@ describe('DjangoError', () => {
})
it('should return null for non-existent field', () => {
- const error = new DjangoError({
+ const error = new MizanError({
error: true,
code: 'VALIDATION_ERROR',
message: 'Invalid input',
@@ -202,7 +202,7 @@ describe('DjangoError', () => {
})
it('should return null for non-validation errors', () => {
- const error = new DjangoError({
+ const error = new MizanError({
error: true,
code: 'NOT_FOUND',
message: 'Not found',
diff --git a/packages/mizan-react/src/__tests__/integration.test.tsx b/packages/mizan-react/src/__tests__/integration.test.tsx
index 98ba739..2d0ab5a 100644
--- a/packages/mizan-react/src/__tests__/integration.test.tsx
+++ b/packages/mizan-react/src/__tests__/integration.test.tsx
@@ -11,7 +11,7 @@ import { renderHook, act } from '@testing-library/react'
import { ReactNode } from 'react'
import { describeIntegration, BACKEND_URL, WS_URL } from '../testing'
import { MizanProvider, useMizan } from '../context'
-import { DjangoError } from '../errors'
+import { MizanError } from '../errors'
import { ChannelConnection } from '../channels/connection'
import { RPCError } from '../channels/connection'
@@ -47,16 +47,16 @@ describeIntegration('Executor framework validation', () => {
it('should return VALIDATION_ERROR with field details for wrong input types', async () => {
const { result } = renderHook(() => useCall(), { wrapper: Wrapper })
- let error: DjangoError | null = null
+ let error: MizanError | null = null
await act(async () => {
try {
await result.current('add', { a: 'hello', b: 'world' })
} catch (e) {
- error = e as DjangoError
+ error = e as MizanError
}
})
- expect(error).toBeInstanceOf(DjangoError)
+ expect(error).toBeInstanceOf(MizanError)
expect(error!.code).toBe('VALIDATION_ERROR')
expect(error!.isValidationError()).toBe(true)
const fieldErrors = error!.getFieldErrors()
@@ -67,48 +67,48 @@ describeIntegration('Executor framework validation', () => {
it('should return NOT_FOUND for non-existent function', async () => {
const { result } = renderHook(() => useCall(), { wrapper: Wrapper })
- let error: DjangoError | null = null
+ let error: MizanError | null = null
await act(async () => {
try {
await result.current('this_function_does_not_exist', {})
} catch (e) {
- error = e as DjangoError
+ error = e as MizanError
}
})
- expect(error).toBeInstanceOf(DjangoError)
+ expect(error).toBeInstanceOf(MizanError)
expect(error!.code).toBe('NOT_FOUND')
})
it('should return FORBIDDEN for auth-required function when anonymous', async () => {
const { result } = renderHook(() => useCall(), { wrapper: Wrapper })
- let error: DjangoError | null = null
+ let error: MizanError | null = null
await act(async () => {
try {
await result.current('whoami', {})
} catch (e) {
- error = e as DjangoError
+ error = e as MizanError
}
})
- expect(error).toBeInstanceOf(DjangoError)
+ expect(error).toBeInstanceOf(MizanError)
expect(error!.isAuthError()).toBe(true)
})
it('should return VALIDATION_ERROR with specific field for missing required input', async () => {
const { result } = renderHook(() => useCall(), { wrapper: Wrapper })
- let error: DjangoError | null = null
+ let error: MizanError | null = null
await act(async () => {
try {
await result.current('echo', {})
} catch (e) {
- error = e as DjangoError
+ error = e as MizanError
}
})
- expect(error).toBeInstanceOf(DjangoError)
+ expect(error).toBeInstanceOf(MizanError)
expect(error!.code).toBe('VALIDATION_ERROR')
const fieldErrors = error!.getFieldErrors()
expect(fieldErrors).not.toBeNull()
@@ -357,16 +357,16 @@ describeIntegration('Auth variations', () => {
it('should reject staff_only for anonymous with UNAUTHORIZED', async () => {
const { result } = renderHook(() => useCall(), { wrapper: Wrapper })
- let error: DjangoError | null = null
+ let error: MizanError | null = null
await act(async () => {
try {
await result.current('staff_only', {})
} catch (e) {
- error = e as DjangoError
+ error = e as MizanError
}
})
- expect(error).toBeInstanceOf(DjangoError)
+ expect(error).toBeInstanceOf(MizanError)
expect(error!.code).toBe('UNAUTHORIZED')
expect(error!.isAuthError()).toBe(true)
})
@@ -374,16 +374,16 @@ describeIntegration('Auth variations', () => {
it('should reject superuser_only for anonymous with UNAUTHORIZED', async () => {
const { result } = renderHook(() => useCall(), { wrapper: Wrapper })
- let error: DjangoError | null = null
+ let error: MizanError | null = null
await act(async () => {
try {
await result.current('superuser_only', {})
} catch (e) {
- error = e as DjangoError
+ error = e as MizanError
}
})
- expect(error).toBeInstanceOf(DjangoError)
+ expect(error).toBeInstanceOf(MizanError)
expect(error!.code).toBe('UNAUTHORIZED')
expect(error!.isAuthError()).toBe(true)
})
@@ -391,17 +391,17 @@ describeIntegration('Auth variations', () => {
it('should reject verified_only for anonymous (callable auth)', async () => {
const { result } = renderHook(() => useCall(), { wrapper: Wrapper })
- let error: DjangoError | null = null
+ let error: MizanError | null = null
await act(async () => {
try {
await result.current('verified_only', {})
} catch (e) {
- error = e as DjangoError
+ error = e as MizanError
}
})
// Callable auth returns False for anonymous, which maps to FORBIDDEN
- expect(error).toBeInstanceOf(DjangoError)
+ expect(error).toBeInstanceOf(MizanError)
expect(error!.code).toBe('FORBIDDEN')
expect(error!.isAuthError()).toBe(true)
})
@@ -445,48 +445,48 @@ describeIntegration('Error code coverage', () => {
it('should return NOT_IMPLEMENTED for NotImplementedError', async () => {
const { result } = renderHook(() => useCall(), { wrapper: Wrapper })
- let error: DjangoError | null = null
+ let error: MizanError | null = null
await act(async () => {
try {
await result.current('not_implemented_fn', {})
} catch (e) {
- error = e as DjangoError
+ error = e as MizanError
}
})
- expect(error).toBeInstanceOf(DjangoError)
+ expect(error).toBeInstanceOf(MizanError)
expect(error!.code).toBe('NOT_IMPLEMENTED')
})
it('should return INTERNAL_ERROR for unhandled RuntimeError', async () => {
const { result } = renderHook(() => useCall(), { wrapper: Wrapper })
- let error: DjangoError | null = null
+ let error: MizanError | null = null
await act(async () => {
try {
await result.current('buggy_fn', {})
} catch (e) {
- error = e as DjangoError
+ error = e as MizanError
}
})
- expect(error).toBeInstanceOf(DjangoError)
+ expect(error).toBeInstanceOf(MizanError)
expect(error!.code).toBe('INTERNAL_ERROR')
})
it('should return FORBIDDEN for PermissionError with wrong secret', async () => {
const { result } = renderHook(() => useCall(), { wrapper: Wrapper })
- let error: DjangoError | null = null
+ let error: MizanError | null = null
await act(async () => {
try {
await result.current('permission_check_fn', { secret: 'wrong' })
} catch (e) {
- error = e as DjangoError
+ error = e as MizanError
}
})
- expect(error).toBeInstanceOf(DjangoError)
+ expect(error).toBeInstanceOf(MizanError)
expect(error!.code).toBe('FORBIDDEN')
expect(error!.isAuthError()).toBe(true)
})
diff --git a/packages/mizan-react/src/channels/hooks.ts b/packages/mizan-react/src/channels/hooks.ts
index 1cc4c68..4941359 100644
--- a/packages/mizan-react/src/channels/hooks.ts
+++ b/packages/mizan-react/src/channels/hooks.ts
@@ -15,12 +15,12 @@ import type {
IncomingPayload,
} from './types'
-export interface UseChannelOptions {
+export interface UseChannelOptions {
/** Called when subscribed successfully */
onSubscribed?: () => void
/** Called when a message is received */
- onMessage?: (message: TDjangoMessage) => void
+ onMessage?: (message: TServerMessage) => void
/** Called on error */
onError?: (error: string) => void
@@ -41,16 +41,16 @@ export interface UseChannelOptions {
*/
export function useChannel<
TParams = undefined,
- TDjangoMessage = unknown,
+ TServerMessage = unknown,
TReactMessage = unknown,
>(
channelName: string,
params?: TParams,
- options: UseChannelOptions = {},
-): ChannelSubscription {
+ options: UseChannelOptions = {},
+): ChannelSubscription {
const { connection, status: connectionStatus } = useChannelContext()
- const [messages, setMessages] = useState([])
+ const [messages, setMessages] = useState([])
const [subscribed, setSubscribed] = useState(false)
const optionsRef = useRef(options)
@@ -104,7 +104,7 @@ export function useChannel<
// Handle data messages
if ('type' in payload && 'data' in payload) {
- const message = payload.data as TDjangoMessage
+ const message = payload.data as TServerMessage
setMessages(prev => {
const next = [...prev, message]
// Trim to max messages
@@ -166,7 +166,7 @@ export function useChannel<
return {
status,
messages,
- send: send as ChannelSubscription['send'],
+ send: send as ChannelSubscription['send'],
unsubscribe,
clearMessages,
}
@@ -177,16 +177,16 @@ export function useChannel<
*/
export function useChannelLatest<
TParams = undefined,
- TDjangoMessage = unknown,
+ TServerMessage = unknown,
TReactMessage = unknown,
>(
channelName: string,
params?: TParams,
- options: UseChannelOptions = {},
-): Omit, 'messages'> & { latest: TDjangoMessage | null } {
- const [latest, setLatest] = useState(null)
+ options: UseChannelOptions = {},
+): Omit, 'messages'> & { latest: TServerMessage | null } {
+ const [latest, setLatest] = useState(null)
- const channel = useChannel(
+ const channel = useChannel(
channelName,
params,
{
diff --git a/packages/mizan-react/src/channels/types.ts b/packages/mizan-react/src/channels/types.ts
index ade0769..0522297 100644
--- a/packages/mizan-react/src/channels/types.ts
+++ b/packages/mizan-react/src/channels/types.ts
@@ -4,12 +4,12 @@
export type ConnectionStatus = 'connecting' | 'connected' | 'disconnected'
-export interface ChannelSubscription {
+export interface ChannelSubscription {
/** Current connection status */
status: ConnectionStatus
/** Received messages */
- messages: TDjangoMessage[]
+ messages: TServerMessage[]
/** Send a message (if channel accepts ReactMessage) */
send: TReactMessage extends never ? never : (message: TReactMessage) => void
diff --git a/packages/mizan-react/src/client/index.ts b/packages/mizan-react/src/client/index.ts
index d40f60f..972ea1d 100644
--- a/packages/mizan-react/src/client/index.ts
+++ b/packages/mizan-react/src/client/index.ts
@@ -8,22 +8,22 @@
*
* ### Client-Side (CSR)
* ```ts
- * import { createDjangoCSRClient, Auth } from 'mizan/client'
+ * import { createMizanCSRClient, Auth } from 'mizan/client'
*
* // Session-based (cookies + CSRF)
- * const client = createDjangoCSRClient(Auth.SESSION)
+ * const client = createMizanCSRClient(Auth.SESSION)
*
* // JWT-based (Bearer token)
- * const client = createDjangoCSRClient(Auth.JWT, { getAccessToken })
+ * const client = createMizanCSRClient(Auth.JWT, { getAccessToken })
*
* const user = await client.json('GET', '/api/accounts/me/')
* ```
*
* ### Server-Side (SSR)
* ```ts
- * import { createDjangoSSRClient } from 'mizan/client'
+ * import { createMizanSSRClient } from 'mizan/client'
*
- * const client = createDjangoSSRClient({
+ * const client = createMizanSSRClient({
* cookies: await cookies() // Next.js cookies()
* })
*
@@ -76,7 +76,7 @@ export type SSRCookies = CookieGetter | {
/**
* The core HTTP client interface for Django requests.
*/
-export interface DjangoHTTPClient {
+export interface MizanHTTPClient {
/**
* Make an HTTP request, returning the raw Response.
*/
@@ -148,7 +148,7 @@ export class HttpError extends Error {
// Internal Utilities
// =============================================================================
-import { getCSRFToken } from '../utils'
+import { getCSRFToken, getCsrfHeaderName, getCsrfCookieName } from '../utils'
interface RequestBuild {
request: RequestInit
@@ -218,24 +218,24 @@ async function buildHttpError(resp: Response, url: URL | string): Promise localStorage.getItem('token')
* })
*/
-export function createDjangoCSRClient(auth: Auth.SESSION, config?: CSRClientConfig): DjangoHTTPClient
-export function createDjangoCSRClient(auth: Auth.JWT, config: JWTClientConfig): DjangoHTTPClient
-export function createDjangoCSRClient(
+export function createMizanCSRClient(auth: Auth.SESSION, config?: CSRClientConfig): MizanHTTPClient
+export function createMizanCSRClient(auth: Auth.JWT, config: JWTClientConfig): MizanHTTPClient
+export function createMizanCSRClient(
auth: Auth,
config?: CSRClientConfig | JWTClientConfig
-): DjangoHTTPClient {
+): MizanHTTPClient {
if (!config?.baseUrl) {
throw new Error(
'baseUrl is required. Pass it via config or use MizanProvider which provides it automatically.'
@@ -254,7 +254,7 @@ export function createDjangoCSRClient(
return {}
}
// Session auth uses CSRF
- return { 'X-CSRFToken': getCSRFToken() ?? '' }
+ return { [getCsrfHeaderName()]: getCSRFToken() ?? '' }
}
function resolveUrl(path: string): string {
@@ -318,7 +318,7 @@ function isCookieGetter(cookies: SSRCookies): cookies is CookieGetter {
function extractCookies(cookies: SSRCookies): { csrf: string; cookieHeader: string } {
if (isCookieGetter(cookies)) {
return {
- csrf: cookies.get('csrftoken')?.value ?? '',
+ csrf: cookies.get(getCsrfCookieName())?.value ?? '',
cookieHeader: cookies.getAll().map(c => `${c.name}=${c.value}`).join('; ')
}
}
@@ -330,13 +330,13 @@ function extractCookies(cookies: SSRCookies): { csrf: string; cookieHeader: stri
* Used in SSR contexts (Next.js server components, server actions, etc.)
*
* @param config - SSR client configuration with cookies
- * @returns DjangoHTTPClient
+ * @returns MizanHTTPClient
*
* @example
* // Next.js server component
* import { cookies } from 'next/headers'
*
- * const client = createDjangoSSRClient({ cookies: await cookies() })
+ * const client = createMizanSSRClient({ cookies: await cookies() })
*/
// Re-export auth types for non-React usage
export type {
@@ -348,10 +348,7 @@ export type {
JWTState,
} from './types'
-// Re-export RouterAdapter for libraries that extend it
-export type { RouterAdapter } from './RouterContext'
-
-export function createDjangoSSRClient(config: SSRClientConfig): DjangoHTTPClient {
+export function createMizanSSRClient(config: SSRClientConfig): MizanHTTPClient {
const baseUrl = getInternalBackendUrl(config.baseUrl)
const { csrf, cookieHeader } = extractCookies(config.cookies)
@@ -361,7 +358,7 @@ export function createDjangoSSRClient(config: SSRClientConfig): DjangoHTTPClient
const requestHeaders: Record = {
'Accept': 'application/json',
- 'X-CSRFToken': csrf,
+ [getCsrfHeaderName()]: csrf,
'Cookie': cookieHeader,
...headers,
}
@@ -390,7 +387,7 @@ export function createDjangoSSRClient(config: SSRClientConfig): DjangoHTTPClient
const requestHeaders: Record = {
'Accept': 'application/json',
- 'X-CSRFToken': csrf,
+ [getCsrfHeaderName()]: csrf,
'Cookie': cookieHeader,
...headers,
}
@@ -454,12 +451,12 @@ interface SessionInitResponse {
* @example
* // In layout.tsx
* const cookieStore = await cookies()
- * const session = await ensureDjangoSession({ cookies: cookieStore })
- * const client = createDjangoSSRClient({
+ * const session = await ensureMizanSession({ cookies: cookieStore })
+ * const client = createMizanSSRClient({
* cookies: { csrf: session.csrf, cookieHeader: session.cookieHeader }
* })
*/
-export async function ensureDjangoSession(config: SSRClientConfig): Promise<{
+export async function ensureMizanSession(config: SSRClientConfig): Promise<{
csrf: string
cookieHeader: string
}> {
@@ -510,7 +507,7 @@ export async function ensureDjangoSession(config: SSRClientConfig): Promise<{
// Re-export error types from the canonical location
export type { FunctionErrorResponse } from '../errors'
-import { DjangoError, type FunctionErrorResponse } from '../errors'
+import { MizanError, type FunctionErrorResponse } from '../errors'
/**
* Success response from a server function
diff --git a/packages/mizan-react/src/client/react.ts b/packages/mizan-react/src/client/react.ts
index 77b2bc0..6397da4 100644
--- a/packages/mizan-react/src/client/react.ts
+++ b/packages/mizan-react/src/client/react.ts
@@ -3,9 +3,9 @@
import { useMemo } from 'react'
import { useJWT } from '../jwt/JWTContext'
import {
- createDjangoCSRClient,
+ createMizanCSRClient,
Auth,
- type DjangoHTTPClient,
+ type MizanHTTPClient,
type CSRClientConfig,
} from './index'
@@ -22,19 +22,19 @@ export type * from './types'
*
* @param auth - Authentication strategy (Auth.SESSION or Auth.JWT)
* @param config - Optional client configuration
- * @returns DjangoHTTPClient
+ * @returns MizanHTTPClient
*
* @example
* // Session-based
- * const client = useDjangoCSRClient(Auth.SESSION)
+ * const client = useMizanCSRClient(Auth.SESSION)
* const user = await client.json('GET', '/api/accounts/me/')
*
* @example
* // JWT-based (requires JWTContext from mizan/jwt)
- * const client = useDjangoCSRClient(Auth.JWT)
+ * const client = useMizanCSRClient(Auth.JWT)
* const user = await client.json('GET', '/api/accounts/me/')
*/
-export function useDjangoCSRClient(auth: Auth, config?: CSRClientConfig): DjangoHTTPClient {
+export function useMizanCSRClient(auth: Auth, config?: CSRClientConfig): MizanHTTPClient {
// Always call useJWT (React hooks must be unconditional)
// Returns null when outside JWTContext
const jwtContext = useJWT()
@@ -43,16 +43,16 @@ export function useDjangoCSRClient(auth: Auth, config?: CSRClientConfig): Django
if (auth === Auth.JWT) {
if (!jwtContext?.getAccessToken) {
throw new Error(
- 'useDjangoCSRClient(Auth.JWT) requires JWTContext from mizan/jwt. ' +
+ 'useMizanCSRClient(Auth.JWT) requires JWTContext from mizan/jwt. ' +
'Wrap your component in JWTContext to use JWT authentication.'
)
}
- return createDjangoCSRClient(Auth.JWT, {
+ return createMizanCSRClient(Auth.JWT, {
...config,
getAccessToken: jwtContext.getAccessToken,
})
}
- return createDjangoCSRClient(Auth.SESSION, config)
+ return createMizanCSRClient(Auth.SESSION, config)
}, [auth, config, jwtContext?.getAccessToken])
}
diff --git a/packages/mizan-react/src/context.tsx b/packages/mizan-react/src/context.tsx
index 103b492..af19cb6 100644
--- a/packages/mizan-react/src/context.tsx
+++ b/packages/mizan-react/src/context.tsx
@@ -33,12 +33,12 @@ import {
} from 'react'
import { ChannelConnection, RPCError } from 'mizan/channels'
import {
- createDjangoCSRClient,
+ createMizanCSRClient,
Auth,
type FunctionResponse,
} from 'mizan/client'
import { useJWT } from './jwt'
-import { DjangoError, type ErrorCode, type FunctionErrorResponse } from './errors'
+import { MizanError, type ErrorCode, type FunctionErrorResponse } from './errors'
import { getCSRFToken } from './utils'
@@ -271,12 +271,12 @@ export function MizanProvider({
// Create HTTP client with appropriate auth method
const httpClient = useMemo(() => {
if (jwt?.getAccessToken) {
- return createDjangoCSRClient(Auth.JWT, {
+ return createMizanCSRClient(Auth.JWT, {
baseUrl,
getAccessToken: jwt.getAccessToken,
})
}
- return createDjangoCSRClient(Auth.SESSION, { baseUrl })
+ return createMizanCSRClient(Auth.SESSION, { baseUrl })
}, [hasJWT, jwt?.getAccessToken, baseUrl])
// Create or use provided connection
@@ -308,9 +308,9 @@ export function MizanProvider({
try {
return await connection.rpc(functionName, input as TInput)
} catch (e) {
- // If it's an RPC error (function error), re-throw as DjangoError
+ // If it's an RPC error (function error), re-throw as MizanError
if (e instanceof RPCError) {
- throw new DjangoError({
+ throw new MizanError({
error: true,
code: e.code as ErrorCode,
message: e.message,
@@ -338,7 +338,7 @@ export function MizanProvider({
const data = await response.json()
if (data.error) {
- throw new DjangoError(data as FunctionErrorResponse)
+ throw new MizanError(data as FunctionErrorResponse)
}
// Server-driven invalidation: process the invalidate array
diff --git a/packages/mizan-react/src/errors.ts b/packages/mizan-react/src/errors.ts
index 261e918..72933d4 100644
--- a/packages/mizan-react/src/errors.ts
+++ b/packages/mizan-react/src/errors.ts
@@ -1,5 +1,5 @@
/**
- * Django Server Error Types
+ * Mizan Server Error Types
*
* Typed errors for server function failures.
*/
@@ -34,7 +34,7 @@ export interface FunctionErrorResponse {
/**
* Error thrown when a server function call fails
*/
-export class DjangoError extends Error {
+export class MizanError extends Error {
/**
* Error code from the server
*/
@@ -52,14 +52,14 @@ export class DjangoError extends Error {
constructor(response: FunctionErrorResponse) {
super(response.message)
- this.name = 'DjangoError'
+ this.name = 'MizanError'
this.code = response.code
this.details = response.details
this.response = response
// Maintains proper stack trace for where error was thrown
if (Error.captureStackTrace) {
- Error.captureStackTrace(this, DjangoError)
+ Error.captureStackTrace(this, MizanError)
}
}
diff --git a/packages/mizan-react/src/forms.ts b/packages/mizan-react/src/forms.ts
index a532763..6af940a 100644
--- a/packages/mizan-react/src/forms.ts
+++ b/packages/mizan-react/src/forms.ts
@@ -24,7 +24,7 @@ import {
} from 'react'
import type { ZodObject, ZodRawShape, ZodError } from 'zod'
import { useMizan } from './context'
-import { DjangoError } from './errors'
+import { MizanError } from './errors'
// Forms always use HTTP transport because Django Allauth and other auth
// systems require full HTTP request semantics (session, cookies, CSRF).
@@ -156,7 +156,7 @@ export type FormsetSubmitResult = Record> {
+export interface MizanFormState> {
/** Current form data - typed by TData */
data: TData
@@ -354,7 +354,7 @@ export interface FormsetErrors> {
/**
* Typed formset state returned by formset hooks.
*/
-export interface DjangoFormsetState> {
+export interface MizanFormsetState> {
/** Array of form data objects - each typed by TData */
forms: TData[]
@@ -449,7 +449,7 @@ export interface FormCoreConfig> {
*/
export function useMizanFormCore>(
config: FormCoreConfig
-): DjangoFormState {
+): MizanFormState {
const { name, zodSchema, options = {} } = config
const {
liveValidation: liveValidationOption = 'field-only',
@@ -768,8 +768,8 @@ export function useMizanFormCore>(
return { success: false, errors: typedErrors }
}
} catch (err) {
- // Handle DjangoError with validation details
- if (err instanceof DjangoError && err.isValidationError()) {
+ // Handle MizanError with validation details
+ if (err instanceof MizanError && err.isValidationError()) {
const rawFieldErrors = err.getFieldErrors()
const fields = {} as { [K in keyof TData]?: FieldError[] }
@@ -894,7 +894,7 @@ export interface FormsetCoreConfig> {
*/
export function useMizanFormsetCore>(
config: FormsetCoreConfig
-): DjangoFormsetState {
+): MizanFormsetState {
const {
name,
initialCount = 1,
diff --git a/packages/mizan-react/src/index.ts b/packages/mizan-react/src/index.ts
index 7620227..22d39fa 100644
--- a/packages/mizan-react/src/index.ts
+++ b/packages/mizan-react/src/index.ts
@@ -1,26 +1,16 @@
/**
- * mizan - Django Server Functions Client
+ * mizan — Server Functions Client
*
- * Frontend client for Django server functions.
- * Server functions are the core primitive - accessed via React hooks.
+ * Frontend client for Mizan server functions.
+ * Server functions are the core primitive — accessed via React hooks.
*
* Two-layer architecture:
*
- * 1. Library layer (this package) - Generic name-based API
- * Used by libraries like Allauth that need to call functions by name.
- *
+ * 1. Library layer (this package) — Generic name-based API
* 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.
*
+ * 2. Generated layer (@/api) — Typed project-specific API
* import { useCurrentUser, useUpdateProfile } from '@/api'
- * const user = useCurrentUser()
- * const updateProfile = useUpdateProfile()
- *
- * The generated code wraps MizanProvider and adds type-safe hooks.
*/
// ============================================================================
@@ -33,7 +23,7 @@ export {
type MizanProviderProps,
type MizanHydration,
- // Hooks (generic name-based API for libraries)
+ // Hooks
useMizan,
useMizanContext,
useMizanCall,
@@ -47,26 +37,18 @@ export {
type PushListener,
type ContextStore,
type Transport,
-
- // Legacy aliases (deprecated, for migration)
- DjangoContext,
- useDjango,
- useDjangoStatus,
- useServerFunction,
- type DjangoContextValue,
- type DjangoContextProps,
} from './context'
// ============================================================================
-// HTTP Client (for SSR or non-React usage)
+// HTTP Client
// ============================================================================
export {
- createDjangoCSRClient,
- createDjangoSSRClient,
- ensureDjangoSession,
+ createMizanCSRClient,
+ createMizanSSRClient,
+ ensureMizanSession,
Auth,
- type DjangoHTTPClient,
+ type MizanHTTPClient,
type CSRClientConfig,
type JWTClientConfig,
type SSRClientConfig,
@@ -77,38 +59,68 @@ export {
// ============================================================================
export {
- DjangoError,
+ MizanError,
type FunctionErrorResponse,
type ErrorCode,
} from './errors'
// ============================================================================
-// Forms (typed form hooks core)
+// Forms
// ============================================================================
export {
- // Single form
useMizanFormCore,
- // Legacy alias
- useMizanFormCore as useDjangoFormCore,
- type DjangoFormState,
+ type MizanFormState,
type FormSchema,
type FormErrors,
type FormOptions,
type FormSubmitResult,
type FormCoreConfig,
- // Formset
useMizanFormsetCore,
- // Legacy alias
- useMizanFormsetCore as useDjangoFormsetCore,
- type DjangoFormsetState,
+ type MizanFormsetState,
type FormsetSchema,
type FormsetErrors,
type FormsetCoreConfig,
type FormsetSubmitResult,
- // Shared types
type FieldSchema,
type FieldChoice,
type FieldError,
type FormMeta,
} from './forms'
+
+// ============================================================================
+// Configuration
+// ============================================================================
+
+export { configureCsrf } from './utils'
+
+// ============================================================================
+// Legacy aliases (deprecated)
+// ============================================================================
+
+export {
+ // Provider aliases
+ DjangoContext,
+ useDjango,
+ useDjangoStatus,
+ useServerFunction,
+ type DjangoContextValue,
+ type DjangoContextProps,
+} from './context'
+
+export {
+ // Client aliases
+ createMizanCSRClient as createDjangoCSRClient,
+ createMizanSSRClient as createDjangoSSRClient,
+ ensureMizanSession as ensureDjangoSession,
+ type MizanHTTPClient as DjangoHTTPClient,
+} from './client/'
+
+export { MizanError as DjangoError } from './errors'
+
+export {
+ useMizanFormCore as useDjangoFormCore,
+ type MizanFormState as DjangoFormState,
+ useMizanFormsetCore as useDjangoFormsetCore,
+ type MizanFormsetState as DjangoFormsetState,
+} from './forms'
diff --git a/packages/mizan-react/src/utils.ts b/packages/mizan-react/src/utils.ts
index bb6c566..bce9ca2 100644
--- a/packages/mizan-react/src/utils.ts
+++ b/packages/mizan-react/src/utils.ts
@@ -2,9 +2,28 @@
* Shared utilities used across mizan-react.
*/
+/** Default CSRF cookie name. Configurable via MizanProvider. */
+let _csrfCookieName = 'csrftoken'
+
+/** Default CSRF header name. Configurable via MizanProvider. */
+let _csrfHeaderName = 'X-CSRFToken'
+
+export function configureCsrf(cookieName: string, headerName: string): void {
+ _csrfCookieName = cookieName
+ _csrfHeaderName = headerName
+}
+
+export function getCsrfCookieName(): string {
+ return _csrfCookieName
+}
+
+export function getCsrfHeaderName(): string {
+ return _csrfHeaderName
+}
+
/** Extract CSRF token from cookies. Returns null during SSR. */
export function getCSRFToken(): string | null {
if (typeof document === 'undefined') return null
- const match = document.cookie.match(/csrftoken=([^;]+)/)
+ const match = document.cookie.match(new RegExp(`${_csrfCookieName}=([^;]+)`))
return match?.[1] ?? null
}