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.
- package/.github/workflows/deploy-docs.yml +57 -0
- package/LICENSE.md +1 -1
- package/README.md +2 -28
- package/TODO.md +8 -1
- package/bun.lock +3 -0
- package/config/upload.config.ts +135 -0
- package/core/App.ts +168 -4
- package/core/ArcheType.ts +122 -0
- package/core/BatchLoader.ts +100 -0
- package/core/ComponentRegistry.ts +4 -3
- package/core/Components.ts +2 -2
- package/core/Decorators.ts +15 -8
- package/core/Entity.ts +193 -14
- package/core/EntityCache.ts +15 -0
- package/core/EntityHookManager.ts +855 -0
- package/core/EntityManager.ts +12 -2
- package/core/ErrorHandler.ts +64 -7
- package/core/FileValidator.ts +284 -0
- package/core/Query.ts +503 -85
- package/core/RequestContext.ts +24 -0
- package/core/RequestLoaders.ts +89 -0
- package/core/SchedulerManager.ts +710 -0
- package/core/UploadManager.ts +261 -0
- package/core/components/UploadComponent.ts +206 -0
- package/core/decorators/EntityHooks.ts +190 -0
- package/core/decorators/ScheduledTask.ts +83 -0
- package/core/events/EntityLifecycleEvents.ts +177 -0
- package/core/processors/ImageProcessor.ts +423 -0
- package/core/storage/LocalStorageProvider.ts +290 -0
- package/core/storage/StorageProvider.ts +112 -0
- package/database/DatabaseHelper.ts +183 -58
- package/database/index.ts +5 -5
- package/database/sqlHelpers.ts +7 -0
- package/docs/README.md +149 -0
- package/docs/_coverpage.md +36 -0
- package/docs/_sidebar.md +23 -0
- package/docs/api/core.md +568 -0
- package/docs/api/hooks.md +554 -0
- package/docs/api/index.md +222 -0
- package/docs/api/query.md +678 -0
- package/docs/api/service.md +744 -0
- package/docs/core-concepts/archetypes.md +512 -0
- package/docs/core-concepts/components.md +498 -0
- package/docs/core-concepts/entity.md +314 -0
- package/docs/core-concepts/hooks.md +683 -0
- package/docs/core-concepts/query.md +588 -0
- package/docs/core-concepts/services.md +647 -0
- package/docs/examples/code-examples.md +425 -0
- package/docs/getting-started.md +337 -0
- package/docs/index.html +97 -0
- package/gql/Generator.ts +58 -35
- package/gql/decorators/Upload.ts +176 -0
- package/gql/helpers.ts +67 -0
- package/gql/index.ts +65 -31
- package/gql/types.ts +1 -1
- package/index.ts +79 -11
- package/package.json +19 -10
- package/rest/Generator.ts +3 -0
- package/rest/index.ts +22 -0
- package/service/Service.ts +1 -1
- package/service/ServiceRegistry.ts +10 -6
- package/service/index.ts +12 -1
- package/tests/bench/insert.bench.ts +59 -0
- package/tests/bench/relations.bench.ts +269 -0
- package/tests/bench/sorting.bench.ts +415 -0
- package/tests/component-hooks.test.ts +1409 -0
- package/tests/component.test.ts +338 -0
- package/tests/errorHandling.test.ts +155 -0
- package/tests/hooks.test.ts +666 -0
- package/tests/query-sorting.test.ts +101 -0
- package/tests/relations.test.ts +169 -0
- package/tests/scheduler.test.ts +724 -0
- package/tsconfig.json +35 -34
- package/types/graphql.types.ts +87 -0
- package/types/hooks.types.ts +141 -0
- package/types/scheduler.types.ts +165 -0
- package/types/upload.types.ts +184 -0
- package/upload/index.ts +140 -0
- package/utils/UploadHelper.ts +305 -0
- package/utils/cronParser.ts +366 -0
- package/utils/errorMessages.ts +151 -0
- 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!* 🚀
|