494 lines
12 KiB
TypeScript
494 lines
12 KiB
TypeScript
import type {
|
|
FeatureModule,
|
|
ProjectOptions,
|
|
} from "../types/electrobun.types.js";
|
|
|
|
/** Render the i18n feature module. */
|
|
export function renderI18nFeature(): string {
|
|
return `import { MessageCatalog } from "@suaveplan/i18n";
|
|
|
|
const messages = {
|
|
en: {
|
|
greeting: "Hello, {name}!",
|
|
welcome: "Welcome to the app",
|
|
settings: "Settings",
|
|
language: "Language",
|
|
},
|
|
es: {
|
|
greeting: "Hola, {name}!",
|
|
welcome: "Bienvenido a la aplicación",
|
|
settings: "Configuración",
|
|
language: "Idioma",
|
|
},
|
|
fr: {
|
|
greeting: "Bonjour, {name} !",
|
|
welcome: "Bienvenue dans l'application",
|
|
settings: "Paramètres",
|
|
language: "Langue",
|
|
},
|
|
};
|
|
|
|
export const catalog = new MessageCatalog({
|
|
defaultLocale: "en",
|
|
fallbackLocale: "en",
|
|
messages,
|
|
});
|
|
|
|
export function setLocale(locale: string): void {
|
|
catalog.setLocale(locale);
|
|
}
|
|
|
|
export function t(key: string, params?: Record<string, string>): string {
|
|
return catalog.get(key, params);
|
|
}
|
|
`;
|
|
}
|
|
|
|
/** Render the basic-networking feature module. */
|
|
export function renderNetworkFeature(): string {
|
|
return `import { NetworkNode } from "@suaveplan/network";
|
|
|
|
let node: NetworkNode | null = null;
|
|
|
|
export async function initializeNetwork(): Promise<NetworkNode> {
|
|
node = new NetworkNode({
|
|
transport: "websocket",
|
|
discovery: {
|
|
enabled: true,
|
|
interval: 5000,
|
|
},
|
|
});
|
|
|
|
node.on("peer:connected", (peerId: string) => {
|
|
console.log(\`Peer connected: \${peerId}\`);
|
|
});
|
|
|
|
node.on("peer:disconnected", (peerId: string) => {
|
|
console.log(\`Peer disconnected: \${peerId}\`);
|
|
});
|
|
|
|
node.on("message", (message: { from: string; data: unknown }) => {
|
|
console.log(\`Message from \${message.from}:\`, message.data);
|
|
});
|
|
|
|
await node.start();
|
|
console.log("Network node started");
|
|
|
|
return node;
|
|
}
|
|
|
|
export function getNode(): NetworkNode | null {
|
|
return node;
|
|
}
|
|
|
|
export async function broadcast(data: unknown): Promise<void> {
|
|
if (node) {
|
|
await node.broadcast(data);
|
|
}
|
|
}
|
|
|
|
export async function shutdownNetwork(): Promise<void> {
|
|
if (node) {
|
|
await node.stop();
|
|
node = null;
|
|
}
|
|
}
|
|
`;
|
|
}
|
|
|
|
/** Render the full-mesh networking feature module. */
|
|
export function renderNetworkFullFeature(): string {
|
|
return `import { NetworkNode } from "@suaveplan/network";
|
|
|
|
let node: NetworkNode | null = null;
|
|
|
|
export async function initializeNetwork(): Promise<NetworkNode> {
|
|
node = new NetworkNode({
|
|
transport: "websocket",
|
|
discovery: {
|
|
enabled: true,
|
|
interval: 5000,
|
|
},
|
|
state: {
|
|
hierarchical: true,
|
|
vectorClocks: true,
|
|
conflictResolution: "last-write-wins",
|
|
},
|
|
governance: {
|
|
roles: ["leader", "follower"],
|
|
electionStrategy: "capability-based",
|
|
autoPromote: true,
|
|
},
|
|
});
|
|
|
|
node.on("peer:connected", (peerId: string) => {
|
|
console.log(\`Peer connected: \${peerId}\`);
|
|
});
|
|
|
|
node.on("peer:disconnected", (peerId: string) => {
|
|
console.log(\`Peer disconnected: \${peerId}\`);
|
|
});
|
|
|
|
node.on("message", (message: { from: string; data: unknown }) => {
|
|
console.log(\`Message from \${message.from}:\`, message.data);
|
|
});
|
|
|
|
node.on("state:sync", (state: unknown) => {
|
|
console.log("State synchronized:", state);
|
|
});
|
|
|
|
node.on("role:changed", (role: string) => {
|
|
console.log(\`Role changed to: \${role}\`);
|
|
});
|
|
|
|
await node.start();
|
|
console.log("Network node started (full mesh mode)");
|
|
|
|
return node;
|
|
}
|
|
|
|
export function getNode(): NetworkNode | null {
|
|
return node;
|
|
}
|
|
|
|
export async function broadcast(data: unknown): Promise<void> {
|
|
if (node) {
|
|
await node.broadcast(data);
|
|
}
|
|
}
|
|
|
|
export function getState<T>(key: string): T | undefined {
|
|
return node?.getState<T>(key);
|
|
}
|
|
|
|
export function setState<T>(key: string, value: T): void {
|
|
node?.setState(key, value);
|
|
}
|
|
|
|
export async function shutdownNetwork(): Promise<void> {
|
|
if (node) {
|
|
await node.stop();
|
|
node = null;
|
|
}
|
|
}
|
|
`;
|
|
}
|
|
|
|
/** Render the api feature module. */
|
|
export function renderApiFeature(): string {
|
|
return `import { defineApp } from "@suaveplan/api";
|
|
|
|
const app = defineApp({
|
|
name: "app-api",
|
|
port: 3001,
|
|
cors: {
|
|
origin: "*",
|
|
credentials: true,
|
|
},
|
|
});
|
|
|
|
app.route("GET", "/api/health", (c) => {
|
|
return c.json({ status: "ok", timestamp: Date.now() });
|
|
});
|
|
|
|
app.route("GET", "/api/info", (c) => {
|
|
return c.json({
|
|
name: "Electrobun App API",
|
|
version: "1.0.0",
|
|
});
|
|
});
|
|
|
|
export async function startApiServer(): Promise<void> {
|
|
await app.start();
|
|
console.log("API server started on port 3001");
|
|
}
|
|
|
|
export { app };
|
|
`;
|
|
}
|
|
|
|
/** Render the api-schemas feature module. */
|
|
export function renderApiSchemasFeature(): string {
|
|
return `import { z } from "@suaveplan/validation";
|
|
|
|
export const UserSchema = z.object({
|
|
id: z.string(),
|
|
email: z.string().email(),
|
|
name: z.string().min(1),
|
|
createdAt: z.string().datetime(),
|
|
});
|
|
|
|
export const CreateUserSchema = UserSchema.omit({
|
|
id: true,
|
|
createdAt: true,
|
|
});
|
|
|
|
export const MessageSchema = z.object({
|
|
id: z.string(),
|
|
from: z.string(),
|
|
to: z.string(),
|
|
content: z.string().min(1),
|
|
timestamp: z.string().datetime(),
|
|
});
|
|
|
|
export const PaginatedResponseSchema = <T extends z.ZodType>(itemSchema: T) =>
|
|
z.object({
|
|
items: z.array(itemSchema),
|
|
total: z.number(),
|
|
page: z.number(),
|
|
pageSize: z.number(),
|
|
});
|
|
|
|
export type User = z.infer<typeof UserSchema>;
|
|
export type CreateUser = z.infer<typeof CreateUserSchema>;
|
|
export type Message = z.infer<typeof MessageSchema>;
|
|
`;
|
|
}
|
|
|
|
/** Render the storage feature module. */
|
|
export function renderStorageFeature(): string {
|
|
return `import { createStorage } from "@suaveplan/storage";
|
|
|
|
export const appStorage = createStorage({
|
|
driver: "file",
|
|
options: {
|
|
directory: "./data",
|
|
namespace: "app",
|
|
},
|
|
});
|
|
|
|
export async function saveData<T>(key: string, value: T): Promise<void> {
|
|
await appStorage.set(key, value);
|
|
}
|
|
|
|
export async function loadData<T>(key: string): Promise<T | undefined> {
|
|
return appStorage.get<T>(key);
|
|
}
|
|
|
|
export async function removeData(key: string): Promise<void> {
|
|
await appStorage.delete(key);
|
|
}
|
|
|
|
export async function listKeys(pattern?: string): Promise<string[]> {
|
|
return appStorage.keys(pattern ?? "*");
|
|
}
|
|
`;
|
|
}
|
|
|
|
/** Render the auth feature module. */
|
|
export function renderAuthFeature(): string {
|
|
return `import { createAuth } from "@suaveplan/auth";
|
|
|
|
const auth = createAuth({
|
|
strategy: "jwt",
|
|
tokenExpiry: "24h",
|
|
refreshExpiry: "7d",
|
|
});
|
|
|
|
export interface AuthSession {
|
|
userId: string;
|
|
token: string;
|
|
expiresAt: number;
|
|
}
|
|
|
|
let currentSession: AuthSession | null = null;
|
|
|
|
export async function login(
|
|
email: string,
|
|
password: string,
|
|
): Promise<AuthSession> {
|
|
const result = await auth.authenticate({ email, password });
|
|
currentSession = {
|
|
userId: result.userId,
|
|
token: result.token,
|
|
expiresAt: result.expiresAt,
|
|
};
|
|
return currentSession;
|
|
}
|
|
|
|
export function getSession(): AuthSession | null {
|
|
return currentSession;
|
|
}
|
|
|
|
export function isAuthenticated(): boolean {
|
|
if (!currentSession) return false;
|
|
return currentSession.expiresAt > Date.now();
|
|
}
|
|
|
|
export async function logout(): Promise<void> {
|
|
if (currentSession) {
|
|
await auth.revoke(currentSession.token);
|
|
currentSession = null;
|
|
}
|
|
}
|
|
|
|
export { auth };
|
|
`;
|
|
}
|
|
|
|
/** Render the form-builder feature module. */
|
|
export function renderFormBuilderFeature(): string {
|
|
return `import { FormBuilder } from "@suaveplan/form-builder";
|
|
|
|
export const contactForm = new FormBuilder()
|
|
.section("personal", "Personal Information")
|
|
.field({
|
|
name: "firstName",
|
|
type: "text",
|
|
label: "First Name",
|
|
required: true,
|
|
validation: { minLength: 1, maxLength: 50 },
|
|
})
|
|
.field({
|
|
name: "lastName",
|
|
type: "text",
|
|
label: "Last Name",
|
|
required: true,
|
|
validation: { minLength: 1, maxLength: 50 },
|
|
})
|
|
.field({
|
|
name: "email",
|
|
type: "email",
|
|
label: "Email Address",
|
|
required: true,
|
|
})
|
|
.section("details", "Details")
|
|
.field({
|
|
name: "subject",
|
|
type: "select",
|
|
label: "Subject",
|
|
options: [
|
|
{ value: "general", label: "General Inquiry" },
|
|
{ value: "support", label: "Technical Support" },
|
|
{ value: "feedback", label: "Feedback" },
|
|
],
|
|
})
|
|
.field({
|
|
name: "message",
|
|
type: "textarea",
|
|
label: "Message",
|
|
required: true,
|
|
validation: { minLength: 10, maxLength: 1000 },
|
|
})
|
|
.build();
|
|
`;
|
|
}
|
|
|
|
/** Render the r3f feature module — output depends on selected r3fModules. */
|
|
export function renderR3fFeature(options: ProjectOptions): string {
|
|
const imports: string[] = [
|
|
'import React, { useRef } from "react";',
|
|
'import { Canvas, useFrame } from "@react-three/fiber";',
|
|
'import { OrbitControls } from "@react-three/drei";',
|
|
];
|
|
const r3fMods = options.r3fModules;
|
|
const extraComponents: string[] = [];
|
|
if (r3fMods.includes("camera")) {
|
|
imports.push(
|
|
'import { AdaptiveDpr, AdaptiveEvents } from "@react-three/drei";'
|
|
);
|
|
extraComponents.push(" <AdaptiveDpr pixelated />");
|
|
extraComponents.push(" <AdaptiveEvents />");
|
|
}
|
|
if (r3fMods.includes("physics")) {
|
|
imports.push(
|
|
'import { Physics, RigidBody } from "@react-three/rapier";'
|
|
);
|
|
}
|
|
if (r3fMods.includes("particles")) {
|
|
imports.push('import { Sparkles } from "@react-three/drei";');
|
|
extraComponents.push(
|
|
" <Sparkles count={100} scale={4} size={2} speed={0.4} />"
|
|
);
|
|
}
|
|
const meshContent = r3fMods.includes("physics")
|
|
? ` <Physics>
|
|
<RigidBody>
|
|
<RotatingBox />
|
|
</RigidBody>
|
|
<RigidBody type="fixed" position={[0, -2, 0]}>
|
|
<mesh>
|
|
<boxGeometry args={[10, 0.2, 10]} />
|
|
<meshStandardMaterial color="#334155" />
|
|
</mesh>
|
|
</RigidBody>
|
|
</Physics>`
|
|
: " <RotatingBox />";
|
|
return `${imports.join("\n")}
|
|
|
|
function RotatingBox() {
|
|
const meshRef = useRef<THREE.Mesh>(null);
|
|
|
|
useFrame((_, delta) => {
|
|
if (meshRef.current) {
|
|
meshRef.current.rotation.x += delta * 0.5;
|
|
meshRef.current.rotation.y += delta * 0.3;
|
|
}
|
|
});
|
|
|
|
return (
|
|
<mesh ref={meshRef}>
|
|
<boxGeometry args={[1, 1, 1]} />
|
|
<meshStandardMaterial color="#3b82f6" />
|
|
</mesh>
|
|
);
|
|
}
|
|
|
|
export function Scene() {
|
|
return (
|
|
<Canvas camera={{ position: [3, 3, 3], fov: 50 }}>
|
|
<ambientLight intensity={0.5} />
|
|
<directionalLight position={[5, 5, 5]} intensity={1} />
|
|
${meshContent}
|
|
${extraComponents.join("\n")}
|
|
<OrbitControls />
|
|
</Canvas>
|
|
);
|
|
}
|
|
`;
|
|
}
|
|
|
|
/**
|
|
* Render any feature module. The `networking` branch chooses between basic
|
|
* and full implementations based on `options.networkingComplexity`.
|
|
*/
|
|
export function renderFeature(
|
|
feature: FeatureModule,
|
|
options: ProjectOptions
|
|
): string {
|
|
switch (feature) {
|
|
case "i18n":
|
|
return renderI18nFeature();
|
|
case "networking":
|
|
return options.networkingComplexity === "full"
|
|
? renderNetworkFullFeature()
|
|
: renderNetworkFeature();
|
|
case "api":
|
|
return renderApiFeature();
|
|
case "api-schemas":
|
|
return renderApiSchemasFeature();
|
|
case "storage":
|
|
return renderStorageFeature();
|
|
case "auth":
|
|
return renderAuthFeature();
|
|
case "form-builder":
|
|
return renderFormBuilderFeature();
|
|
case "r3f":
|
|
return renderR3fFeature(options);
|
|
}
|
|
}
|
|
|
|
/** Filesystem path for a feature's output file. */
|
|
export function featurePath(feature: FeatureModule): string {
|
|
if (feature === "networking") {
|
|
return "src/features/network.ts";
|
|
}
|
|
if (feature === "api-schemas") {
|
|
return "src/features/schemas.ts";
|
|
}
|
|
if (feature === "r3f") {
|
|
return "src/features/r3f.tsx";
|
|
}
|
|
return `src/features/${feature}.ts`;
|
|
}
|