@xbg.solutions/create-backend 1.0.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/bin/create-backend.js +3 -0
- package/lib/cli.d.ts +12 -0
- package/lib/cli.js +55 -0
- package/lib/cli.js.map +1 -0
- package/lib/commands/add-util.d.ts +9 -0
- package/lib/commands/add-util.js +119 -0
- package/lib/commands/add-util.js.map +1 -0
- package/lib/commands/init.d.ts +11 -0
- package/lib/commands/init.js +372 -0
- package/lib/commands/init.js.map +1 -0
- package/lib/commands/sync.d.ts +10 -0
- package/lib/commands/sync.js +161 -0
- package/lib/commands/sync.js.map +1 -0
- package/lib/utils-registry.d.ts +25 -0
- package/lib/utils-registry.js +187 -0
- package/lib/utils-registry.js.map +1 -0
- package/package.json +38 -0
- package/src/project-template/__examples__/README.md +559 -0
- package/src/project-template/__examples__/blog-platform.model.ts +528 -0
- package/src/project-template/__examples__/communications-usage.ts +175 -0
- package/src/project-template/__examples__/ecommerce-store.model.ts +1200 -0
- package/src/project-template/__examples__/saas-multi-tenant.model.ts +798 -0
- package/src/project-template/__examples__/user.model.ts +221 -0
- package/src/project-template/__scripts__/deploy.js +115 -0
- package/src/project-template/__scripts__/generate.js +122 -0
- package/src/project-template/__scripts__/setup.js +425 -0
- package/src/project-template/__scripts__/validate.js +325 -0
- package/src/project-template/firebase.json +32 -0
- package/src/project-template/firestore.rules +12 -0
- package/src/project-template/functions/jest.config.js +49 -0
- package/src/project-template/functions/src/index.ts +46 -0
- package/src/project-template/functions/tsconfig.json +38 -0
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
# Example Data Models
|
|
2
|
+
|
|
3
|
+
This directory contains complete, production-ready data models for common application types. These examples demonstrate best practices for defining entities, relationships, access control, and business rules.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 📚 Available Models
|
|
8
|
+
|
|
9
|
+
### 1. Blog Platform (`blog-platform.model.ts`)
|
|
10
|
+
|
|
11
|
+
A complete content management system with social features.
|
|
12
|
+
|
|
13
|
+
**Entities:** 8 entities, 3 junction tables
|
|
14
|
+
- User, Post, Comment, Category, Tag
|
|
15
|
+
- PostLike, UserFollow, PostTag
|
|
16
|
+
|
|
17
|
+
**Features:**
|
|
18
|
+
- User authentication and profiles
|
|
19
|
+
- Posts with rich content
|
|
20
|
+
- Nested comments (3 levels deep)
|
|
21
|
+
- Categories and tags
|
|
22
|
+
- Social features (likes, follows)
|
|
23
|
+
- Content moderation workflow
|
|
24
|
+
|
|
25
|
+
**Use Cases:**
|
|
26
|
+
- Blog platforms
|
|
27
|
+
- News sites
|
|
28
|
+
- Content communities
|
|
29
|
+
- Knowledge bases
|
|
30
|
+
|
|
31
|
+
**To use:**
|
|
32
|
+
```bash
|
|
33
|
+
npm run generate __examples__/blog-platform.model.ts
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
### 2. E-commerce Store (`ecommerce-store.model.ts`)
|
|
39
|
+
|
|
40
|
+
A complete online store with inventory, orders, and payments.
|
|
41
|
+
|
|
42
|
+
**Entities:** 17 entities, 2 junction tables
|
|
43
|
+
- Customer, Address, Product, ProductVariant, Category, Brand
|
|
44
|
+
- Cart, CartItem, Order, OrderItem, Shipment
|
|
45
|
+
- Review, Collection, ProductImage
|
|
46
|
+
- ProductCollection
|
|
47
|
+
|
|
48
|
+
**Features:**
|
|
49
|
+
- Product catalog with variants (size, color, etc.)
|
|
50
|
+
- Shopping cart and checkout
|
|
51
|
+
- Order management with status tracking
|
|
52
|
+
- Inventory management
|
|
53
|
+
- Customer reviews and ratings
|
|
54
|
+
- Payment integration (Stripe)
|
|
55
|
+
- Shipping and delivery tracking
|
|
56
|
+
|
|
57
|
+
**Use Cases:**
|
|
58
|
+
- E-commerce stores
|
|
59
|
+
- Marketplaces
|
|
60
|
+
- Physical product sales
|
|
61
|
+
- Digital product sales
|
|
62
|
+
|
|
63
|
+
**To use:**
|
|
64
|
+
```bash
|
|
65
|
+
npm run generate __examples__/ecommerce-store.model.ts
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
### 3. SaaS Multi-Tenant (`saas-multi-tenant.model.ts`)
|
|
71
|
+
|
|
72
|
+
A B2B SaaS application with workspaces and team collaboration.
|
|
73
|
+
|
|
74
|
+
**Entities:** 12 entities
|
|
75
|
+
- User, Organization, OrganizationMember
|
|
76
|
+
- Workspace, WorkspaceMember, Project
|
|
77
|
+
- Invitation, ApiKey
|
|
78
|
+
- UsageLog, AuditLog
|
|
79
|
+
|
|
80
|
+
**Features:**
|
|
81
|
+
- Multi-tenant workspace architecture
|
|
82
|
+
- Hierarchical permissions (Org → Workspace → Project)
|
|
83
|
+
- Team collaboration
|
|
84
|
+
- Subscription management (Stripe)
|
|
85
|
+
- Usage tracking and billing
|
|
86
|
+
- API key management
|
|
87
|
+
- Audit logging
|
|
88
|
+
|
|
89
|
+
**Use Cases:**
|
|
90
|
+
- B2B SaaS platforms
|
|
91
|
+
- Project management tools
|
|
92
|
+
- Collaboration software
|
|
93
|
+
- Team productivity apps
|
|
94
|
+
|
|
95
|
+
**To use:**
|
|
96
|
+
```bash
|
|
97
|
+
npm run generate __examples__/saas-multi-tenant.model.ts
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## 🎯 How to Use These Models
|
|
103
|
+
|
|
104
|
+
### Step 1: Choose a Model
|
|
105
|
+
|
|
106
|
+
Pick the model that closest matches your application type. You can:
|
|
107
|
+
- Use it as-is
|
|
108
|
+
- Modify it for your needs
|
|
109
|
+
- Combine multiple models
|
|
110
|
+
- Use as a learning reference
|
|
111
|
+
|
|
112
|
+
### Step 2: Generate Code
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
cd functions
|
|
116
|
+
npm run generate __examples__/<model-file>.ts
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
This generates:
|
|
120
|
+
- **Entities**: TypeScript interfaces in `src/entities/`
|
|
121
|
+
- **Repositories**: Data access layer in `src/repositories/`
|
|
122
|
+
- **Services**: Business logic in `src/services/`
|
|
123
|
+
- **Controllers**: HTTP endpoints in `src/controllers/`
|
|
124
|
+
|
|
125
|
+
### Step 3: Register Controllers
|
|
126
|
+
|
|
127
|
+
Edit `functions/src/app.ts` to register your routes:
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
import { PostController } from './controllers/PostController';
|
|
131
|
+
import { PostService } from './services/PostService';
|
|
132
|
+
import { PostRepository } from './repositories/PostRepository';
|
|
133
|
+
|
|
134
|
+
// Initialize
|
|
135
|
+
const postRepository = new PostRepository();
|
|
136
|
+
const postService = new PostService(postRepository);
|
|
137
|
+
const postController = new PostController(postService, '/api/v1/posts');
|
|
138
|
+
|
|
139
|
+
// Register routes
|
|
140
|
+
postController.registerRoutes(app);
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Step 4: Customize Business Logic
|
|
144
|
+
|
|
145
|
+
The generated code provides CRUD operations. Add custom methods:
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
// functions/src/services/PostService.ts
|
|
149
|
+
export class PostService extends BaseService<Post> {
|
|
150
|
+
// Generated CRUD methods are here
|
|
151
|
+
|
|
152
|
+
// Add your custom business logic
|
|
153
|
+
async publishPost(postId: string, authorId: string): Promise<Post> {
|
|
154
|
+
const post = await this.repository.findById(postId);
|
|
155
|
+
|
|
156
|
+
if (post.authorId !== authorId) {
|
|
157
|
+
throw new Error('Unauthorized');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const updated = await this.repository.update(postId, {
|
|
161
|
+
published: true,
|
|
162
|
+
publishedAt: new Date(),
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// Notify followers
|
|
166
|
+
this.eventBus.publish('post.published', { post: updated });
|
|
167
|
+
|
|
168
|
+
return updated;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Step 5: Add Event Subscribers
|
|
174
|
+
|
|
175
|
+
Create event handlers for side effects:
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
// functions/src/subscribers/post-subscribers.ts
|
|
179
|
+
import { eventBus } from '../utilities/events';
|
|
180
|
+
import { emailConnector } from '../utilities/email-connector';
|
|
181
|
+
|
|
182
|
+
eventBus.subscribe('post.published', async (event) => {
|
|
183
|
+
const { post } = event.payload;
|
|
184
|
+
|
|
185
|
+
// Send notifications to followers
|
|
186
|
+
await notifyFollowers(post);
|
|
187
|
+
|
|
188
|
+
// Update analytics
|
|
189
|
+
await trackPublish(post);
|
|
190
|
+
});
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Step 6: Write Tests
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
// functions/src/services/__tests__/PostService.test.ts
|
|
197
|
+
describe('PostService', () => {
|
|
198
|
+
it('prevents users from liking their own posts', async () => {
|
|
199
|
+
const post = await createTestPost({ authorId: 'user-123' });
|
|
200
|
+
|
|
201
|
+
await expect(
|
|
202
|
+
postService.likePost(post.id, 'user-123')
|
|
203
|
+
).rejects.toThrow('You cannot like your own post');
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Step 7: Deploy
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
npm run build
|
|
212
|
+
npm run validate
|
|
213
|
+
npm run deploy
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## 📖 Understanding the Data Model Format
|
|
219
|
+
|
|
220
|
+
### Entity Definition
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
EntityName: {
|
|
224
|
+
fields: {
|
|
225
|
+
fieldName: {
|
|
226
|
+
type: 'string' | 'email' | 'number' | 'boolean' | 'timestamp' | etc.,
|
|
227
|
+
required: true | false,
|
|
228
|
+
unique: true | false,
|
|
229
|
+
default: value,
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
relationships: {
|
|
233
|
+
relationName: {
|
|
234
|
+
type: 'one-to-one' | 'one-to-many' | 'many-to-one' | 'many-to-many',
|
|
235
|
+
entity: 'TargetEntity',
|
|
236
|
+
foreignKey: 'fieldName', // For one-to-many
|
|
237
|
+
through: 'JunctionTable', // For many-to-many
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
access: {
|
|
241
|
+
create: ['role1', 'role2'],
|
|
242
|
+
read: ['public' | 'role'],
|
|
243
|
+
update: ['self', 'admin'],
|
|
244
|
+
delete: ['admin'],
|
|
245
|
+
},
|
|
246
|
+
validation: {
|
|
247
|
+
fieldName: 'Validation message',
|
|
248
|
+
},
|
|
249
|
+
businessRules: [
|
|
250
|
+
'Rule description 1',
|
|
251
|
+
'Rule description 2',
|
|
252
|
+
],
|
|
253
|
+
indexes: [
|
|
254
|
+
{ fields: ['field1'], unique: true },
|
|
255
|
+
{ fields: ['field1', 'field2'] },
|
|
256
|
+
],
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Field Types
|
|
261
|
+
|
|
262
|
+
- `string` - Short text (up to 255 chars)
|
|
263
|
+
- `text` - Long text (unlimited)
|
|
264
|
+
- `email` - Email address with validation
|
|
265
|
+
- `url` - URL with validation
|
|
266
|
+
- `phone` - Phone number
|
|
267
|
+
- `number` - Integer or decimal
|
|
268
|
+
- `boolean` - True/false
|
|
269
|
+
- `timestamp` - Date and time
|
|
270
|
+
- `enum` - Predefined values
|
|
271
|
+
- `reference` - Foreign key to another entity
|
|
272
|
+
- `array` - Array of values
|
|
273
|
+
- `json` - Arbitrary JSON data
|
|
274
|
+
|
|
275
|
+
### Relationship Types
|
|
276
|
+
|
|
277
|
+
**One-to-Many:**
|
|
278
|
+
```typescript
|
|
279
|
+
User: {
|
|
280
|
+
relationships: {
|
|
281
|
+
posts: {
|
|
282
|
+
type: 'one-to-many',
|
|
283
|
+
entity: 'Post',
|
|
284
|
+
foreignKey: 'authorId',
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
**Many-to-One:**
|
|
291
|
+
```typescript
|
|
292
|
+
Post: {
|
|
293
|
+
relationships: {
|
|
294
|
+
author: {
|
|
295
|
+
type: 'many-to-one',
|
|
296
|
+
entity: 'User',
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
**Many-to-Many:**
|
|
303
|
+
```typescript
|
|
304
|
+
Post: {
|
|
305
|
+
relationships: {
|
|
306
|
+
tags: {
|
|
307
|
+
type: 'many-to-many',
|
|
308
|
+
entity: 'Tag',
|
|
309
|
+
through: 'PostTag', // Junction table
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### Access Control
|
|
316
|
+
|
|
317
|
+
- `public` - Anyone (unauthenticated)
|
|
318
|
+
- `authenticated` - Any logged-in user
|
|
319
|
+
- `self` - Owner of the resource
|
|
320
|
+
- `admin` - Admin users
|
|
321
|
+
- `moderator` - Moderator users
|
|
322
|
+
- Custom roles defined in your app
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## 🔧 Customization Tips
|
|
327
|
+
|
|
328
|
+
### Combining Models
|
|
329
|
+
|
|
330
|
+
You can combine entities from multiple models:
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
import { BlogPlatformModel } from './blog-platform.model';
|
|
334
|
+
import { EcommerceStoreModel } from './ecommerce-store.model';
|
|
335
|
+
|
|
336
|
+
export const MyModel: DataModelSpecification = {
|
|
337
|
+
entities: {
|
|
338
|
+
// Blog entities
|
|
339
|
+
...BlogPlatformModel.entities,
|
|
340
|
+
|
|
341
|
+
// E-commerce entities
|
|
342
|
+
...EcommerceStoreModel.entities,
|
|
343
|
+
|
|
344
|
+
// Your custom entities
|
|
345
|
+
MyCustomEntity: {
|
|
346
|
+
// ...
|
|
347
|
+
},
|
|
348
|
+
},
|
|
349
|
+
};
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
### Adding Fields
|
|
353
|
+
|
|
354
|
+
```typescript
|
|
355
|
+
User: {
|
|
356
|
+
fields: {
|
|
357
|
+
// ...existing fields
|
|
358
|
+
customField: {
|
|
359
|
+
type: 'string',
|
|
360
|
+
required: false,
|
|
361
|
+
},
|
|
362
|
+
},
|
|
363
|
+
}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### Modifying Relationships
|
|
367
|
+
|
|
368
|
+
```typescript
|
|
369
|
+
Post: {
|
|
370
|
+
relationships: {
|
|
371
|
+
// Add new relationship
|
|
372
|
+
attachments: {
|
|
373
|
+
type: 'one-to-many',
|
|
374
|
+
entity: 'Attachment',
|
|
375
|
+
foreignKey: 'postId',
|
|
376
|
+
},
|
|
377
|
+
},
|
|
378
|
+
}
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### Adding Business Rules
|
|
382
|
+
|
|
383
|
+
```typescript
|
|
384
|
+
Product: {
|
|
385
|
+
businessRules: [
|
|
386
|
+
'Products with pending orders cannot be deleted',
|
|
387
|
+
'Price changes require approval',
|
|
388
|
+
'Out-of-stock products are hidden from catalog',
|
|
389
|
+
],
|
|
390
|
+
}
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
|
|
395
|
+
## 🎓 Learning Resources
|
|
396
|
+
|
|
397
|
+
- **[Building with AI Agents](__docs__/building-with-ai-agents.md)**: Complete guide for AI-assisted development
|
|
398
|
+
- **[Testing Philosophy](../functions/src/__tests__/README.md)**: How to write effective tests
|
|
399
|
+
- **[Main README](../README.md)**: Project overview and setup
|
|
400
|
+
|
|
401
|
+
---
|
|
402
|
+
|
|
403
|
+
## 💡 Best Practices
|
|
404
|
+
|
|
405
|
+
### 1. Start Simple
|
|
406
|
+
|
|
407
|
+
Begin with a minimal model and add complexity as needed:
|
|
408
|
+
|
|
409
|
+
```typescript
|
|
410
|
+
// Start with this
|
|
411
|
+
User: {
|
|
412
|
+
fields: {
|
|
413
|
+
email: { type: 'email', unique: true, required: true },
|
|
414
|
+
name: { type: 'string', required: true },
|
|
415
|
+
},
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Add features incrementally
|
|
419
|
+
User: {
|
|
420
|
+
fields: {
|
|
421
|
+
email: { type: 'email', unique: true, required: true },
|
|
422
|
+
name: { type: 'string', required: true },
|
|
423
|
+
avatarUrl: { type: 'url', required: false }, // Added
|
|
424
|
+
bio: { type: 'text', required: false }, // Added
|
|
425
|
+
},
|
|
426
|
+
}
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
### 2. Use Descriptive Names
|
|
430
|
+
|
|
431
|
+
```typescript
|
|
432
|
+
// ❌ Bad
|
|
433
|
+
u: { type: 'reference', entity: 'U' }
|
|
434
|
+
|
|
435
|
+
// ✅ Good
|
|
436
|
+
authorId: { type: 'reference', entity: 'User' }
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
### 3. Define Access Control Early
|
|
440
|
+
|
|
441
|
+
```typescript
|
|
442
|
+
Post: {
|
|
443
|
+
access: {
|
|
444
|
+
create: ['authenticated'], // Who can create
|
|
445
|
+
read: ['public'], // Who can read
|
|
446
|
+
update: ['self', 'admin'], // Who can update
|
|
447
|
+
delete: ['admin'], // Who can delete
|
|
448
|
+
},
|
|
449
|
+
}
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### 4. Document Business Rules
|
|
453
|
+
|
|
454
|
+
```typescript
|
|
455
|
+
Order: {
|
|
456
|
+
businessRules: [
|
|
457
|
+
'Orders cannot be deleted, only cancelled',
|
|
458
|
+
'Cancelled orders release inventory',
|
|
459
|
+
'Order total must include tax and shipping',
|
|
460
|
+
],
|
|
461
|
+
}
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
### 5. Plan for Scale
|
|
465
|
+
|
|
466
|
+
```typescript
|
|
467
|
+
Post: {
|
|
468
|
+
indexes: [
|
|
469
|
+
{ fields: ['authorId', 'published'] }, // For listing user's published posts
|
|
470
|
+
{ fields: ['categoryId', 'publishedAt'] }, // For category pages
|
|
471
|
+
{ fields: ['published', 'isFeatured'] }, // For homepage
|
|
472
|
+
],
|
|
473
|
+
}
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
---
|
|
477
|
+
|
|
478
|
+
## 🐛 Common Issues
|
|
479
|
+
|
|
480
|
+
### Issue: Circular Dependencies
|
|
481
|
+
|
|
482
|
+
**Problem:** Entity A references Entity B, which references Entity A.
|
|
483
|
+
|
|
484
|
+
**Solution:** Use one-way references or junction tables:
|
|
485
|
+
|
|
486
|
+
```typescript
|
|
487
|
+
// ❌ Circular
|
|
488
|
+
User: { relationships: { follows: { type: 'many-to-many', entity: 'User' } } }
|
|
489
|
+
|
|
490
|
+
// ✅ Better - Use junction table
|
|
491
|
+
UserFollow: {
|
|
492
|
+
fields: {
|
|
493
|
+
followerId: { type: 'reference', entity: 'User' },
|
|
494
|
+
followingId: { type: 'reference', entity: 'User' },
|
|
495
|
+
},
|
|
496
|
+
}
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
### Issue: Missing Foreign Keys
|
|
500
|
+
|
|
501
|
+
**Problem:** One-to-many relationship without foreignKey specified.
|
|
502
|
+
|
|
503
|
+
**Solution:** Always specify foreignKey:
|
|
504
|
+
|
|
505
|
+
```typescript
|
|
506
|
+
User: {
|
|
507
|
+
relationships: {
|
|
508
|
+
posts: {
|
|
509
|
+
type: 'one-to-many',
|
|
510
|
+
entity: 'Post',
|
|
511
|
+
foreignKey: 'authorId', // Required!
|
|
512
|
+
},
|
|
513
|
+
},
|
|
514
|
+
}
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
### Issue: Many-to-Many Without Junction
|
|
518
|
+
|
|
519
|
+
**Problem:** Many-to-many relationship missing `through` table.
|
|
520
|
+
|
|
521
|
+
**Solution:** Create a junction table:
|
|
522
|
+
|
|
523
|
+
```typescript
|
|
524
|
+
// Define the junction table
|
|
525
|
+
PostTag: {
|
|
526
|
+
fields: {
|
|
527
|
+
postId: { type: 'reference', entity: 'Post', required: true },
|
|
528
|
+
tagId: { type: 'reference', entity: 'Tag', required: true },
|
|
529
|
+
},
|
|
530
|
+
indexes: [
|
|
531
|
+
{ fields: ['postId', 'tagId'], unique: true },
|
|
532
|
+
],
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Reference it in the many-to-many
|
|
536
|
+
Post: {
|
|
537
|
+
relationships: {
|
|
538
|
+
tags: {
|
|
539
|
+
type: 'many-to-many',
|
|
540
|
+
entity: 'Tag',
|
|
541
|
+
through: 'PostTag', // Junction table
|
|
542
|
+
},
|
|
543
|
+
},
|
|
544
|
+
}
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
---
|
|
548
|
+
|
|
549
|
+
## 📞 Getting Help
|
|
550
|
+
|
|
551
|
+
- **Questions**: Open a [GitHub Discussion](https://github.com/xbg-solutions/boilerplate_backend/discussions)
|
|
552
|
+
- **Issues**: Report bugs via [GitHub Issues](https://github.com/xbg-solutions/boilerplate_backend/issues)
|
|
553
|
+
- **Documentation**: See [__docs__/](__docs__/) directory
|
|
554
|
+
|
|
555
|
+
---
|
|
556
|
+
|
|
557
|
+
**Happy modeling!** 🚀
|
|
558
|
+
|
|
559
|
+
These examples are production-ready starting points. Customize them for your needs, and remember: start simple, iterate, and let AI help you build.
|