A channel's message slots are named from the client, on every backend
The IR called them react-message and django-message, so a FastAPI channel had to declare a DjangoMessage. They are client-message and server-message now, and the direction words hold wherever a channel is declared: Params / ClientMessage / ServerMessage, with mizan-core deriving <Pascal>Params and friends so no backend names a type itself. Django's ReactChannel and FastAPI's ReactChannel are both Channel. mizan-fastapi never registered a channels extension, so build_ir() emitted no channel at all and every payload type was invisible to codegen. It registers one now. RegistryExtension is an ABC requiring all(), which is what the IR reads — an extension that cannot enumerate its registrations no longer exists. The gate that should have caught the rename could not: tests/afi registered no channel because mizan-rust had no channel registry to register one in, so a five-package rename of the wire contract passed byte-parity without a channel byte crossing it. mizan-rust grows ChannelSlotKind, a CHANNELS slice, a #[mizan::channel] macro, and KDL emission whose wire_to_pascal matches Python's split; the AFI fixture now carries a channel with every slot and one with a single slot, so all three backends prove the contract byte for byte. MizanChannel held three Option<String> beside three has_*() predicates and unwrapped them with defaults; it holds an ordered slot vector, so an absent slot is absent rather than defaulted. The channels target emitted a React hooks file that a stage1-only consumer could not compile — react emits that now. The codegen's parity tests byte-compared emitted source against baselines without ever compiling it: they compile the generated crate and run its tests, import the generated Python package and call every method, and typecheck each TypeScript target against a consumer. Also fixed at source: app_visitor printed its import diagnostic to stdout, the stream export_mizan_ir writes KDL to, so a failed import silently corrupted the IR; the apps root was hardcoded to "apps"; _default_literal crashed build_ir on any non-JSON-serializable field default; Django and mizan-core derived Pascal names two different ways, disagreeing on every dotted channel name. ir.py builds a document and renders templates/ir/document.kdl.j2 rather than appending KDL strings with hand-tracked indentation, and named types resolve to a fixed point — a model reachable only through a union branch was referenced by a ref that no type block ever defined. The rest is the write-gate's own classifiers run over the standing tree: relative imports, silent swallows, Protocol contracts that should be ABCs, emitters hand-rendering target source, catch-all arms over closed enums, and comments narrating the project rather than the code. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1
examples/django-react-site/harness/src/api/.gitattributes
generated
vendored
Normal file
1
examples/django-react-site/harness/src/api/.gitattributes
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
* linguist-generated=true
|
||||
@@ -1,40 +1,22 @@
|
||||
'use client'
|
||||
|
||||
// AUTO-GENERATED by mizan - do not edit manually
|
||||
// Regenerate with: npm run schemas
|
||||
|
||||
import { useChannel, type ChannelSubscription } from 'mizan/channels'
|
||||
|
||||
import type { ChatParams, ChatReactMessage, ChatDjangoMessage, NotificationsDjangoMessage, PresenceDjangoMessage, PrivateDjangoMessage } from './generated.channels'
|
||||
import type { ChatParams, ChatClientMessage, ChatServerMessage, NotificationsServerMessage, PresenceServerMessage, PrivateServerMessage } from './channels'
|
||||
|
||||
// ============================================================================
|
||||
// Channel Hooks
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Hook for the chat channel.
|
||||
*/
|
||||
export function useChatChannel(params: ChatParams): ChannelSubscription<ChatParams, ChatDjangoMessage, ChatReactMessage> {
|
||||
return useChannel('chat', params)
|
||||
export function useChatChannel(params: ChatParams): ChannelSubscription<ChatParams, ChatServerMessage, ChatClientMessage> {
|
||||
return useChannel('chat', params)
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for the notifications channel.
|
||||
*/
|
||||
export function useNotificationsChannel(): ChannelSubscription<Record<string, never>, NotificationsDjangoMessage, never> {
|
||||
return useChannel('notifications', {})
|
||||
export function useNotificationsChannel(): ChannelSubscription<Record<string, never>, NotificationsServerMessage, never> {
|
||||
return useChannel('notifications', {})
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for the presence channel.
|
||||
*/
|
||||
export function usePresenceChannel(): ChannelSubscription<Record<string, never>, PresenceDjangoMessage, never> {
|
||||
return useChannel('presence', {})
|
||||
export function usePresenceChannel(): ChannelSubscription<Record<string, never>, PresenceServerMessage, never> {
|
||||
return useChannel('presence', {})
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for the private channel.
|
||||
*/
|
||||
export function usePrivateChannel(): ChannelSubscription<Record<string, never>, PrivateDjangoMessage, never> {
|
||||
return useChannel('private', {})
|
||||
export function usePrivateChannel(): ChannelSubscription<Record<string, never>, PrivateServerMessage, never> {
|
||||
return useChannel('private', {})
|
||||
}
|
||||
|
||||
|
||||
369
examples/django-react-site/harness/src/api/channels.ts
generated
369
examples/django-react-site/harness/src/api/channels.ts
generated
@@ -1,337 +1,48 @@
|
||||
// AUTO-GENERATED by mizan - do not edit manually
|
||||
// Regenerate with: npm run schemas
|
||||
|
||||
// ============================================================================
|
||||
// OpenAPI Types (generated by openapi-typescript)
|
||||
// ============================================================================
|
||||
|
||||
export interface paths {
|
||||
"/channels/chat/params": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/** Chat channel params */
|
||||
post: operations["chatParams"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/channels/chat/react": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/** Chat React→Django message */
|
||||
post: operations["chatReactMessage"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/channels/chat/django": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/** Chat Django→React message */
|
||||
post: operations["chatDjangoMessage"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/channels/notifications/django": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/** Notifications Django→React message */
|
||||
post: operations["notificationsDjangoMessage"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/channels/presence/django": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/** Presence Django→React message */
|
||||
post: operations["presenceDjangoMessage"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/channels/private/django": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/** Private Django→React message */
|
||||
post: operations["privateDjangoMessage"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
}
|
||||
export type webhooks = Record<string, never>;
|
||||
export interface components {
|
||||
schemas: {
|
||||
/** BaseModel */
|
||||
BaseModel: Record<string, never>;
|
||||
/** ChatParams */
|
||||
ChatParams: {
|
||||
/** Room */
|
||||
room: string;
|
||||
};
|
||||
/** ChatReactMessage */
|
||||
ChatReactMessage: {
|
||||
/** Text */
|
||||
text: string;
|
||||
};
|
||||
/** ChatDjangoMessage */
|
||||
ChatDjangoMessage: {
|
||||
/** Text */
|
||||
text: string;
|
||||
};
|
||||
/** NotificationsDjangoMessage */
|
||||
NotificationsDjangoMessage: {
|
||||
/** Text */
|
||||
text: string;
|
||||
};
|
||||
/** PresenceDjangoMessage */
|
||||
PresenceDjangoMessage: {
|
||||
/** Value */
|
||||
value: number;
|
||||
};
|
||||
/** PrivateDjangoMessage */
|
||||
PrivateDjangoMessage: {
|
||||
/** Text */
|
||||
text: string;
|
||||
};
|
||||
};
|
||||
responses: never;
|
||||
parameters: never;
|
||||
requestBodies: never;
|
||||
headers: never;
|
||||
pathItems: never;
|
||||
}
|
||||
export type $defs = Record<string, never>;
|
||||
export interface operations {
|
||||
chatParams: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["ChatParams"];
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description OK */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["BaseModel"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
chatReactMessage: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["ChatReactMessage"];
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description OK */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["BaseModel"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
chatDjangoMessage: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description OK */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["ChatDjangoMessage"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
notificationsDjangoMessage: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description OK */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["NotificationsDjangoMessage"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
presenceDjangoMessage: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description OK */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["PresenceDjangoMessage"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
privateDjangoMessage: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description OK */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["PrivateDjangoMessage"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
export interface ChatClientMessage {
|
||||
text: string
|
||||
}
|
||||
|
||||
export interface ChatParams {
|
||||
room: string
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Convenience Type Exports
|
||||
// ============================================================================
|
||||
export interface ChatServerMessage {
|
||||
text: string
|
||||
}
|
||||
|
||||
export type ChatParams = components["schemas"]["ChatParams"]
|
||||
export type ChatReactMessage = components["schemas"]["ChatReactMessage"]
|
||||
export type ChatDjangoMessage = components["schemas"]["ChatDjangoMessage"]
|
||||
export type NotificationsDjangoMessage = components["schemas"]["NotificationsDjangoMessage"]
|
||||
export type PresenceDjangoMessage = components["schemas"]["PresenceDjangoMessage"]
|
||||
export type PrivateDjangoMessage = components["schemas"]["PrivateDjangoMessage"]
|
||||
export interface NotificationsServerMessage {
|
||||
text: string
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Channel Registry
|
||||
// ============================================================================
|
||||
export interface PresenceServerMessage {
|
||||
value: number
|
||||
}
|
||||
|
||||
export interface PrivateServerMessage {
|
||||
text: string
|
||||
}
|
||||
|
||||
export const CHANNELS = {
|
||||
chat: {
|
||||
name: 'chat',
|
||||
pascalName: 'Chat',
|
||||
hasParams: true,
|
||||
hasReactMessage: true,
|
||||
hasDjangoMessage: true,
|
||||
paramsType: 'ChatParams',
|
||||
reactMessageType: 'ChatReactMessage',
|
||||
djangoMessageType: 'ChatDjangoMessage',
|
||||
},
|
||||
notifications: {
|
||||
name: 'notifications',
|
||||
pascalName: 'Notifications',
|
||||
hasParams: false,
|
||||
hasReactMessage: false,
|
||||
hasDjangoMessage: true,
|
||||
djangoMessageType: 'NotificationsDjangoMessage',
|
||||
},
|
||||
presence: {
|
||||
name: 'presence',
|
||||
pascalName: 'Presence',
|
||||
hasParams: false,
|
||||
hasReactMessage: false,
|
||||
hasDjangoMessage: true,
|
||||
djangoMessageType: 'PresenceDjangoMessage',
|
||||
},
|
||||
private: {
|
||||
name: 'private',
|
||||
pascalName: 'Private',
|
||||
hasParams: false,
|
||||
hasReactMessage: false,
|
||||
hasDjangoMessage: true,
|
||||
djangoMessageType: 'PrivateDjangoMessage',
|
||||
},
|
||||
'chat': {
|
||||
name: 'chat',
|
||||
pascalName: 'Chat',
|
||||
paramsType: 'ChatParams',
|
||||
clientMessageType: 'ChatClientMessage',
|
||||
serverMessageType: 'ChatServerMessage',
|
||||
},
|
||||
'notifications': {
|
||||
name: 'notifications',
|
||||
pascalName: 'Notifications',
|
||||
serverMessageType: 'NotificationsServerMessage',
|
||||
},
|
||||
'presence': {
|
||||
name: 'presence',
|
||||
pascalName: 'Presence',
|
||||
serverMessageType: 'PresenceServerMessage',
|
||||
},
|
||||
'private': {
|
||||
name: 'private',
|
||||
pascalName: 'Private',
|
||||
serverMessageType: 'PrivateServerMessage',
|
||||
},
|
||||
} as const
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// AUTO-GENERATED by mizan — do not edit
|
||||
|
||||
import { mizanFetch } from '@mizan/base'
|
||||
|
||||
import type { currentUserOutput } from '../types'
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// AUTO-GENERATED by mizan — do not edit
|
||||
|
||||
import { mizanFetch } from '@mizan/base'
|
||||
|
||||
import type { greetOutput } from '../types'
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// AUTO-GENERATED by mizan — do not edit
|
||||
|
||||
import { mizanCall } from '@mizan/base'
|
||||
|
||||
import type { addInput, addOutput } from '../types'
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
// AUTO-GENERATED by mizan — do not edit
|
||||
|
||||
import { mizanCall } from '@mizan/base'
|
||||
|
||||
import type { buggyFnOutput } from '../types'
|
||||
|
||||
export function callBuggyFn(): Promise<buggyFnOutput> {
|
||||
return mizanCall('buggy_fn', {})
|
||||
}
|
||||
7
examples/django-react-site/harness/src/api/functions/divide.ts
generated
Normal file
7
examples/django-react-site/harness/src/api/functions/divide.ts
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
import { mizanCall } from '@mizan/base'
|
||||
|
||||
import type { divideInput, divideOutput } from '../types'
|
||||
|
||||
export function callDivide(args: divideInput): Promise<divideOutput> {
|
||||
return mizanCall('divide', args)
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
// AUTO-GENERATED by mizan — do not edit
|
||||
|
||||
import { mizanCall } from '@mizan/base'
|
||||
|
||||
import type { echoInput, echoOutput } from '../types'
|
||||
|
||||
7
examples/django-react-site/harness/src/api/functions/echoTransform.ts
generated
Normal file
7
examples/django-react-site/harness/src/api/functions/echoTransform.ts
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
import { mizanCall } from '@mizan/base'
|
||||
|
||||
import type { echoTransformInput, echoTransformOutput } from '../types'
|
||||
|
||||
export function callEchoTransform(args: echoTransformInput): Promise<echoTransformOutput> {
|
||||
return mizanCall('echo_transform', args)
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
// AUTO-GENERATED by mizan — do not edit
|
||||
|
||||
import { mizanCall } from '@mizan/base'
|
||||
|
||||
import type { httpOnlyEchoInput, httpOnlyEchoOutput } from '../types'
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// AUTO-GENERATED by mizan — do not edit
|
||||
|
||||
import { mizanCall } from '@mizan/base'
|
||||
|
||||
import type { jwtObtainOutput } from '../types'
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// AUTO-GENERATED by mizan — do not edit
|
||||
|
||||
import { mizanCall } from '@mizan/base'
|
||||
|
||||
import type { jwtRefreshInput, jwtRefreshOutput } from '../types'
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// AUTO-GENERATED by mizan — do not edit
|
||||
|
||||
import { mizanCall } from '@mizan/base'
|
||||
|
||||
import type { multiplyInput, multiplyOutput } from '../types'
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
// AUTO-GENERATED by mizan — do not edit
|
||||
|
||||
import { mizanCall } from '@mizan/base'
|
||||
|
||||
import type { notImplementedFnOutput } from '../types'
|
||||
|
||||
export function callNotImplementedFn(): Promise<notImplementedFnOutput> {
|
||||
return mizanCall('not_implemented_fn', {})
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
// AUTO-GENERATED by mizan — do not edit
|
||||
|
||||
import { mizanCall } from '@mizan/base'
|
||||
|
||||
import type { permissionCheckFnInput, permissionCheckFnOutput } from '../types'
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// AUTO-GENERATED by mizan — do not edit
|
||||
|
||||
import { mizanCall } from '@mizan/base'
|
||||
|
||||
import type { staffOnlyOutput } from '../types'
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// AUTO-GENERATED by mizan — do not edit
|
||||
|
||||
import { mizanCall } from '@mizan/base'
|
||||
|
||||
import type { superuserOnlyOutput } from '../types'
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// AUTO-GENERATED by mizan — do not edit
|
||||
|
||||
import { mizanCall } from '@mizan/base'
|
||||
|
||||
import type { verifiedOnlyOutput } from '../types'
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// AUTO-GENERATED by mizan — do not edit
|
||||
|
||||
import { mizanCall } from '@mizan/base'
|
||||
|
||||
import type { whoamiOutput } from '../types'
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// AUTO-GENERATED by mizan — do not edit
|
||||
|
||||
import { mizanCall } from '@mizan/base'
|
||||
|
||||
import type { wsWhoamiOutput } from '../types'
|
||||
|
||||
19
examples/django-react-site/harness/src/api/index.ts
generated
19
examples/django-react-site/harness/src/api/index.ts
generated
@@ -1,24 +1,21 @@
|
||||
// AUTO-GENERATED by mizan — do not edit
|
||||
|
||||
export * from './types'
|
||||
|
||||
export { fetchGlobalContext, type GlobalContextData, type GlobalContextParams } from './contexts/global'
|
||||
export { fetchLocalContext, type LocalContextData, type LocalContextParams } from './contexts/local'
|
||||
|
||||
export { callEcho } from './functions/echo'
|
||||
export { callAdd } from './functions/add'
|
||||
export { callWhoami } from './functions/whoami'
|
||||
export { callDivide } from './functions/divide'
|
||||
export { callEcho } from './functions/echo'
|
||||
export { callEchoTransform } from './functions/echoTransform'
|
||||
export { callHttpOnlyEcho } from './functions/httpOnlyEcho'
|
||||
export { callJwtObtain } from './functions/jwtObtain'
|
||||
export { callJwtRefresh } from './functions/jwtRefresh'
|
||||
export { callMultiply } from './functions/multiply'
|
||||
export { callPermissionCheckFn } from './functions/permissionCheckFn'
|
||||
export { callStaffOnly } from './functions/staffOnly'
|
||||
export { callSuperuserOnly } from './functions/superuserOnly'
|
||||
export { callVerifiedOnly } from './functions/verifiedOnly'
|
||||
export { callMultiply } from './functions/multiply'
|
||||
export { callNotImplementedFn } from './functions/notImplementedFn'
|
||||
export { callBuggyFn } from './functions/buggyFn'
|
||||
export { callPermissionCheckFn } from './functions/permissionCheckFn'
|
||||
export { callWhoami } from './functions/whoami'
|
||||
export { callWsWhoami } from './functions/wsWhoami'
|
||||
export { callJwtObtain } from './functions/jwtObtain'
|
||||
export { callJwtRefresh } from './functions/jwtRefresh'
|
||||
|
||||
// Stage 2 framework adapter
|
||||
export * from './react'
|
||||
|
||||
84
examples/django-react-site/harness/src/api/react.tsx
generated
84
examples/django-react-site/harness/src/api/react.tsx
generated
@@ -1,7 +1,5 @@
|
||||
'use client'
|
||||
|
||||
// AUTO-GENERATED by mizan — do not edit
|
||||
|
||||
import {
|
||||
createContext,
|
||||
useCallback,
|
||||
@@ -14,17 +12,15 @@ import {
|
||||
} from 'react'
|
||||
import {
|
||||
configure,
|
||||
initSession,
|
||||
mizanCall,
|
||||
mizanFetch,
|
||||
MizanError,
|
||||
registerContext,
|
||||
type ContextState,
|
||||
} from '@mizan/base'
|
||||
|
||||
import { fetchGlobalContext, type GlobalContextData, type GlobalContextParams, fetchLocalContext, type LocalContextData, type LocalContextParams, callEcho, callAdd, callWhoami, callHttpOnlyEcho, callStaffOnly, callSuperuserOnly, callVerifiedOnly, callMultiply, callNotImplementedFn, callBuggyFn, callPermissionCheckFn, callWsWhoami, callJwtObtain, callJwtRefresh, type currentUserOutput, type greetOutput } from './index'
|
||||
import { fetchGlobalContext, type GlobalContextData, type GlobalContextParams, fetchLocalContext, type LocalContextData, type LocalContextParams, callAdd, callDivide, callEcho, callEchoTransform, callHttpOnlyEcho, callJwtObtain, callJwtRefresh, callMultiply, callPermissionCheckFn, callStaffOnly, callSuperuserOnly, callVerifiedOnly, callWhoami, callWsWhoami, type currentUserOutput, type greetOutput, } from './index'
|
||||
|
||||
// Internal — runs inside a Provider, registers with the kernel exactly once.
|
||||
// Runs inside a Provider, registers with the kernel exactly once.
|
||||
function useContextSubscription<T>(
|
||||
name: string,
|
||||
params: Record<string, any>,
|
||||
@@ -45,7 +41,8 @@ function useContextSubscription<T>(
|
||||
return useSyncExternalStore(handle.subscribe, handle.getState, handle.getState)
|
||||
}
|
||||
|
||||
// Internal — wraps an imperative call() with isPending / error state.
|
||||
|
||||
// Wraps an imperative call() with isPending / error state.
|
||||
interface MutationHook<TArgs, TResult> {
|
||||
mutate: (args: TArgs) => Promise<TResult>
|
||||
isPending: boolean
|
||||
@@ -74,8 +71,6 @@ function useMutation<TArgs, TResult>(
|
||||
return { mutate, isPending, error }
|
||||
}
|
||||
|
||||
// ── Global Context ──
|
||||
|
||||
const GlobalCtx = createContext<ContextState<GlobalContextData> | null>(null)
|
||||
|
||||
export function GlobalContextProvider({ children }: { children: ReactNode }) {
|
||||
@@ -94,8 +89,6 @@ export function useCurrentUser(): currentUserOutput | null {
|
||||
return useGlobalContext().data?.current_user ?? null
|
||||
}
|
||||
|
||||
// ── Local Context ──
|
||||
|
||||
const LocalCtx = createContext<ContextState<LocalContextData> | null>(null)
|
||||
|
||||
export function LocalContext({ children, ...params }: LocalContextParams & { children: ReactNode }) {
|
||||
@@ -113,22 +106,42 @@ export function useGreet(): greetOutput | null {
|
||||
return useLocalContext().data?.greet ?? null
|
||||
}
|
||||
|
||||
export function useEcho() {
|
||||
return useMutation<Parameters<typeof callEcho>[0], Awaited<ReturnType<typeof callEcho>>>(callEcho)
|
||||
}
|
||||
|
||||
export function useAdd() {
|
||||
return useMutation<Parameters<typeof callAdd>[0], Awaited<ReturnType<typeof callAdd>>>(callAdd)
|
||||
}
|
||||
|
||||
export function useWhoami() {
|
||||
return useMutation<void, Awaited<ReturnType<typeof callWhoami>>>(() => callWhoami() as any)
|
||||
export function useDivide() {
|
||||
return useMutation<Parameters<typeof callDivide>[0], Awaited<ReturnType<typeof callDivide>>>(callDivide)
|
||||
}
|
||||
|
||||
export function useEcho() {
|
||||
return useMutation<Parameters<typeof callEcho>[0], Awaited<ReturnType<typeof callEcho>>>(callEcho)
|
||||
}
|
||||
|
||||
export function useEchoTransform() {
|
||||
return useMutation<Parameters<typeof callEchoTransform>[0], Awaited<ReturnType<typeof callEchoTransform>>>(callEchoTransform)
|
||||
}
|
||||
|
||||
export function useHttpOnlyEcho() {
|
||||
return useMutation<Parameters<typeof callHttpOnlyEcho>[0], Awaited<ReturnType<typeof callHttpOnlyEcho>>>(callHttpOnlyEcho)
|
||||
}
|
||||
|
||||
export function useJwtObtain() {
|
||||
return useMutation<void, Awaited<ReturnType<typeof callJwtObtain>>>(() => callJwtObtain() as any)
|
||||
}
|
||||
|
||||
export function useJwtRefresh() {
|
||||
return useMutation<Parameters<typeof callJwtRefresh>[0], Awaited<ReturnType<typeof callJwtRefresh>>>(callJwtRefresh)
|
||||
}
|
||||
|
||||
export function useMultiply() {
|
||||
return useMutation<Parameters<typeof callMultiply>[0], Awaited<ReturnType<typeof callMultiply>>>(callMultiply)
|
||||
}
|
||||
|
||||
export function usePermissionCheckFn() {
|
||||
return useMutation<Parameters<typeof callPermissionCheckFn>[0], Awaited<ReturnType<typeof callPermissionCheckFn>>>(callPermissionCheckFn)
|
||||
}
|
||||
|
||||
export function useStaffOnly() {
|
||||
return useMutation<void, Awaited<ReturnType<typeof callStaffOnly>>>(() => callStaffOnly() as any)
|
||||
}
|
||||
@@ -141,36 +154,14 @@ export function useVerifiedOnly() {
|
||||
return useMutation<void, Awaited<ReturnType<typeof callVerifiedOnly>>>(() => callVerifiedOnly() as any)
|
||||
}
|
||||
|
||||
export function useMultiply() {
|
||||
return useMutation<Parameters<typeof callMultiply>[0], Awaited<ReturnType<typeof callMultiply>>>(callMultiply)
|
||||
}
|
||||
|
||||
export function useNotImplementedFn() {
|
||||
return useMutation<void, Awaited<ReturnType<typeof callNotImplementedFn>>>(() => callNotImplementedFn() as any)
|
||||
}
|
||||
|
||||
export function useBuggyFn() {
|
||||
return useMutation<void, Awaited<ReturnType<typeof callBuggyFn>>>(() => callBuggyFn() as any)
|
||||
}
|
||||
|
||||
export function usePermissionCheckFn() {
|
||||
return useMutation<Parameters<typeof callPermissionCheckFn>[0], Awaited<ReturnType<typeof callPermissionCheckFn>>>(callPermissionCheckFn)
|
||||
export function useWhoami() {
|
||||
return useMutation<void, Awaited<ReturnType<typeof callWhoami>>>(() => callWhoami() as any)
|
||||
}
|
||||
|
||||
export function useWsWhoami() {
|
||||
return useMutation<void, Awaited<ReturnType<typeof callWsWhoami>>>(() => callWsWhoami() as any)
|
||||
}
|
||||
|
||||
export function useJwtObtain() {
|
||||
return useMutation<void, Awaited<ReturnType<typeof callJwtObtain>>>(() => callJwtObtain() as any)
|
||||
}
|
||||
|
||||
export function useJwtRefresh() {
|
||||
return useMutation<Parameters<typeof callJwtRefresh>[0], Awaited<ReturnType<typeof callJwtRefresh>>>(callJwtRefresh)
|
||||
}
|
||||
|
||||
// ── MizanContext root provider ──
|
||||
|
||||
export interface MizanContextProps {
|
||||
/** Base URL for protocol endpoints. Defaults to "/api/mizan". */
|
||||
baseUrl?: string
|
||||
@@ -180,8 +171,8 @@ export interface MizanContextProps {
|
||||
}
|
||||
|
||||
/**
|
||||
* Root provider — calls configure() once and mounts the global context (if defined).
|
||||
* Must wrap any component using Mizan-generated hooks.
|
||||
* Calls configure() once and mounts the global context when one is defined.
|
||||
* Every component reading a generated hook resolves through this provider.
|
||||
*/
|
||||
export function MizanContext({ baseUrl, session, children }: MizanContextProps) {
|
||||
const configured = useRef(false)
|
||||
@@ -195,12 +186,7 @@ export function MizanContext({ baseUrl, session, children }: MizanContextProps)
|
||||
return <GlobalContextProvider>{children}</GlobalContextProvider>
|
||||
}
|
||||
|
||||
// ── Imperative escape hatch ──
|
||||
|
||||
/**
|
||||
* Returns the imperative kernel API. For test harnesses or rare cases where
|
||||
* a typed generated hook does not fit. Most app code should use the typed hooks.
|
||||
*/
|
||||
/** The untyped kernel entry points, bound to the configured client. */
|
||||
export function useMizan() {
|
||||
return { call: mizanCall, fetch: mizanFetch }
|
||||
}
|
||||
|
||||
2678
examples/django-react-site/harness/src/api/schema.json
generated
2678
examples/django-react-site/harness/src/api/schema.json
generated
File diff suppressed because it is too large
Load Diff
2230
examples/django-react-site/harness/src/api/types.ts
generated
2230
examples/django-react-site/harness/src/api/types.ts
generated
File diff suppressed because it is too large
Load Diff
@@ -1,17 +1,13 @@
|
||||
/**
|
||||
* E2E Test Fixtures
|
||||
*
|
||||
* Each fixture uses GENERATED mizan hooks (not raw call()).
|
||||
* Playwright reads the DOM to verify behavior.
|
||||
*
|
||||
* URL hash selects the fixture: #echo, #add, #multiply, etc.
|
||||
* The URL hash selects which fixture component mounts; each one drives a
|
||||
* generated mizan hook and writes the outcome into `data-testid` nodes that
|
||||
* the Playwright spec reads.
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useRef } from 'react'
|
||||
import { ChannelProvider } from 'mizan/channels'
|
||||
|
||||
// Generated typed hooks — the actual mizan API
|
||||
import {
|
||||
MizanContext,
|
||||
useEcho,
|
||||
useAdd,
|
||||
useMultiply,
|
||||
@@ -19,8 +15,8 @@ import {
|
||||
useStaffOnly,
|
||||
useSuperuserOnly,
|
||||
useVerifiedOnly,
|
||||
useNotImplementedFn,
|
||||
useBuggyFn,
|
||||
useEchoTransform,
|
||||
useDivide,
|
||||
usePermissionCheckFn,
|
||||
useCurrentUser,
|
||||
MizanError,
|
||||
@@ -49,12 +45,11 @@ export function Fixtures() {
|
||||
case 'staff-only': return <StaffOnly />
|
||||
case 'superuser-only': return <SuperuserOnly />
|
||||
case 'verified-only': return <VerifiedOnly />
|
||||
case 'not-implemented': return <NotImplemented />
|
||||
case 'internal-error': return <InternalError />
|
||||
case 'permission-error': return <PermissionError_ />
|
||||
case 'permission-success': return <PermissionSuccess />
|
||||
case 'not-implemented': return <UnsupportedEchoMode />
|
||||
case 'internal-error': return <DivideByZero />
|
||||
case 'permission-error': return <PermissionDenied />
|
||||
case 'permission-success': return <PermissionGranted />
|
||||
case 'context-current-user': return <ContextCurrentUser />
|
||||
// Form fixtures removed — forms codegen deferred per Blazr scope
|
||||
case 'channel-chat': return <ChannelChatFixture />
|
||||
default: return <div data-testid="ready">Harness ready. Set #hash.</div>
|
||||
}
|
||||
@@ -117,15 +112,15 @@ function Multiply() {
|
||||
}
|
||||
|
||||
function NotFound() {
|
||||
// Deliberately call a non-existent function via the raw primitive
|
||||
// Names a function the backend never registered, so no generated hook
|
||||
// exists for it and the raw kernel primitive is the only way to send it.
|
||||
const { call } = useMizan()
|
||||
const [error, setError] = useState<unknown>()
|
||||
useEffect(() => { call('does_not_exist').catch(setError) }, [call])
|
||||
useEffect(() => { call('does_not_exist', {}).catch(setError) }, [call])
|
||||
return <Result error={error} />
|
||||
}
|
||||
|
||||
function ValidationError() {
|
||||
// Send wrong types to add (strings instead of numbers)
|
||||
const { mutate } = useAdd()
|
||||
const [error, setError] = useState<unknown>()
|
||||
useEffect(() => { (mutate as any)({ a: 'not_a_number', b: 'also_not' }).catch(setError) }, [mutate])
|
||||
@@ -152,22 +147,22 @@ function VerifiedOnly() {
|
||||
return <Result data={data} error={error} />
|
||||
}
|
||||
|
||||
function NotImplemented() {
|
||||
const { data, error } = useRun(useNotImplementedFn)
|
||||
function UnsupportedEchoMode() {
|
||||
const { data, error } = useRun(useEchoTransform, { text: 'e2e-test', mode: 'rot13' })
|
||||
return <Result data={data} error={error} />
|
||||
}
|
||||
|
||||
function InternalError() {
|
||||
const { data, error } = useRun(useBuggyFn)
|
||||
function DivideByZero() {
|
||||
const { data, error } = useRun(useDivide, { numerator: 1, denominator: 0 })
|
||||
return <Result data={data} error={error} />
|
||||
}
|
||||
|
||||
function PermissionError_() {
|
||||
function PermissionDenied() {
|
||||
const { data, error } = useRun(usePermissionCheckFn, { secret: 'wrong' })
|
||||
return <Result data={data} error={error} />
|
||||
}
|
||||
|
||||
function PermissionSuccess() {
|
||||
function PermissionGranted() {
|
||||
const { data, error } = useRun(usePermissionCheckFn, { secret: 'open-sesame' })
|
||||
return <Result data={data} error={error} />
|
||||
}
|
||||
@@ -175,20 +170,19 @@ function PermissionSuccess() {
|
||||
// ─── Context fixtures ───────────────────────────────────────────────────────
|
||||
|
||||
function ContextCurrentUser() {
|
||||
// useCurrentUser throws if context not loaded yet, so catch that
|
||||
try {
|
||||
const user = useCurrentUser()
|
||||
return <pre data-testid="result">{JSON.stringify(user)}</pre>
|
||||
} catch {
|
||||
return <div>loading context...</div>
|
||||
}
|
||||
const user = useCurrentUser()
|
||||
if (user === null) return <div data-testid="pending">loading context</div>
|
||||
return <pre data-testid="result">{JSON.stringify(user)}</pre>
|
||||
}
|
||||
|
||||
// ─── Channel fixtures ───────────────────────────────────────────────────────
|
||||
|
||||
function ChannelChatFixture() {
|
||||
// MizanContext already includes ChannelProvider
|
||||
return <ChannelChat />
|
||||
return (
|
||||
<ChannelProvider>
|
||||
<ChannelChat />
|
||||
</ChannelProvider>
|
||||
)
|
||||
}
|
||||
|
||||
function ChannelChat() {
|
||||
@@ -197,9 +191,8 @@ function ChannelChat() {
|
||||
const prevStatus = useRef(chat.status)
|
||||
|
||||
useEffect(() => {
|
||||
// Send once when status transitions to 'connected' (meaning subscribed)
|
||||
// The hook maps subscribed → 'connected', but we need to wait for it
|
||||
// to go through 'connecting' first (before subscription is confirmed)
|
||||
// The hook reports 'connected' only once the server confirms the
|
||||
// subscription, and it passes through 'connecting' on the way there.
|
||||
const wasConnecting = prevStatus.current === 'connecting'
|
||||
prevStatus.current = chat.status
|
||||
|
||||
|
||||
Reference in New Issue
Block a user