speexjs 0.2.0

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 (67) hide show
  1. package/README.md +555 -0
  2. package/dist/cli/index.d.ts +1 -0
  3. package/dist/cli/index.js +1017 -0
  4. package/dist/cli/index.js.map +1 -0
  5. package/dist/client/index.d.ts +73 -0
  6. package/dist/client/index.js +927 -0
  7. package/dist/client/index.js.map +1 -0
  8. package/dist/client/signals/index.d.ts +62 -0
  9. package/dist/client/signals/index.js +248 -0
  10. package/dist/client/signals/index.js.map +1 -0
  11. package/dist/client/vdom/index.d.ts +50 -0
  12. package/dist/client/vdom/index.js +540 -0
  13. package/dist/client/vdom/index.js.map +1 -0
  14. package/dist/client/vdom/jsx-runtime.d.ts +9 -0
  15. package/dist/client/vdom/jsx-runtime.js +203 -0
  16. package/dist/client/vdom/jsx-runtime.js.map +1 -0
  17. package/dist/index-CMkhSDh7.d.ts +97 -0
  18. package/dist/index.d.ts +18 -0
  19. package/dist/index.js +6402 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/jsx-DGrnv8QB.d.ts +8 -0
  22. package/dist/response-Ca8KWK5_.d.ts +173 -0
  23. package/dist/rpc/index.d.ts +70 -0
  24. package/dist/rpc/index.js +136 -0
  25. package/dist/rpc/index.js.map +1 -0
  26. package/dist/schema/index.d.ts +231 -0
  27. package/dist/schema/index.js +1160 -0
  28. package/dist/schema/index.js.map +1 -0
  29. package/dist/server/auth/index.d.ts +61 -0
  30. package/dist/server/auth/index.js +462 -0
  31. package/dist/server/auth/index.js.map +1 -0
  32. package/dist/server/cache/index.d.ts +45 -0
  33. package/dist/server/cache/index.js +238 -0
  34. package/dist/server/cache/index.js.map +1 -0
  35. package/dist/server/container/index.d.ts +20 -0
  36. package/dist/server/container/index.js +62 -0
  37. package/dist/server/container/index.js.map +1 -0
  38. package/dist/server/controller/index.d.ts +37 -0
  39. package/dist/server/controller/index.js +139 -0
  40. package/dist/server/controller/index.js.map +1 -0
  41. package/dist/server/database/index.d.ts +461 -0
  42. package/dist/server/database/index.js +1977 -0
  43. package/dist/server/database/index.js.map +1 -0
  44. package/dist/server/events/index.d.ts +29 -0
  45. package/dist/server/events/index.js +159 -0
  46. package/dist/server/events/index.js.map +1 -0
  47. package/dist/server/gate/index.d.ts +36 -0
  48. package/dist/server/gate/index.js +169 -0
  49. package/dist/server/gate/index.js.map +1 -0
  50. package/dist/server/http/index.d.ts +45 -0
  51. package/dist/server/http/index.js +871 -0
  52. package/dist/server/http/index.js.map +1 -0
  53. package/dist/server/index.d.ts +79 -0
  54. package/dist/server/index.js +4185 -0
  55. package/dist/server/index.js.map +1 -0
  56. package/dist/server/middleware/index.d.ts +5 -0
  57. package/dist/server/middleware/index.js +416 -0
  58. package/dist/server/middleware/index.js.map +1 -0
  59. package/dist/server/router/index.d.ts +5 -0
  60. package/dist/server/router/index.js +231 -0
  61. package/dist/server/router/index.js.map +1 -0
  62. package/dist/server/storage/index.d.ts +66 -0
  63. package/dist/server/storage/index.js +244 -0
  64. package/dist/server/storage/index.js.map +1 -0
  65. package/dist/session-guard-CZeN87L9.d.ts +48 -0
  66. package/dist/types-CXH8hPei.d.ts +38 -0
  67. package/package.json +138 -0
