create-frootjs 0.0.0-stage → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +68 -2
  3. package/dist/generator.d.ts +21 -0
  4. package/dist/generator.d.ts.map +1 -0
  5. package/dist/generator.js +62 -0
  6. package/dist/generator.js.map +1 -0
  7. package/dist/index.d.ts +7 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +215 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/templates/fullstack.d.ts +7 -0
  12. package/dist/templates/fullstack.d.ts.map +1 -0
  13. package/dist/templates/fullstack.js +224 -0
  14. package/dist/templates/fullstack.js.map +1 -0
  15. package/dist/templates/index.d.ts +10 -0
  16. package/dist/templates/index.d.ts.map +1 -0
  17. package/dist/templates/index.js +26 -0
  18. package/dist/templates/index.js.map +1 -0
  19. package/dist/templates/minimal.d.ts +7 -0
  20. package/dist/templates/minimal.d.ts.map +1 -0
  21. package/dist/templates/minimal.js +89 -0
  22. package/dist/templates/minimal.js.map +1 -0
  23. package/dist/templates/modular.d.ts +7 -0
  24. package/dist/templates/modular.d.ts.map +1 -0
  25. package/dist/templates/modular.js +300 -0
  26. package/dist/templates/modular.js.map +1 -0
  27. package/dist/templates/mvc.d.ts +7 -0
  28. package/dist/templates/mvc.d.ts.map +1 -0
  29. package/dist/templates/mvc.js +239 -0
  30. package/dist/templates/mvc.js.map +1 -0
  31. package/dist/templates/openapi.d.ts +7 -0
  32. package/dist/templates/openapi.d.ts.map +1 -0
  33. package/dist/templates/openapi.js +206 -0
  34. package/dist/templates/openapi.js.map +1 -0
  35. package/dist/templates/streaming.d.ts +7 -0
  36. package/dist/templates/streaming.d.ts.map +1 -0
  37. package/dist/templates/streaming.js +211 -0
  38. package/dist/templates/streaming.js.map +1 -0
  39. package/dist/templates/types.d.ts +17 -0
  40. package/dist/templates/types.d.ts.map +1 -0
  41. package/dist/templates/types.js +47 -0
  42. package/dist/templates/types.js.map +1 -0
  43. package/dist/templates/websocket.d.ts +7 -0
  44. package/dist/templates/websocket.d.ts.map +1 -0
  45. package/dist/templates/websocket.js +198 -0
  46. package/dist/templates/websocket.js.map +1 -0
  47. package/dist/ui.d.ts +73 -0
  48. package/dist/ui.d.ts.map +1 -0
  49. package/dist/ui.js +236 -0
  50. package/dist/ui.js.map +1 -0
  51. package/package.json +45 -4
