balda-js 0.0.1 → 0.0.3

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 (65) hide show
  1. package/package.json +1 -6
  2. package/.husky/pre-commit +0 -19
  3. package/.nvmrc +0 -1
  4. package/docs/README.md +0 -135
  5. package/docs/blog/authors.yml +0 -6
  6. package/docs/blog/tags.yml +0 -4
  7. package/docs/cli.md +0 -109
  8. package/docs/docs/core-concepts/controllers.md +0 -393
  9. package/docs/docs/core-concepts/middleware.md +0 -302
  10. package/docs/docs/core-concepts/request-response.md +0 -486
  11. package/docs/docs/core-concepts/routing.md +0 -388
  12. package/docs/docs/core-concepts/server.md +0 -332
  13. package/docs/docs/cron/overview.md +0 -70
  14. package/docs/docs/examples/rest-api.md +0 -595
  15. package/docs/docs/getting-started/configuration.md +0 -168
  16. package/docs/docs/getting-started/installation.md +0 -125
  17. package/docs/docs/getting-started/quick-start.md +0 -273
  18. package/docs/docs/intro.md +0 -46
  19. package/docs/docs/plugins/cookie.md +0 -424
  20. package/docs/docs/plugins/cors.md +0 -295
  21. package/docs/docs/plugins/file.md +0 -382
  22. package/docs/docs/plugins/helmet.md +0 -388
  23. package/docs/docs/plugins/json.md +0 -338
  24. package/docs/docs/plugins/log.md +0 -592
  25. package/docs/docs/plugins/overview.md +0 -390
  26. package/docs/docs/plugins/rate-limiter.md +0 -347
  27. package/docs/docs/plugins/static.md +0 -352
  28. package/docs/docs/plugins/swagger.md +0 -411
  29. package/docs/docs/plugins/urlencoded.md +0 -76
  30. package/docs/docs/testing/examples.md +0 -384
  31. package/docs/docs/testing/mock-server.md +0 -311
  32. package/docs/docs/testing/overview.md +0 -76
  33. package/docs/docusaurus.config.ts +0 -144
  34. package/docs/intro.md +0 -78
  35. package/docs/package.json +0 -46
  36. package/docs/sidebars.ts +0 -72
  37. package/docs/static/.nojekyll +0 -0
  38. package/docs/static/img/docusaurus-social-card.jpg +0 -0
  39. package/docs/static/img/docusaurus.png +0 -0
  40. package/docs/static/img/favicon.ico +0 -0
  41. package/docs/static/img/logo.svg +0 -1
  42. package/docs/static/img/undraw_docusaurus_mountain.svg +0 -37
  43. package/docs/static/img/undraw_docusaurus_react.svg +0 -170
  44. package/docs/static/img/undraw_docusaurus_tree.svg +0 -40
  45. package/docs/tsconfig.json +0 -8
  46. package/speed_test.sh +0 -3
  47. package/test/benchmark/index.ts +0 -17
  48. package/test/cli/cli.ts +0 -7
  49. package/test/commands/test.ts +0 -42
  50. package/test/controllers/file_upload.ts +0 -29
  51. package/test/controllers/urlencoded.ts +0 -13
  52. package/test/controllers/users.ts +0 -111
  53. package/test/cron/index.ts +0 -6
  54. package/test/cron/test_cron.ts +0 -8
  55. package/test/cron/test_cron_imported.ts +0 -8
  56. package/test/native_env.ts +0 -16
  57. package/test/resources/test.txt +0 -1
  58. package/test/server/index.ts +0 -3
  59. package/test/server/instance.ts +0 -63
  60. package/test/suite/upload.test.ts +0 -23
  61. package/test/suite/urlencoded.test.ts +0 -23
  62. package/test/suite/users.test.ts +0 -76
  63. package/todo.md +0 -9
  64. package/tsconfig.json +0 -24
  65. package/vitest.config.ts +0 -17
