Restructure tree by role; rename mizan-runtime → mizan-base
packages/ flattens into: backends/ server protocol adapters (mizan-django, mizan-ts) frontends/ client kernel + framework adapters (mizan-base, mizan-react, mizan-vue, mizan-svelte) workers/ runtime workers (mizan-ssr) cores/ shared language-level primitives (empty for now; mizan-python forthcoming) The frontend kernel (was packages/mizan-runtime, now frontends/mizan-base) is renamed to reflect its role — it's the shared base that frontend adapters depend on directly. Reflects the substrate position that per-framework adapters wrap a single shared kernel; codegen targets the adapter, not the raw kernel. Path updates landed in: Makefile, two Gitea workflows, Dockerfile.test, four example/harness config files, .claude/settings.local.json, four docs (CLAUDE/ISSUES/ROADMAP/AFI_ARCHITECTURE), four codegen templates (stage1 + react/vue/svelte adapters), and three package.jsons (the mizan-base rename plus mizan-vue/svelte peerDeps). Generated files under examples/django-react-site/harness/src/api/ still reference @mizan/runtime — left as-is; they're regenerated artifacts and the harness is non-functional pending the React wrapper-layer codegen. Also folded in a pre-existing fix: the Gitea workflows had working-directory: react / django pointing at a layout that predates packages/, never updated. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2
workers/mizan-ssr/src/index.ts
Normal file
2
workers/mizan-ssr/src/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
// The SSR package is a Bun worker subprocess (worker.tsx).
|
||||
// It is not imported as a library. Django's SSRBridge spawns it.
|
||||
29
workers/mizan-ssr/src/test-worker.tsx
Normal file
29
workers/mizan-ssr/src/test-worker.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Test SSR worker — registers simple components for the test suite.
|
||||
*/
|
||||
|
||||
import { registerComponent } from './worker'
|
||||
|
||||
// Simple component that renders props
|
||||
function Hello({ name }: { name: string }) {
|
||||
return <div>Hello, {name}!</div>
|
||||
}
|
||||
|
||||
// Component that renders a list
|
||||
function UserProfile({ user_id, name }: { user_id: number; name: string }) {
|
||||
return (
|
||||
<div className="profile">
|
||||
<h1>{name}</h1>
|
||||
<span>ID: {user_id}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Component that throws during render
|
||||
function Broken() {
|
||||
throw new Error('Intentional render error')
|
||||
}
|
||||
|
||||
registerComponent('Hello', Hello)
|
||||
registerComponent('UserProfile', UserProfile)
|
||||
registerComponent('Broken', Broken)
|
||||
72
workers/mizan-ssr/src/worker.tsx
Normal file
72
workers/mizan-ssr/src/worker.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import { renderToString } from 'react-dom/server'
|
||||
import { createElement } from 'react'
|
||||
|
||||
const cache = new Map<string, any>()
|
||||
|
||||
function respond(msg: Record<string, any>): void {
|
||||
Bun.write(Bun.stdout, JSON.stringify(msg) + '\n')
|
||||
}
|
||||
|
||||
async function handleMessage(msg: any): Promise<void> {
|
||||
if (msg.method === 'ping') {
|
||||
respond({ id: msg.id, pong: true })
|
||||
return
|
||||
}
|
||||
|
||||
if (msg.method === 'render') {
|
||||
const { file, props } = msg.params ?? {}
|
||||
|
||||
if (!file) {
|
||||
respond({ id: msg.id, error: 'Missing file path' })
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
let mod = cache.get(file)
|
||||
if (!mod) {
|
||||
mod = await import(file)
|
||||
cache.set(file, mod)
|
||||
}
|
||||
|
||||
const Component = mod.default || Object.values(mod)[0]
|
||||
if (!Component) {
|
||||
respond({ id: msg.id, error: `No component exported from ${file}` })
|
||||
return
|
||||
}
|
||||
|
||||
const html = renderToString(createElement(Component, props ?? {}))
|
||||
respond({ id: msg.id, html })
|
||||
} catch (e: any) {
|
||||
respond({ id: msg.id, error: e.message })
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
respond({ id: msg.id, error: `Unknown method: ${msg.method}` })
|
||||
}
|
||||
|
||||
async function processInput(): Promise<void> {
|
||||
const reader = Bun.stdin.stream().getReader()
|
||||
const decoder = new TextDecoder()
|
||||
let buffer = ''
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read()
|
||||
if (done) break
|
||||
|
||||
buffer += decoder.decode(value, { stream: true })
|
||||
|
||||
let i: number
|
||||
while ((i = buffer.indexOf('\n')) !== -1) {
|
||||
const line = buffer.slice(0, i).trim()
|
||||
buffer = buffer.slice(i + 1)
|
||||
if (line) {
|
||||
try { await handleMessage(JSON.parse(line)) }
|
||||
catch (e: any) { respond({ id: -1, error: e.message }) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
respond({ id: 0, ready: true })
|
||||
processInput()
|
||||
Reference in New Issue
Block a user