speexjs 0.2.3 → 0.4.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.
- package/README.md +32 -60
- package/dist/cli/index.js +300 -91
- package/dist/cli/index.js.map +1 -1
- package/dist/{index-CMkhSDh7.d.ts → index-C4xilc_E.d.ts} +4 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.js +2989 -1864
- package/dist/index.js.map +1 -1
- package/dist/rpc/index.d.ts +1 -1
- package/dist/schema/index.d.ts +36 -30
- package/dist/schema/index.js +12 -229
- package/dist/schema/index.js.map +1 -1
- package/dist/server/auth/index.d.ts +3 -1
- package/dist/server/auth/index.js +18 -12
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/cache/index.d.ts +2 -1
- package/dist/server/controller/index.d.ts +2 -1
- package/dist/server/database/index.d.ts +34 -2
- package/dist/server/database/index.js +1101 -30
- package/dist/server/database/index.js.map +1 -1
- package/dist/server/gate/index.d.ts +2 -1
- package/dist/server/http/index.js +19 -2
- package/dist/server/http/index.js.map +1 -1
- package/dist/server/index.d.ts +58 -4
- package/dist/server/index.js +2058 -716
- package/dist/server/index.js.map +1 -1
- package/dist/server/middleware/index.d.ts +2 -1
- package/dist/server/middleware/index.js +85 -34
- package/dist/server/middleware/index.js.map +1 -1
- package/dist/server/router/index.d.ts +2 -1
- package/dist/server/router/index.js +8 -6
- package/dist/server/router/index.js.map +1 -1
- package/dist/{types-CXH8hPei.d.ts → types-aW38f63o.d.ts} +1 -1
- package/package.json +142 -136
package/dist/cli/index.js
CHANGED
|
@@ -124,7 +124,7 @@ import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
|
124
124
|
import { dirname, resolve } from "path";
|
|
125
125
|
var TEMPLATES = {
|
|
126
126
|
blank: {
|
|
127
|
-
dirs: ["src"],
|
|
127
|
+
dirs: ["src", "src/config"],
|
|
128
128
|
files: {
|
|
129
129
|
"package.json": (name) => JSON.stringify(
|
|
130
130
|
{
|
|
@@ -135,13 +135,15 @@ var TEMPLATES = {
|
|
|
135
135
|
scripts: {
|
|
136
136
|
dev: "speexjs serve",
|
|
137
137
|
build: "speexjs build",
|
|
138
|
-
start: "node dist/index.js"
|
|
138
|
+
start: "node dist/index.js",
|
|
139
|
+
lint: "tsc --noEmit"
|
|
139
140
|
},
|
|
140
141
|
dependencies: {
|
|
141
142
|
speexjs: "latest"
|
|
142
143
|
},
|
|
143
144
|
devDependencies: {
|
|
144
145
|
"@types/node": "^26.0.1",
|
|
146
|
+
tsx: "^4.19.0",
|
|
145
147
|
typescript: "^5.7.0"
|
|
146
148
|
}
|
|
147
149
|
},
|
|
@@ -161,7 +163,8 @@ var TEMPLATES = {
|
|
|
161
163
|
isolatedModules: true,
|
|
162
164
|
resolveJsonModule: true,
|
|
163
165
|
outDir: "./dist",
|
|
164
|
-
rootDir: "./src"
|
|
166
|
+
rootDir: "./src",
|
|
167
|
+
skipLibCheck: true
|
|
165
168
|
},
|
|
166
169
|
include: ["src/**/*.ts"],
|
|
167
170
|
exclude: ["node_modules", "dist"]
|
|
@@ -170,28 +173,61 @@ var TEMPLATES = {
|
|
|
170
173
|
2
|
|
171
174
|
),
|
|
172
175
|
"src/index.ts": `import { speexjs } from 'speexjs/server'
|
|
176
|
+
import { schema } from 'speexjs/schema'
|
|
177
|
+
import { Config } from './config/index.js'
|
|
173
178
|
|
|
179
|
+
// \u2500\u2500\u2500 Application \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
174
180
|
const app = speexjs()
|
|
175
181
|
|
|
176
|
-
|
|
177
|
-
|
|
182
|
+
// \u2500\u2500\u2500 Routes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
178
183
|
app.get('/', async ({ response }) => {
|
|
179
|
-
return response.html(
|
|
184
|
+
return response.html(\`
|
|
185
|
+
<!DOCTYPE html>
|
|
186
|
+
<html lang="en">
|
|
187
|
+
<head>
|
|
188
|
+
<meta charset="UTF-8" />
|
|
189
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
190
|
+
<title>SpeexJS App</title>
|
|
191
|
+
</head>
|
|
192
|
+
<body>
|
|
193
|
+
<h1>SpeexJS \u{1F680}</h1>
|
|
194
|
+
<p>Server is running on port \${Config.port}</p>
|
|
195
|
+
</body>
|
|
196
|
+
</html>
|
|
197
|
+
\`)
|
|
180
198
|
})
|
|
181
199
|
|
|
182
|
-
|
|
183
|
-
|
|
200
|
+
// \u2500\u2500\u2500 Health Check \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
201
|
+
app.get('/api/health', async ({ response }) => {
|
|
202
|
+
return response.json({
|
|
203
|
+
status: 'ok',
|
|
204
|
+
timestamp: new Date().toISOString(),
|
|
205
|
+
uptime: process.uptime(),
|
|
206
|
+
})
|
|
184
207
|
})
|
|
185
|
-
`,
|
|
186
|
-
"src/app.ts": `import { speexjs } from 'speexjs/server'
|
|
187
208
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}
|
|
209
|
+
// \u2500\u2500\u2500 Start Server \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
210
|
+
app.listen(Config.port, () => {
|
|
211
|
+
console.log(\`\u2713 SpeexJS running at http://localhost:\${Config.port}\`)
|
|
212
|
+
})
|
|
192
213
|
`,
|
|
193
|
-
".
|
|
214
|
+
"src/config/index.ts": `export const Config = {
|
|
215
|
+
port: Number(process.env.PORT) || 3000,
|
|
216
|
+
host: process.env.HOST || 'localhost',
|
|
217
|
+
env: process.env.NODE_ENV || 'development',
|
|
218
|
+
appKey: process.env.APP_KEY || '',
|
|
219
|
+
isDev: process.env.NODE_ENV !== 'production',
|
|
220
|
+
isProd: process.env.NODE_ENV === 'production',
|
|
221
|
+
} as const
|
|
222
|
+
`,
|
|
223
|
+
".env.example": `# Server
|
|
224
|
+
PORT=3000
|
|
194
225
|
NODE_ENV=development
|
|
226
|
+
HOST=localhost
|
|
227
|
+
|
|
228
|
+
# Auth (change in production!)
|
|
229
|
+
APP_KEY=your-base64-32-byte-key-here
|
|
230
|
+
SESSION_SECRET=change-this-in-production
|
|
195
231
|
`,
|
|
196
232
|
".gitignore": `node_modules/
|
|
197
233
|
dist/
|
|
@@ -228,6 +264,7 @@ dist/
|
|
|
228
264
|
},
|
|
229
265
|
devDependencies: {
|
|
230
266
|
"@types/node": "^26.0.1",
|
|
267
|
+
tsx: "^4.19.0",
|
|
231
268
|
typescript: "^5.7.0"
|
|
232
269
|
}
|
|
233
270
|
},
|
|
@@ -258,14 +295,17 @@ dist/
|
|
|
258
295
|
2
|
|
259
296
|
),
|
|
260
297
|
"src/server/index.ts": `import { speexjs } from 'speexjs/server'
|
|
298
|
+
import { schema } from 'speexjs/schema'
|
|
261
299
|
import { UserController } from './controllers/user.controller.js'
|
|
262
300
|
|
|
263
301
|
const PORT = Number(process.env.PORT) || 3000
|
|
264
302
|
|
|
265
303
|
const app = speexjs()
|
|
266
304
|
|
|
305
|
+
// \u2500\u2500\u2500 Controllers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
267
306
|
app.controller(UserController)
|
|
268
307
|
|
|
308
|
+
// \u2500\u2500\u2500 Routes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
269
309
|
app.get('/', async ({ response }) => {
|
|
270
310
|
return response.html(\`
|
|
271
311
|
<!DOCTYPE html>
|
|
@@ -283,8 +323,9 @@ app.get('/', async ({ response }) => {
|
|
|
283
323
|
\`)
|
|
284
324
|
})
|
|
285
325
|
|
|
326
|
+
// \u2500\u2500\u2500 Start Server \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
286
327
|
app.listen(PORT, () => {
|
|
287
|
-
console.log(
|
|
328
|
+
console.log(\`\u2713 SpeexJS running on http://localhost:\${PORT}\`)
|
|
288
329
|
})
|
|
289
330
|
`,
|
|
290
331
|
"src/server/controllers/user.controller.ts": `import { Controller, get, post } from 'speexjs/server'
|
|
@@ -352,8 +393,17 @@ body {
|
|
|
352
393
|
message?: string
|
|
353
394
|
}
|
|
354
395
|
`,
|
|
355
|
-
".env.example":
|
|
396
|
+
".env.example": `# Server
|
|
397
|
+
PORT=3000
|
|
356
398
|
NODE_ENV=development
|
|
399
|
+
HOST=localhost
|
|
400
|
+
|
|
401
|
+
# Auth (change in production!)
|
|
402
|
+
APP_KEY=your-base64-32-byte-key-here
|
|
403
|
+
SESSION_SECRET=change-this-in-production
|
|
404
|
+
|
|
405
|
+
# Database (example)
|
|
406
|
+
DATABASE_URL=postgresql://localhost:5432/myapp
|
|
357
407
|
`,
|
|
358
408
|
".gitignore": `node_modules/
|
|
359
409
|
dist/
|
|
@@ -363,7 +413,7 @@ dist/
|
|
|
363
413
|
}
|
|
364
414
|
},
|
|
365
415
|
"api-only": {
|
|
366
|
-
dirs: ["src", "src/controllers", "src/middleware"],
|
|
416
|
+
dirs: ["src", "src/config", "src/controllers", "src/middleware"],
|
|
367
417
|
files: {
|
|
368
418
|
"package.json": (name) => JSON.stringify(
|
|
369
419
|
{
|
|
@@ -381,6 +431,7 @@ dist/
|
|
|
381
431
|
},
|
|
382
432
|
devDependencies: {
|
|
383
433
|
"@types/node": "^26.0.1",
|
|
434
|
+
tsx: "^4.19.0",
|
|
384
435
|
typescript: "^5.7.0"
|
|
385
436
|
}
|
|
386
437
|
},
|
|
@@ -409,28 +460,59 @@ dist/
|
|
|
409
460
|
2
|
|
410
461
|
),
|
|
411
462
|
"src/index.ts": `import { speexjs } from 'speexjs/server'
|
|
463
|
+
import { schema } from 'speexjs/schema'
|
|
464
|
+
import { Config } from './config/index.js'
|
|
465
|
+
import { HealthController } from './controllers/health.controller.js'
|
|
412
466
|
|
|
413
|
-
|
|
414
|
-
|
|
467
|
+
// \u2500\u2500\u2500 Application \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
415
468
|
const app = speexjs()
|
|
416
469
|
|
|
470
|
+
// \u2500\u2500\u2500 Controllers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
471
|
+
app.controller(HealthController)
|
|
472
|
+
|
|
473
|
+
// \u2500\u2500\u2500 Routes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
417
474
|
app.get('/api/health', async ({ response }) => {
|
|
418
|
-
return response.json({
|
|
475
|
+
return response.json({
|
|
476
|
+
status: 'ok',
|
|
477
|
+
timestamp: new Date().toISOString(),
|
|
478
|
+
uptime: process.uptime(),
|
|
479
|
+
env: Config.env,
|
|
480
|
+
})
|
|
419
481
|
})
|
|
420
482
|
|
|
421
|
-
|
|
422
|
-
|
|
483
|
+
// \u2500\u2500\u2500 Start Server \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
484
|
+
app.listen(Config.port, () => {
|
|
485
|
+
console.log(\`\u2713 SpeexJS API running at http://localhost:\${Config.port}\`)
|
|
423
486
|
})
|
|
487
|
+
`,
|
|
488
|
+
"src/config/index.ts": `export const Config = {
|
|
489
|
+
port: Number(process.env.PORT) || 3000,
|
|
490
|
+
host: process.env.HOST || 'localhost',
|
|
491
|
+
env: process.env.NODE_ENV || 'development',
|
|
492
|
+
appKey: process.env.APP_KEY || '',
|
|
493
|
+
isDev: process.env.NODE_ENV !== 'production',
|
|
494
|
+
isProd: process.env.NODE_ENV === 'production',
|
|
495
|
+
} as const
|
|
424
496
|
`,
|
|
425
497
|
"src/controllers/health.controller.ts": `import { Controller, get } from 'speexjs/server'
|
|
498
|
+
import { schema } from 'speexjs/schema'
|
|
499
|
+
|
|
500
|
+
// \u2500\u2500\u2500 Health Response Schema \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
501
|
+
const HealthResponse = schema.object({
|
|
502
|
+
status: schema.string(),
|
|
503
|
+
uptime: schema.number(),
|
|
504
|
+
timestamp: schema.string(),
|
|
505
|
+
})
|
|
426
506
|
|
|
427
507
|
export class HealthController extends Controller {
|
|
428
508
|
@get('/health')
|
|
429
509
|
async check({ response }) {
|
|
430
|
-
|
|
510
|
+
const payload = HealthResponse.parse({
|
|
431
511
|
status: 'ok',
|
|
432
512
|
uptime: process.uptime(),
|
|
513
|
+
timestamp: new Date().toISOString(),
|
|
433
514
|
})
|
|
515
|
+
return response.json(payload)
|
|
434
516
|
}
|
|
435
517
|
}
|
|
436
518
|
`,
|
|
@@ -452,8 +534,17 @@ export function auth() {
|
|
|
452
534
|
}
|
|
453
535
|
}
|
|
454
536
|
`,
|
|
455
|
-
".env.example":
|
|
537
|
+
".env.example": `# Server
|
|
538
|
+
PORT=3000
|
|
456
539
|
NODE_ENV=development
|
|
540
|
+
HOST=localhost
|
|
541
|
+
|
|
542
|
+
# Auth (change in production!)
|
|
543
|
+
APP_KEY=your-base64-32-byte-key-here
|
|
544
|
+
SESSION_SECRET=change-this-in-production
|
|
545
|
+
|
|
546
|
+
# Database (example)
|
|
547
|
+
DATABASE_URL=postgresql://localhost:5432/myapp
|
|
457
548
|
`,
|
|
458
549
|
".gitignore": `node_modules/
|
|
459
550
|
dist/
|
|
@@ -476,7 +567,7 @@ function toPascalCase(str) {
|
|
|
476
567
|
async function initProject(name, options) {
|
|
477
568
|
const targetDir = resolve(process.cwd(), name);
|
|
478
569
|
if (existsSync(targetDir)) {
|
|
479
|
-
console.error(colors.red(`Directory '${name}'
|
|
570
|
+
console.error(colors.red(`Directory '${name}' already exists!`));
|
|
480
571
|
process.exit(1);
|
|
481
572
|
}
|
|
482
573
|
const templateName = getTemplate(String(options.template || "blank"));
|
|
@@ -484,7 +575,7 @@ async function initProject(name, options) {
|
|
|
484
575
|
if (!template) {
|
|
485
576
|
console.error(
|
|
486
577
|
colors.red(
|
|
487
|
-
`
|
|
578
|
+
`Unknown template '${options.template}'. Use: blank, fullstack, api-only`
|
|
488
579
|
)
|
|
489
580
|
);
|
|
490
581
|
process.exit(1);
|
|
@@ -552,7 +643,7 @@ function makeController(name) {
|
|
|
552
643
|
const fullPath = resolve2(targetDir, fileName);
|
|
553
644
|
const varName = toCamelCase(name);
|
|
554
645
|
if (existsSync2(fullPath)) {
|
|
555
|
-
console.error(colors.red(`File ${fileName}
|
|
646
|
+
console.error(colors.red(`File ${fileName} already exists!`));
|
|
556
647
|
process.exit(1);
|
|
557
648
|
}
|
|
558
649
|
mkdirSync2(targetDir, { recursive: true });
|
|
@@ -592,7 +683,7 @@ export const ${varName}Controller = ${className}
|
|
|
592
683
|
`;
|
|
593
684
|
writeFileSync2(fullPath, content, "utf-8");
|
|
594
685
|
console.log(
|
|
595
|
-
`${colors.green("\u2705")} Controller ${colors.bold(className)}
|
|
686
|
+
`${colors.green("\u2705")} Controller ${colors.bold(className)} created at ${colors.cyan(fileName)}`
|
|
596
687
|
);
|
|
597
688
|
}
|
|
598
689
|
|
|
@@ -615,7 +706,7 @@ function makeMiddleware(name) {
|
|
|
615
706
|
const targetDir = resolve3(process.cwd(), "src/server/middleware");
|
|
616
707
|
const fullPath = resolve3(targetDir, fileName);
|
|
617
708
|
if (existsSync3(fullPath)) {
|
|
618
|
-
console.error(colors.red(`File ${fileName}
|
|
709
|
+
console.error(colors.red(`File ${fileName} already exists!`));
|
|
619
710
|
process.exit(1);
|
|
620
711
|
}
|
|
621
712
|
mkdirSync3(targetDir, { recursive: true });
|
|
@@ -634,7 +725,7 @@ export function ${functionName}(options?: Record<string, unknown>) {
|
|
|
634
725
|
`;
|
|
635
726
|
writeFileSync3(fullPath, content, "utf-8");
|
|
636
727
|
console.log(
|
|
637
|
-
`${colors.green("\u2705")} Middleware ${colors.bold(functionName)}
|
|
728
|
+
`${colors.green("\u2705")} Middleware ${colors.bold(functionName)} created at ${colors.cyan(fileName)}`
|
|
638
729
|
);
|
|
639
730
|
}
|
|
640
731
|
|
|
@@ -654,36 +745,111 @@ function makeSchema(name) {
|
|
|
654
745
|
const targetDir = resolve4(process.cwd(), "src/schemas");
|
|
655
746
|
const fullPath = resolve4(targetDir, fileName);
|
|
656
747
|
if (existsSync4(fullPath)) {
|
|
657
|
-
console.error(colors.red(`File ${fileName}
|
|
748
|
+
console.error(colors.red(`File ${fileName} already exists!`));
|
|
658
749
|
process.exit(1);
|
|
659
750
|
}
|
|
660
751
|
mkdirSync4(targetDir, { recursive: true });
|
|
661
|
-
const content = `import {
|
|
752
|
+
const content = `import { schema, type Infer } from 'speexjs/schema'
|
|
662
753
|
|
|
663
|
-
export const ${schemaName} =
|
|
664
|
-
id:
|
|
665
|
-
name:
|
|
666
|
-
createdAt:
|
|
667
|
-
updatedAt:
|
|
754
|
+
export const ${schemaName} = schema.object({
|
|
755
|
+
id: schema.string().uuid(),
|
|
756
|
+
name: schema.string().min(1).max(255),
|
|
757
|
+
createdAt: schema.string().datetime(),
|
|
758
|
+
updatedAt: schema.string().datetime().optional(),
|
|
668
759
|
})
|
|
669
760
|
|
|
670
761
|
export type ${typeName} = Infer<typeof ${schemaName}>
|
|
671
762
|
|
|
672
|
-
export const create${typeName}Schema =
|
|
673
|
-
name:
|
|
763
|
+
export const create${typeName}Schema = schema.object({
|
|
764
|
+
name: schema.string().min(1).max(255),
|
|
674
765
|
})
|
|
675
766
|
|
|
676
767
|
export type Create${typeName} = Infer<typeof create${typeName}Schema>
|
|
677
768
|
`;
|
|
678
769
|
writeFileSync4(fullPath, content, "utf-8");
|
|
679
770
|
console.log(
|
|
680
|
-
`${colors.green("\u2705")} Schema ${colors.bold(schemaName)}
|
|
771
|
+
`${colors.green("\u2705")} Schema ${colors.bold(schemaName)} created at ${colors.cyan(fileName)}`
|
|
681
772
|
);
|
|
682
773
|
}
|
|
683
774
|
|
|
684
|
-
// src/cli/commands/
|
|
685
|
-
import { existsSync as existsSync5,
|
|
775
|
+
// src/cli/commands/make-migration.ts
|
|
776
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
686
777
|
import { resolve as resolve5 } from "path";
|
|
778
|
+
function toSnakeCase(str) {
|
|
779
|
+
return str.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "");
|
|
780
|
+
}
|
|
781
|
+
function toCamelCase3(str) {
|
|
782
|
+
return str.replace(/[-_\s]+(.)?/g, (_, c) => (c ?? "").toUpperCase()).replace(/^(.)/, (c) => c.toLowerCase());
|
|
783
|
+
}
|
|
784
|
+
function makeMigration(name) {
|
|
785
|
+
const fileName = `${Date.now()}_${toSnakeCase(name)}.ts`;
|
|
786
|
+
const targetDir = resolve5(process.cwd(), "src/database/migrations");
|
|
787
|
+
const fullPath = resolve5(targetDir, fileName);
|
|
788
|
+
if (existsSync5(fullPath)) {
|
|
789
|
+
console.error(colors.red(`File ${fileName} already exists!`));
|
|
790
|
+
process.exit(1);
|
|
791
|
+
}
|
|
792
|
+
mkdirSync5(targetDir, { recursive: true });
|
|
793
|
+
const className = toCamelCase3(name).charAt(0).toUpperCase() + toCamelCase3(name).slice(1);
|
|
794
|
+
const content = `import { SchemaBuilder } from 'speexjs/server/database'
|
|
795
|
+
|
|
796
|
+
export async function up(schema: SchemaBuilder): Promise<void> {
|
|
797
|
+
schema.createTable('${toSnakeCase(name)}', (table) => {
|
|
798
|
+
table.increments('id')
|
|
799
|
+
table.timestamps()
|
|
800
|
+
})
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
export async function down(schema: SchemaBuilder): Promise<void> {
|
|
804
|
+
schema.dropTable('${toSnakeCase(name)}')
|
|
805
|
+
}
|
|
806
|
+
`;
|
|
807
|
+
writeFileSync5(fullPath, content, "utf-8");
|
|
808
|
+
console.log(`${colors.green("\u2705")} Migration ${colors.bold(className)} created at ${colors.cyan(fileName)}`);
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
// src/cli/commands/make-model.ts
|
|
812
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
813
|
+
import { resolve as resolve6 } from "path";
|
|
814
|
+
function toPascalCase5(str) {
|
|
815
|
+
return str.replace(/[-_\s]+(.)?/g, (_, c) => (c ?? "").toUpperCase()).replace(/^(.)/, (c) => c.toUpperCase());
|
|
816
|
+
}
|
|
817
|
+
function toSnakeCase2(str) {
|
|
818
|
+
return str.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "");
|
|
819
|
+
}
|
|
820
|
+
function toPlural(str) {
|
|
821
|
+
if (str.endsWith("s")) return str;
|
|
822
|
+
if (str.endsWith("y")) return str.slice(0, -1) + "ies";
|
|
823
|
+
return str + "s";
|
|
824
|
+
}
|
|
825
|
+
function makeModel(name) {
|
|
826
|
+
const className = toPascalCase5(name);
|
|
827
|
+
const tableName = toPlural(toSnakeCase2(name));
|
|
828
|
+
const fileName = `${toSnakeCase2(name)}.model.ts`;
|
|
829
|
+
const targetDir = resolve6(process.cwd(), "src/models");
|
|
830
|
+
const fullPath = resolve6(targetDir, fileName);
|
|
831
|
+
if (existsSync6(fullPath)) {
|
|
832
|
+
console.error(colors.red(`File ${fileName} already exists!`));
|
|
833
|
+
process.exit(1);
|
|
834
|
+
}
|
|
835
|
+
mkdirSync6(targetDir, { recursive: true });
|
|
836
|
+
const content = `import { Model } from 'speexjs/server/database'
|
|
837
|
+
|
|
838
|
+
export class ${className} extends Model {
|
|
839
|
+
static table = '${tableName}'
|
|
840
|
+
|
|
841
|
+
// Define relationships here
|
|
842
|
+
// belongsTo(RelatedModel, 'foreign_key', 'owner_key')
|
|
843
|
+
// hasMany(RelatedModel, 'foreign_key', 'local_key')
|
|
844
|
+
}
|
|
845
|
+
`;
|
|
846
|
+
writeFileSync6(fullPath, content, "utf-8");
|
|
847
|
+
console.log(`${colors.green("\u2705")} Model ${colors.bold(className)} created at ${colors.cyan(fileName)}`);
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// src/cli/commands/list-routes.ts
|
|
851
|
+
import { existsSync as existsSync7, readdirSync, readFileSync } from "fs";
|
|
852
|
+
import { resolve as resolve7 } from "path";
|
|
687
853
|
function extractDecorators(content) {
|
|
688
854
|
const pattern = /@(get|post|put|patch|del|delete)\s*\(\s*'([^']+)'\s*\)/g;
|
|
689
855
|
const results = [];
|
|
@@ -697,10 +863,10 @@ function extractDecorators(content) {
|
|
|
697
863
|
return results;
|
|
698
864
|
}
|
|
699
865
|
function listRoutes() {
|
|
700
|
-
const routesDir =
|
|
701
|
-
if (!
|
|
866
|
+
const routesDir = resolve7(process.cwd(), "src/server/controllers");
|
|
867
|
+
if (!existsSync7(routesDir)) {
|
|
702
868
|
console.log(
|
|
703
|
-
` ${colors.yellow("!")}
|
|
869
|
+
` ${colors.yellow("!")} No routes registered. Create a controller first:`
|
|
704
870
|
);
|
|
705
871
|
console.log(` ${colors.cyan("speexjs make:controller <name>")}`);
|
|
706
872
|
return;
|
|
@@ -708,17 +874,17 @@ function listRoutes() {
|
|
|
708
874
|
const files = readdirSync(routesDir).filter((f) => f.endsWith(".ts"));
|
|
709
875
|
if (files.length === 0) {
|
|
710
876
|
console.log(
|
|
711
|
-
` ${colors.yellow("!")}
|
|
877
|
+
` ${colors.yellow("!")} No routes registered. Create a controller first:`
|
|
712
878
|
);
|
|
713
879
|
console.log(` ${colors.cyan("speexjs make:controller <name>")}`);
|
|
714
880
|
return;
|
|
715
881
|
}
|
|
716
882
|
let total = 0;
|
|
717
883
|
console.log();
|
|
718
|
-
console.log(` ${colors.bold("\u{1F4CB}
|
|
884
|
+
console.log(` ${colors.bold("\u{1F4CB} Route List:")}`);
|
|
719
885
|
console.log();
|
|
720
886
|
for (const file of files) {
|
|
721
|
-
const content = readFileSync(
|
|
887
|
+
const content = readFileSync(resolve7(routesDir, file), "utf-8");
|
|
722
888
|
const routes = extractDecorators(content);
|
|
723
889
|
if (routes.length > 0) {
|
|
724
890
|
console.log(` ${colors.cyan("\u2500\u2500")} ${colors.bold(file.replace(".controller.ts", ""))} ${colors.cyan("\u2500\u2500")}`);
|
|
@@ -730,13 +896,14 @@ function listRoutes() {
|
|
|
730
896
|
console.log();
|
|
731
897
|
}
|
|
732
898
|
}
|
|
733
|
-
console.log(` ${colors.dim(`${total} route${total !== 1 ? "s" : ""}
|
|
899
|
+
console.log(` ${colors.dim(`${total} route${total !== 1 ? "s" : ""} found`)}`);
|
|
734
900
|
console.log();
|
|
735
901
|
}
|
|
736
902
|
|
|
737
903
|
// src/cli/commands/serve.ts
|
|
738
|
-
import { existsSync as
|
|
739
|
-
import { resolve as
|
|
904
|
+
import { existsSync as existsSync8 } from "fs";
|
|
905
|
+
import { resolve as resolve8 } from "path";
|
|
906
|
+
import { pathToFileURL } from "url";
|
|
740
907
|
|
|
741
908
|
// src/native/logger.ts
|
|
742
909
|
var LOG_LEVELS = {
|
|
@@ -745,27 +912,18 @@ var LOG_LEVELS = {
|
|
|
745
912
|
warn: 2,
|
|
746
913
|
error: 3
|
|
747
914
|
};
|
|
748
|
-
var TIMEZONE_OFFSETS = {
|
|
749
|
-
WIB: 7,
|
|
750
|
-
WITA: 8,
|
|
751
|
-
WIT: 9,
|
|
752
|
-
UTC: 0
|
|
753
|
-
};
|
|
754
915
|
function pad(n) {
|
|
755
916
|
return n.toString().padStart(2, "0");
|
|
756
917
|
}
|
|
757
918
|
function formatTimestamp(tz) {
|
|
758
|
-
const offset = TIMEZONE_OFFSETS[tz ?? ""] ?? 7;
|
|
759
919
|
const now = /* @__PURE__ */ new Date();
|
|
760
|
-
const
|
|
761
|
-
const
|
|
762
|
-
const
|
|
763
|
-
const
|
|
764
|
-
const
|
|
765
|
-
const
|
|
766
|
-
|
|
767
|
-
const s = pad(local.getSeconds());
|
|
768
|
-
return `${y}-${M}-${d} ${h}:${m}:${s} ${tz ?? "WIB"}`;
|
|
920
|
+
const y = now.getUTCFullYear();
|
|
921
|
+
const M = pad(now.getUTCMonth() + 1);
|
|
922
|
+
const d = pad(now.getUTCDate());
|
|
923
|
+
const h = pad(now.getUTCHours());
|
|
924
|
+
const m = pad(now.getUTCMinutes());
|
|
925
|
+
const s = pad(now.getUTCSeconds());
|
|
926
|
+
return `${y}-${M}-${d} ${h}:${m}:${s} UTC`;
|
|
769
927
|
}
|
|
770
928
|
var LEVEL_PREFIX = {
|
|
771
929
|
debug: "DEBUG",
|
|
@@ -790,7 +948,7 @@ var Logger = class _Logger {
|
|
|
790
948
|
this._name = options?.name ?? "";
|
|
791
949
|
this._useColors = options?.colors ?? isColorSupported();
|
|
792
950
|
this._useTimestamps = options?.timestamps ?? true;
|
|
793
|
-
this._timezone = options?.timezone ?? "
|
|
951
|
+
this._timezone = options?.timezone ?? "UTC";
|
|
794
952
|
}
|
|
795
953
|
_format(level, msg, meta) {
|
|
796
954
|
const parts = [];
|
|
@@ -848,6 +1006,22 @@ var Logger = class _Logger {
|
|
|
848
1006
|
var logger = new Logger();
|
|
849
1007
|
|
|
850
1008
|
// src/cli/commands/serve.ts
|
|
1009
|
+
function toFileUrl(path) {
|
|
1010
|
+
return pathToFileURL(path).href;
|
|
1011
|
+
}
|
|
1012
|
+
async function ensureTsLoader() {
|
|
1013
|
+
if (process.execArgv.some((a) => a.includes("strip-types") || a.includes("tsx") || a.includes("ts-node"))) {
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
for (const mod of ["tsx", "ts-node/esm"]) {
|
|
1017
|
+
try {
|
|
1018
|
+
await import(mod);
|
|
1019
|
+
return;
|
|
1020
|
+
} catch {
|
|
1021
|
+
continue;
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
851
1025
|
async function serve(options) {
|
|
852
1026
|
const opts = {
|
|
853
1027
|
port: options.port || options.p || 3e3,
|
|
@@ -856,31 +1030,38 @@ async function serve(options) {
|
|
|
856
1030
|
};
|
|
857
1031
|
const port = parseInt(String(opts.port), 10);
|
|
858
1032
|
const host = String(opts.host);
|
|
859
|
-
const serverEntry =
|
|
860
|
-
const serverEntryAlt =
|
|
861
|
-
const serverEntryIndex =
|
|
1033
|
+
const serverEntry = resolve8(process.cwd(), "src/app.ts");
|
|
1034
|
+
const serverEntryAlt = resolve8(process.cwd(), "src/server/index.ts");
|
|
1035
|
+
const serverEntryIndex = resolve8(process.cwd(), "src/index.ts");
|
|
862
1036
|
let entryPath = null;
|
|
863
|
-
if (
|
|
864
|
-
else if (
|
|
865
|
-
else if (
|
|
1037
|
+
if (existsSync8(serverEntry)) entryPath = serverEntry;
|
|
1038
|
+
else if (existsSync8(serverEntryAlt)) entryPath = serverEntryAlt;
|
|
1039
|
+
else if (existsSync8(serverEntryIndex)) entryPath = serverEntryIndex;
|
|
866
1040
|
if (!entryPath) {
|
|
867
1041
|
console.error(
|
|
868
1042
|
colors.red(
|
|
869
|
-
"
|
|
1043
|
+
"Entry point not found. Create src/app.ts or src/index.ts"
|
|
870
1044
|
)
|
|
871
1045
|
);
|
|
872
1046
|
process.exit(1);
|
|
873
1047
|
}
|
|
1048
|
+
if (opts.dev) {
|
|
1049
|
+
try {
|
|
1050
|
+
await ensureTsLoader();
|
|
1051
|
+
} catch {
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
const entryUrl = toFileUrl(entryPath);
|
|
874
1055
|
if (opts.dev) {
|
|
875
1056
|
logger.info(
|
|
876
1057
|
`Development server starting at ${colors.cyan(`http://${host}:${port}`)}`
|
|
877
1058
|
);
|
|
878
1059
|
try {
|
|
879
|
-
const { app } = await import(
|
|
1060
|
+
const { app } = await import(entryUrl);
|
|
880
1061
|
if (!app || typeof app.listen !== "function") {
|
|
881
1062
|
console.error(
|
|
882
1063
|
colors.red(
|
|
883
|
-
"Entry point
|
|
1064
|
+
"Entry point must export { app } with .listen() method"
|
|
884
1065
|
)
|
|
885
1066
|
);
|
|
886
1067
|
process.exit(1);
|
|
@@ -892,16 +1073,16 @@ async function serve(options) {
|
|
|
892
1073
|
console.log();
|
|
893
1074
|
});
|
|
894
1075
|
} catch (err) {
|
|
895
|
-
console.error(colors.red(`
|
|
1076
|
+
console.error(colors.red(`Failed to start server: ${err.message}`));
|
|
896
1077
|
process.exit(1);
|
|
897
1078
|
}
|
|
898
1079
|
} else {
|
|
899
1080
|
try {
|
|
900
|
-
const { app } = await import(
|
|
1081
|
+
const { app } = await import(entryUrl);
|
|
901
1082
|
if (!app || typeof app.listen !== "function") {
|
|
902
1083
|
console.error(
|
|
903
1084
|
colors.red(
|
|
904
|
-
"Entry point
|
|
1085
|
+
"Entry point must export { app } with .listen() method"
|
|
905
1086
|
)
|
|
906
1087
|
);
|
|
907
1088
|
process.exit(1);
|
|
@@ -913,7 +1094,7 @@ async function serve(options) {
|
|
|
913
1094
|
console.log();
|
|
914
1095
|
});
|
|
915
1096
|
} catch (err) {
|
|
916
|
-
console.error(colors.red(`
|
|
1097
|
+
console.error(colors.red(`Failed to start server: ${err.message}`));
|
|
917
1098
|
process.exit(1);
|
|
918
1099
|
}
|
|
919
1100
|
}
|
|
@@ -925,16 +1106,20 @@ function showHelp() {
|
|
|
925
1106
|
console.log("Fullstack JavaScript/TypeScript Framework");
|
|
926
1107
|
console.log();
|
|
927
1108
|
console.log(`${colors.bold("Usage:")}`);
|
|
928
|
-
console.log(" SpeexJS init [name] [options]
|
|
1109
|
+
console.log(" SpeexJS init [name] [options] Create new project");
|
|
929
1110
|
console.log(" SpeexJS make:controller <name> Generate controller");
|
|
930
1111
|
console.log(" SpeexJS make:middleware <name> Generate middleware");
|
|
1112
|
+
console.log(" SpeexJS make:migration <name> Generate migration");
|
|
1113
|
+
console.log(" SpeexJS make:model <name> Generate model");
|
|
931
1114
|
console.log(" SpeexJS make:schema <name> Generate schema");
|
|
932
|
-
console.log(" SpeexJS
|
|
933
|
-
console.log(" SpeexJS
|
|
934
|
-
console.log(" SpeexJS
|
|
1115
|
+
console.log(" SpeexJS migrate Run migrations");
|
|
1116
|
+
console.log(" SpeexJS db:seed Seed the database");
|
|
1117
|
+
console.log(" SpeexJS list-routes View all routes");
|
|
1118
|
+
console.log(" SpeexJS serve [options] Run server");
|
|
1119
|
+
console.log(" SpeexJS --help Show help");
|
|
935
1120
|
console.log();
|
|
936
1121
|
console.log(`${colors.bold("Aliases:")}`);
|
|
937
|
-
console.log(" SpeexJS -v, --version
|
|
1122
|
+
console.log(" SpeexJS -v, --version View version");
|
|
938
1123
|
console.log();
|
|
939
1124
|
console.log(`${colors.bold("Options:")}`);
|
|
940
1125
|
console.log(" --template <type> blank, fullstack, api-only");
|
|
@@ -952,7 +1137,7 @@ async function main() {
|
|
|
952
1137
|
}
|
|
953
1138
|
case "make:controller": {
|
|
954
1139
|
if (!parsed.args[0]) {
|
|
955
|
-
console.error(colors.red("
|
|
1140
|
+
console.error(colors.red("Controller name required"));
|
|
956
1141
|
console.log(` ${colors.cyan("SpeexJS make:controller <name>")}`);
|
|
957
1142
|
process.exit(1);
|
|
958
1143
|
}
|
|
@@ -961,7 +1146,7 @@ async function main() {
|
|
|
961
1146
|
}
|
|
962
1147
|
case "make:middleware": {
|
|
963
1148
|
if (!parsed.args[0]) {
|
|
964
|
-
console.error(colors.red("
|
|
1149
|
+
console.error(colors.red("Middleware name required"));
|
|
965
1150
|
console.log(` ${colors.cyan("SpeexJS make:middleware <name>")}`);
|
|
966
1151
|
process.exit(1);
|
|
967
1152
|
}
|
|
@@ -970,13 +1155,37 @@ async function main() {
|
|
|
970
1155
|
}
|
|
971
1156
|
case "make:schema": {
|
|
972
1157
|
if (!parsed.args[0]) {
|
|
973
|
-
console.error(colors.red("
|
|
1158
|
+
console.error(colors.red("Schema name required"));
|
|
974
1159
|
console.log(` ${colors.cyan("SpeexJS make:schema <name>")}`);
|
|
975
1160
|
process.exit(1);
|
|
976
1161
|
}
|
|
977
1162
|
await makeSchema(parsed.args[0]);
|
|
978
1163
|
break;
|
|
979
1164
|
}
|
|
1165
|
+
case "make:migration": {
|
|
1166
|
+
if (!parsed.args[0]) {
|
|
1167
|
+
console.error(colors.red("Migration name required"));
|
|
1168
|
+
console.log(` ${colors.cyan("SpeexJS make:migration <name>")}`);
|
|
1169
|
+
process.exit(1);
|
|
1170
|
+
}
|
|
1171
|
+
await makeMigration(parsed.args[0]);
|
|
1172
|
+
break;
|
|
1173
|
+
}
|
|
1174
|
+
case "make:model": {
|
|
1175
|
+
if (!parsed.args[0]) {
|
|
1176
|
+
console.error(colors.red("Model name required"));
|
|
1177
|
+
console.log(` ${colors.cyan("SpeexJS make:model <name>")}`);
|
|
1178
|
+
process.exit(1);
|
|
1179
|
+
}
|
|
1180
|
+
await makeModel(parsed.args[0]);
|
|
1181
|
+
break;
|
|
1182
|
+
}
|
|
1183
|
+
case "migrate":
|
|
1184
|
+
case "db:seed": {
|
|
1185
|
+
const label = command === "migrate" ? "Migration" : "Database seeding";
|
|
1186
|
+
console.log(`${colors.yellow("\u23F3")} ${label} coming soon...`);
|
|
1187
|
+
break;
|
|
1188
|
+
}
|
|
980
1189
|
case "list-routes":
|
|
981
1190
|
case "routes":
|
|
982
1191
|
case "lr": {
|
|
@@ -1002,7 +1211,7 @@ async function main() {
|
|
|
1002
1211
|
}
|
|
1003
1212
|
default: {
|
|
1004
1213
|
if (command) {
|
|
1005
|
-
console.error(`${colors.red(`
|
|
1214
|
+
console.error(`${colors.red(`Unknown command '${command}'`)}`);
|
|
1006
1215
|
console.log();
|
|
1007
1216
|
}
|
|
1008
1217
|
showHelp();
|