@@ -0,0 +1,239 @@
1
+ /**
2
+ * MVC / Layered Architecture Template
3
+ * Organizes code by layer: Routes -> Controllers -> Services -> Models.
4
+ */
5
+ import { COMMON_GITIGNORE, COMMON_TSCONFIG } from './types.js';
6
+ export const mvcTemplate = {
7
+ id: 'mvc',
8
+ name: 'MVC / Layered Architecture',
9
+ description: 'Structured enterprise layout: routes, controllers, services, and models',
10
+ generate: (projectName) => {
11
+ return {
12
+ 'package.json': JSON.stringify({
13
+ name: projectName,
14
+ version: '1.0.0',
15
+ type: 'module',
16
+ scripts: {
17
+ dev: 'node --experimental-strip-types src/server.ts',
18
+ build: 'tsc',
19
+ start: 'node dist/server.js',
20
+ test: 'node --test tests/*.test.ts',
21
+ },
22
+ dependencies: {
23
+ frootjs: '^1.0.1',
24
+ },
25
+ devDependencies: {
26
+ '@types/node': '^22.10.0',
27
+ typescript: '^5.8.0',
28
+ },
29
+ }, null, 2) + '\n',
30
+ 'tsconfig.json': COMMON_TSCONFIG,
31
+ '.gitignore': COMMON_GITIGNORE,
32
+ 'README.md': `# ${projectName}
33
+
34
+ Froot application built with the **MVC / Layered Architecture** pattern.
35
+
36
+ ## Directory Structure
37
+
38
+ \`\`\`
39
+ src/
40
+ ├── controllers/ # Request handlers & HTTP response formatting
41
+ │ ├── health.controller.ts
42
+ │ └── user.controller.ts
43
+ ├── services/ # Core business logic & database interaction
44
+ │ └── user.service.ts
45
+ ├── models/ # TypeScript interfaces & domain types
46
+ │ └── user.model.ts
47
+ ├── routes/ # Froot route definitions & sub-app mounting
48
+ │ ├── health.routes.ts
49
+ │ ├── user.routes.ts
50
+ │ └── index.ts
51
+ ├── middleware/ # Custom onion middleware
52
+ │ └── request-logger.ts
53
+ ├── app.ts # Application assembly
54
+ └── server.ts # Server entry point
55
+ \`\`\`
56
+
57
+ ## Getting Started
58
+
59
+ \`\`\`bash
60
+ # 1. Install dependencies
61
+ pnpm install
62
+
63
+ # 2. Run in development mode
64
+ pnpm run dev
65
+
66
+ # 3. Build for production
67
+ pnpm run build
68
+ pnpm start
69
+ \`\`\`
70
+ `,
71
+ 'src/models/user.model.ts': `export interface User {
72
+ id: string;
73
+ name: string;
74
+ email: string;
75
+ role: 'admin' | 'user' | 'guest';
76
+ createdAt: string;
77
+ }
78
+
79
+ export interface CreateUserInput {
80
+ name: string;
81
+ email: string;
82
+ role?: 'admin' | 'user' | 'guest';
83
+ }
84
+ `,
85
+ 'src/services/user.service.ts': `import type { User, CreateUserInput } from '../models/user.model.js';
86
+
87
+ // In-memory mock database
88
+ const usersTable = new Map<string, User>([
89
+ ['usr_1', { id: 'usr_1', name: 'Alice Smith', email: 'alice@example.com', role: 'admin', createdAt: new Date().toISOString() }],
90
+ ['usr_2', { id: 'usr_2', name: 'Bob Jones', email: 'bob@example.com', role: 'user', createdAt: new Date().toISOString() }],
91
+ ]);
92
+
93
+ export class UserService {
94
+ static async findAll(): Promise<User[]> {
95
+ return Array.from(usersTable.values());
96
+ }
97
+
98
+ static async findById(id: string): Promise<User | null> {
99
+ return usersTable.get(id) ?? null;
100
+ }
101
+
102
+ static async create(input: CreateUserInput): Promise<User> {
103
+ const id = 'usr_' + Math.random().toString(36).substring(2, 9);
104
+ const user: User = {
105
+ id,
106
+ name: input.name,
107
+ email: input.email,
108
+ role: input.role ?? 'user',
109
+ createdAt: new Date().toISOString(),
110
+ };
111
+ usersTable.set(id, user);
112
+ return user;
113
+ }
114
+
115
+ static async delete(id: string): Promise<boolean> {
116
+ return usersTable.delete(id);
117
+ }
118
+ }
119
+ `,
120
+ 'src/controllers/health.controller.ts': `import type { Context } from 'frootjs';
121
+
122
+ export class HealthController {
123
+ static getHealth(c: Context) {
124
+ return c.json({
125
+ status: 'ok',
126
+ uptime: process.uptime(),
127
+ timestamp: new Date().toISOString(),
128
+ });
129
+ }
130
+ }
131
+ `,
132
+ 'src/controllers/user.controller.ts': `import type { Context } from 'frootjs';
133
+ import { UserService } from '../services/user.service.js';
134
+ import type { CreateUserInput } from '../models/user.model.js';
135
+
136
+ export class UserController {
137
+ static async list(c: Context) {
138
+ const users = await UserService.findAll();
139
+ return c.json(users);
140
+ }
141
+
142
+ static async getById(c: Context) {
143
+ const id = c.req.param('id');
144
+ const user = await UserService.findById(id);
145
+ if (!user) {
146
+ return c.json({ error: 'User not found' }, 404);
147
+ }
148
+ return c.json(user);
149
+ }
150
+
151
+ static async create(c: Context) {
152
+ const body = await c.req.json<CreateUserInput>();
153
+ if (!body.name || !body.email) {
154
+ return c.json({ error: 'Name and email are required' }, 400);
155
+ }
156
+ const user = await UserService.create(body);
157
+ return c.json(user, 201);
158
+ }
159
+
160
+ static async remove(c: Context) {
161
+ const id = c.req.param('id');
162
+ const ok = await UserService.delete(id);
163
+ if (!ok) {
164
+ return c.json({ error: 'User not found' }, 404);
165
+ }
166
+ return c.json({ success: true, message: 'User deleted' });
167
+ }
168
+ }
169
+ `,
170
+ 'src/routes/health.routes.ts': `import { Froot } from 'frootjs';
171
+ import { HealthController } from '../controllers/health.controller.js';
172
+
173
+ export const healthRoutes = new Froot();
174
+
175
+ healthRoutes.get('/health', HealthController.getHealth);
176
+ `,
177
+ 'src/routes/user.routes.ts': `import { Froot } from 'frootjs';
178
+ import { UserController } from '../controllers/user.controller.js';
179
+
180
+ export const userRoutes = new Froot();
181
+
182
+ userRoutes.get('/users', UserController.list);
183
+ userRoutes.get('/users/:id', UserController.getById);
184
+ userRoutes.post('/users', UserController.create);
185
+ userRoutes.delete('/users/:id', UserController.remove);
186
+ `,
187
+ 'src/routes/index.ts': `import { Froot } from 'frootjs';
188
+ import { healthRoutes } from './health.routes.js';
189
+ import { userRoutes } from './user.routes.js';
190
+
191
+ export const apiRoutes = new Froot();
192
+
193
+ // Mount sub-apps
194
+ apiRoutes.route('/api', healthRoutes);
195
+ apiRoutes.route('/api', userRoutes);
196
+ `,
197
+ 'src/middleware/request-logger.ts': `import type { MiddlewareHandler } from 'frootjs';
198
+
199
+ export const requestLogger: MiddlewareHandler = async (c, next) => {
200
+ const start = performance.now();
201
+ await next();
202
+ const duration = (performance.now() - start).toFixed(2);
203
+ console.log(\`[\${c.req.method}] \${c.req.path} - \${c.statusCode} (\${duration}ms)\`);
204
+ };
205
+ `,
206
+ 'src/app.ts': `import { Froot } from 'frootjs';
207
+ import { cors } from 'frootjs/cors';
208
+ import { requestLogger } from './middleware/request-logger.js';
209
+ import { apiRoutes } from './routes/index.js';
210
+
211
+ export const app = new Froot();
212
+
213
+ // Global middleware
214
+ app.use(cors());
215
+ app.use(requestLogger);
216
+
217
+ // Mount API routes
218
+ app.route('', apiRoutes);
219
+
220
+ // Root route
221
+ app.get('/', (c) => {
222
+ return c.json({
223
+ message: 'Welcome to Froot MVC API',
224
+ docs: '/api/health',
225
+ });
226
+ });
227
+ `,
228
+ 'src/server.ts': `import { app } from './app.js';
229
+
230
+ const PORT = Number(process.env.PORT) || 3000;
231
+
232
+ app.listen(PORT, () => {
233
+ console.log(\`Server is running on http://localhost:\${PORT}\`);
234
+ });
235
+ `,
236
+ };
237
+ },
238
+ };
239
+ //# sourceMappingURL=mvc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mvc.js","sourceRoot":"","sources":["../../src/templates/mvc.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAuC,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAEpG,MAAM,CAAC,MAAM,WAAW,GAAuB;IAC7C,EAAE,EAAE,KAAK;IACT,IAAI,EAAE,4BAA4B;IAClC,WAAW,EAAE,yEAAyE;IACtF,QAAQ,EAAE,CAAC,WAAmB,EAAmB,EAAE;QACjD,OAAO;YACL,cAAc,EAAE,IAAI,CAAC,SAAS,CAC5B;gBACE,IAAI,EAAE,WAAW;gBACjB,OAAO,EAAE,OAAO;gBAChB,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE;oBACP,GAAG,EAAE,+CAA+C;oBACpD,KAAK,EAAE,KAAK;oBACZ,KAAK,EAAE,qBAAqB;oBAC5B,IAAI,EAAE,6BAA6B;iBACpC;gBACD,YAAY,EAAE;oBACZ,OAAO,EAAE,QAAQ;iBAClB;gBACD,eAAe,EAAE;oBACf,aAAa,EAAE,UAAU;oBACzB,UAAU,EAAE,QAAQ;iBACrB;aACF,EACD,IAAI,EACJ,CAAC,CACF,GAAG,IAAI;YAER,eAAe,EAAE,eAAe;YAChC,YAAY,EAAE,gBAAgB;YAE9B,WAAW,EAAE,KAAK,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsClC;YAEK,0BAA0B,EAAE;;;;;;;;;;;;;CAajC;YAEK,8BAA8B,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkCrC;YAEK,sCAAsC,EAAE;;;;;;;;;;;CAW7C;YAEK,oCAAoC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqC3C;YAEK,6BAA6B,EAAE;;;;;;CAMpC;YAEK,2BAA2B,EAAE;;;;;;;;;CASlC;YAEK,qBAAqB,EAAE;;;;;;;;;CAS5B;YAEK,kCAAkC,EAAE;;;;;;;;CAQzC;YAEK,YAAY,EAAE;;;;;;;;;;;;;;;;;;;;;CAqBnB;YAEK,eAAe,EAAE;;;;;;;CAOtB;SACI,CAAC;IACJ,CAAC;CACF,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * OpenAPI 3.1 & Scalar Docs Template
3
+ * Self-documenting REST API with schema validation, OpenAPI 3.1 JSON, and Scalar interactive UI.
4
+ */
5
+ import { TemplateDefinition } from './types.js';
6
+ export declare const openapiTemplate: TemplateDefinition;
7
+ //# sourceMappingURL=openapi.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openapi.d.ts","sourceRoot":"","sources":["../../src/templates/openapi.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,kBAAkB,EAAsD,MAAM,YAAY,CAAC;AAEpG,eAAO,MAAM,eAAe,EAAE,kBAgN7B,CAAC"}
@@ -0,0 +1,206 @@
1
+ /**
2
+ * OpenAPI 3.1 & Scalar Docs Template
3
+ * Self-documenting REST API with schema validation, OpenAPI 3.1 JSON, and Scalar interactive UI.
4
+ */
5
+ import { COMMON_GITIGNORE, COMMON_TSCONFIG } from './types.js';
6
+ export const openapiTemplate = {
7
+ id: 'openapi',
8
+ name: 'OpenAPI 3.1 & Interactive Docs',
9
+ description: 'Self-documenting REST API with auto-generated OpenAPI 3.1 spec & Scalar reference UI',
10
+ generate: (projectName) => {
11
+ return {
12
+ 'package.json': JSON.stringify({
13
+ name: projectName,
14
+ version: '1.0.0',
15
+ type: 'module',
16
+ scripts: {
17
+ dev: 'node --experimental-strip-types src/server.ts',
18
+ build: 'tsc',
19
+ start: 'node dist/server.js',
20
+ test: 'node --test tests/*.test.ts',
21
+ },
22
+ dependencies: {
23
+ frootjs: '^1.0.1',
24
+ },
25
+ devDependencies: {
26
+ '@types/node': '^22.10.0',
27
+ typescript: '^5.8.0',
28
+ },
29
+ }, null, 2) + '\n',
30
+ 'tsconfig.json': COMMON_TSCONFIG,
31
+ '.gitignore': COMMON_GITIGNORE,
32
+ 'README.md': `# ${projectName}
33
+
34
+ Froot application built with the **OpenAPI 3.1 & Interactive Docs** pattern.
35
+
36
+ ## Features
37
+
38
+ - **Automatic OpenAPI 3.1 Spec**: Machine-readable spec available at \`/openapi.json\`
39
+ - **Scalar Interactive UI**: Modern, high-performance API reference documentation at \`/docs\`
40
+ - **Typed Schema Contracts**: Schemas defined inline with routes, supporting JSON Schema, Standard Schema v1, and Zod
41
+
42
+ ## Getting Started
43
+
44
+ \`\`\`bash
45
+ # 1. Install dependencies
46
+ pnpm install
47
+
48
+ # 2. Run in development mode
49
+ pnpm run dev
50
+
51
+ # 3. Explore Interactive Docs
52
+ http://localhost:3000/docs
53
+ \`\`\`
54
+ `,
55
+ 'src/schemas.ts': `export const TaskSchema = {
56
+ type: 'object',
57
+ properties: {
58
+ id: { type: 'string', description: 'Unique task identifier' },
59
+ title: { type: 'string', description: 'Task title' },
60
+ completed: { type: 'boolean', description: 'Completion status' },
61
+ createdAt: { type: 'string', format: 'date-time' },
62
+ },
63
+ required: ['id', 'title', 'completed', 'createdAt'],
64
+ };
65
+
66
+ export const CreateTaskInputSchema = {
67
+ type: 'object',
68
+ properties: {
69
+ title: { type: 'string', description: 'Title of the new task' },
70
+ },
71
+ required: ['title'],
72
+ };
73
+
74
+ export const ErrorResponseSchema = {
75
+ type: 'object',
76
+ properties: {
77
+ error: { type: 'string', description: 'Error message' },
78
+ },
79
+ required: ['error'],
80
+ };
81
+ `,
82
+ 'src/routes/tasks.ts': `import { Froot } from 'frootjs';
83
+ import { TaskSchema, CreateTaskInputSchema, ErrorResponseSchema } from '../schemas.js';
84
+
85
+ export interface Task {
86
+ id: string;
87
+ title: string;
88
+ completed: boolean;
89
+ createdAt: string;
90
+ }
91
+
92
+ const tasks: Task[] = [
93
+ { id: 'task_1', title: 'Spin up Froot project', completed: true, createdAt: new Date().toISOString() },
94
+ { id: 'task_2', title: 'Explore Scalar documentation at /docs', completed: false, createdAt: new Date().toISOString() },
95
+ ];
96
+
97
+ export const taskRoutes = new Froot();
98
+
99
+ // List tasks
100
+ taskRoutes.get(
101
+ '/tasks',
102
+ {
103
+ summary: 'List all tasks',
104
+ description: 'Retrieves the complete list of scheduled tasks.',
105
+ tags: ['Tasks'],
106
+ operationId: 'listTasks',
107
+ response: {
108
+ type: 'array',
109
+ items: TaskSchema,
110
+ },
111
+ },
112
+ (c) => c.json(tasks)
113
+ );
114
+
115
+ // Get task by ID
116
+ taskRoutes.get(
117
+ '/tasks/:id',
118
+ {
119
+ summary: 'Get task by ID',
120
+ tags: ['Tasks'],
121
+ operationId: 'getTaskById',
122
+ params: {
123
+ type: 'object',
124
+ properties: {
125
+ id: { type: 'string', description: 'Task ID' },
126
+ },
127
+ required: ['id'],
128
+ },
129
+ response: TaskSchema,
130
+ errors: {
131
+ 404: ErrorResponseSchema,
132
+ },
133
+ },
134
+ (c) => {
135
+ const id = c.req.param('id');
136
+ const task = tasks.find((t) => t.id === id);
137
+ if (!task) {
138
+ return c.json({ error: 'Task not found' }, 404);
139
+ }
140
+ return c.json(task);
141
+ }
142
+ );
143
+
144
+ // Create task
145
+ taskRoutes.post(
146
+ '/tasks',
147
+ {
148
+ summary: 'Create a new task',
149
+ tags: ['Tasks'],
150
+ operationId: 'createTask',
151
+ body: CreateTaskInputSchema,
152
+ response: TaskSchema,
153
+ errors: {
154
+ 400: ErrorResponseSchema,
155
+ },
156
+ },
157
+ async (c) => {
158
+ const body = await c.req.json<{ title?: string }>();
159
+ if (!body.title) {
160
+ return c.json({ error: 'Title is required' }, 400);
161
+ }
162
+ const newTask: Task = {
163
+ id: 'task_' + Math.random().toString(36).substring(2, 9),
164
+ title: body.title,
165
+ completed: false,
166
+ createdAt: new Date().toISOString(),
167
+ };
168
+ tasks.push(newTask);
169
+ return c.json(newTask, 201);
170
+ }
171
+ );
172
+ `,
173
+ 'src/server.ts': `import { Froot } from 'frootjs';
174
+ import { cors } from 'frootjs/cors';
175
+ import { taskRoutes } from './routes/tasks.js';
176
+
177
+ export const app = new Froot();
178
+
179
+ app.use(cors());
180
+
181
+ // Mount sub-routes
182
+ app.route('/api', taskRoutes);
183
+
184
+ // Root redirect to /docs
185
+ app.get('/', (c) => {
186
+ return c.redirect('/docs');
187
+ });
188
+
189
+ // Auto-mount OpenAPI 3.1 specification (/openapi.json) and Scalar UI (/docs)
190
+ app.openapi({
191
+ title: '${projectName} API',
192
+ version: '1.0.0',
193
+ description: 'Self-documenting API powered by Froot OpenAPI 3.1 & Scalar Reference UI.',
194
+ });
195
+
196
+ const PORT = Number(process.env.PORT) || 3000;
197
+ app.listen(PORT, () => {
198
+ console.log(\`Server running on http://localhost:\${PORT}\`);
199
+ console.log(\`Interactive Docs available at http://localhost:\${PORT}/docs\`);
200
+ console.log(\`OpenAPI JSON available at http://localhost:\${PORT}/openapi.json\`);
201
+ });
202
+ `,
203
+ };
204
+ },
205
+ };
206
+ //# sourceMappingURL=openapi.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openapi.js","sourceRoot":"","sources":["../../src/templates/openapi.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAuC,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAEpG,MAAM,CAAC,MAAM,eAAe,GAAuB;IACjD,EAAE,EAAE,SAAS;IACb,IAAI,EAAE,gCAAgC;IACtC,WAAW,EAAE,sFAAsF;IACnG,QAAQ,EAAE,CAAC,WAAmB,EAAmB,EAAE;QACjD,OAAO;YACL,cAAc,EAAE,IAAI,CAAC,SAAS,CAC5B;gBACE,IAAI,EAAE,WAAW;gBACjB,OAAO,EAAE,OAAO;gBAChB,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE;oBACP,GAAG,EAAE,+CAA+C;oBACpD,KAAK,EAAE,KAAK;oBACZ,KAAK,EAAE,qBAAqB;oBAC5B,IAAI,EAAE,6BAA6B;iBACpC;gBACD,YAAY,EAAE;oBACZ,OAAO,EAAE,QAAQ;iBAClB;gBACD,eAAe,EAAE;oBACf,aAAa,EAAE,UAAU;oBACzB,UAAU,EAAE,QAAQ;iBACrB;aACF,EACD,IAAI,EACJ,CAAC,CACF,GAAG,IAAI;YAER,eAAe,EAAE,eAAe;YAChC,YAAY,EAAE,gBAAgB;YAE9B,WAAW,EAAE,KAAK,WAAW;;;;;;;;;;;;;;;;;;;;;;CAsBlC;YAEK,gBAAgB,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;CA0BvB;YAEK,qBAAqB,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0F5B;YAEK,eAAe,EAAE;;;;;;;;;;;;;;;;;;YAkBX,WAAW;;;;;;;;;;;CAWtB;SACI,CAAC;IACJ,CAAC;CACF,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Streaming & Server-Sent Events (SSE) Template
3
+ * Demonstrates real-time SSE streaming, LLM-style token streaming, and an interactive HTML live client.
4
+ */
5
+ import { TemplateDefinition } from './types.js';
6
+ export declare const streamingTemplate: TemplateDefinition;
7
+ //# sourceMappingURL=streaming.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"streaming.d.ts","sourceRoot":"","sources":["../../src/templates/streaming.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,kBAAkB,EAAsD,MAAM,YAAY,CAAC;AAEpG,eAAO,MAAM,iBAAiB,EAAE,kBAmN/B,CAAC"}
@@ -0,0 +1,211 @@
1
+ /**
2
+ * Streaming & Server-Sent Events (SSE) Template
3
+ * Demonstrates real-time SSE streaming, LLM-style token streaming, and an interactive HTML live client.
4
+ */
5
+ import { COMMON_GITIGNORE, COMMON_TSCONFIG } from './types.js';
6
+ export const streamingTemplate = {
7
+ id: 'streaming',
8
+ name: 'Real-Time Streaming & SSE',
9
+ description: 'Server-Sent Events, AI token streaming simulation, and interactive web dashboard',
10
+ generate: (projectName) => {
11
+ return {
12
+ 'package.json': JSON.stringify({
13
+ name: projectName,
14
+ version: '1.0.0',
15
+ type: 'module',
16
+ scripts: {
17
+ dev: 'node --experimental-strip-types src/server.ts',
18
+ build: 'tsc',
19
+ start: 'node dist/server.js',
20
+ },
21
+ dependencies: {
22
+ frootjs: '^1.0.1',
23
+ },
24
+ devDependencies: {
25
+ '@types/node': '^22.10.0',
26
+ typescript: '^5.8.0',
27
+ },
28
+ }, null, 2) + '\n',
29
+ 'tsconfig.json': COMMON_TSCONFIG,
30
+ '.gitignore': COMMON_GITIGNORE,
31
+ 'README.md': `# ${projectName}
32
+
33
+ Froot application built with the **Real-Time Streaming & Server-Sent Events (SSE)** pattern.
34
+
35
+ ## Features
36
+
37
+ - **SSE Telemetry Channel**: Streams real-time metrics (CPU memory, active requests) using \`streamSSE\`
38
+ - **AI Token Streamer**: Demonstrates streaming LLM-style markdown tokens chunk-by-chunk using \`streamText\`
39
+ - **Live Interactive Dashboard**: Embedded web client displaying real-time graphs and live chat stream
40
+
41
+ ## Getting Started
42
+
43
+ \`\`\`bash
44
+ # 1. Install dependencies
45
+ pnpm install
46
+
47
+ # 2. Run in development mode
48
+ pnpm run dev
49
+
50
+ # 3. Open in browser
51
+ http://localhost:3000
52
+ \`\`\`
53
+ `,
54
+ 'src/server.ts': `import { Froot } from 'frootjs';
55
+ import { cors } from 'frootjs/cors';
56
+ import { streamSSE, streamText, sleep } from 'frootjs/streaming';
57
+
58
+ export const app = new Froot();
59
+
60
+ app.use(cors());
61
+
62
+ // Embedded Dashboard HTML
63
+ app.get('/', (c) => {
64
+ return c.html(\`<!DOCTYPE html>
65
+ <html lang="en">
66
+ <head>
67
+ <meta charset="UTF-8">
68
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
69
+ <title>Froot Streaming Demo</title>
70
+ <style>
71
+ * { box-sizing: border-box; margin: 0; padding: 0; }
72
+ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #0f172a; color: #f8fafc; padding: 2rem; }
73
+ .container { max-width: 800px; margin: 0 auto; display: flex; flex-direction: column; gap: 1.5rem; }
74
+ .card { background: #1e293b; border: 1px solid #334155; border-radius: 8px; padding: 1.5rem; }
75
+ h1 { color: #10b981; font-size: 1.75rem; margin-bottom: 0.5rem; }
76
+ h2 { font-size: 1.25rem; margin-bottom: 1rem; color: #38bdf8; }
77
+ pre { background: #0b0f19; padding: 1rem; border-radius: 6px; overflow-x: auto; font-size: 0.9rem; line-height: 1.4; }
78
+ .metrics { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; margin-bottom: 1rem; }
79
+ .metric-box { background: #0b0f19; padding: 1rem; border-radius: 6px; text-align: center; }
80
+ .metric-value { font-size: 1.5rem; font-weight: bold; color: #10b981; }
81
+ .metric-label { font-size: 0.8rem; color: #94a3b8; text-transform: uppercase; margin-top: 0.25rem; }
82
+ button { background: #10b981; color: #0f172a; border: none; padding: 0.6rem 1.2rem; border-radius: 6px; font-weight: bold; cursor: pointer; }
83
+ button:hover { background: #059669; }
84
+ </style>
85
+ </head>
86
+ <body>
87
+ <div class="container">
88
+ <div>
89
+ <h1>Froot Streaming Engine</h1>
90
+ <p style="color: #94a3b8;">High-throughput Server-Sent Events & chunked HTTP streaming</p>
91
+ </div>
92
+
93
+ <!-- SSE Metrics Card -->
94
+ <div class="card">
95
+ <h2>Live Telemetry Stream (SSE)</h2>
96
+ <div class="metrics">
97
+ <div class="metric-box">
98
+ <div id="m-memory" class="metric-value">--</div>
99
+ <div class="metric-label">Memory (MB)</div>
100
+ </div>
101
+ <div class="metric-box">
102
+ <div id="m-uptime" class="metric-value">--</div>
103
+ <div class="metric-label">Uptime (s)</div>
104
+ </div>
105
+ <div class="metric-box">
106
+ <div id="m-count" class="metric-value">0</div>
107
+ <div class="metric-label">Events Received</div>
108
+ </div>
109
+ </div>
110
+ <pre id="sse-log">Connecting to /sse/metrics...</pre>
111
+ </div>
112
+
113
+ <!-- LLM Stream Card -->
114
+ <div class="card">
115
+ <h2>AI Token Stream Simulation</h2>
116
+ <div style="margin-bottom: 1rem;">
117
+ <button id="btn-stream">Generate Streaming Text</button>
118
+ </div>
119
+ <pre id="stream-output">Click button to trigger token-by-token stream...</pre>
120
+ </div>
121
+ </div>
122
+
123
+ <script>
124
+ // SSE Stream
125
+ let count = 0;
126
+ const es = new EventSource('/sse/metrics');
127
+ const log = document.getElementById('sse-log');
128
+ es.addEventListener('telemetry', (e) => {
129
+ const data = JSON.parse(e.data);
130
+ count++;
131
+ document.getElementById('m-memory').textContent = data.memoryUsageMB;
132
+ document.getElementById('m-uptime').textContent = data.uptimeSec;
133
+ document.getElementById('m-count').textContent = count;
134
+ log.textContent = 'Last event: ' + JSON.stringify(data, null, 2);
135
+ });
136
+
137
+ // Token Stream
138
+ const btn = document.getElementById('btn-stream');
139
+ const output = document.getElementById('stream-output');
140
+ btn.addEventListener('click', async () => {
141
+ btn.disabled = true;
142
+ output.textContent = '';
143
+ const response = await fetch('/api/chat/stream');
144
+ const reader = response.body.getReader();
145
+ const decoder = new TextDecoder();
146
+ while (true) {
147
+ const { value, done } = await reader.read();
148
+ if (done) break;
149
+ output.textContent += decoder.decode(value);
150
+ }
151
+ btn.disabled = false;
152
+ });
153
+ </script>
154
+ </body>
155
+ </html>\`);
156
+ });
157
+
158
+ // Real-time SSE Telemetry endpoint
159
+ app.get('/sse/metrics', (c) => {
160
+ return streamSSE(
161
+ c,
162
+ async (stream) => {
163
+ let eventId = 1;
164
+ while (!stream.signal.aborted) {
165
+ const mem = process.memoryUsage();
166
+ const payload = {
167
+ memoryUsageMB: Math.round(mem.rss / 1024 / 1024),
168
+ uptimeSec: Math.floor(process.uptime()),
169
+ timestamp: new Date().toISOString(),
170
+ };
171
+
172
+ await stream.writeSSE({
173
+ event: 'telemetry',
174
+ id: String(eventId++),
175
+ data: JSON.stringify(payload),
176
+ });
177
+
178
+ await sleep(1000, stream.signal);
179
+ }
180
+ },
181
+ { heartbeatMs: 15_000 }
182
+ );
183
+ });
184
+
185
+ // AI Token Streaming Simulation endpoint
186
+ app.get('/api/chat/stream', (c) => {
187
+ const sampleResponse = [
188
+ 'Hello! ', 'I ', 'am ', 'streaming ', 'from ', 'a ', 'Froot ', 'server. ',
189
+ 'Each ', 'token ', 'is ', 'dispatched ', 'with ', 'zero-copy ', 'efficiency.\\n\\n',
190
+ 'Froot ', 'supports ', 'standards-compliant ', 'WHATWG ', 'ReadableStream ',
191
+ 'pipelines ', 'compatible ', 'with ', 'all ', 'modern ', 'edge ', 'runtimes.',
192
+ ];
193
+
194
+ return streamText(c, async (stream) => {
195
+ for (const chunk of sampleResponse) {
196
+ if (stream.signal.aborted) break;
197
+ await stream.write(chunk);
198
+ await sleep(60, stream.signal);
199
+ }
200
+ });
201
+ });
202
+
203
+ const PORT = Number(process.env.PORT) || 3000;
204
+ app.listen(PORT, () => {
205
+ console.log(\`Streaming server running on http://localhost:\${PORT}\`);
206
+ });
207
+ `,
208
+ };
209
+ },
210
+ };
211
+ //# sourceMappingURL=streaming.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"streaming.js","sourceRoot":"","sources":["../../src/templates/streaming.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAuC,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAEpG,MAAM,CAAC,MAAM,iBAAiB,GAAuB;IACnD,EAAE,EAAE,WAAW;IACf,IAAI,EAAE,2BAA2B;IACjC,WAAW,EAAE,kFAAkF;IAC/F,QAAQ,EAAE,CAAC,WAAmB,EAAmB,EAAE;QACjD,OAAO;YACL,cAAc,EAAE,IAAI,CAAC,SAAS,CAC5B;gBACE,IAAI,EAAE,WAAW;gBACjB,OAAO,EAAE,OAAO;gBAChB,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE;oBACP,GAAG,EAAE,+CAA+C;oBACpD,KAAK,EAAE,KAAK;oBACZ,KAAK,EAAE,qBAAqB;iBAC7B;gBACD,YAAY,EAAE;oBACZ,OAAO,EAAE,QAAQ;iBAClB;gBACD,eAAe,EAAE;oBACf,aAAa,EAAE,UAAU;oBACzB,UAAU,EAAE,QAAQ;iBACrB;aACF,EACD,IAAI,EACJ,CAAC,CACF,GAAG,IAAI;YAER,eAAe,EAAE,eAAe;YAChC,YAAY,EAAE,gBAAgB;YAE9B,WAAW,EAAE,KAAK,WAAW;;;;;;;;;;;;;;;;;;;;;;CAsBlC;YAEK,eAAe,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyJtB;SACI,CAAC;IACJ,CAAC;CACF,CAAC"}