@@ -0,0 +1,1017 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/native/args.ts
4
+ function parseArgs(argv) {
5
+ const args = argv.slice(2);
6
+ const result = {
7
+ command: "",
8
+ args: [],
9
+ options: {}
10
+ };
11
+ if (args.length === 0) return result;
12
+ const first = args[0];
13
+ if (first.includes(":")) {
14
+ result.command = first;
15
+ parseOptions(args.slice(1), result);
16
+ return result;
17
+ }
18
+ result.command = first;
19
+ if (args.length > 1) {
20
+ const second = args[1];
21
+ if (!second.startsWith("-")) {
22
+ result.subcommand = second;
23
+ result.args.push(second);
24
+ parseOptions(args.slice(2), result);
25
+ return result;
26
+ }
27
+ }
28
+ parseOptions(args.slice(1), result);
29
+ return result;
30
+ }
31
+ function parseOptions(argv, result) {
32
+ let i = 0;
33
+ while (i < argv.length) {
34
+ const arg = argv[i];
35
+ if (arg === "--") {
36
+ for (let j = i + 1; j < argv.length; j++) {
37
+ result.args.push(argv[j]);
38
+ }
39
+ break;
40
+ }
41
+ if (arg.startsWith("--no-")) {
42
+ const key = arg.slice(5);
43
+ if (key) {
44
+ result.options[key] = false;
45
+ }
46
+ i++;
47
+ continue;
48
+ }
49
+ if (arg.startsWith("--")) {
50
+ const key = arg.slice(2);
51
+ if (i + 1 < argv.length && !argv[i + 1].startsWith("-")) {
52
+ const val = argv[i + 1];
53
+ const existing = result.options[key];
54
+ if (existing !== void 0) {
55
+ result.options[key] = Array.isArray(existing) ? [...existing, val] : [existing, val];
56
+ } else {
57
+ result.options[key] = val;
58
+ }
59
+ i += 2;
60
+ } else {
61
+ result.options[key] = true;
62
+ i++;
63
+ }
64
+ continue;
65
+ }
66
+ if (arg.startsWith("-") && arg.length === 2) {
67
+ const key = arg.slice(1);
68
+ if (i + 1 < argv.length && !argv[i + 1].startsWith("-")) {
69
+ const val = argv[i + 1];
70
+ result.options[key] = val;
71
+ i += 2;
72
+ } else {
73
+ result.options[key] = true;
74
+ i++;
75
+ }
76
+ continue;
77
+ }
78
+ result.args.push(arg);
79
+ i++;
80
+ }
81
+ }
82
+
83
+ // src/native/colors.ts
84
+ var RESET = "\x1B[0m";
85
+ var codes = {
86
+ red: "\x1B[31m",
87
+ green: "\x1B[32m",
88
+ yellow: "\x1B[33m",
89
+ blue: "\x1B[34m",
90
+ magenta: "\x1B[35m",
91
+ cyan: "\x1B[36m",
92
+ white: "\x1B[37m",
93
+ gray: "\x1B[90m",
94
+ bold: "\x1B[1m",
95
+ dim: "\x1B[2m",
96
+ italic: "\x1B[3m",
97
+ underline: "\x1B[4m"
98
+ };
99
+ var colors = {
100
+ red: (s) => `${codes.red}${s}${RESET}`,
101
+ green: (s) => `${codes.green}${s}${RESET}`,
102
+ yellow: (s) => `${codes.yellow}${s}${RESET}`,
103
+ blue: (s) => `${codes.blue}${s}${RESET}`,
104
+ magenta: (s) => `${codes.magenta}${s}${RESET}`,
105
+ cyan: (s) => `${codes.cyan}${s}${RESET}`,
106
+ gray: (s) => `${codes.gray}${s}${RESET}`,
107
+ white: (s) => `${codes.white}${s}${RESET}`,
108
+ bold: (s) => `${codes.bold}${s}${RESET}`,
109
+ dim: (s) => `${codes.dim}${s}${RESET}`,
110
+ italic: (s) => `${codes.italic}${s}${RESET}`,
111
+ underline: (s) => `${codes.underline}${s}${RESET}`
112
+ };
113
+ function isColorSupported() {
114
+ if (typeof process === "undefined") return false;
115
+ if (process.env.NO_COLOR) return false;
116
+ if (process.env.FORCE_COLOR) return true;
117
+ if (!process.stdout) return false;
118
+ if (!process.stdout.isTTY) return false;
119
+ return true;
120
+ }
121
+
122
+ // src/cli/commands/init.ts
123
+ import { existsSync, mkdirSync, writeFileSync } from "fs";
124
+ import { dirname, resolve } from "path";
125
+ var TEMPLATES = {
126
+ blank: {
127
+ dirs: ["src"],
128
+ files: {
129
+ "package.json": (name) => JSON.stringify(
130
+ {
131
+ name,
132
+ version: "0.1.0",
133
+ type: "module",
134
+ private: true,
135
+ scripts: {
136
+ dev: "SpeedX serve",
137
+ build: "SpeedX build",
138
+ start: "node dist/index.js"
139
+ },
140
+ dependencies: {
141
+ SpeedX: "latest"
142
+ },
143
+ devDependencies: {
144
+ "@types/node": "^26.0.1",
145
+ typescript: "^5.7.0"
146
+ }
147
+ },
148
+ null,
149
+ 2
150
+ ),
151
+ "tsconfig.json": JSON.stringify(
152
+ {
153
+ compilerOptions: {
154
+ target: "ES2022",
155
+ module: "ESNext",
156
+ moduleResolution: "bundler",
157
+ strict: true,
158
+ declaration: true,
159
+ sourceMap: true,
160
+ esModuleInterop: true,
161
+ isolatedModules: true,
162
+ resolveJsonModule: true,
163
+ outDir: "./dist",
164
+ rootDir: "./src"
165
+ },
166
+ include: ["src/**/*.ts"],
167
+ exclude: ["node_modules", "dist"]
168
+ },
169
+ null,
170
+ 2
171
+ ),
172
+ "src/index.ts": `import { SpeedX } from 'speedx/server'
173
+
174
+ const app = SpeedX()
175
+
176
+ const PORT = Number(process.env.PORT) || 3000
177
+
178
+ app.get('/', async ({ response }) => {
179
+ return response.html('<h1>SpeedX \u{1F680}</h1>')
180
+ })
181
+
182
+ app.listen(PORT, () => {
183
+ console.log(\`SpeedX running on http://localhost:\${PORT}\`)
184
+ })
185
+ `,
186
+ "src/app.ts": `import { SpeedX } from 'speedx/server'
187
+
188
+ export function createApp() {
189
+ const app = SpeedX()
190
+ return app
191
+ }
192
+ `,
193
+ ".env.example": `PORT=3000
194
+ NODE_ENV=development
195
+ `,
196
+ ".gitignore": `node_modules/
197
+ dist/
198
+ .env
199
+ *.log
200
+ `
201
+ }
202
+ },
203
+ fullstack: {
204
+ dirs: [
205
+ "src/server",
206
+ "src/server/controllers",
207
+ "src/server/middleware",
208
+ "src/client",
209
+ "src/client/components",
210
+ "src/client/pages",
211
+ "src/shared",
212
+ "public"
213
+ ],
214
+ files: {
215
+ "package.json": (name) => JSON.stringify(
216
+ {
217
+ name,
218
+ version: "0.1.0",
219
+ type: "module",
220
+ private: true,
221
+ scripts: {
222
+ dev: "SpeedX serve",
223
+ build: "tsc",
224
+ start: "node dist/server/index.js"
225
+ },
226
+ dependencies: {
227
+ SpeedX: "latest"
228
+ },
229
+ devDependencies: {
230
+ "@types/node": "^26.0.1",
231
+ typescript: "^5.7.0"
232
+ }
233
+ },
234
+ null,
235
+ 2
236
+ ),
237
+ "tsconfig.json": JSON.stringify(
238
+ {
239
+ compilerOptions: {
240
+ target: "ES2022",
241
+ module: "ESNext",
242
+ moduleResolution: "bundler",
243
+ strict: true,
244
+ declaration: true,
245
+ sourceMap: true,
246
+ esModuleInterop: true,
247
+ isolatedModules: true,
248
+ resolveJsonModule: true,
249
+ jsx: "react-jsx",
250
+ jsxImportSource: "@SpeedX/vdom",
251
+ outDir: "./dist",
252
+ rootDir: "./src"
253
+ },
254
+ include: ["src/**/*.ts", "src/**/*.tsx"],
255
+ exclude: ["node_modules", "dist"]
256
+ },
257
+ null,
258
+ 2
259
+ ),
260
+ "src/server/index.ts": `import { SpeedX } from 'speedx/server'
261
+ import { UserController } from './controllers/user.controller.js'
262
+
263
+ const PORT = Number(process.env.PORT) || 3000
264
+
265
+ const app = SpeedX()
266
+
267
+ app.controller(UserController)
268
+
269
+ app.get('/', async ({ response }) => {
270
+ return response.html(\`
271
+ <!DOCTYPE html>
272
+ <html lang="en">
273
+ <head>
274
+ <meta charset="UTF-8" />
275
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
276
+ <title>SpeedX Fullstack</title>
277
+ </head>
278
+ <body>
279
+ <div id="root"></div>
280
+ <script type="module" src="/client/index.js"></script>
281
+ </body>
282
+ </html>
283
+ \`)
284
+ })
285
+
286
+ app.listen(PORT, () => {
287
+ console.log(\`SpeedX running on http://localhost:\${PORT}\`)
288
+ })
289
+ `,
290
+ "src/server/controllers/user.controller.ts": `import { Controller, get, post } from 'speedx/server'
291
+
292
+ export class UserController extends Controller {
293
+ @get('/users')
294
+ async index({ response }) {
295
+ return response.json({ data: [] })
296
+ }
297
+
298
+ @post('/users')
299
+ async store({ request, response }) {
300
+ const body = await request.body()
301
+ return response.json({ data: body }, 201)
302
+ }
303
+ }
304
+ `,
305
+ "src/client/index.ts": `import { createApp } from './app.js'
306
+
307
+ document.addEventListener('DOMContentLoaded', () => {
308
+ createApp().mount('#root')
309
+ })
310
+ `,
311
+ "src/client/app.ts": `export function createApp() {
312
+ function mount(selector: string) {
313
+ const root = document.querySelector(selector)
314
+ if (!root) {
315
+ console.error('Root element not found:', selector)
316
+ return
317
+ }
318
+ root.innerHTML = \`
319
+ <div style="text-align:center;padding:2rem">
320
+ <h1>SpeedX Fullstack</h1>
321
+ <p>Welcome to SpeedX!</p>
322
+ </div>
323
+ \`
324
+ }
325
+
326
+ return { mount }
327
+ }
328
+ `,
329
+ "public/style.css": `* {
330
+ margin: 0;
331
+ padding: 0;
332
+ box-sizing: border-box;
333
+ }
334
+
335
+ body {
336
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
337
+ background: #0f172a;
338
+ color: #e2e8f0;
339
+ min-height: 100vh;
340
+ }
341
+
342
+ #root {
343
+ display: flex;
344
+ align-items: center;
345
+ justify-content: center;
346
+ min-height: 100vh;
347
+ }
348
+ `,
349
+ "src/shared/types.ts": `export interface ApiResponse<T = unknown> {
350
+ success: boolean
351
+ data: T
352
+ message?: string
353
+ }
354
+ `,
355
+ ".env.example": `PORT=3000
356
+ NODE_ENV=development
357
+ `,
358
+ ".gitignore": `node_modules/
359
+ dist/
360
+ .env
361
+ *.log
362
+ `
363
+ }
364
+ },
365
+ "api-only": {
366
+ dirs: ["src", "src/controllers", "src/middleware"],
367
+ files: {
368
+ "package.json": (name) => JSON.stringify(
369
+ {
370
+ name,
371
+ version: "0.1.0",
372
+ type: "module",
373
+ private: true,
374
+ scripts: {
375
+ dev: "SpeedX serve",
376
+ build: "tsc",
377
+ start: "node dist/index.js"
378
+ },
379
+ dependencies: {
380
+ SpeedX: "latest"
381
+ },
382
+ devDependencies: {
383
+ "@types/node": "^26.0.1",
384
+ typescript: "^5.7.0"
385
+ }
386
+ },
387
+ null,
388
+ 2
389
+ ),
390
+ "tsconfig.json": JSON.stringify(
391
+ {
392
+ compilerOptions: {
393
+ target: "ES2022",
394
+ module: "ESNext",
395
+ moduleResolution: "bundler",
396
+ strict: true,
397
+ declaration: true,
398
+ sourceMap: true,
399
+ esModuleInterop: true,
400
+ isolatedModules: true,
401
+ resolveJsonModule: true,
402
+ outDir: "./dist",
403
+ rootDir: "./src"
404
+ },
405
+ include: ["src/**/*.ts"],
406
+ exclude: ["node_modules", "dist"]
407
+ },
408
+ null,
409
+ 2
410
+ ),
411
+ "src/index.ts": `import { SpeedX } from 'speedx/server'
412
+
413
+ const PORT = Number(process.env.PORT) || 3000
414
+
415
+ const app = SpeedX()
416
+
417
+ app.get('/api/health', async ({ response }) => {
418
+ return response.json({ status: 'ok', timestamp: new Date().toISOString() })
419
+ })
420
+
421
+ app.listen(PORT, () => {
422
+ console.log(\`SpeedX API running on http://localhost:\${PORT}\`)
423
+ })
424
+ `,
425
+ "src/controllers/health.controller.ts": `import { Controller, get } from 'speedx/server'
426
+
427
+ export class HealthController extends Controller {
428
+ @get('/health')
429
+ async check({ response }) {
430
+ return response.json({
431
+ status: 'ok',
432
+ uptime: process.uptime(),
433
+ })
434
+ }
435
+ }
436
+ `,
437
+ "src/middleware/auth.ts": `import type { RouteContext } from 'speedx/server/router'
438
+
439
+ export function auth() {
440
+ return async (ctx: RouteContext, next: () => Promise<void>) => {
441
+ const token = ctx.request.headers.get('authorization')
442
+
443
+ if (!token) {
444
+ ctx.response.status(401).json({
445
+ error: 'Unauthorized',
446
+ message: 'Missing authorization header',
447
+ })
448
+ return
449
+ }
450
+
451
+ await next()
452
+ }
453
+ }
454
+ `,
455
+ ".env.example": `PORT=3000
456
+ NODE_ENV=development
457
+ `,
458
+ ".gitignore": `node_modules/
459
+ dist/
460
+ .env
461
+ *.log
462
+ `
463
+ }
464
+ }
465
+ };
466
+ var TEMPLATE_ALIASES = {
467
+ api: "api-only",
468
+ full: "fullstack"
469
+ };
470
+ function getTemplate(name) {
471
+ return TEMPLATE_ALIASES[name] ?? name;
472
+ }
473
+ function toPascalCase(str) {
474
+ return str.replace(/[-_\s]+(.)?/g, (_, c) => (c ?? "").toUpperCase()).replace(/^(.)/, (c) => c.toUpperCase());
475
+ }
476
+ async function initProject(name, options) {
477
+ const targetDir = resolve(process.cwd(), name);
478
+ if (existsSync(targetDir)) {
479
+ console.error(colors.red(`Directory '${name}' sudah ada!`));
480
+ process.exit(1);
481
+ }
482
+ const templateName = getTemplate(String(options.template || "blank"));
483
+ const template = TEMPLATES[templateName];
484
+ if (!template) {
485
+ console.error(
486
+ colors.red(
487
+ `Template '${options.template}' tidak dikenal. Gunakan: blank, fullstack, api-only`
488
+ )
489
+ );
490
+ process.exit(1);
491
+ }
492
+ mkdirSync(targetDir, { recursive: true });
493
+ for (const dir of template.dirs) {
494
+ mkdirSync(resolve(targetDir, dir), { recursive: true });
495
+ }
496
+ for (const [filePath, content] of Object.entries(template.files)) {
497
+ const fullPath = resolve(targetDir, filePath);
498
+ mkdirSync(dirname(fullPath), { recursive: true });
499
+ const resolvedContent = typeof content === "function" ? content(name) : content;
500
+ writeFileSync(fullPath, resolvedContent, "utf-8");
501
+ }
502
+ if (options.git !== false) {
503
+ try {
504
+ const { execSync } = await import("child_process");
505
+ execSync("git init", { cwd: targetDir, stdio: "ignore" });
506
+ } catch {
507
+ }
508
+ }
509
+ if (options.install !== false) {
510
+ const pm = String(options["package-manager"] || options.packageManager || "npm");
511
+ try {
512
+ const { execSync } = await import("child_process");
513
+ console.log(` ${colors.cyan("\u2192")} Installing dependencies with ${pm}...`);
514
+ execSync(`${pm} install`, { cwd: targetDir, stdio: "inherit" });
515
+ } catch {
516
+ console.log(
517
+ ` ${colors.yellow("!")} Dependency install skipped. Run '${pm} install' manually.`
518
+ );
519
+ }
520
+ }
521
+ const packageManager = String(options["package-manager"] || options.packageManager || "npm");
522
+ console.log();
523
+ console.log(`${colors.bold("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557")}`);
524
+ console.log(`${colors.bold("\u2551")} ${colors.green("SpeedX \u{1F680} Project Created")}${colors.bold(" \u2551")}`);
525
+ console.log(`${colors.bold("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D")}`);
526
+ console.log();
527
+ console.log(` ${colors.bold("Name:")} ${toPascalCase(name)}`);
528
+ console.log(` ${colors.bold("Template:")} ${templateName}`);
529
+ console.log(` ${colors.bold("Dir:")} ${targetDir}`);
530
+ console.log();
531
+ console.log(` ${colors.cyan("$")} cd ${name}`);
532
+ console.log(` ${colors.cyan("$")} ${packageManager} run dev`);
533
+ console.log();
534
+ }
535
+
536
+ // src/cli/commands/make-controller.ts
537
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
538
+ import { resolve as resolve2 } from "path";
539
+ function toPascalCase2(str) {
540
+ return str.replace(/[-_\s]+(.)?/g, (_, c) => (c ?? "").toUpperCase()).replace(/^(.)/, (c) => c.toUpperCase());
541
+ }
542
+ function toKebabCase(str) {
543
+ return str.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/([A-Z])([A-Z][a-z])/g, "$1-$2").toLowerCase();
544
+ }
545
+ function toCamelCase(str) {
546
+ return str.replace(/[-_\s]+(.)?/g, (_, c) => (c ?? "").toUpperCase()).replace(/^(.)/, (c) => c.toLowerCase());
547
+ }
548
+ function makeController(name) {
549
+ const className = `${toPascalCase2(name)}Controller`;
550
+ const fileName = `${toKebabCase(name)}.controller.ts`;
551
+ const targetDir = resolve2(process.cwd(), "src/server/controllers");
552
+ const fullPath = resolve2(targetDir, fileName);
553
+ const varName = toCamelCase(name);
554
+ if (existsSync2(fullPath)) {
555
+ console.error(colors.red(`File ${fileName} sudah ada!`));
556
+ process.exit(1);
557
+ }
558
+ mkdirSync2(targetDir, { recursive: true });
559
+ const content = `import { Controller, get, post, put, del } from 'speedx/server'
560
+ import type { RouteContext } from 'speedx/server/router'
561
+
562
+ export class ${className} extends Controller {
563
+ @get('/')
564
+ async index({ response }: RouteContext) {
565
+ return response.json({ data: [] })
566
+ }
567
+
568
+ @get('/:id')
569
+ async show({ response, params }: RouteContext) {
570
+ return response.json({ data: { id: params.id } })
571
+ }
572
+
573
+ @post('/')
574
+ async store({ request, response }: RouteContext) {
575
+ const body = await request.body()
576
+ return response.json({ data: body }, 201)
577
+ }
578
+
579
+ @put('/:id')
580
+ async update({ request, response, params }: RouteContext) {
581
+ const body = await request.body()
582
+ return response.json({ data: { id: params.id, ...body } })
583
+ }
584
+
585
+ @del('/:id')
586
+ async destroy({ response, params }: RouteContext) {
587
+ return response.json({ message: \`${className} deleted \${params.id}\` })
588
+ }
589
+ }
590
+
591
+ export const ${varName}Controller = ${className}
592
+ `;
593
+ writeFileSync2(fullPath, content, "utf-8");
594
+ console.log(
595
+ `${colors.green("\u2705")} Controller ${colors.bold(className)} dibuat di ${colors.cyan(fileName)}`
596
+ );
597
+ }
598
+
599
+ // src/cli/commands/make-middleware.ts
600
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
601
+ import { resolve as resolve3 } from "path";
602
+ function toPascalCase3(str) {
603
+ return str.replace(/[-_\s]+(.)?/g, (_, c) => (c ?? "").toUpperCase()).replace(/^(.)/, (c) => c.toUpperCase());
604
+ }
605
+ function toKebabCase2(str) {
606
+ return str.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/([A-Z])([A-Z][a-z])/g, "$1-$2").toLowerCase();
607
+ }
608
+ function toCamelCase2(str) {
609
+ return str.replace(/[-_\s]+(.)?/g, (_, c) => (c ?? "").toUpperCase()).replace(/^(.)/, (c) => c.toLowerCase());
610
+ }
611
+ function makeMiddleware(name) {
612
+ const functionName = toCamelCase2(name);
613
+ const className = toPascalCase3(name);
614
+ const fileName = `${toKebabCase2(name)}.middleware.ts`;
615
+ const targetDir = resolve3(process.cwd(), "src/server/middleware");
616
+ const fullPath = resolve3(targetDir, fileName);
617
+ if (existsSync3(fullPath)) {
618
+ console.error(colors.red(`File ${fileName} sudah ada!`));
619
+ process.exit(1);
620
+ }
621
+ mkdirSync3(targetDir, { recursive: true });
622
+ const content = `import type { RouteContext } from 'speedx/server/router'
623
+
624
+ export function ${functionName}(options?: Record<string, unknown>) {
625
+ return async (ctx: RouteContext, next: () => Promise<void>) => {
626
+ const start = Date.now()
627
+
628
+ await next()
629
+
630
+ const duration = Date.now() - start
631
+ console.log(\`[${className}Middleware] \${ctx.request.method} \${ctx.request.url} \${duration}ms\`)
632
+ }
633
+ }
634
+ `;
635
+ writeFileSync3(fullPath, content, "utf-8");
636
+ console.log(
637
+ `${colors.green("\u2705")} Middleware ${colors.bold(functionName)} dibuat di ${colors.cyan(fileName)}`
638
+ );
639
+ }
640
+
641
+ // src/cli/commands/make-schema.ts
642
+ import { existsSync as existsSync4, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
643
+ import { resolve as resolve4 } from "path";
644
+ function toPascalCase4(str) {
645
+ return str.replace(/[-_\s]+(.)?/g, (_, c) => (c ?? "").toUpperCase()).replace(/^(.)/, (c) => c.toUpperCase());
646
+ }
647
+ function toKebabCase3(str) {
648
+ return str.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/([A-Z])([A-Z][a-z])/g, "$1-$2").toLowerCase();
649
+ }
650
+ function makeSchema(name) {
651
+ const schemaName = `${toPascalCase4(name)}Schema`;
652
+ const typeName = toPascalCase4(name);
653
+ const fileName = `${toKebabCase3(name)}.schema.ts`;
654
+ const targetDir = resolve4(process.cwd(), "src/schemas");
655
+ const fullPath = resolve4(targetDir, fileName);
656
+ if (existsSync4(fullPath)) {
657
+ console.error(colors.red(`File ${fileName} sudah ada!`));
658
+ process.exit(1);
659
+ }
660
+ mkdirSync4(targetDir, { recursive: true });
661
+ const content = `import { s, type Infer } from 'speedx/schema'
662
+
663
+ export const ${schemaName} = s.object({
664
+ id: s.string().uuid(),
665
+ name: s.string().min(1).max(255),
666
+ createdAt: s.string().datetime(),
667
+ updatedAt: s.string().datetime().optional(),
668
+ })
669
+
670
+ export type ${typeName} = Infer<typeof ${schemaName}>
671
+
672
+ export const create${typeName}Schema = s.object({
673
+ name: s.string().min(1).max(255),
674
+ })
675
+
676
+ export type Create${typeName} = Infer<typeof create${typeName}Schema>
677
+ `;
678
+ writeFileSync4(fullPath, content, "utf-8");
679
+ console.log(
680
+ `${colors.green("\u2705")} Schema ${colors.bold(schemaName)} dibuat di ${colors.cyan(fileName)}`
681
+ );
682
+ }
683
+
684
+ // src/cli/commands/list-routes.ts
685
+ import { existsSync as existsSync5, readdirSync, readFileSync } from "fs";
686
+ import { resolve as resolve5 } from "path";
687
+ function extractDecorators(content) {
688
+ const pattern = /@(get|post|put|patch|del|delete)\s*\(\s*'([^']+)'\s*\)/g;
689
+ const results = [];
690
+ let match = pattern.exec(content);
691
+ while (match !== null) {
692
+ const method = match[1] === "del" ? "DELETE" : match[1].toUpperCase();
693
+ const path = match[2];
694
+ results.push({ method, path });
695
+ match = pattern.exec(content);
696
+ }
697
+ return results;
698
+ }
699
+ function listRoutes() {
700
+ const routesDir = resolve5(process.cwd(), "src/server/controllers");
701
+ if (!existsSync5(routesDir)) {
702
+ console.log(
703
+ ` ${colors.yellow("!")} Tidak ada route terdaftar. Buat controller dulu:`
704
+ );
705
+ console.log(` ${colors.cyan("speedx make:controller <name>")}`);
706
+ return;
707
+ }
708
+ const files = readdirSync(routesDir).filter((f) => f.endsWith(".ts"));
709
+ if (files.length === 0) {
710
+ console.log(
711
+ ` ${colors.yellow("!")} Tidak ada route terdaftar. Buat controller dulu:`
712
+ );
713
+ console.log(` ${colors.cyan("speedx make:controller <name>")}`);
714
+ return;
715
+ }
716
+ let total = 0;
717
+ console.log();
718
+ console.log(` ${colors.bold("\u{1F4CB} Daftar Route:")}`);
719
+ console.log();
720
+ for (const file of files) {
721
+ const content = readFileSync(resolve5(routesDir, file), "utf-8");
722
+ const routes = extractDecorators(content);
723
+ if (routes.length > 0) {
724
+ console.log(` ${colors.cyan("\u2500\u2500")} ${colors.bold(file.replace(".controller.ts", ""))} ${colors.cyan("\u2500\u2500")}`);
725
+ for (const { method, path } of routes) {
726
+ const coloredMethod = method === "GET" ? colors.green(method.padEnd(8)) : method === "POST" ? colors.blue(method.padEnd(8)) : method === "PUT" || method === "PATCH" ? colors.yellow(method.padEnd(8)) : colors.red(method.padEnd(8));
727
+ console.log(` ${coloredMethod} ${path}`);
728
+ total++;
729
+ }
730
+ console.log();
731
+ }
732
+ }
733
+ console.log(` ${colors.dim(`${total} route${total !== 1 ? "s" : ""} ditemukan`)}`);
734
+ console.log();
735
+ }
736
+
737
+ // src/cli/commands/serve.ts
738
+ import { existsSync as existsSync6 } from "fs";
739
+ import { resolve as resolve6 } from "path";
740
+
741
+ // src/native/logger.ts
742
+ var LOG_LEVELS = {
743
+ debug: 0,
744
+ info: 1,
745
+ warn: 2,
746
+ error: 3
747
+ };
748
+ var TIMEZONE_OFFSETS = {
749
+ WIB: 7,
750
+ WITA: 8,
751
+ WIT: 9,
752
+ UTC: 0
753
+ };
754
+ function pad(n) {
755
+ return n.toString().padStart(2, "0");
756
+ }
757
+ function formatTimestamp(tz) {
758
+ const offset = TIMEZONE_OFFSETS[tz ?? ""] ?? 7;
759
+ const now = /* @__PURE__ */ new Date();
760
+ const utc = now.getTime() + now.getTimezoneOffset() * 6e4;
761
+ const local = new Date(utc + offset * 36e5);
762
+ const y = local.getFullYear();
763
+ const M = pad(local.getMonth() + 1);
764
+ const d = pad(local.getDate());
765
+ const h = pad(local.getHours());
766
+ const m = pad(local.getMinutes());
767
+ const s = pad(local.getSeconds());
768
+ return `${y}-${M}-${d} ${h}:${m}:${s} ${tz ?? "WIB"}`;
769
+ }
770
+ var LEVEL_PREFIX = {
771
+ debug: "DEBUG",
772
+ info: "INFO",
773
+ warn: "WARN",
774
+ error: "ERROR"
775
+ };
776
+ var LEVEL_COLOR = {
777
+ debug: colors.gray,
778
+ info: colors.cyan,
779
+ warn: colors.yellow,
780
+ error: colors.red
781
+ };
782
+ var Logger = class _Logger {
783
+ _level;
784
+ _name;
785
+ _useColors;
786
+ _useTimestamps;
787
+ _timezone;
788
+ constructor(options) {
789
+ this._level = options?.level ?? "info";
790
+ this._name = options?.name ?? "";
791
+ this._useColors = options?.colors ?? isColorSupported();
792
+ this._useTimestamps = options?.timestamps ?? true;
793
+ this._timezone = options?.timezone ?? "WIB";
794
+ }
795
+ _format(level, msg, meta) {
796
+ const parts = [];
797
+ if (this._useTimestamps) {
798
+ parts.push(formatTimestamp(this._timezone));
799
+ }
800
+ if (this._useColors) {
801
+ parts.push(LEVEL_COLOR[level](LEVEL_PREFIX[level]));
802
+ } else {
803
+ parts.push(LEVEL_PREFIX[level]);
804
+ }
805
+ if (this._name) {
806
+ parts.push(this._useColors ? colors.dim(`[${this._name}]`) : `[${this._name}]`);
807
+ }
808
+ if (this._useColors) {
809
+ parts.push(LEVEL_COLOR[level](msg));
810
+ } else {
811
+ parts.push(msg);
812
+ }
813
+ if (meta && Object.keys(meta).length > 0) {
814
+ parts.push(JSON.stringify(meta));
815
+ }
816
+ return parts.join(" ");
817
+ }
818
+ debug(msg, meta) {
819
+ if (LOG_LEVELS[this._level] > LOG_LEVELS.debug) return;
820
+ console.debug(this._format("debug", msg, meta));
821
+ }
822
+ info(msg, meta) {
823
+ if (LOG_LEVELS[this._level] > LOG_LEVELS.info) return;
824
+ console.info(this._format("info", msg, meta));
825
+ }
826
+ warn(msg, meta) {
827
+ if (LOG_LEVELS[this._level] > LOG_LEVELS.warn) return;
828
+ console.warn(this._format("warn", msg, meta));
829
+ }
830
+ error(msg, meta) {
831
+ if (LOG_LEVELS[this._level] > LOG_LEVELS.error) return;
832
+ console.error(this._format("error", msg, meta));
833
+ }
834
+ child(name) {
835
+ const childName = this._name ? `${this._name}:${name}` : name;
836
+ return new _Logger({
837
+ level: this._level,
838
+ name: childName,
839
+ colors: this._useColors,
840
+ timestamps: this._useTimestamps,
841
+ timezone: this._timezone
842
+ });
843
+ }
844
+ setLevel(level) {
845
+ this._level = level;
846
+ }
847
+ };
848
+ var logger = new Logger();
849
+
850
+ // src/cli/commands/serve.ts
851
+ async function serve(options) {
852
+ const opts = {
853
+ port: options.port || options.p || 3e3,
854
+ host: options.host || options.H || "localhost",
855
+ dev: options.dev !== false
856
+ };
857
+ const port = parseInt(String(opts.port), 10);
858
+ const host = String(opts.host);
859
+ const serverEntry = resolve6(process.cwd(), "src/app.ts");
860
+ const serverEntryAlt = resolve6(process.cwd(), "src/server/index.ts");
861
+ const serverEntryIndex = resolve6(process.cwd(), "src/index.ts");
862
+ let entryPath = null;
863
+ if (existsSync6(serverEntry)) entryPath = serverEntry;
864
+ else if (existsSync6(serverEntryAlt)) entryPath = serverEntryAlt;
865
+ else if (existsSync6(serverEntryIndex)) entryPath = serverEntryIndex;
866
+ if (!entryPath) {
867
+ console.error(
868
+ colors.red(
869
+ "Tidak ditemukan file entry point. Buat src/app.ts atau src/index.ts"
870
+ )
871
+ );
872
+ process.exit(1);
873
+ }
874
+ if (opts.dev) {
875
+ logger.info(
876
+ `Development server starting at ${colors.cyan(`http://${host}:${port}`)}`
877
+ );
878
+ try {
879
+ const { app } = await import(entryPath);
880
+ if (!app || typeof app.listen !== "function") {
881
+ console.error(
882
+ colors.red(
883
+ "Entry point harus export { app } dengan method .listen()"
884
+ )
885
+ );
886
+ process.exit(1);
887
+ }
888
+ app.listen(port, host, () => {
889
+ console.log();
890
+ console.log(` ${colors.bold("SpeedX")} ${colors.green("running")}`);
891
+ console.log(` ${colors.dim("\u2192")} ${colors.cyan(`http://${host}:${port}`)}`);
892
+ console.log();
893
+ });
894
+ } catch (err) {
895
+ console.error(colors.red(`Gagal menjalankan server: ${err.message}`));
896
+ process.exit(1);
897
+ }
898
+ } else {
899
+ try {
900
+ const { app } = await import(entryPath);
901
+ if (!app || typeof app.listen !== "function") {
902
+ console.error(
903
+ colors.red(
904
+ "Entry point harus export { app } dengan method .listen()"
905
+ )
906
+ );
907
+ process.exit(1);
908
+ }
909
+ app.listen(port, host, () => {
910
+ console.log();
911
+ console.log(` ${colors.bold("SpeedX")} ${colors.green("running")}`);
912
+ console.log(` ${colors.dim("\u2192")} ${colors.cyan(`http://${host}:${port}`)}`);
913
+ console.log();
914
+ });
915
+ } catch (err) {
916
+ console.error(colors.red(`Gagal menjalankan server: ${err.message}`));
917
+ process.exit(1);
918
+ }
919
+ }
920
+ }
921
+
922
+ // src/cli/index.ts
923
+ function showHelp() {
924
+ console.log(`${colors.bold("SpeedX")} ${colors.cyan("v0.2.0")}`);
925
+ console.log("Fullstack JavaScript/TypeScript Framework");
926
+ console.log();
927
+ console.log(`${colors.bold("Usage:")}`);
928
+ console.log(" SpeedX init [name] [options] Buat project baru");
929
+ console.log(" SpeedX make:controller <name> Generate controller");
930
+ console.log(" SpeedX make:middleware <name> Generate middleware");
931
+ console.log(" SpeedX make:schema <name> Generate schema");
932
+ console.log(" SpeedX list-routes Lihat semua route");
933
+ console.log(" SpeedX serve [options] Jalankan server");
934
+ console.log(" SpeedX --help Bantuan ini");
935
+ console.log();
936
+ console.log(`${colors.bold("Aliases:")}`);
937
+ console.log(" SpeedX -v, --version Lihat versi");
938
+ console.log();
939
+ console.log(`${colors.bold("Options:")}`);
940
+ console.log(" --template <type> blank, fullstack, api-only");
941
+ console.log(" --frontend <fe> super, react, vue");
942
+ console.log(" --port <number> Port server (default: 3000)");
943
+ console.log(" --host <string> Host address (default: localhost)");
944
+ }
945
+ async function main() {
946
+ const parsed = parseArgs(process.argv);
947
+ const command = parsed.command;
948
+ switch (command) {
949
+ case "init": {
950
+ await initProject(parsed.args[0] || "my-app", parsed.options);
951
+ break;
952
+ }
953
+ case "make:controller": {
954
+ if (!parsed.args[0]) {
955
+ console.error(colors.red("Nama controller diperlukan"));
956
+ console.log(` ${colors.cyan("SpeedX make:controller <name>")}`);
957
+ process.exit(1);
958
+ }
959
+ await makeController(parsed.args[0]);
960
+ break;
961
+ }
962
+ case "make:middleware": {
963
+ if (!parsed.args[0]) {
964
+ console.error(colors.red("Nama middleware diperlukan"));
965
+ console.log(` ${colors.cyan("SpeedX make:middleware <name>")}`);
966
+ process.exit(1);
967
+ }
968
+ await makeMiddleware(parsed.args[0]);
969
+ break;
970
+ }
971
+ case "make:schema": {
972
+ if (!parsed.args[0]) {
973
+ console.error(colors.red("Nama schema diperlukan"));
974
+ console.log(` ${colors.cyan("SpeedX make:schema <name>")}`);
975
+ process.exit(1);
976
+ }
977
+ await makeSchema(parsed.args[0]);
978
+ break;
979
+ }
980
+ case "list-routes":
981
+ case "routes":
982
+ case "lr": {
983
+ await listRoutes();
984
+ break;
985
+ }
986
+ case "serve":
987
+ case "dev": {
988
+ await serve(parsed.options);
989
+ break;
990
+ }
991
+ case "help":
992
+ case "--help":
993
+ case "-h": {
994
+ showHelp();
995
+ break;
996
+ }
997
+ case "version":
998
+ case "--version":
999
+ case "-v": {
1000
+ console.log("SpeedX v0.2.0");
1001
+ break;
1002
+ }
1003
+ default: {
1004
+ if (command) {
1005
+ console.error(`${colors.red(`Command '${command}' tidak dikenal`)}`);
1006
+ console.log();
1007
+ }
1008
+ showHelp();
1009
+ if (command) process.exit(1);
1010
+ }
1011
+ }
1012
+ }
1013
+ main().catch((err) => {
1014
+ console.error(colors.red(`Error: ${err.message}`));
1015
+ process.exit(1);
1016
+ });
1017
+ //# sourceMappingURL=index.js.map