@tamayrp14/fire-api 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 (99) hide show
  1. package/README.md +276 -2
  2. package/bin/fire.js +139 -0
  3. package/bin/fire.mjs +670 -0
  4. package/bun.lock +20 -0
  5. package/docs/GETTING_STARTED.md +150 -0
  6. package/docs/LAUNCH_KIT.md +236 -0
  7. package/docs/superpowers/plans/2026-11-14-fire-api-framework.md +144 -0
  8. package/docs/superpowers/specs/fire-api-design-spec.md +111 -0
  9. package/examples/basic-api/index.ts +177 -0
  10. package/package.json +68 -4
  11. package/src/ai/mcp.ts +140 -0
  12. package/src/auth/auth.ts +110 -0
  13. package/src/auth/crypto.ts +70 -0
  14. package/src/auth/jwt.ts +105 -0
  15. package/src/cache/cache.ts +72 -0
  16. package/src/cli/bench.ts +118 -0
  17. package/src/cli/compile.ts +29 -0
  18. package/src/cli/create-app.ts +284 -0
  19. package/src/cli/docker.ts +51 -0
  20. package/src/cli/generator.ts +75 -0
  21. package/src/cli/routes.ts +76 -0
  22. package/src/client.ts +120 -0
  23. package/src/compat/express.ts +139 -0
  24. package/src/core/app.ts +723 -0
  25. package/src/core/context.ts +218 -0
  26. package/src/core/error.ts +42 -0
  27. package/src/core/router.ts +262 -0
  28. package/src/db/db-interface.ts +9 -0
  29. package/src/db/hub.ts +256 -0
  30. package/src/db/memory-adapter.ts +202 -0
  31. package/src/db/migrator.ts +100 -0
  32. package/src/db/sqlite-adapter.ts +58 -0
  33. package/src/docs/collection.ts +57 -0
  34. package/src/docs/scalar-ui.ts +25 -0
  35. package/src/events/event-bus.ts +61 -0
  36. package/src/index.ts +37 -0
  37. package/src/jobs/cron.ts +77 -0
  38. package/src/jobs/queue.ts +108 -0
  39. package/src/logging/logger.ts +66 -0
  40. package/src/metrics/metrics.ts +66 -0
  41. package/src/modules/product/product.route.ts +36 -0
  42. package/src/modules/product/product.schema.ts +10 -0
  43. package/src/schema/mock.ts +60 -0
  44. package/src/schema/openapi.ts +101 -0
  45. package/src/schema/schema.ts +519 -0
  46. package/src/schema/validator.ts +5 -0
  47. package/src/shield/compress.ts +61 -0
  48. package/src/shield/cors.ts +94 -0
  49. package/src/shield/headers.ts +55 -0
  50. package/src/shield/idempotency.ts +76 -0
  51. package/src/shield/rate-limiter.ts +81 -0
  52. package/src/shield/sanitizer.ts +23 -0
  53. package/src/shield/webhook.ts +101 -0
  54. package/src/static/serve.ts +51 -0
  55. package/src/storage/storage.ts +176 -0
  56. package/src/stream/sse.ts +89 -0
  57. package/src/studio/studio.ts +221 -0
  58. package/src/types.ts +241 -0
  59. package/src/ws/ws.ts +118 -0
  60. package/test/ai-mcp.test.ts +61 -0
  61. package/test/auth.test.ts +108 -0
  62. package/test/bench.test.ts +31 -0
  63. package/test/cache.test.ts +36 -0
  64. package/test/cli-create.test.ts +64 -0
  65. package/test/client.test.ts +54 -0
  66. package/test/collection-export.test.ts +52 -0
  67. package/test/compat.test.ts +48 -0
  68. package/test/compress.test.ts +48 -0
  69. package/test/context.test.ts +48 -0
  70. package/test/db.test.ts +62 -0
  71. package/test/docker.test.ts +28 -0
  72. package/test/events.test.ts +62 -0
  73. package/test/file-upload.test.ts +116 -0
  74. package/test/hooks.test.ts +77 -0
  75. package/test/idempotency.test.ts +65 -0
  76. package/test/integration.test.ts +111 -0
  77. package/test/jobs.test.ts +60 -0
  78. package/test/metrics.test.ts +40 -0
  79. package/test/mock.test.ts +33 -0
  80. package/test/multi-tenant.test.ts +80 -0
  81. package/test/observability.test.ts +95 -0
  82. package/test/openapi.test.ts +41 -0
  83. package/test/orm-relations.test.ts +85 -0
  84. package/test/orm-shortcuts.test.ts +52 -0
  85. package/test/rbac.test.ts +85 -0
  86. package/test/router-fastpath.test.ts +42 -0
  87. package/test/router.test.ts +84 -0
  88. package/test/schema.test.ts +49 -0
  89. package/test/seeder-migrator.test.ts +78 -0
  90. package/test/shield.test.ts +55 -0
  91. package/test/shorthand-dx.test.ts +31 -0
  92. package/test/sse.test.ts +43 -0
  93. package/test/static-serve.test.ts +53 -0
  94. package/test/storage.test.ts +31 -0
  95. package/test/studio.test.ts +43 -0
  96. package/test/tx.test.ts +92 -0
  97. package/test/webhook.test.ts +113 -0
  98. package/test/ws.test.ts +54 -0
  99. package/tsconfig.json +19 -0
