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,498 @@
|
|
|
1
|
+
# Component System
|
|
2
|
+
|
|
3
|
+
Components are the fundamental building blocks of BunSane's Entity-Component-System (ECS) architecture. They define the data and behavior that can be attached to entities, providing unparalleled flexibility in data modeling.
|
|
4
|
+
|
|
5
|
+
## 🎯 What is a Component?
|
|
6
|
+
|
|
7
|
+
A Component is a pure data structure that represents a specific aspect or capability of an entity. Unlike traditional object-oriented classes, components are:
|
|
8
|
+
|
|
9
|
+
- **Composable**: Can be mixed and matched on entities as needed
|
|
10
|
+
- **Type-Safe**: Full TypeScript support with compile-time guarantees
|
|
11
|
+
- **Database-Backed**: Automatically persisted to PostgreSQL
|
|
12
|
+
- **Decorator-Driven**: Use simple decorators to define data properties
|
|
13
|
+
- **Indexed**: Support for database indexing on frequently queried fields
|
|
14
|
+
|
|
15
|
+
## 🏗️ Creating Components
|
|
16
|
+
|
|
17
|
+
### Basic Component Structure
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { Component, CompData, BaseComponent } from 'bunsane';
|
|
21
|
+
|
|
22
|
+
@Component
|
|
23
|
+
export class UserProfile extends BaseComponent {
|
|
24
|
+
@CompData()
|
|
25
|
+
name: string = '';
|
|
26
|
+
|
|
27
|
+
@CompData()
|
|
28
|
+
email: string = '';
|
|
29
|
+
|
|
30
|
+
@CompData({ indexed: true })
|
|
31
|
+
username: string = '';
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Component Registration
|
|
36
|
+
|
|
37
|
+
Components must be decorated with `@Component` and extend `BaseComponent`. The framework automatically:
|
|
38
|
+
|
|
39
|
+
- Registers the component with the ComponentRegistry
|
|
40
|
+
- Generates a unique type ID for database storage
|
|
41
|
+
- Creates database tables for component data
|
|
42
|
+
- Sets up indexing for marked properties
|
|
43
|
+
|
|
44
|
+
## 📊 Component Data Properties
|
|
45
|
+
|
|
46
|
+
### Basic Data Properties
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
@Component
|
|
50
|
+
export class ProductInfo extends BaseComponent {
|
|
51
|
+
@CompData()
|
|
52
|
+
title: string = '';
|
|
53
|
+
|
|
54
|
+
@CompData()
|
|
55
|
+
description: string = '';
|
|
56
|
+
|
|
57
|
+
@CompData()
|
|
58
|
+
price: number = 0;
|
|
59
|
+
|
|
60
|
+
@CompData()
|
|
61
|
+
inStock: boolean = true;
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Indexed Properties
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
@Component
|
|
69
|
+
export class BlogPost extends BaseComponent {
|
|
70
|
+
@CompData()
|
|
71
|
+
title: string = '';
|
|
72
|
+
|
|
73
|
+
@CompData()
|
|
74
|
+
content: string = '';
|
|
75
|
+
|
|
76
|
+
@CompData({ indexed: true })
|
|
77
|
+
authorId: string = '';
|
|
78
|
+
|
|
79
|
+
@CompData({ indexed: true })
|
|
80
|
+
publishedAt: Date = new Date();
|
|
81
|
+
|
|
82
|
+
@CompData({ indexed: true })
|
|
83
|
+
tags: string[] = [];
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Complex Data Types
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
@Component
|
|
91
|
+
export class UserPreferences extends BaseComponent {
|
|
92
|
+
@CompData()
|
|
93
|
+
theme: 'light' | 'dark' | 'auto' = 'light';
|
|
94
|
+
|
|
95
|
+
@CompData()
|
|
96
|
+
notifications: {
|
|
97
|
+
email: boolean;
|
|
98
|
+
push: boolean;
|
|
99
|
+
sms: boolean;
|
|
100
|
+
} = {
|
|
101
|
+
email: true,
|
|
102
|
+
push: false,
|
|
103
|
+
sms: false
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
@CompData()
|
|
107
|
+
language: string = 'en';
|
|
108
|
+
|
|
109
|
+
@CompData()
|
|
110
|
+
timezone: string = 'UTC';
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## 🔧 Component Methods
|
|
115
|
+
|
|
116
|
+
### Custom Methods
|
|
117
|
+
|
|
118
|
+
Components can include methods for data manipulation and business logic:
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
@Component
|
|
122
|
+
export class ShoppingCart extends BaseComponent {
|
|
123
|
+
@CompData()
|
|
124
|
+
items: CartItem[] = [];
|
|
125
|
+
|
|
126
|
+
@CompData()
|
|
127
|
+
total: number = 0;
|
|
128
|
+
|
|
129
|
+
// Custom method to add items
|
|
130
|
+
addItem(productId: string, quantity: number, price: number) {
|
|
131
|
+
const existingItem = this.items.find(item => item.productId === productId);
|
|
132
|
+
|
|
133
|
+
if (existingItem) {
|
|
134
|
+
existingItem.quantity += quantity;
|
|
135
|
+
} else {
|
|
136
|
+
this.items.push({
|
|
137
|
+
productId,
|
|
138
|
+
quantity,
|
|
139
|
+
price,
|
|
140
|
+
addedAt: new Date()
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
this.recalculateTotal();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Custom method to remove items
|
|
148
|
+
removeItem(productId: string) {
|
|
149
|
+
this.items = this.items.filter(item => item.productId !== productId);
|
|
150
|
+
this.recalculateTotal();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Private helper method
|
|
154
|
+
private recalculateTotal() {
|
|
155
|
+
this.total = this.items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
interface CartItem {
|
|
160
|
+
productId: string;
|
|
161
|
+
quantity: number;
|
|
162
|
+
price: number;
|
|
163
|
+
addedAt: Date;
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## 💾 Component Persistence
|
|
168
|
+
|
|
169
|
+
### Automatic Database Schema
|
|
170
|
+
|
|
171
|
+
When you create a component, BunSane automatically:
|
|
172
|
+
|
|
173
|
+
1. **Creates Database Tables**:
|
|
174
|
+
- `components` - Stores component data as JSON
|
|
175
|
+
- `entity_components` - Links entities to their components
|
|
176
|
+
- `component_indexes` - Stores indexed field values
|
|
177
|
+
|
|
178
|
+
2. **Handles Data Types**:
|
|
179
|
+
- Primitive types (string, number, boolean)
|
|
180
|
+
- Complex objects and arrays
|
|
181
|
+
- Date objects (stored as ISO strings)
|
|
182
|
+
- Custom classes (serialized as JSON)
|
|
183
|
+
|
|
184
|
+
### Manual Persistence
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
// Create component instance
|
|
188
|
+
const profile = new UserProfile();
|
|
189
|
+
profile.name = 'John Doe';
|
|
190
|
+
profile.email = 'john@example.com';
|
|
191
|
+
profile.username = 'johndoe';
|
|
192
|
+
|
|
193
|
+
// Save to database (called automatically when entity is saved)
|
|
194
|
+
await profile.save(entityId);
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## 🔍 Component Queries
|
|
198
|
+
|
|
199
|
+
### Accessing Component Data
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
// Get component from entity
|
|
203
|
+
const userEntity = await Entity.FindById(userId);
|
|
204
|
+
const profile = userEntity.get(UserProfile);
|
|
205
|
+
|
|
206
|
+
// Access data properties
|
|
207
|
+
console.log(profile.name); // 'John Doe'
|
|
208
|
+
console.log(profile.email); // 'john@example.com'
|
|
209
|
+
|
|
210
|
+
// Get all data as object
|
|
211
|
+
const profileData = profile.data();
|
|
212
|
+
console.log(profileData); // { name: 'John Doe', email: 'john@example.com', username: 'johndoe' }
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Component Metadata
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
// Get component type ID
|
|
219
|
+
const typeId = profile.getTypeID();
|
|
220
|
+
console.log(typeId); // 'sha256_hash_of_UserProfile'
|
|
221
|
+
|
|
222
|
+
// Get component properties
|
|
223
|
+
const properties = profile.properties();
|
|
224
|
+
console.log(properties); // ['name', 'email', 'username']
|
|
225
|
+
|
|
226
|
+
// Get indexed properties
|
|
227
|
+
const indexedProps = profile.indexedProperties();
|
|
228
|
+
console.log(indexedProps); // ['username']
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## 🔄 Component Updates
|
|
232
|
+
|
|
233
|
+
### Updating Component Data
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
// Update existing component on entity
|
|
237
|
+
await userEntity.set(UserProfile, {
|
|
238
|
+
name: 'Jane Doe',
|
|
239
|
+
email: 'jane@example.com'
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
// Save changes
|
|
243
|
+
await userEntity.save();
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Partial Updates
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
// Update only specific fields
|
|
250
|
+
const profile = userEntity.get(UserProfile);
|
|
251
|
+
profile.name = 'Updated Name';
|
|
252
|
+
|
|
253
|
+
// Mark as dirty for saving
|
|
254
|
+
profile.setDirty(true);
|
|
255
|
+
await userEntity.save();
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## 🏷️ Component Relationships
|
|
259
|
+
|
|
260
|
+
### Reference Components
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
@Component
|
|
264
|
+
export class BlogPost extends BaseComponent {
|
|
265
|
+
@CompData()
|
|
266
|
+
title: string = '';
|
|
267
|
+
|
|
268
|
+
@CompData()
|
|
269
|
+
content: string = '';
|
|
270
|
+
|
|
271
|
+
@CompData({ indexed: true })
|
|
272
|
+
authorId: string = ''; // Reference to User entity
|
|
273
|
+
|
|
274
|
+
@CompData()
|
|
275
|
+
categoryIds: string[] = []; // References to Category entities
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
@Component
|
|
279
|
+
export class Comment extends BaseComponent {
|
|
280
|
+
@CompData()
|
|
281
|
+
content: string = '';
|
|
282
|
+
|
|
283
|
+
@CompData({ indexed: true })
|
|
284
|
+
postId: string = ''; // Reference to BlogPost entity
|
|
285
|
+
|
|
286
|
+
@CompData({ indexed: true })
|
|
287
|
+
authorId: string = ''; // Reference to User entity
|
|
288
|
+
|
|
289
|
+
@CompData()
|
|
290
|
+
parentId?: string; // Reference to parent Comment (for nested comments)
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Polymorphic References
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
@Component
|
|
298
|
+
export class Like extends BaseComponent {
|
|
299
|
+
@CompData({ indexed: true })
|
|
300
|
+
userId: string = '';
|
|
301
|
+
|
|
302
|
+
@CompData({ indexed: true })
|
|
303
|
+
targetType: 'post' | 'comment' | 'user' = 'post';
|
|
304
|
+
|
|
305
|
+
@CompData({ indexed: true })
|
|
306
|
+
targetId: string = '';
|
|
307
|
+
|
|
308
|
+
@CompData()
|
|
309
|
+
createdAt: Date = new Date();
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## 🎨 Advanced Component Patterns
|
|
314
|
+
|
|
315
|
+
### Validation Components
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
@Component
|
|
319
|
+
export class EmailValidation extends BaseComponent {
|
|
320
|
+
@CompData()
|
|
321
|
+
isValid: boolean = false;
|
|
322
|
+
|
|
323
|
+
@CompData()
|
|
324
|
+
validationErrors: string[] = [];
|
|
325
|
+
|
|
326
|
+
@CompData()
|
|
327
|
+
lastValidated: Date = new Date();
|
|
328
|
+
|
|
329
|
+
// Custom validation method
|
|
330
|
+
validateEmail(email: string): boolean {
|
|
331
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
332
|
+
this.isValid = emailRegex.test(email);
|
|
333
|
+
|
|
334
|
+
if (!this.isValid) {
|
|
335
|
+
this.validationErrors = ['Invalid email format'];
|
|
336
|
+
} else {
|
|
337
|
+
this.validationErrors = [];
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
this.lastValidated = new Date();
|
|
341
|
+
return this.isValid;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### Computed Properties
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
@Component
|
|
350
|
+
export class ProductStats extends BaseComponent {
|
|
351
|
+
@CompData()
|
|
352
|
+
viewCount: number = 0;
|
|
353
|
+
|
|
354
|
+
@CompData()
|
|
355
|
+
purchaseCount: number = 0;
|
|
356
|
+
|
|
357
|
+
@CompData()
|
|
358
|
+
rating: number = 0;
|
|
359
|
+
|
|
360
|
+
@CompData()
|
|
361
|
+
reviewCount: number = 0;
|
|
362
|
+
|
|
363
|
+
// Computed property (not stored in DB)
|
|
364
|
+
get averageRating(): number {
|
|
365
|
+
return this.reviewCount > 0 ? this.rating / this.reviewCount : 0;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Computed property for popularity score
|
|
369
|
+
get popularityScore(): number {
|
|
370
|
+
return (this.viewCount * 0.3) + (this.purchaseCount * 0.7);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Method to update stats
|
|
374
|
+
recordView() {
|
|
375
|
+
this.viewCount++;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
recordPurchase() {
|
|
379
|
+
this.purchaseCount++;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
addRating(newRating: number) {
|
|
383
|
+
this.rating = ((this.rating * this.reviewCount) + newRating) / (this.reviewCount + 1);
|
|
384
|
+
this.reviewCount++;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### Event Components
|
|
390
|
+
|
|
391
|
+
```typescript
|
|
392
|
+
@Component
|
|
393
|
+
export class AuditLog extends BaseComponent {
|
|
394
|
+
@CompData({ indexed: true })
|
|
395
|
+
entityId: string = '';
|
|
396
|
+
|
|
397
|
+
@CompData({ indexed: true })
|
|
398
|
+
action: 'create' | 'update' | 'delete' = 'create';
|
|
399
|
+
|
|
400
|
+
@CompData()
|
|
401
|
+
changes: Record<string, { old: any; new: any }> = {};
|
|
402
|
+
|
|
403
|
+
@CompData()
|
|
404
|
+
timestamp: Date = new Date();
|
|
405
|
+
|
|
406
|
+
@CompData()
|
|
407
|
+
userId?: string;
|
|
408
|
+
|
|
409
|
+
@CompData()
|
|
410
|
+
ipAddress?: string;
|
|
411
|
+
|
|
412
|
+
// Method to record changes
|
|
413
|
+
recordChanges(oldData: any, newData: any) {
|
|
414
|
+
const changes: Record<string, { old: any; new: any }> = {};
|
|
415
|
+
|
|
416
|
+
for (const key in newData) {
|
|
417
|
+
if (oldData[key] !== newData[key]) {
|
|
418
|
+
changes[key] = {
|
|
419
|
+
old: oldData[key],
|
|
420
|
+
new: newData[key]
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
this.changes = changes;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
## 🔧 Component Registry
|
|
431
|
+
|
|
432
|
+
### Component Management
|
|
433
|
+
|
|
434
|
+
```typescript
|
|
435
|
+
import ComponentRegistry from 'bunsane/core/ComponentRegistry';
|
|
436
|
+
|
|
437
|
+
// Check if component is registered
|
|
438
|
+
const isRegistered = ComponentRegistry.isComponentReady('UserProfile');
|
|
439
|
+
console.log(isRegistered); // true
|
|
440
|
+
|
|
441
|
+
// Get component ID
|
|
442
|
+
const componentId = ComponentRegistry.getComponentId('UserProfile');
|
|
443
|
+
console.log(componentId); // 'sha256_hash'
|
|
444
|
+
|
|
445
|
+
// Get all registered components
|
|
446
|
+
const allComponents = ComponentRegistry.getAllComponents();
|
|
447
|
+
console.log(allComponents); // ['UserProfile', 'ProductInfo', ...]
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
## 📈 Best Practices
|
|
451
|
+
|
|
452
|
+
### Component Design
|
|
453
|
+
|
|
454
|
+
- **Single Responsibility**: Each component should represent one clear concept
|
|
455
|
+
- **Minimal Data**: Keep components focused and avoid bloated data structures
|
|
456
|
+
- **Consistent Naming**: Use clear, descriptive names for components and properties
|
|
457
|
+
- **Index Strategically**: Only index fields that are frequently queried
|
|
458
|
+
- **Type Safety**: Leverage TypeScript for robust type checking
|
|
459
|
+
|
|
460
|
+
### Performance Considerations
|
|
461
|
+
|
|
462
|
+
- **Lazy Loading**: Only load components when needed
|
|
463
|
+
- **Batch Operations**: Use entity batching for multiple component operations
|
|
464
|
+
- **Efficient Queries**: Leverage indexed fields for fast lookups
|
|
465
|
+
- **Memory Management**: Be mindful of component size and complexity
|
|
466
|
+
|
|
467
|
+
### Error Handling
|
|
468
|
+
|
|
469
|
+
```typescript
|
|
470
|
+
try {
|
|
471
|
+
const component = new UserProfile();
|
|
472
|
+
component.name = userData.name;
|
|
473
|
+
component.email = userData.email;
|
|
474
|
+
|
|
475
|
+
// Validate before saving
|
|
476
|
+
if (!component.email.includes('@')) {
|
|
477
|
+
throw new Error('Invalid email format');
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
await entity.add(component).save();
|
|
481
|
+
} catch (error) {
|
|
482
|
+
console.error('Failed to create user profile:', error);
|
|
483
|
+
// Handle error appropriately
|
|
484
|
+
}
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
## 🚀 What's Next?
|
|
488
|
+
|
|
489
|
+
Now that you understand Components, let's explore:
|
|
490
|
+
|
|
491
|
+
- **[ArcheTypes](archetypes.md)** - Reusable entity templates
|
|
492
|
+
- **[Entity System](entity.md)** - How entities use components
|
|
493
|
+
- **[Query System](query.md)** - Efficient data retrieval
|
|
494
|
+
- **[Services](services.md)** - Business logic and GraphQL integration
|
|
495
|
+
|
|
496
|
+
---
|
|
497
|
+
|
|
498
|
+
*Ready to build with components? Let's look at [ArcheTypes](archetypes.md) next!* 🚀
|