bunsane 0.1.0 → 0.1.2

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 (82) hide show
  1. package/.github/workflows/deploy-docs.yml +57 -0
  2. package/LICENSE.md +1 -1
  3. package/README.md +2 -28
  4. package/TODO.md +8 -1
  5. package/bun.lock +3 -0
  6. package/config/upload.config.ts +135 -0
  7. package/core/App.ts +168 -4
  8. package/core/ArcheType.ts +122 -0
  9. package/core/BatchLoader.ts +100 -0
  10. package/core/ComponentRegistry.ts +4 -3
  11. package/core/Components.ts +2 -2
  12. package/core/Decorators.ts +15 -8
  13. package/core/Entity.ts +193 -14
  14. package/core/EntityCache.ts +15 -0
  15. package/core/EntityHookManager.ts +855 -0
  16. package/core/EntityManager.ts +12 -2
  17. package/core/ErrorHandler.ts +64 -7
  18. package/core/FileValidator.ts +284 -0
  19. package/core/Query.ts +503 -85
  20. package/core/RequestContext.ts +24 -0
  21. package/core/RequestLoaders.ts +89 -0
  22. package/core/SchedulerManager.ts +710 -0
  23. package/core/UploadManager.ts +261 -0
  24. package/core/components/UploadComponent.ts +206 -0
  25. package/core/decorators/EntityHooks.ts +190 -0
  26. package/core/decorators/ScheduledTask.ts +83 -0
  27. package/core/events/EntityLifecycleEvents.ts +177 -0
  28. package/core/processors/ImageProcessor.ts +423 -0
  29. package/core/storage/LocalStorageProvider.ts +290 -0
  30. package/core/storage/StorageProvider.ts +112 -0
  31. package/database/DatabaseHelper.ts +183 -58
  32. package/database/index.ts +5 -5
  33. package/database/sqlHelpers.ts +7 -0
  34. package/docs/README.md +149 -0
  35. package/docs/_coverpage.md +36 -0
  36. package/docs/_sidebar.md +23 -0
  37. package/docs/api/core.md +568 -0
  38. package/docs/api/hooks.md +554 -0
  39. package/docs/api/index.md +222 -0
  40. package/docs/api/query.md +678 -0
  41. package/docs/api/service.md +744 -0
  42. package/docs/core-concepts/archetypes.md +512 -0
  43. package/docs/core-concepts/components.md +498 -0
  44. package/docs/core-concepts/entity.md +314 -0
  45. package/docs/core-concepts/hooks.md +683 -0
  46. package/docs/core-concepts/query.md +588 -0
  47. package/docs/core-concepts/services.md +647 -0
  48. package/docs/examples/code-examples.md +425 -0
  49. package/docs/getting-started.md +337 -0
  50. package/docs/index.html +97 -0
  51. package/gql/Generator.ts +58 -35
  52. package/gql/decorators/Upload.ts +176 -0
  53. package/gql/helpers.ts +67 -0
  54. package/gql/index.ts +65 -31
  55. package/gql/types.ts +1 -1
  56. package/index.ts +79 -11
  57. package/package.json +19 -10
  58. package/rest/Generator.ts +3 -0
  59. package/rest/index.ts +22 -0
  60. package/service/Service.ts +1 -1
  61. package/service/ServiceRegistry.ts +10 -6
  62. package/service/index.ts +12 -1
  63. package/tests/bench/insert.bench.ts +59 -0
  64. package/tests/bench/relations.bench.ts +269 -0
  65. package/tests/bench/sorting.bench.ts +415 -0
  66. package/tests/component-hooks.test.ts +1409 -0
  67. package/tests/component.test.ts +338 -0
  68. package/tests/errorHandling.test.ts +155 -0
  69. package/tests/hooks.test.ts +666 -0
  70. package/tests/query-sorting.test.ts +101 -0
  71. package/tests/relations.test.ts +169 -0
  72. package/tests/scheduler.test.ts +724 -0
  73. package/tsconfig.json +35 -34
  74. package/types/graphql.types.ts +87 -0
  75. package/types/hooks.types.ts +141 -0
  76. package/types/scheduler.types.ts +165 -0
  77. package/types/upload.types.ts +184 -0
  78. package/upload/index.ts +140 -0
  79. package/utils/UploadHelper.ts +305 -0
  80. package/utils/cronParser.ts +366 -0
  81. package/utils/errorMessages.ts +151 -0
  82. package/core/Events.ts +0 -0