package/bin/fire.mjs ADDED
@@ -0,0 +1,670 @@
1
+ #!/usr/bin/env node
2
+
3
+ // bin/fire.js
4
+ import * as path5 from "path";
5
+
6
+ // src/cli/generator.ts
7
+ import * as fs from "node:fs";
8
+ import * as path from "node:path";
9
+ function generateResource(resourceName, baseDir = process.cwd()) {
10
+ const cleanName = resourceName.toLowerCase().replace(/[^a-z0-9_-]/g, "");
11
+ const capitalized = cleanName.charAt(0).toUpperCase() + cleanName.slice(1);
12
+ const targetDir = path.join(baseDir, "src", "modules", cleanName);
13
+ if (!fs.existsSync(targetDir)) {
14
+ fs.mkdirSync(targetDir, { recursive: true });
15
+ }
16
+ const schemaPath = path.join(targetDir, `${cleanName}.schema.ts`);
17
+ const routePath = path.join(targetDir, `${cleanName}.route.ts`);
18
+ const schemaContent = `import { s } from 'fire-api';
19
+
20
+ export const Create${capitalized}Schema = s.object({
21
+ name: s.string().min(2).description('${capitalized} title or name'),
22
+ status: s.enum(['active', 'draft']).default('active'),
23
+ });
24
+
25
+ export const ${capitalized}IdSchema = s.object({
26
+ id: s.string().min(1),
27
+ });
28
+ `;
29
+ const routeContent = `import { createRouter } from 'fire-api';
30
+ import { Create${capitalized}Schema, ${capitalized}IdSchema } from './${cleanName}.schema';
31
+
32
+ export const ${cleanName}Router = createRouter('/${cleanName}s');
33
+
34
+ // GET /${cleanName}s
35
+ ${cleanName}Router.get('/', {
36
+ cache: { ttl: 60, tags: ['${cleanName}s'] },
37
+ ai: { description: 'Fetch list of ${cleanName}s', tags: ['${capitalized}'] },
38
+ handler: async (ctx) => {
39
+ return ctx.db.${cleanName}s.find();
40
+ },
41
+ });
42
+
43
+ // POST /${cleanName}s
44
+ ${cleanName}Router.post('/', {
45
+ schema: { body: Create${capitalized}Schema },
46
+ ai: { description: 'Create a new ${cleanName}', tags: ['${capitalized}'] },
47
+ handler: async (ctx) => {
48
+ const created = await ctx.db.${cleanName}s.insert(ctx.body);
49
+ await ctx.cache.invalidate('${cleanName}s');
50
+ return ctx.created(created);
51
+ },
52
+ });
53
+
54
+ // GET /${cleanName}s/:id
55
+ ${cleanName}Router.get('/:id', {
56
+ schema: { params: ${capitalized}IdSchema },
57
+ ai: { description: 'Get a ${cleanName} by unique ID', tags: ['${capitalized}'] },
58
+ handler: async (ctx) => {
59
+ const item = await ctx.db.${cleanName}s.findById(ctx.params.id);
60
+ if (!item) return ctx.notFound('${capitalized} not found');
61
+ return ctx.ok(item);
62
+ },
63
+ });
64
+
65
+ // DELETE /${cleanName}s/:id
66
+ ${cleanName}Router.delete('/:id', async (ctx) => {
67
+ await ctx.db.${cleanName}s.delete(ctx.params.id);
68
+ return { deleted: true };
69
+ });
70
+ `;
71
+ fs.writeFileSync(schemaPath, schemaContent, "utf-8");
72
+ fs.writeFileSync(routePath, routeContent, "utf-8");
73
+ return { routeFile: routePath, schemaFile: schemaPath };
74
+ }
75
+
76
+ // src/cli/bench.ts
77
+ function percentile(sorted, p) {
78
+ if (sorted.length === 0)
79
+ return 0;
80
+ const index = Math.ceil(p / 100 * sorted.length) - 1;
81
+ return sorted[Math.max(0, Math.min(index, sorted.length - 1))];
82
+ }
83
+ async function runBenchmark(url, options = {}) {
84
+ const concurrency = options.concurrency || 10;
85
+ const total = options.totalRequests || 200;
86
+ const fetchFn = options.customFetch || globalThis.fetch;
87
+ const latencies = [];
88
+ let successfulRequests = 0;
89
+ let failedRequests = 0;
90
+ let sent = 0;
91
+ const startTime = performance.now();
92
+ async function worker() {
93
+ while (sent < total) {
94
+ sent++;
95
+ const reqStart = performance.now();
96
+ try {
97
+ const res = await fetchFn(url);
98
+ const reqDuration = performance.now() - reqStart;
99
+ latencies.push(reqDuration);
100
+ if (res.ok) {
101
+ successfulRequests++;
102
+ } else {
103
+ failedRequests++;
104
+ }
105
+ } catch {
106
+ const reqDuration = performance.now() - reqStart;
107
+ latencies.push(reqDuration);
108
+ failedRequests++;
109
+ }
110
+ }
111
+ }
112
+ const workers = Array.from({ length: concurrency }, () => worker());
113
+ await Promise.all(workers);
114
+ const durationMs = performance.now() - startTime;
115
+ const rps = Math.round(latencies.length / (durationMs / 1000) * 100) / 100;
116
+ latencies.sort((a, b) => a - b);
117
+ const sum = latencies.reduce((acc, val) => acc + val, 0);
118
+ return {
119
+ url,
120
+ totalRequests: latencies.length,
121
+ successfulRequests,
122
+ failedRequests,
123
+ durationMs: Math.round(durationMs),
124
+ rps,
125
+ latency: {
126
+ min: Math.round((latencies[0] || 0) * 100) / 100,
127
+ max: Math.round((latencies[latencies.length - 1] || 0) * 100) / 100,
128
+ avg: Math.round(sum / (latencies.length || 1) * 100) / 100,
129
+ p50: Math.round(percentile(latencies, 50) * 100) / 100,
130
+ p90: Math.round(percentile(latencies, 90) * 100) / 100,
131
+ p95: Math.round(percentile(latencies, 95) * 100) / 100,
132
+ p99: Math.round(percentile(latencies, 99) * 100) / 100
133
+ }
134
+ };
135
+ }
136
+ function formatBenchmarkReport(stats) {
137
+ const successRate = stats.totalRequests > 0 ? (stats.successfulRequests / stats.totalRequests * 100).toFixed(1) : "0";
138
+ return `
139
+ ==================================================
140
+ \uD83D\uDD25 FireAPI Benchmark Engine
141
+ ==================================================
142
+ Target: ${stats.url}
143
+ Duration: ${stats.durationMs} ms
144
+ Total Reqs: ${stats.totalRequests}
145
+ Success Rate: ${successRate}% (${stats.successfulRequests} ok / ${stats.failedRequests} fail)
146
+
147
+ \uD83D\uDE80 Requests/sec: ${stats.rps.toLocaleString()} req/s
148
+
149
+ Latency (ms):
150
+ Min: ${stats.latency.min} ms
151
+ P50: ${stats.latency.p50} ms
152
+ P90: ${stats.latency.p90} ms
153
+ P95: ${stats.latency.p95} ms
154
+ P99: ${stats.latency.p99} ms
155
+ Max: ${stats.latency.max} ms
156
+ ==================================================
157
+ `;
158
+ }
159
+
160
+ // src/cli/docker.ts
161
+ import * as fs2 from "node:fs";
162
+ import * as path2 from "node:path";
163
+ function generateDockerFiles(baseDir = process.cwd()) {
164
+ const dockerfilePath = path2.join(baseDir, "Dockerfile");
165
+ const dockerignorePath = path2.join(baseDir, ".dockerignore");
166
+ const dockerfileContent = `# ==============================================
167
+ # \uD83D\uDD25 FireAPI Ultra-Lightweight Production Container (< 25MB)
168
+ # ==============================================
169
+
170
+ # Stage 1: Build & Dependencies
171
+ FROM oven/bun:alpine AS base
172
+ WORKDIR /app
173
+
174
+ RUN addgroup -S appgroup && adduser -S appuser -G appgroup
175
+
176
+ COPY package.json bun.lock* ./
177
+ RUN bun install --production
178
+
179
+ COPY . .
180
+ RUN chown -R appuser:appgroup /app
181
+
182
+ # Stage 2: Distroless Production Runner
183
+ FROM oven/bun:alpine AS runner
184
+ WORKDIR /app
185
+
186
+ COPY --from=base /app /app
187
+
188
+ USER appuser
189
+ EXPOSE 3000
190
+
191
+ ENV NODE_ENV=production
192
+ ENV PORT=3000
193
+
194
+ CMD ["bun", "run", "src/index.ts"]
195
+ `;
196
+ const dockerignoreContent = `node_modules
197
+ dist
198
+ .git
199
+ .env*
200
+ *.log
201
+ uploads
202
+ `;
203
+ fs2.writeFileSync(dockerfilePath, dockerfileContent, "utf-8");
204
+ fs2.writeFileSync(dockerignorePath, dockerignoreContent, "utf-8");
205
+ return { dockerfilePath, dockerignorePath };
206
+ }
207
+
208
+ // src/cli/compile.ts
209
+ import { execSync } from "child_process";
210
+ import * as fs3 from "fs";
211
+ import * as path3 from "path";
212
+ function compileBinary(options = {}) {
213
+ const entry = options.entry || (fs3.existsSync("./src/index.ts") ? "./src/index.ts" : "./index.ts");
214
+ const output = options.output || "./dist/server";
215
+ const minifyFlag = options.minify !== false ? "--minify" : "";
216
+ const outDir = path3.dirname(output);
217
+ if (!fs3.existsSync(outDir)) {
218
+ fs3.mkdirSync(outDir, { recursive: true });
219
+ }
220
+ const command = `bun build ${entry} --compile ${minifyFlag} --outfile ${output}`.trim();
221
+ try {
222
+ const stdout = execSync(command, { encoding: "utf-8", stdio: "pipe" });
223
+ return { command, success: true, output: stdout };
224
+ } catch (err) {
225
+ return { command, success: false, output: err.message };
226
+ }
227
+ }
228
+
229
+ // src/cli/create-app.ts
230
+ import * as fs4 from "fs";
231
+ import * as path4 from "path";
232
+ function createAppProject(options) {
233
+ const projectName = options.name || "my-fire-api";
234
+ const baseDir = options.targetDir || path4.resolve(process.cwd(), projectName);
235
+ if (!fs4.existsSync(baseDir)) {
236
+ fs4.mkdirSync(baseDir, { recursive: true });
237
+ }
238
+ const filesCreated = [];
239
+ const writeFile = (relPath, content) => {
240
+ const fullPath = path4.join(baseDir, relPath);
241
+ const dir = path4.dirname(fullPath);
242
+ if (!fs4.existsSync(dir)) {
243
+ fs4.mkdirSync(dir, { recursive: true });
244
+ }
245
+ fs4.writeFileSync(fullPath, content.trim() + `
246
+ `, "utf-8");
247
+ filesCreated.push(relPath);
248
+ };
249
+ writeFile("package.json", JSON.stringify({
250
+ name: projectName,
251
+ version: "1.0.0",
252
+ description: "High-performance API built with FireAPI",
253
+ type: "module",
254
+ main: "src/index.ts",
255
+ scripts: {
256
+ dev: "bun --watch src/index.ts",
257
+ start: "bun src/index.ts",
258
+ test: "bun test",
259
+ compile: "fire compile ./dist/server",
260
+ docker: "fire docker",
261
+ routes: "fire routes"
262
+ },
263
+ dependencies: {
264
+ "fire-api": "^1.0.0"
265
+ },
266
+ devDependencies: {
267
+ "@types/node": "^20.0.0",
268
+ typescript: "^5.0.0"
269
+ }
270
+ }, null, 2));
271
+ writeFile("tsconfig.json", JSON.stringify({
272
+ compilerOptions: {
273
+ target: "ESNext",
274
+ module: "ESNext",
275
+ moduleResolution: "bundler",
276
+ strict: true,
277
+ skipLibCheck: true,
278
+ esModuleInterop: true,
279
+ allowSyntheticDefaultImports: true,
280
+ forceConsistentCasingInFileNames: true,
281
+ outDir: "./dist"
282
+ },
283
+ include: ["src/**/*", "test/**/*"]
284
+ }, null, 2));
285
+ const envContent = `PORT=3000
286
+ NODE_ENV=development
287
+ JWT_SECRET=super_secret_jwt_key_change_me_in_prod
288
+ DB_DRIVER=sqlite
289
+ DB_URL=./data.db
290
+ CORS_ORIGIN=*
291
+ `;
292
+ writeFile(".env.example", envContent);
293
+ writeFile(".env", envContent);
294
+ writeFile(".gitignore", `node_modules/
295
+ dist/
296
+ data.db
297
+ *.log
298
+ .env
299
+ .DS_Store
300
+ `);
301
+ writeFile("README.md", `# \uD83D\uDD25 ${projectName}
302
+
303
+ High-performance, modern Web Standard API built with **FireAPI**.
304
+
305
+ ## \uD83D\uDE80 Quick Start
306
+
307
+ \`\`\`bash
308
+ # 1. Install dependencies
309
+ bun install
310
+ # or
311
+ pnpm install
312
+
313
+ # 2. Run development server (with auto-reload)
314
+ bun dev
315
+
316
+ # 3. View Interactive API Docs
317
+ http://localhost:3000/docs
318
+
319
+ # 4. View Dev Dashboard (FireStudio)
320
+ http://localhost:3000/studio
321
+
322
+ # 5. Run Tests
323
+ bun test
324
+ \`\`\`
325
+ `);
326
+ writeFile("src/index.ts", `import { createApp, enableStudio } from 'fire-api';
327
+ import { userRouter } from './modules/users/users.router';
328
+ import { authRouter } from './modules/auth/auth.router';
329
+
330
+ const port = Number(process.env.PORT) || 3000;
331
+
332
+ const app = createApp({
333
+ port,
334
+ database: {
335
+ driver: (process.env.DB_DRIVER as any) || 'sqlite',
336
+ url: process.env.DB_URL || ':memory:',
337
+ },
338
+ security: {
339
+ cors: true,
340
+ rateLimit: { max: 500, window: '1m' },
341
+ },
342
+ health: true, // Auto /healthz & /readyz
343
+ docs: '/docs', // Scalar Docs at /docs & OpenAPI at /openapi.json
344
+ });
345
+
346
+ // Enable Embedded Dev Studio at /studio
347
+ enableStudio(app, { path: '/studio' });
348
+
349
+ // Mount Domain Modules
350
+ app.use(authRouter);
351
+ app.use(userRouter);
352
+
353
+ // Start Server
354
+ app.listen(port, () => {
355
+ console.log(\`\uD83D\uDD25 \${projectName} running at http://localhost:\${port}\`);
356
+ console.log(\`\uD83D\uDCD6 Interactive Docs: http://localhost:\${port}/docs\`);
357
+ console.log(\`\uD83D\uDDA5️ FireStudio: http://localhost:\${port}/studio\`);
358
+ console.log(\`\uD83E\uDE7A Health Probes: http://localhost:\${port}/healthz\`);
359
+ });
360
+
361
+ export type AppRouter = typeof app;
362
+ export default app;
363
+ `);
364
+ writeFile("src/modules/users/users.schema.ts", `import { s, type Infer } from 'fire-api';
365
+
366
+ export const UserSchema = s.object({
367
+ id: s.string().optional(),
368
+ name: s.string().min(2).description('Full name of the user'),
369
+ email: s.string().email().description('Unique email address'),
370
+ role: s.enum(['admin', 'user', 'moderator']).default('user'),
371
+ createdAt: s.string().optional(),
372
+ });
373
+
374
+ export const CreateUserSchema = s.object({
375
+ name: s.string().min(2),
376
+ email: s.string().email(),
377
+ role: s.enum(['admin', 'user', 'moderator']).default('user'),
378
+ });
379
+
380
+ export type User = Infer<typeof UserSchema>;
381
+ export type CreateUserInput = Infer<typeof CreateUserSchema>;
382
+ `);
383
+ writeFile("src/modules/users/users.router.ts", `import { createRouter } from 'fire-api';
384
+ import { CreateUserSchema } from './users.schema';
385
+
386
+ export const userRouter = createRouter('/users');
387
+
388
+ // GET /users - List all users
389
+ userRouter.get('/', {
390
+ ai: { description: 'Retrieve all users', tags: ['Users'] },
391
+ handler: async (ctx) => {
392
+ const users = await ctx.db.users.find();
393
+ return ctx.ok(users);
394
+ },
395
+ });
396
+
397
+ // GET /users/:id - Get user by id with relational posts
398
+ userRouter.get('/:id', {
399
+ ai: { description: 'Get a user by ID', tags: ['Users'] },
400
+ handler: async (ctx) => {
401
+ const user = await ctx.db.users.include('posts').findById(ctx.params.id);
402
+ if (!user) return ctx.notFound('User not found');
403
+ return ctx.ok(user);
404
+ },
405
+ });
406
+
407
+ // POST /users - Create user with validation & idempotency shield
408
+ userRouter.post('/', {
409
+ schema: { body: CreateUserSchema },
410
+ idempotent: true,
411
+ ai: { description: 'Create a new user', tags: ['Users'] },
412
+ handler: async (ctx) => {
413
+ const existing = await ctx.db.users.findOne({ email: ctx.body.email });
414
+ if (existing) {
415
+ return ctx.badRequest('Email is already registered');
416
+ }
417
+
418
+ const newUser = await ctx.db.users.insert(ctx.body);
419
+ await ctx.emit('user.created', newUser);
420
+
421
+ return ctx.created(newUser);
422
+ },
423
+ });
424
+
425
+ // DELETE /users/:id - Delete user cleanly
426
+ userRouter.delete('/:id', {
427
+ ai: { description: 'Delete a user by ID', tags: ['Users'] },
428
+ handler: async (ctx) => {
429
+ await ctx.db.users.delete(ctx.params.id);
430
+ return ctx.ok({ deleted: true });
431
+ },
432
+ });
433
+ `);
434
+ writeFile("src/modules/auth/auth.router.ts", `import { createAuth } from 'fire-api';
435
+
436
+ // Turnkey Web Crypto PBKDF2 + JWT Authentication Sub-Router
437
+ export const authRouter = createAuth({
438
+ jwtSecret: process.env.JWT_SECRET || 'dev_secret_key_123',
439
+ tokenExpiresIn: '7d',
440
+ });
441
+ `);
442
+ writeFile("test/app.test.ts", `import { describe, expect, it } from 'bun:test';
443
+ import app from '../src/index';
444
+
445
+ describe('${projectName} API E2E Suite', () => {
446
+ it('responds with 200 on /healthz probe', async () => {
447
+ const res = await app.handle(new Request('http://localhost:3000/healthz'));
448
+ expect(res.status).toBe(200);
449
+ const body = await res.json();
450
+ expect(body.status).toBe('ok');
451
+ });
452
+
453
+ it('creates and retrieves a user seamlessly', async () => {
454
+ // 1. Create User
455
+ const createRes = await app.handle(
456
+ new Request('http://localhost:3000/users', {
457
+ method: 'POST',
458
+ headers: {
459
+ 'Content-Type': 'application/json',
460
+ 'X-Idempotency-Key': 'key-initial-admin',
461
+ },
462
+ body: JSON.stringify({
463
+ name: 'Super Admin',
464
+ email: 'admin@fireapi.dev',
465
+ role: 'admin',
466
+ }),
467
+ })
468
+ );
469
+
470
+ expect(createRes.status).toBe(201);
471
+ const createdUser = await createRes.json();
472
+ expect(createdUser.name).toBe('Super Admin');
473
+
474
+ // 2. Query Users
475
+ const listRes = await app.handle(new Request('http://localhost:3000/users'));
476
+ expect(listRes.status).toBe(200);
477
+ const users = await listRes.json();
478
+ expect(users.length).toBeGreaterThanOrEqual(1);
479
+ });
480
+ });
481
+ `);
482
+ return { projectDir: baseDir, filesCreated };
483
+ }
484
+
485
+ // src/cli/routes.ts
486
+ function formatRoutesTable(router) {
487
+ const routes = router.getRoutes();
488
+ if (routes.length === 0) {
489
+ return "No routes registered in application.";
490
+ }
491
+ const rows = routes.map((r) => {
492
+ const roles = r.options.roles && r.options.roles.length > 0 ? r.options.roles.join(", ") : "-";
493
+ const cache = r.options.cache ? `${r.options.cache.ttl}s` : "-";
494
+ const idempotent = r.options.idempotent ? "✓" : "-";
495
+ const auth = r.options.security ? "Required" : r.options.roles ? "Role-Guarded" : "Public";
496
+ const description = r.options.ai?.description || "-";
497
+ return {
498
+ method: r.method,
499
+ path: r.path,
500
+ auth,
501
+ roles,
502
+ cache,
503
+ idempotent,
504
+ description
505
+ };
506
+ });
507
+ const headers = {
508
+ method: "METHOD",
509
+ path: "ROUTE PATH",
510
+ auth: "AUTH",
511
+ roles: "ROLES",
512
+ cache: "CACHE",
513
+ idempotent: "IDEMPOTENT",
514
+ description: "DESCRIPTION"
515
+ };
516
+ const colWidths = {
517
+ method: Math.max(headers.method.length, ...rows.map((r) => r.method.length)),
518
+ path: Math.max(headers.path.length, ...rows.map((r) => r.path.length)),
519
+ auth: Math.max(headers.auth.length, ...rows.map((r) => r.auth.length)),
520
+ roles: Math.max(headers.roles.length, ...rows.map((r) => r.roles.length)),
521
+ cache: Math.max(headers.cache.length, ...rows.map((r) => r.cache.length)),
522
+ idempotent: Math.max(headers.idempotent.length, ...rows.map((r) => r.idempotent.length)),
523
+ description: Math.max(headers.description.length, ...rows.map((r) => r.description.length))
524
+ };
525
+ const pad = (str, len) => str.padEnd(len, " ");
526
+ const headerLine = `| ${pad(headers.method, colWidths.method)} | ${pad(headers.path, colWidths.path)} | ${pad(headers.auth, colWidths.auth)} | ${pad(headers.roles, colWidths.roles)} | ${pad(headers.cache, colWidths.cache)} | ${pad(headers.idempotent, colWidths.idempotent)} | ${pad(headers.description, colWidths.description)} |`;
527
+ const sepLine = `|-${"-".repeat(colWidths.method)}-|-${"-".repeat(colWidths.path)}-|-${"-".repeat(colWidths.auth)}-|-${"-".repeat(colWidths.roles)}-|-${"-".repeat(colWidths.cache)}-|-${"-".repeat(colWidths.idempotent)}-|-${"-".repeat(colWidths.description)}-|`;
528
+ const bodyLines = rows.map((r) => `| ${pad(r.method, colWidths.method)} | ${pad(r.path, colWidths.path)} | ${pad(r.auth, colWidths.auth)} | ${pad(r.roles, colWidths.roles)} | ${pad(r.cache, colWidths.cache)} | ${pad(r.idempotent, colWidths.idempotent)} | ${pad(r.description, colWidths.description)} |`);
529
+ return [
530
+ `\uD83D\uDD25 FireAPI Route Manifest (${routes.length} total endpoints):`,
531
+ sepLine,
532
+ headerLine,
533
+ sepLine,
534
+ ...bodyLines,
535
+ sepLine
536
+ ].join(`
537
+ `);
538
+ }
539
+
540
+ // bin/fire.js
541
+ var [, , cmd, subcmd, target] = process.argv;
542
+ if (!cmd || cmd === "--help" || cmd === "-h") {
543
+ console.log(`
544
+ \uD83D\uDD25 FireAPI CLI - Next-Gen Modern API Toolkit
545
+
546
+ Usage:
547
+ npx fire-api <command> [options]
548
+
549
+ Commands:
550
+ create, new <name> Scaffold a complete, production-grade FireAPI project
551
+ routes [entry] Display visual terminal table of all registered routes
552
+ g, generate resource <name> Generate a new resource module (route + schema)
553
+ bench <url> Run ultra-fast load benchmark against an endpoint
554
+ docker Generate production-hardened multi-stage Dockerfile
555
+ compile [output] Compile standalone single-binary application
556
+ info Display FireAPI engine version and environment
557
+ help Show this help message
558
+
559
+ Examples:
560
+ npx fire-api create my-store-api
561
+ npx fire-api routes
562
+ npx fire-api g resource product
563
+ npx fire-api bench http://localhost:3000/books
564
+ npx fire-api compile ./dist/api-server
565
+ npx fire-api docker
566
+ `);
567
+ process.exit(0);
568
+ }
569
+ if (cmd === "create" || cmd === "new" || cmd === "init") {
570
+ const appName = subcmd || "my-fire-api";
571
+ console.log(`
572
+ \uD83D\uDD25 Scaffolding brand new FireAPI application in ./${appName}...
573
+ `);
574
+ const result = createAppProject({ name: appName });
575
+ console.log(`✨ Created ${result.filesCreated.length} files successfully:`);
576
+ result.filesCreated.forEach((f) => console.log(` \uD83D\uDCC4 ${f}`));
577
+ console.log(`
578
+ \uD83C\uDF89 FireAPI application ready!
579
+ To start developing:
580
+
581
+ cd ${appName}
582
+ bun install # (or pnpm install / npm install)
583
+ bun dev # Launches dev server with live reload at http://localhost:3000
584
+
585
+ \uD83D\uDCD6 Interactive Docs: http://localhost:3000/docs
586
+ \uD83D\uDDA5️ FireStudio: http://localhost:3000/studio
587
+ \uD83E\uDE7A Health Probes: http://localhost:3000/healthz
588
+ `);
589
+ process.exit(0);
590
+ }
591
+ if (cmd === "routes" || cmd === "route:list") {
592
+ const entryPath = subcmd || "./src/index.ts";
593
+ const fullPath = path5.resolve(process.cwd(), entryPath);
594
+ try {
595
+ const mod = await import(fullPath);
596
+ const app = mod.default || mod.app;
597
+ if (app && app.router) {
598
+ console.log(`
599
+ ` + formatRoutesTable(app.router) + `
600
+ `);
601
+ } else {
602
+ console.error("❌ Could not find exported FireApp instance in " + entryPath);
603
+ }
604
+ } catch (err) {
605
+ console.error("❌ Error inspecting routes:", err.message);
606
+ }
607
+ process.exit(0);
608
+ }
609
+ if (cmd === "info") {
610
+ console.log(`
611
+ \uD83D\uDD25 FireAPI Framework v1.0.0
612
+ ⚡ Engine: Bun / Node.js 20+ Native Web Standards
613
+ \uD83D\uDEE1️ Security Shield: Enabled by default
614
+ \uD83D\uDDC4️ Database Hub: SQLite, Postgres, MySQL, Mongo, In-Memory
615
+ \uD83E\uDD16 AI/MCP: Native Model Context Protocol Support
616
+ ⚡ JIT Fast-Path & Gzip Compression: Enabled
617
+ \uD83D\uDD12 RBAC & Policy Gates: Enabled
618
+ `);
619
+ process.exit(0);
620
+ }
621
+ if (cmd === "docker") {
622
+ const result = generateDockerFiles();
623
+ console.log(`
624
+ ✨ Successfully generated production Docker container files!`);
625
+ console.log(` \uD83D\uDC33 Dockerfile: ${result.dockerfilePath}`);
626
+ console.log(` \uD83D\uDCC4 .dockerignore: ${result.dockerignorePath}
627
+ `);
628
+ process.exit(0);
629
+ } else if (cmd === "compile") {
630
+ console.log("⚡ Compiling standalone native binary with Bun...");
631
+ const res = compileBinary({ output: subcmd || "./dist/server" });
632
+ if (res.success) {
633
+ console.log(`✨ Binary successfully compiled! Output: ${subcmd || "./dist/server"}`);
634
+ process.exit(0);
635
+ } else {
636
+ console.error(`❌ Compilation failed: ${res.output}`);
637
+ process.exit(1);
638
+ }
639
+ } else if (cmd === "bench") {
640
+ const url = subcmd;
641
+ if (!url) {
642
+ console.error("❌ Please specify a target URL, e.g.: npx fire-api bench http://localhost:3000/books");
643
+ process.exit(1);
644
+ }
645
+ console.log(`\uD83D\uDD25 Launching benchmark against ${url}...`);
646
+ runBenchmark(url, { concurrency: 20, totalRequests: 500 }).then((stats) => {
647
+ console.log(formatBenchmarkReport(stats));
648
+ process.exit(0);
649
+ }).catch((err) => {
650
+ console.error("Benchmark error:", err);
651
+ process.exit(1);
652
+ });
653
+ } else if (cmd === "g" || cmd === "generate") {
654
+ if (subcmd === "resource") {
655
+ if (!target) {
656
+ console.error("❌ Please specify a resource name, e.g.: npx fire-api g resource product");
657
+ process.exit(1);
658
+ }
659
+ const result = generateResource(target);
660
+ console.log(`
661
+ ✨ Successfully generated ${target} resource!`);
662
+ console.log(` \uD83D\uDCC4 Schema: ${result.schemaFile}`);
663
+ console.log(` \uD83D\uDEE3️ Route: ${result.routeFile}
664
+ `);
665
+ process.exit(0);
666
+ }
667
+ } else {
668
+ console.error(`Unknown command: ${cmd}. Run "npx fire-api --help" for available commands.`);
669
+ process.exit(1);
670
+ }
package/bun.lock ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "lockfileVersion": 2,
3
+ "configVersion": 1,
4
+ "workspaces": {
5
+ "": {
6
+ "name": "fire-api",
7
+ "devDependencies": {
8
+ "@types/node": "^22.13.0",
9
+ "typescript": "^5.7.3",
10
+ },
11
+ },
12
+ },
13
+ "packages": {
14
+ "@types/node": ["@types/node@22.20.5", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-U2+DNr+wSjpsTS/wZGYHq7GcwfuSmKiKvoPvK22zwTlRhU91yOniN4qRR5KhIjvif7ysw/dz/hKmfDH0Ris4aA=="],
15
+
16
+ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
17
+
18
+ "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
19
+ }
20
+ }