@@ -1,70 +0,0 @@
1
- ---
2
- sidebar_position: 6
3
- ---
4
-
5
- # Overview
6
-
7
- Balda.js includes a **cron job decorator** that lets you schedule background tasks directly from your controllers or services.
8
-
9
- > **Peer dependency** — Balda.js intentionally leaves the scheduling engine to the community-standard [`node-cron`](https://www.npmjs.com/package/node-cron). To enable cron support you must install it yourself:
10
- >
11
- > ```bash
12
- > # with npm
13
- > npm install node-cron
14
- >
15
- > # or with yarn / pnpm / bun
16
- > yarn add node-cron
17
- > pnpm add node-cron
18
- > bun add node-cron
19
- > ```
20
-
21
- ## Basic Usage
22
-
23
- ```typescript
24
- import { cron } from 'balda-js';
25
-
26
- class TickCron {
27
- // Run every minute
28
- @cron('* * * * *')
29
- async handle() {
30
- console.log('Cron executed!');
31
- }
32
- }
33
- ```
34
-
35
- The decorator accepts any valid [cron expression](https://crontab.guru/) supported by `node-cron`.
36
-
37
- ## Advanced Options
38
-
39
- You can pass an **options object** as the second argument to control timezone, concurrency, etc.
40
-
41
- ```typescript
42
- @cron('0 0 * * *', { timezone: 'UTC' })
43
- async handle() {
44
- await this.cleanupService.run();
45
- }
46
- ```
47
-
48
- ## Error Handling
49
-
50
- By default, if the decorated method throws, Balda.js will log the error (using the configured logger) but will **not** crash your server.
51
-
52
- You can set a global cron error handler to handle the errors:
53
-
54
- ```typescript
55
- server.setGlobalCronErrorHandler((error) => {
56
- console.error(error);
57
- });
58
- ```
59
-
60
- ## Starting Cron Jobs
61
-
62
- You can import registered cron jobs classes that have one or more `@cron` decorated methods or directly import the `@cron` decorator and use it to decorate your own methods.
63
-
64
- ```typescript
65
- import './cron/specific_cron_job';
66
-
67
- server.startRegisteredCrons(['./cron/cron_jobs.{ts,js}'], () => {
68
- console.log('Cron jobs started');
69
- });
70
- ```
@@ -1,595 +0,0 @@
1
- ---
2
- sidebar_position: 1
3
- ---
4
-
5
- # REST API Example
6
-
7
- A complete REST API example using Balda.js with validation, serialization, and error handling.
8
-
9
- ## Project Structure
10
-
11
- ```
12
- src/
13
- ├── controllers/
14
- │ ├── users.controller.ts
15
- │ ├── posts.controller.ts
16
- │ └── auth.controller.ts
17
- ├── middleware/
18
- │ ├── auth.middleware.ts
19
- │ └── validation.middleware.ts
20
- ├── schemas/
21
- │ ├── user.schema.ts
22
- │ └── post.schema.ts
23
- ├── services/
24
- │ ├── user.service.ts
25
- │ └── post.service.ts
26
- └── server.ts
27
- ```
28
-
29
- ## Server Setup
30
-
31
- ```typescript
32
- // src/server.ts
33
- import { Server } from 'balda-js';
34
-
35
- const server = new Server({
36
- port: 3000,
37
- host: 'localhost',
38
- controllerPatterns: ['./src/controllers/**/*.ts'],
39
- plugins: {
40
- cors: {
41
- origin: ['http://localhost:3000', 'http://localhost:3001'],
42
- credentials: true
43
- },
44
- json: {
45
- sizeLimit: '10mb',
46
- strict: true
47
- },
48
- cookie: {
49
- secret: process.env.COOKIE_SECRET || 'dev-secret',
50
- secure: false,
51
- httpOnly: true
52
- },
53
- helmet: {
54
- contentSecurityPolicy: false
55
- },
56
- log: {
57
- logRequest: true,
58
- logResponse: true
59
- }
60
- },
61
- swagger: {
62
- type: 'standard',
63
- models: {
64
- User: {
65
- type: 'object',
66
- properties: {
67
- id: { type: 'number' },
68
- email: { type: 'string' },
69
- name: { type: 'string' },
70
- createdAt: { type: 'string', format: 'date-time' }
71
- }
72
- },
73
- Post: {
74
- type: 'object',
75
- properties: {
76
- id: { type: 'number' },
77
- title: { type: 'string' },
78
- content: { type: 'string' },
79
- authorId: { type: 'number' },
80
- createdAt: { type: 'string', format: 'date-time' }
81
- }
82
- }
83
- }
84
- }
85
- });
86
-
87
- // Global error handler
88
- server.setErrorHandler((req, res, next, error) => {
89
- console.error('Error:', error);
90
-
91
- if (error.name === 'ValidationError') {
92
- return res.badRequest({
93
- error: 'Validation failed',
94
- details: error.message
95
- });
96
- }
97
-
98
- if (error.name === 'UnauthorizedError') {
99
- return res.unauthorized({ error: 'Authentication required' });
100
- }
101
-
102
- res.internalServerError({ error: 'Internal server error' });
103
- });
104
-
105
- server.listen(({ port, host }) => {
106
- console.log(`🚀 Server running on http://${host}:${port}`);
107
- console.log(`📚 API Documentation: http://${host}:${port}/docs`);
108
- });
109
- ```
110
-
111
- ## Data Schemas
112
-
113
- ```typescript
114
- // src/schemas/user.schema.ts
115
- import { Type } from '@sinclair/typebox';
116
-
117
- export const CreateUserSchema = Type.Object({
118
- email: Type.String({ format: 'email' }),
119
- password: Type.String({ minLength: 8 }),
120
- name: Type.String({ minLength: 1, maxLength: 100 })
121
- });
122
-
123
- export const UpdateUserSchema = Type.Partial(CreateUserSchema);
124
-
125
- export const UserSchema = Type.Object({
126
- id: Type.Number(),
127
- email: Type.String({ format: 'email' }),
128
- name: Type.String(),
129
- createdAt: Type.String({ format: 'date-time' }),
130
- updatedAt: Type.String({ format: 'date-time' })
131
- });
132
-
133
- export const LoginSchema = Type.Object({
134
- email: Type.String({ format: 'email' }),
135
- password: Type.String()
136
- });
137
-
138
- export type CreateUser = Static<typeof CreateUserSchema>;
139
- export type UpdateUser = Static<typeof UpdateUserSchema>;
140
- export type User = Static<typeof UserSchema>;
141
- export type Login = Static<typeof LoginSchema>;
142
- ```
143
-
144
- ```typescript
145
- // src/schemas/post.schema.ts
146
- import { Type } from '@sinclair/typebox';
147
-
148
- export const CreatePostSchema = Type.Object({
149
- title: Type.String({ minLength: 1, maxLength: 200 }),
150
- content: Type.String({ minLength: 1 }),
151
- published: Type.Optional(Type.Boolean())
152
- });
153
-
154
- export const UpdatePostSchema = Type.Partial(CreatePostSchema);
155
-
156
- export const PostSchema = Type.Object({
157
- id: Type.Number(),
158
- title: Type.String(),
159
- content: Type.String(),
160
- published: Type.Boolean(),
161
- authorId: Type.Number(),
162
- createdAt: Type.String({ format: 'date-time' }),
163
- updatedAt: Type.String({ format: 'date-time' })
164
- });
165
-
166
- export type CreatePost = Static<typeof CreatePostSchema>;
167
- export type UpdatePost = Static<typeof UpdatePostSchema>;
168
- export type Post = Static<typeof PostSchema>;
169
- ```
170
-
171
- ## Services
172
-
173
- ```typescript
174
- // src/services/user.service.ts
175
- import { User, CreateUser, UpdateUser } from '../schemas/user.schema';
176
-
177
- export class UserService {
178
- private users: User[] = [];
179
- private nextId = 1;
180
-
181
- async findAll(): Promise<User[]> {
182
- return this.users;
183
- }
184
-
185
- async findById(id: number): Promise<User | null> {
186
- return this.users.find(user => user.id === id) || null;
187
- }
188
-
189
- async findByEmail(email: string): Promise<User | null> {
190
- return this.users.find(user => user.email === email) || null;
191
- }
192
-
193
- async create(data: CreateUser): Promise<User> {
194
- const existingUser = await this.findByEmail(data.email);
195
- if (existingUser) {
196
- throw new Error('User with this email already exists');
197
- }
198
-
199
- const user: User = {
200
- id: this.nextId++,
201
- email: data.email,
202
- name: data.name,
203
- createdAt: new Date().toISOString(),
204
- updatedAt: new Date().toISOString()
205
- };
206
-
207
- this.users.push(user);
208
- return user;
209
- }
210
-
211
- async update(id: number, data: UpdateUser): Promise<User | null> {
212
- const userIndex = this.users.findIndex(user => user.id === id);
213
- if (userIndex === -1) {
214
- return null;
215
- }
216
-
217
- const updatedUser = {
218
- ...this.users[userIndex],
219
- ...data,
220
- updatedAt: new Date().toISOString()
221
- };
222
-
223
- this.users[userIndex] = updatedUser;
224
- return updatedUser;
225
- }
226
-
227
- async delete(id: number): Promise<boolean> {
228
- const userIndex = this.users.findIndex(user => user.id === id);
229
- if (userIndex === -1) {
230
- return false;
231
- }
232
-
233
- this.users.splice(userIndex, 1);
234
- return true;
235
- }
236
- }
237
-
238
- export const userService = new UserService();
239
- ```
240
-
241
- ```typescript
242
- // src/services/post.service.ts
243
- import { Post, CreatePost, UpdatePost } from '../schemas/post.schema';
244
-
245
- export class PostService {
246
- private posts: Post[] = [];
247
- private nextId = 1;
248
-
249
- async findAll(published?: boolean): Promise<Post[]> {
250
- if (published !== undefined) {
251
- return this.posts.filter(post => post.published === published);
252
- }
253
- return this.posts;
254
- }
255
-
256
- async findById(id: number): Promise<Post | null> {
257
- return this.posts.find(post => post.id === id) || null;
258
- }
259
-
260
- async findByAuthor(authorId: number): Promise<Post[]> {
261
- return this.posts.filter(post => post.authorId === authorId);
262
- }
263
-
264
- async create(data: CreatePost, authorId: number): Promise<Post> {
265
- const post: Post = {
266
- id: this.nextId++,
267
- title: data.title,
268
- content: data.content,
269
- published: data.published || false,
270
- authorId,
271
- createdAt: new Date().toISOString(),
272
- updatedAt: new Date().toISOString()
273
- };
274
-
275
- this.posts.push(post);
276
- return post;
277
- }
278
-
279
- async update(id: number, data: UpdatePost): Promise<Post | null> {
280
- const postIndex = this.posts.findIndex(post => post.id === id);
281
- if (postIndex === -1) {
282
- return null;
283
- }
284
-
285
- const updatedPost = {
286
- ...this.posts[postIndex],
287
- ...data,
288
- updatedAt: new Date().toISOString()
289
- };
290
-
291
- this.posts[postIndex] = updatedPost;
292
- return updatedPost;
293
- }
294
-
295
- async delete(id: number): Promise<boolean> {
296
- const postIndex = this.posts.findIndex(post => post.id === id);
297
- if (postIndex === -1) {
298
- return false;
299
- }
300
-
301
- this.posts.splice(postIndex, 1);
302
- return true;
303
- }
304
- }
305
-
306
- export const postService = new PostService();
307
- ```
308
-
309
- ## Middleware
310
-
311
- ```typescript
312
- // src/middleware/auth.middleware.ts
313
- import { Request, Response, NextFunction } from 'balda-js';
314
- import { userService } from '../services/user.service';
315
-
316
- export const authMiddleware = async (req: Request, res: Response, next: NextFunction) => {
317
- const token = req.headers.authorization?.replace('Bearer ', '');
318
-
319
- if (!token) {
320
- return res.unauthorized({ error: 'Authentication token required' });
321
- }
322
-
323
- try {
324
- // In a real app, you'd verify the JWT token here
325
- // For this example, we'll use a simple user ID lookup
326
- const userId = parseInt(token);
327
- const user = await userService.findById(userId);
328
-
329
- if (!user) {
330
- return res.unauthorized({ error: 'Invalid authentication token' });
331
- }
332
-
333
- req.user = user;
334
- next();
335
- } catch (error) {
336
- return res.unauthorized({ error: 'Invalid authentication token' });
337
- }
338
- };
339
-
340
- export const optionalAuthMiddleware = async (req: Request, res: Response, next: NextFunction) => {
341
- const token = req.headers.authorization?.replace('Bearer ', '');
342
-
343
- if (token) {
344
- try {
345
- const userId = parseInt(token);
346
- const user = await userService.findById(userId);
347
- if (user) {
348
- req.user = user;
349
- }
350
- } catch (error) {
351
- // Ignore auth errors for optional auth
352
- }
353
- }
354
-
355
- next();
356
- };
357
- ```
358
-
359
- ## Controllers
360
-
361
- ```typescript
362
- // src/controllers/users.controller.ts
363
- import {
364
- controller,
365
- get,
366
- post,
367
- put,
368
- del,
369
- validate,
370
- serialize,
371
- middleware
372
- } from 'balda-js';
373
- import { Request, Response } from 'balda-js';
374
- import {
375
- CreateUserSchema,
376
- UpdateUserSchema,
377
- UserSchema,
378
- LoginSchema
379
- } from '../schemas/user.schema';
380
- import { userService } from '../services/user.service';
381
- import { authMiddleware } from '../middleware/auth.middleware';
382
-
383
- @controller('/users')
384
- export class UsersController {
385
- @get('/')
386
- @serialize(Type.Array(UserSchema))
387
- async getAllUsers(req: Request, res: Response) {
388
- const users = await userService.findAll();
389
- res.json(users);
390
- }
391
-
392
- @get('/:id')
393
- @serialize(UserSchema)
394
- async getUserById(req: Request, res: Response) {
395
- const id = parseInt(req.params.id);
396
- const user = await userService.findById(id);
397
-
398
- if (!user) {
399
- return res.notFound({ error: 'User not found' });
400
- }
401
-
402
- res.json(user);
403
- }
404
-
405
- @post('/')
406
- @validate.body(CreateUserSchema)
407
- @serialize(UserSchema)
408
- async createUser(req: Request, res: Response, body: CreateUser) {
409
- try {
410
- const user = await userService.create(body);
411
- res.created(user);
412
- } catch (error) {
413
- return res.conflict({ error: error.message });
414
- }
415
- }
416
-
417
- @put('/:id')
418
- @middleware(authMiddleware)
419
- @validate.body(UpdateUserSchema)
420
- @serialize(UserSchema)
421
- async updateUser(req: Request, res: Response, body: UpdateUser) {
422
- const id = parseInt(req.params.id);
423
-
424
- // Ensure users can only update their own profile
425
- if (req.user.id !== id) {
426
- return res.forbidden({ error: 'Cannot update other users' });
427
- }
428
-
429
- const user = await userService.update(id, body);
430
-
431
- if (!user) {
432
- return res.notFound({ error: 'User not found' });
433
- }
434
-
435
- res.json(user);
436
- }
437
-
438
- @del('/:id')
439
- @middleware(authMiddleware)
440
- async deleteUser(req: Request, res: Response) {
441
- const id = parseInt(req.params.id);
442
-
443
- // Ensure users can only delete their own account
444
- if (req.user.id !== id) {
445
- return res.forbidden({ error: 'Cannot delete other users' });
446
- }
447
-
448
- const deleted = await userService.delete(id);
449
-
450
- if (!deleted) {
451
- return res.notFound({ error: 'User not found' });
452
- }
453
-
454
- res.noContent();
455
- }
456
-
457
- @post('/login')
458
- @validate.body(LoginSchema)
459
- async login(req: Request, res: Response, body: Login) {
460
- const user = await userService.findByEmail(body.email);
461
-
462
- if (!user) {
463
- return res.unauthorized({ error: 'Invalid credentials' });
464
- }
465
-
466
- // In a real app, you'd verify the password hash here
467
- // For this example, we'll just return the user ID as a token
468
- res.json({
469
- token: user.id.toString(),
470
- user: {
471
- id: user.id,
472
- email: user.email,
473
- name: user.name
474
- }
475
- });
476
- }
477
- }
478
- ```
479
-
480
- ```typescript
481
- // src/controllers/posts.controller.ts
482
- import {
483
- controller,
484
- get,
485
- post,
486
- put,
487
- del,
488
- validate,
489
- serialize,
490
- middleware,
491
- Request,
492
- Response
493
- } from 'balda-js';
494
- import { Type, Static } from '@sinclair/typebox';
495
- import {
496
- CreatePostSchema,
497
- UpdatePostSchema,
498
- PostSchema
499
- } from '../schemas/post.schema';
500
- import { postService } from '../services/post.service';
501
- import { authMiddleware, optionalAuthMiddleware } from '../middleware/auth.middleware';
502
-
503
- @controller('/posts')
504
- export class PostsController {
505
- @get('/')
506
- @middleware(optionalAuthMiddleware)
507
- @serialize(Type.Array(PostSchema))
508
- async getAllPosts(req: Request, res: Response) {
509
- const published = req.query.published === 'true';
510
- const posts = await postService.findAll(published);
511
- res.json(posts);
512
- }
513
-
514
- @get('/:id')
515
- @serialize(PostSchema)
516
- async getPostById(req: Request, res: Response) {
517
- const id = parseInt(req.params.id);
518
- const post = await postService.findById(id);
519
-
520
- if (!post) {
521
- return res.notFound({ error: 'Post not found' });
522
- }
523
-
524
- // Only show unpublished posts to their authors
525
- if (!post.published && (!req.user || req.user.id !== post.authorId)) {
526
- return res.notFound({ error: 'Post not found' });
527
- }
528
-
529
- res.json(post);
530
- }
531
-
532
- @post('/')
533
- @middleware(authMiddleware)
534
- @validate.body(CreatePostSchema)
535
- @serialize(PostSchema)
536
- async createPost(req: Request, res: Response, body: Static<CreatePostSchema>) {
537
- const post = await postService.create(body, req.user.id);
538
- res.created(post);
539
- }
540
-
541
- @put('/:id')
542
- @middleware(authMiddleware)
543
- @validate.body(UpdatePostSchema)
544
- @serialize(PostSchema)
545
- async updatePost(req: Request, res: Response, body: UpdatePost) {
546
- const id = parseInt(req.params.id);
547
- const existingPost = await postService.findById(id);
548
-
549
- if (!existingPost) {
550
- return res.notFound({ error: 'Post not found' });
551
- }
552
-
553
- // Ensure users can only update their own posts
554
- if (req.user.id !== existingPost.authorId) {
555
- return res.forbidden({ error: 'Cannot update other users\' posts' });
556
- }
557
-
558
- const post = await postService.update(id, body);
559
- res.json(post);
560
- }
561
-
562
- @del('/:id')
563
- @middleware(authMiddleware)
564
- async deletePost(req: Request, res: Response) {
565
- const id = parseInt(req.params.id);
566
- const existingPost = await postService.findById(id);
567
-
568
- if (!existingPost) {
569
- return res.notFound({ error: 'Post not found' });
570
- }
571
-
572
- // Ensure users can only delete their own posts
573
- if (req.user.id !== existingPost.authorId) {
574
- return res.forbidden({ error: 'Cannot delete other users\' posts' });
575
- }
576
-
577
- const deleted = await postService.delete(id);
578
- res.noContent();
579
- }
580
-
581
- @get('/author/:authorId')
582
- @serialize(Type.Array(PostSchema))
583
- async getPostsByAuthor(req: Request, res: Response) {
584
- const authorId = parseInt(req.params.authorId);
585
- const posts = await postService.findByAuthor(authorId);
586
-
587
- // Filter out unpublished posts unless the user is the author
588
- const filteredPosts = posts.filter(post =>
589
- post.published || (req.user && req.user.id === authorId)
590
- );
591
-
592
- res.json(filteredPosts);
593
- }
594
- }
595
- ```