Initial Commit
This commit is contained in:
493
packages/devtools/tooling/electrobun/src/templates/features.ts
Normal file
493
packages/devtools/tooling/electrobun/src/templates/features.ts
Normal file
@@ -0,0 +1,493 @@
|
||||
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`;
|
||||
}
|
||||
Reference in New Issue
Block a user