@@ -0,0 +1,588 @@
1
+ # Query System
2
+
3
+ BunSane's Query system provides a powerful, type-safe way to retrieve and filter entities from your database. It supports complex filtering, relationships, sorting, pagination, and eager loading - all while maintaining excellent performance.
4
+
5
+ ## 🎯 What is the Query System?
6
+
7
+ The Query system is BunSane's fluent API for database operations. Unlike traditional ORMs, it works directly with your Entity-Component-System architecture, providing efficient queries that leverage your component structure.
8
+
9
+ ### Key Features
10
+
11
+ - **Type Safety**: Full TypeScript support with compile-time guarantees
12
+ - **Fluent API**: Chainable methods for building complex queries
13
+ - **Component-Based**: Query by component presence and data
14
+ - **Relationship Support**: Efficient loading of related entities
15
+ - **Performance Optimized**: Smart query generation and execution
16
+ - **Pagination**: Built-in support for offset-based pagination
17
+
18
+ ## 🏗️ Basic Queries
19
+
20
+ ### Simple Component Queries
21
+
22
+ ```typescript
23
+ import { Query } from 'bunsane';
24
+
25
+ // Find all entities with UserTag component
26
+ const users = await new Query()
27
+ .with(UserTag)
28
+ .exec();
29
+
30
+ // Find entities with multiple required components
31
+ const usersWithDetails = await new Query()
32
+ .with(UserTag)
33
+ .with(NameComponent)
34
+ .with(EmailComponent)
35
+ .exec();
36
+ ```
37
+
38
+ ### Single Entity Queries
39
+
40
+ ```typescript
41
+ // Find entity by ID
42
+ const user = await new Query()
43
+ .with(UserTag)
44
+ .findById('user-123')
45
+ .exec();
46
+
47
+ // Convenience method for single entity
48
+ const userEntity = await new Query()
49
+ .findOneById('user-123');
50
+ ```
51
+
52
+ ## 🔍 Filtering
53
+
54
+ ### Component-Based Filters
55
+
56
+ ```typescript
57
+ // Filter by component data using Query.filter()
58
+ const users = await new Query()
59
+ .with(UserTag)
60
+ .with(EmailComponent, Query.filters(
61
+ Query.filter("value", Query.filterOp.EQ, "john@example.com")
62
+ ))
63
+ .exec();
64
+
65
+ // Multiple filters on same component (AND condition)
66
+ const premiumUsers = await new Query()
67
+ .with(UserTag)
68
+ .with(StatusComponent, Query.filters(
69
+ Query.filter("value", Query.filterOp.EQ, "active")
70
+ ))
71
+ .with(PlanComponent, Query.filters(
72
+ Query.filter("value", Query.filterOp.EQ, "premium")
73
+ ))
74
+ .exec();
75
+ ```
76
+
77
+ ### Advanced Filtering
78
+
79
+ ```typescript
80
+ // Range filters
81
+ const recentUsers = await new Query()
82
+ .with(UserTag)
83
+ .with(CreatedAtComponent, Query.filters(
84
+ Query.filter("value", Query.filterOp.GTE, new Date('2025-01-01')),
85
+ Query.filter("value", Query.filterOp.LTE, new Date('2025-12-31'))
86
+ ))
87
+ .exec();
88
+
89
+ // LIKE queries
90
+ const johnUsers = await new Query()
91
+ .with(UserTag)
92
+ .with(NameComponent, Query.filters(
93
+ Query.filter("value", Query.filterOp.LIKE, "John%")
94
+ ))
95
+ .exec();
96
+
97
+ // IN queries
98
+ const specificUsers = await new Query()
99
+ .with(UserTag)
100
+ .with(AuthorComponent, Query.filters(
101
+ Query.filter("value", Query.filterOp.IN, ["user-1", "user-2", "user-3"])
102
+ ))
103
+ .exec();
104
+ ```
105
+
106
+ ### Exclusion Filters
107
+
108
+ ```typescript
109
+ // Exclude entities with certain components
110
+ const activeUsers = await new Query()
111
+ .with(UserTag)
112
+ .without(BannedComponent)
113
+ .exec();
114
+
115
+ // Combine inclusion and exclusion
116
+ const verifiedUnbannedUsers = await new Query()
117
+ .with(UserTag)
118
+ .with(VerifiedComponent)
119
+ .without(BannedComponent)
120
+ .exec();
121
+ ```
122
+
123
+ ## 🔗 Relationships and Eager Loading
124
+
125
+ ### Component Relationships
126
+
127
+ ```typescript
128
+ // Query posts with author information
129
+ const posts = await new Query()
130
+ .with(PostTag)
131
+ .with(AuthorComponent)
132
+ .exec();
133
+
134
+ // Load related components efficiently
135
+ const postsWithDetails = await new Query()
136
+ .with(PostTag)
137
+ .eagerLoadComponents([
138
+ TitleComponent,
139
+ ContentComponent,
140
+ DateComponent
141
+ ])
142
+ .exec();
143
+
144
+ // Each post entity will have related components loaded
145
+ for (const post of postsWithDetails) {
146
+ const title = await post.get(TitleComponent);
147
+ const content = await post.get(ContentComponent);
148
+ console.log(`Post: ${title?.value}, Content: ${content?.value}`);
149
+ }
150
+ ```
151
+
152
+ ### Batch Loading Related Entities
153
+
154
+ ```typescript
155
+ import { BatchLoader } from 'bunsane';
156
+
157
+ // Load posts with their authors in batch
158
+ const posts = await new Query()
159
+ .with(PostTag)
160
+ .exec();
161
+
162
+ // Batch load all authors
163
+ const authors = await BatchLoader.loadRelatedEntitiesBatched(
164
+ posts,
165
+ AuthorComponent,
166
+ Entity.LoadMultiple
167
+ );
168
+
169
+ // Associate authors with posts
170
+ const postsWithAuthors = posts.map(post => ({
171
+ post,
172
+ author: authors.get(post.id)
173
+ }));
174
+ ```
175
+
176
+ ### Full Entity Population
177
+
178
+ ```typescript
179
+ // Load complete entity data
180
+ const fullPosts = await new Query()
181
+ .with(PostTag)
182
+ .populate() // Loads all components for each entity
183
+ .exec();
184
+
185
+ // Now all component data is immediately available
186
+ for (const post of fullPosts) {
187
+ // No async calls needed - all data is loaded
188
+ const title = post.get(TitleComponent);
189
+ const content = post.get(ContentComponent);
190
+ const author = post.get(AuthorComponent);
191
+ }
192
+ ```
193
+
194
+ ## 📊 Sorting and Ordering
195
+
196
+ ### Basic Sorting
197
+
198
+ ```typescript
199
+ // Sort by component field
200
+ const usersByName = await new Query()
201
+ .with(UserTag)
202
+ .with(NameComponent)
203
+ .sortBy(NameComponent, "value", "ASC")
204
+ .exec();
205
+
206
+ // Sort by multiple fields
207
+ const usersByStatusAndName = await new Query()
208
+ .with(UserTag)
209
+ .with(StatusComponent)
210
+ .with(NameComponent)
211
+ .orderBy([
212
+ { component: "StatusComponent", property: "value", direction: "DESC" },
213
+ { component: "NameComponent", property: "value", direction: "ASC" }
214
+ ])
215
+ .exec();
216
+ ```
217
+
218
+ ### Advanced Sorting
219
+
220
+ ```typescript
221
+ // Sort with null handling
222
+ const usersByLastLogin = await new Query()
223
+ .with(UserTag)
224
+ .with(LastLoginComponent)
225
+ .sortBy(LastLoginComponent, "value", "DESC", true) // nulls first
226
+ .exec();
227
+
228
+ // Multiple sort criteria with different null handling
229
+ const sortedUsers = await new Query()
230
+ .with(UserTag)
231
+ .with(PriorityComponent)
232
+ .with(NameComponent)
233
+ .orderBy([
234
+ { component: "PriorityComponent", property: "value", direction: "DESC", nullsFirst: false },
235
+ { component: "NameComponent", property: "value", direction: "ASC", nullsFirst: false }
236
+ ])
237
+ .exec();
238
+ ```
239
+
240
+ ## 📄 Pagination
241
+
242
+ ### Offset-Based Pagination
243
+
244
+ ```typescript
245
+ // Basic pagination
246
+ const page1 = await new Query()
247
+ .with(UserTag)
248
+ .take(10)
249
+ .offset(0)
250
+ .exec();
251
+
252
+ const page2 = await new Query()
253
+ .with(UserTag)
254
+ .take(10)
255
+ .offset(10)
256
+ .exec();
257
+ ```
258
+
259
+ ### Pagination Helper
260
+
261
+ ```typescript
262
+ class PaginatedQuery {
263
+ static async execute(query: Query, page: number = 1, limit: number = 10) {
264
+ const offset = (page - 1) * limit;
265
+
266
+ // Get paginated results
267
+ const results = await query
268
+ .take(limit)
269
+ .offset(offset)
270
+ .exec();
271
+
272
+ return {
273
+ data: results,
274
+ pagination: {
275
+ page,
276
+ limit,
277
+ offset,
278
+ hasNext: results.length === limit,
279
+ hasPrev: page > 1
280
+ }
281
+ };
282
+ }
283
+ }
284
+
285
+ // Usage
286
+ const result = await PaginatedQuery.execute(
287
+ new Query().with(UserTag),
288
+ 2, // page
289
+ 20 // limit
290
+ );
291
+
292
+ console.log(result.pagination);
293
+ // { page: 2, limit: 20, offset: 20, hasNext: true, hasPrev: true }
294
+ ```
295
+
296
+ ## 🎯 Advanced Query Patterns
297
+
298
+ ### Conditional Query Building
299
+
300
+ ```typescript
301
+ class UserQueryBuilder {
302
+ private query: Query;
303
+
304
+ constructor() {
305
+ this.query = new Query().with(UserTag);
306
+ }
307
+
308
+ withStatus(status?: string) {
309
+ if (status) {
310
+ this.query = this.query.with(StatusComponent, Query.filters(
311
+ Query.filter("value", Query.filterOp.EQ, status)
312
+ ));
313
+ }
314
+ return this;
315
+ }
316
+
317
+ withRole(role?: string) {
318
+ if (role) {
319
+ this.query = this.query.with(RoleComponent, Query.filters(
320
+ Query.filter("value", Query.filterOp.EQ, role)
321
+ ));
322
+ }
323
+ return this;
324
+ }
325
+
326
+ withDateRange(startDate?: Date, endDate?: Date) {
327
+ if (startDate || endDate) {
328
+ const filters = [];
329
+ if (startDate) {
330
+ filters.push(Query.filter("value", Query.filterOp.GTE, startDate));
331
+ }
332
+ if (endDate) {
333
+ filters.push(Query.filter("value", Query.filterOp.LTE, endDate));
334
+ }
335
+ this.query = this.query.with(CreatedAtComponent, Query.filters(...filters));
336
+ }
337
+ return this;
338
+ }
339
+
340
+ async execute() {
341
+ return await this.query.exec();
342
+ }
343
+ }
344
+
345
+ // Usage
346
+ const users = await new UserQueryBuilder()
347
+ .withStatus('active')
348
+ .withRole('admin')
349
+ .withDateRange(new Date('2025-01-01'), new Date('2025-12-31'))
350
+ .execute();
351
+ ```
352
+
353
+ ### Query Composition
354
+
355
+ ```typescript
356
+ // Base queries
357
+ const baseUserQuery = new Query().with(UserTag);
358
+
359
+ const activeUsersQuery = baseUserQuery
360
+ .with(StatusComponent, Query.filters(
361
+ Query.filter("value", Query.filterOp.EQ, "active")
362
+ ));
363
+
364
+ const adminQuery = activeUsersQuery
365
+ .with(RoleComponent, Query.filters(
366
+ Query.filter("value", Query.filterOp.EQ, "admin")
367
+ ));
368
+
369
+ const premiumQuery = activeUsersQuery
370
+ .with(PlanComponent, Query.filters(
371
+ Query.filter("value", Query.filterOp.EQ, "premium")
372
+ ));
373
+
374
+ // Execute multiple queries
375
+ const [admins, premiumUsers] = await Promise.all([
376
+ adminQuery.exec(),
377
+ premiumQuery.exec()
378
+ ]);
379
+ ```
380
+
381
+ ### Real-world Service Patterns
382
+
383
+ Based on actual service implementations:
384
+
385
+ ```typescript
386
+ // From UserService.ts - Finding users by email
387
+ const userCheck = await new Query()
388
+ .with(UserTag)
389
+ .with(EmailComponent, Query.filters(
390
+ Query.filter("value", Query.filterOp.EQ, input.email)
391
+ ))
392
+ .exec();
393
+
394
+ // From PostService.ts - Posts with eager loading
395
+ const posts = await new Query()
396
+ .with(PostTag)
397
+ .eagerLoadComponents([
398
+ TitleComponent,
399
+ ContentComponent,
400
+ AuthorComponent,
401
+ ImageViewComponent
402
+ ])
403
+ .exec();
404
+
405
+ // From PostService.ts - Related entity batch loading
406
+ if (isFieldRequested(info, 'author')) {
407
+ context.authors = await BatchLoader.loadRelatedEntitiesBatched(
408
+ entities,
409
+ AuthorComponent,
410
+ Entity.LoadMultiple
411
+ );
412
+ }
413
+ ```
414
+
415
+ ## ⚡ Performance Optimization
416
+
417
+ ### Efficient Loading Strategies
418
+
419
+ ```typescript
420
+ // Use eager loading to reduce database round trips
421
+ const postsWithDetails = await new Query()
422
+ .with(PostTag)
423
+ .eagerLoadComponents([
424
+ TitleComponent,
425
+ ContentComponent,
426
+ AuthorComponent,
427
+ DateComponent
428
+ ])
429
+ .exec();
430
+
431
+ // Single query loads all required data
432
+ for (const post of postsWithDetails) {
433
+ const title = await post.get(TitleComponent);
434
+ const content = await post.get(ContentComponent);
435
+ const author = await post.get(AuthorComponent);
436
+ // Components are already loaded - no additional queries
437
+ }
438
+ ```
439
+
440
+ ### Query Result Caching
441
+
442
+ ```typescript
443
+ class CachedQuery {
444
+ private static cache = new Map<string, { data: any[], timestamp: number }>();
445
+ private static CACHE_TTL = 5 * 60 * 1000; // 5 minutes
446
+
447
+ static async execute(query: Query, cacheKey: string) {
448
+ // Check cache
449
+ const cached = this.cache.get(cacheKey);
450
+ if (cached && Date.now() - cached.timestamp < this.CACHE_TTL) {
451
+ return cached.data;
452
+ }
453
+
454
+ // Execute query
455
+ const data = await query.exec();
456
+
457
+ // Cache result
458
+ this.cache.set(cacheKey, {
459
+ data,
460
+ timestamp: Date.now()
461
+ });
462
+
463
+ return data;
464
+ }
465
+
466
+ static invalidate(cacheKey: string) {
467
+ this.cache.delete(cacheKey);
468
+ }
469
+
470
+ static clearAll() {
471
+ this.cache.clear();
472
+ }
473
+ }
474
+
475
+ // Usage
476
+ const users = await CachedQuery.execute(
477
+ new Query().with(UserTag),
478
+ 'active-users'
479
+ );
480
+ ```
481
+
482
+ ### Component Design for Query Performance
483
+
484
+ ```typescript
485
+ // Design components to optimize queries
486
+ @Component
487
+ class UserStatusComponent extends BaseComponent {
488
+ @CompData()
489
+ value: 'active' | 'inactive' | 'banned' = 'active';
490
+ }
491
+
492
+ @Component
493
+ class UserEmailComponent extends BaseComponent {
494
+ @CompData()
495
+ value: string = '';
496
+ }
497
+
498
+ // Efficient queries leverage component structure
499
+ const activeUsers = await new Query()
500
+ .with(UserTag)
501
+ .with(UserStatusComponent, Query.filters(
502
+ Query.filter("value", Query.filterOp.EQ, "active")
503
+ ))
504
+ .eagerLoadComponents([UserEmailComponent])
505
+ .exec();
506
+ ```
507
+
508
+ ## 🔧 Best Practices
509
+
510
+ ### Query Design
511
+
512
+ - **Component-First**: Design queries around component requirements
513
+ - **Eager Loading**: Use `eagerLoadComponents()` for frequently accessed data
514
+ - **Selective Filtering**: Apply filters at the component level for precision
515
+ - **Pagination**: Always use `take()` and `offset()` for large result sets
516
+ - **Batch Operations**: Use BatchLoader for relationship loading
517
+
518
+ ### Error Handling
519
+
520
+ ```typescript
521
+ try {
522
+ const users = await new Query()
523
+ .with(UserTag)
524
+ .with(EmailComponent, Query.filters(
525
+ Query.filter("value", Query.filterOp.EQ, userEmail)
526
+ ))
527
+ .exec();
528
+
529
+ if (users.length === 0) {
530
+ throw new Error('User not found');
531
+ }
532
+
533
+ return users[0];
534
+
535
+ } catch (error) {
536
+ logger.error('Query failed', {
537
+ error: error.message,
538
+ query: 'findUserByEmail',
539
+ email: userEmail
540
+ });
541
+
542
+ throw new Error('Unable to find user');
543
+ }
544
+ ```
545
+
546
+ ### Query Validation
547
+
548
+ ```typescript
549
+ class ValidatedQuery {
550
+ static async safeExecute(query: Query, options: {
551
+ maxResults?: number;
552
+ timeout?: number;
553
+ } = {}) {
554
+ const { maxResults = 1000, timeout = 30000 } = options;
555
+
556
+ // Add safety limits
557
+ query = query.take(maxResults);
558
+
559
+ // Execute with timeout
560
+ const result = await Promise.race([
561
+ query.exec(),
562
+ new Promise((_, reject) =>
563
+ setTimeout(() => reject(new Error('Query timeout')), timeout)
564
+ )
565
+ ]);
566
+
567
+ return result;
568
+ }
569
+ }
570
+
571
+ // Usage
572
+ const users = await ValidatedQuery.safeExecute(
573
+ new Query().with(UserTag),
574
+ { maxResults: 100, timeout: 10000 }
575
+ );
576
+ ```
577
+
578
+ ## 🚀 What's Next?
579
+
580
+ Now that you understand the Query system, let's explore:
581
+
582
+ - **[Lifecycle Hooks](hooks.md)** - Business logic integration
583
+ - **[Entity System](entity.md)** - How entities work with queries
584
+ - **[Services](services.md)** - Using queries in services
585
+
586
+ ---
587
+
588
+ *Ready to master data retrieval? Let's look at [Lifecycle Hooks](hooks.md) next!* 🚀