@launchframe/mcp 1.1.4 → 1.1.6

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.
@@ -2,7 +2,10 @@
2
2
  "permissions": {
3
3
  "allow": [
4
4
  "Bash(wc:*)",
5
- "Bash(grep:*)"
5
+ "Bash(grep:*)",
6
+ "Bash(python3:*)",
7
+ "Bash(xargs cat:*)",
8
+ "Bash(git:*)"
6
9
  ]
7
10
  }
8
11
  }
@@ -15,14 +15,14 @@ All routes are protected by default via the global `BetterAuthGuard` (registered
15
15
  |------|-------------|
16
16
  | `business_user` | Default role for all registered users |
17
17
  | `superadmin` | Granted via admin panel; full access |
18
- | `regular_user` | B2B2C variant only — end-customer of the SaaS |
18
+ | `customer` | B2B2C variant only — end-customer of the SaaS |
19
19
 
20
20
  ## Session Flow
21
21
 
22
22
  1. Request hits `BetterAuthGuard`
23
23
  2. Guard checks for `@AllowAnonymous` / `@OptionalAuth` metadata
24
24
  3. Calls `auth.api.getSession({ headers })` via Better Auth
25
- 4. Rejects `regular_user` on non-`@CustomerPortal` routes
25
+ 4. Rejects `customer` on non-`@CustomerPortal` routes
26
26
  5. Attaches `request.session` and `request.user`
27
27
 
28
28
  ## Decorators
@@ -32,7 +32,7 @@ All routes are protected by default via the global `BetterAuthGuard` (registered
32
32
  | `@AllowAnonymous()` | Route is fully public — no auth check |
33
33
  | `@Public()` | Alias for `@AllowAnonymous()` |
34
34
  | `@OptionalAuth()` | Auth checked but not required; `request.user` may be undefined |
35
- | `@CustomerPortal()` | Allows `regular_user` role (B2B2C variant) |
35
+ | `@CustomerPortal()` | Allows `customer` role (B2B2C variant) |
36
36
  | `@UserSession()` | Param decorator — injects the `User` from session |
37
37
  | `@Session()` | Param decorator — injects full `{ user, session }` object |
38
38
 
@@ -58,18 +58,6 @@ Roles: `business_user`, `superadmin`, `customer` (B2B2C variant)
58
58
  | created_at | timestamp | NO |
59
59
  | updated_at | timestamp | NO |
60
60
 
61
- #### `oauth_tokens`
62
- | Column | Type | Nullable | Default |
63
- |--------|------|----------|---------|
64
- | id | integer (PK, serial) | NO | nextval |
65
- | token_type | text | NO | |
66
- | user_id | integer (FK → users.id) | NO | |
67
- | access_token | text | NO | |
68
- | refresh_token | text | NO | |
69
- | expires_at | timestamp | NO | |
70
- | created_at | timestamp | NO | CURRENT_TIMESTAMP |
71
- | updated_at | timestamp | NO | CURRENT_TIMESTAMP |
72
-
73
61
  ---
74
62
 
75
63
  ### Subscriptions
@@ -20,7 +20,7 @@ Extends Base by adding workspace/project isolation.
20
20
 
21
21
  ### B2B2C
22
22
  Extends Base by adding a separate customer-facing experience (end-users of your customers).
23
- - Adds `regular_user` role
23
+ - Adds `customer` role
24
24
  - Adds `customers-portal` frontend service
25
25
  - Adds `@CustomerPortal()` route decorator for customer-only endpoints
26
26
  - B2B2C can also be combined with multi-tenancy
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@launchframe/mcp",
3
- "version": "1.1.4",
3
+ "version": "1.1.6",
4
4
  "description": "LaunchFrame MCP Server — knowledge tools for AI agents building LaunchFrame projects",
5
5
  "bin": {
6
6
  "launchframe-mcp": "dist/index.js"
7
7
  },
8
8
  "main": "dist/index.js",
9
9
  "scripts": {
10
- "build": "tsc && cp -r src/content dist/content",
10
+ "build": "rm -rf dist && tsc && cp -r src/content dist/content",
11
11
  "start": "node dist/index.js",
12
12
  "prepublishOnly": "npm run build"
13
13
  },
@@ -1,64 +0,0 @@
1
- # LaunchFrame Auth Overview
2
-
3
- ## Guard Architecture
4
-
5
- All routes are protected by default via the global `BetterAuthGuard` (registered in `app.module.ts` as `APP_GUARD`).
6
-
7
- **Import paths:**
8
- - Decorators: `src/modules/auth/auth.decorator.ts`
9
- - Guard: `src/modules/auth/better-auth.guard.ts`
10
- - Base guard: `src/modules/auth/guards/base-auth.guard.ts`
11
-
12
- ## Roles
13
-
14
- | Role | Description |
15
- |------|-------------|
16
- | `business_user` | Default role for all registered users |
17
- | `superadmin` | Granted via admin panel; full access |
18
- | `customer` | B2B2C variant only — end-customer of the SaaS |
19
-
20
- ## Session Flow
21
-
22
- 1. Request hits `BetterAuthGuard`
23
- 2. Guard checks for `@AllowAnonymous` / `@OptionalAuth` metadata
24
- 3. Calls `auth.api.getSession({ headers })` via Better Auth
25
- 4. Rejects `customer` on non-`@CustomerPortal` routes
26
- 5. Attaches `request.session` and `request.user`
27
-
28
- ## Decorators
29
-
30
- | Decorator | Effect |
31
- |-----------|--------|
32
- | `@AllowAnonymous()` | Route is fully public — no auth check |
33
- | `@Public()` | Alias for `@AllowAnonymous()` |
34
- | `@OptionalAuth()` | Auth checked but not required; `request.user` may be undefined |
35
- | `@CustomerPortal()` | Allows `customer` role (B2B2C variant) |
36
- | `@UserSession()` | Param decorator — injects the `User` from session |
37
- | `@Session()` | Param decorator — injects full `{ user, session }` object |
38
-
39
- ## Example Usage
40
-
41
- ```typescript
42
- import { Controller, Get } from '@nestjs/common';
43
- import { AllowAnonymous, UserSession } from '../auth/auth.decorator';
44
- import { User } from '../users/user.entity';
45
-
46
- @Controller('example')
47
- export class ExampleController {
48
- // Public — no auth
49
- @Get('public')
50
- @AllowAnonymous()
51
- getPublic() { ... }
52
-
53
- // Protected (default) — business_user or superadmin
54
- @Get('me')
55
- getMe(@UserSession() user: User) { ... }
56
- }
57
- ```
58
-
59
- ## Better Auth Setup
60
-
61
- Better Auth instance: `src/modules/auth/auth.ts`
62
- - Adapts to TypeORM via `betterAuthTypeOrmAdapter`
63
- - Plugins: email/password, Google OAuth, API keys, admin
64
- - Session stored in `sessions` table; user in `user` table (Better Auth schema)
@@ -1,27 +0,0 @@
1
- # Credits Deduction Pattern
2
-
3
- Add `@DeductCredits(n)` + `@UseGuards(CreditsGuard)` to any route that should cost credits.
4
-
5
- ```typescript
6
- import { UseGuards } from '@nestjs/common';
7
- import { CreditsGuard } from '../credits/guards/credits.guard';
8
- import { DeductCredits } from '../credits/decorators/deduct-credits.decorator';
9
-
10
- @DeductCredits(10)
11
- @UseGuards(CreditsGuard)
12
- @Post('ai-operation')
13
- async handler(@UserSession() user: User) {
14
- // CreditsGuard runs before the handler and deducts based on monetization strategy
15
- // Handler only runs if credits check passes
16
- }
17
- ```
18
-
19
- ## Rules
20
-
21
- - **Both** `@DeductCredits(n)` and `@UseGuards(CreditsGuard)` are required together — neither works alone.
22
- - `n` is the number of credits to deduct per call.
23
- - `CreditsGuard` reads the active monetization strategy from `MonetizationConfigService` (cached 24h in Redis).
24
- - The guard behaviour depends on strategy (see `credits_get_monetization_strategies`).
25
- - Source files:
26
- - `src/modules/credits/decorators/deduct-credits.decorator.ts`
27
- - `src/modules/credits/guards/credits.guard.ts`
@@ -1,25 +0,0 @@
1
- # Credits Monetization Strategies
2
-
3
- The active strategy is stored in `AdminSettings` and cached for 24h in Redis via `MonetizationConfigService`.
4
-
5
- ## Strategies
6
-
7
- | Strategy | Behaviour |
8
- |----------|-----------|
9
- | `free` | All credit checks bypass — no deduction ever |
10
- | `subscription` | Credit checks bypass — use feature gates to restrict instead |
11
- | `credits` | Deduct `n` credits from user balance; 402 if insufficient |
12
- | `hybrid` | Deduct from `subscription.plan.monthlyCredits` allowance first; bill Polar overage when exhausted |
13
-
14
- ## Hybrid details
15
-
16
- - Monthly allowance: `subscription.plan.monthlyCredits` (column on `SubscriptionPlan` entity, NOT a feature code)
17
- - Overage rate: `subscription.plan.overageRate` (column on `SubscriptionPlan` entity)
18
- - Overage is charged via Polar meter API
19
-
20
- ## Choosing a strategy
21
-
22
- - **SaaS with flat plans** → `subscription` (gates control feature access, no per-call cost)
23
- - **API / AI product** → `credits` (pay-as-you-go balance)
24
- - **AI + subscription tiers** → `hybrid` (included monthly quota + Polar overage billing)
25
- - **Early stage / free tier** → `free` (disable all metering)
@@ -1,51 +0,0 @@
1
- # Cron Jobs — LaunchFrame Pattern
2
-
3
- ## Key Rules
4
-
5
- - **Single service**: All cron jobs live in `src/jobs/cron.service.ts`. Do NOT create new cron services unless the job is genuinely complex enough to warrant its own service.
6
- - **Lightweight methods**: Cron methods should enqueue work to Bull and return. Never do heavy processing inline.
7
- - `ScheduleModule.forRoot()` is already registered in `app.module.ts` — do not add it again.
8
-
9
- ## Imports
10
-
11
- ```typescript
12
- import { Cron, CronExpression } from '@nestjs/schedule';
13
- import { InjectQueue } from '@nestjs/bull';
14
- import { Queue } from 'bull';
15
- ```
16
-
17
- ## Available CronExpression values
18
-
19
- ```
20
- CronExpression.EVERY_MINUTE
21
- CronExpression.EVERY_30_MINUTES
22
- CronExpression.EVERY_HOUR
23
- CronExpression.EVERY_DAY_AT_MIDNIGHT
24
- CronExpression.EVERY_WEEK
25
- ```
26
-
27
- Use a raw cron string (e.g. `'0 9 * * 1-5'`) if no preset fits.
28
-
29
- ## Example method
30
-
31
- ```typescript
32
- @Cron(CronExpression.EVERY_HOUR)
33
- async processWebhooks() {
34
- this.logger.log('Starting webhook processing job...');
35
- try {
36
- const items = await this.repo.find({ where: { processed: false } });
37
- for (const item of items) {
38
- await this.queue.add({ id: item.id }, {
39
- attempts: 3,
40
- backoff: { type: 'exponential', delay: 2000 },
41
- });
42
- }
43
- } catch (error) {
44
- this.logger.error('Error in processWebhooks:', error);
45
- }
46
- }
47
- ```
48
-
49
- ## Module registration
50
-
51
- `CronService` is already a provider in `JobsModule`. If you inject a new queue or repository, add the corresponding `BullModule.registerQueue` / `TypeOrmModule.forFeature` to `JobsModule` imports.
@@ -1,97 +0,0 @@
1
- # TypeORM Entity Conventions in LaunchFrame
2
-
3
- ## Required Decorators
4
-
5
- Every entity must have these four:
6
-
7
- ```typescript
8
- @Entity('snake_case_table_name')
9
- export class MyEntity {
10
- @PrimaryGeneratedColumn()
11
- id: number;
12
-
13
- // ... domain columns ...
14
-
15
- @CreateDateColumn()
16
- createdAt: Date;
17
-
18
- @UpdateDateColumn()
19
- updatedAt: Date;
20
- }
21
- ```
22
-
23
- ## Naming Strategy
24
-
25
- A global `SnakeNamingStrategy` is configured in TypeORM. This means:
26
- - `createdAt` → `created_at` automatically
27
- - `userId` → `user_id` automatically
28
- - Do NOT add `name: 'column_name'` unless you need to deviate from the convention.
29
-
30
- Exception: some legacy entities specify explicit `name:` — that is acceptable but not required for new entities.
31
-
32
- ## Column Types
33
-
34
- ```typescript
35
- // String
36
- @Column() name: string;
37
- @Column({ type: 'text', nullable: true }) description: string;
38
- @Column({ type: 'varchar', length: 255, nullable: true }) slug: string;
39
-
40
- // Number
41
- @Column({ type: 'int' }) count: number;
42
- @Column({ type: 'decimal', precision: 10, scale: 2, nullable: true }) amount: number;
43
-
44
- // Boolean
45
- @Column({ default: false }) isActive: boolean;
46
-
47
- // JSON
48
- @Column({ type: 'jsonb' }) metadata: Record<string, any>;
49
- @Column({ type: 'jsonb', nullable: true }) options: Record<string, any>;
50
-
51
- // Enum (define enum in same file)
52
- export enum MyStatus { ACTIVE = 'active', INACTIVE = 'inactive' }
53
- @Column({ type: 'enum', enum: MyStatus }) status: MyStatus;
54
-
55
- // UUID primary key
56
- @PrimaryGeneratedColumn('uuid') id: string;
57
- ```
58
-
59
- ## Relations
60
-
61
- ```typescript
62
- // Foreign key column + relation (preferred pattern)
63
- @Column({ type: 'int' })
64
- userId: number;
65
-
66
- @ManyToOne(() => User, { onDelete: 'CASCADE' })
67
- user: User;
68
-
69
- // One-to-many
70
- @OneToMany(() => CreditTransaction, (tx) => tx.user)
71
- transactions: CreditTransaction[];
72
- ```
73
-
74
- ## Multi-Tenancy
75
-
76
- For multi-tenant variants, add `projectId` to every domain entity:
77
-
78
- ```typescript
79
- // MULTI_TENANT_FIELDS_START
80
- @Column() projectId: number;
81
- // MULTI_TENANT_FIELDS_END
82
- ```
83
-
84
- Leave the section markers in place — the CLI uses them to splice in multi-tenant fields.
85
-
86
- ## File Location
87
-
88
- ```
89
- src/modules/<domain>/entities/<entity-name>.entity.ts
90
- ```
91
-
92
- Register in the module via `TypeOrmModule.forFeature([MyEntity])`.
93
-
94
- ## Migration
95
-
96
- Migrations live in `src/migrations/`. Name format: `<timestamp>-<PascalCaseDescription>.ts`.
97
- Run inside the backend container: `npm run migration:run`.
@@ -1,123 +0,0 @@
1
- # Environment Variable Conventions
2
-
3
- ## Single Centralized `.env`
4
-
5
- All environment variables live in **one file**: `infrastructure/.env`
6
-
7
- Docker Compose mounts it into every container via `env_file:` — **never create per-service `.env` files**.
8
-
9
- ## Variable Naming
10
-
11
- Variables are **canonical** (no service-specific prefixes like `REACT_APP_`, `VITE_`, or `NEXT_PUBLIC_`).
12
- The `docker-compose.yml` files map canonical names to the appropriate prefixed versions for each service.
13
-
14
- Example mapping in docker-compose:
15
- ```yaml
16
- environment:
17
- - VITE_API_BASE_URL=${API_BASE_URL}
18
- - NEXT_PUBLIC_PRIMARY_DOMAIN=${PRIMARY_DOMAIN}
19
- ```
20
-
21
- ## Key Variables Reference
22
-
23
- ### Application
24
- ```
25
- NODE_ENV=production
26
- APP_NAME=
27
- PRIMARY_DOMAIN=
28
- ```
29
-
30
- ### Database (PostgreSQL)
31
- ```
32
- DB_HOST=database
33
- DB_PORT=5432
34
- DB_USERNAME=postgres
35
- DB_PASSWORD=
36
- DB_DATABASE=
37
- DATABASE_URL=postgresql://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_DATABASE}
38
- ```
39
-
40
- ### Redis
41
- ```
42
- REDIS_HOST=redis
43
- REDIS_PORT=6379
44
- REDIS_PASSWORD=
45
- REDIS_URL=redis://${REDIS_HOST}:${REDIS_PORT}
46
- ```
47
-
48
- ### Service URLs & Ports
49
- ```
50
- BACKEND_PORT=4000
51
- API_BASE_URL=http://localhost:4000
52
- BACKEND_API_URL=http://backend:4000
53
-
54
- ADMIN_FRONTEND_PORT=3001
55
- ADMIN_BASE_URL=http://localhost:3001
56
-
57
- CUSTOMERS_FRONTEND_PORT=3000
58
- FRONTEND_BASE_URL=http://localhost:3000
59
-
60
- WEBSITE_PORT=8080
61
- WEBSITE_BASE_URL=http://localhost:8080
62
- ```
63
-
64
- ### Authentication & Security
65
- ```
66
- BETTER_AUTH_SECRET= # min 32 chars, generate: openssl rand -base64 32
67
- INITIAL_CREDITS=100 # credits granted to new users
68
- BULL_ADMIN_TOKEN= # Bull Board dashboard access
69
- ```
70
-
71
- ### Email
72
- ```
73
- RESEND_API_KEY= # production email (Resend)
74
- MAIL_HOST= # SMTP host (dev: Mailtrap)
75
- MAIL_USER=
76
- MAIL_PASSWORD=
77
- MAIL_FROM=noreply@${PRIMARY_DOMAIN}
78
- ```
79
-
80
- ### Payments (Polar.sh)
81
- ```
82
- POLAR_ACCESS_TOKEN=
83
- POLAR_SUCCESS_URL=${ADMIN_BASE_URL}/payments/success?checkout_id={CHECKOUT_ID}
84
- POLAR_WEBHOOK_SECRET=
85
- # POLAR_ENVIRONMENT=sandbox # defaults to 'sandbox' in dev, 'production' in prod
86
- ```
87
-
88
- ### Google
89
- ```
90
- GOOGLE_CLIENT_ID=
91
- GOOGLE_CLIENT_SECRET=
92
- GOOGLE_REDIRECT_URI=http://localhost:4000/auth/google/callback
93
- GOOGLE_ANALYTICS_ID=
94
- GOOGLE_CLOUD_STORAGE_BUCKET=
95
- ```
96
-
97
- ### Monitoring
98
- ```
99
- BACKEND_SENTRY_DSN=
100
- ADMIN_FRONTEND_SENTRY_DSN=
101
- CUSTOMERS_PORTAL_SENTRY_DSN=
102
- MIXPANEL_PROJECT_TOKEN=
103
- ```
104
-
105
- ### Real-time
106
- ```
107
- PUSHER_KEY=
108
- PUSHER_CLUSTER=
109
- ```
110
-
111
- ## Adding a New Variable
112
-
113
- 1. Add it to `infrastructure/base/.env.example` with a comment
114
- 2. Add it to `infrastructure/.env` (your local copy, never committed)
115
- 3. Reference it in the relevant `docker-compose.yml` `environment:` section if a prefix is needed
116
- 4. Access in NestJS via `process.env.VAR_NAME` or via `ConfigService`
117
-
118
- ## Rules
119
-
120
- - **NEVER** commit `.env` — it is gitignored
121
- - **NEVER** create per-service `.env` files
122
- - All secrets (auth, DB, API keys) go in `infrastructure/.env` only
123
- - Use `base/.env.example` as the template — it has all variables with placeholder values
@@ -1,74 +0,0 @@
1
- # Feature Gate System Overview
2
-
3
- ## Architecture
4
-
5
- Features are fully generic and defined in the database — there are no hardcoded feature codes.
6
-
7
- | Table | Purpose |
8
- |-------|---------|
9
- | `subscription_plan_features` | Feature definitions: `code`, `name`, `featureType` (boolean/numeric/text), `defaultValue` |
10
- | `subscription_plan_feature_values` | Per-plan feature values: links plan → feature → value (JSONB) |
11
-
12
- Features are created and managed via the admin portal. The template ships with a single `basic_access` feature for the free plan as a starting point.
13
-
14
- ## Querying Features
15
-
16
- ```typescript
17
- import { UserSubscriptionService } from '../subscriptions/services/user-subscription.service';
18
-
19
- // Returns Record<string, any> — keys are feature codes, values are the raw stored value
20
- const features = await this.userSubscriptionService.getCurrentFeatures(userId);
21
-
22
- // Example: check a boolean feature
23
- const hasFeature = features['your_feature_code'] === true;
24
-
25
- // Example: check a numeric feature (-1 means unlimited)
26
- const limit = features['your_feature_code'] as number ?? 0;
27
- const isUnlimited = limit === -1;
28
- ```
29
-
30
- ## Check Pattern (no decorator — manual check only)
31
-
32
- ```typescript
33
- import { Injectable, ForbiddenException } from '@nestjs/common';
34
- import { UserSubscriptionService } from '../subscriptions/services/user-subscription.service';
35
-
36
- @Injectable()
37
- export class YourService {
38
- constructor(
39
- private readonly userSubscriptionService: UserSubscriptionService,
40
- ) {}
41
-
42
- async guardedOperation(userId: number) {
43
- const features = await this.userSubscriptionService.getCurrentFeatures(userId);
44
-
45
- // Boolean feature
46
- if (!features['your_boolean_feature']) {
47
- throw new ForbiddenException('Your plan does not include this feature');
48
- }
49
-
50
- // Numeric feature with limit
51
- const limit = features['your_numeric_feature'] as number ?? 0;
52
- const currentCount = await this.getCount(userId);
53
- if (limit !== -1 && currentCount >= limit) {
54
- throw new ForbiddenException(`Plan limit reached (${limit})`);
55
- }
56
- }
57
- }
58
- ```
59
-
60
- ## Unlimited Sentinel
61
-
62
- For numeric features, `-1` means unlimited. Always check for it:
63
- ```typescript
64
- const isUnlimited = limit === -1;
65
- if (!isUnlimited && currentCount >= limit) { ... }
66
- ```
67
-
68
- ## Credits-specific Plan Fields
69
-
70
- Monthly credits and overage rate are NOT generic features — they live as dedicated columns on `SubscriptionPlan`:
71
- - `plan.monthlyCredits: number | null` — monthly credit allowance (null = not applicable)
72
- - `plan.overageRate: number | null` — per-credit overage cost (null = no overage)
73
-
74
- These are read directly from the subscription, not via `getCurrentFeatures()`.
@@ -1,44 +0,0 @@
1
- # NestJS Module Structure in LaunchFrame
2
-
3
- ## Folder Layout
4
-
5
- Every domain lives under `src/modules/<domain>/`:
6
-
7
- ```
8
- src/modules/<domain>/
9
- ├── <domain>.module.ts # Module definition
10
- ├── <domain>.service.ts # Business logic
11
- ├── <domain>.controller.ts # HTTP routes (if applicable)
12
- └── entities/
13
- └── <entity>.entity.ts # TypeORM entity
14
- ```
15
-
16
- ## Module Rules
17
-
18
- 1. **Entity registration**: Use `TypeOrmModule.forFeature([...entities])` inside `imports`.
19
- 2. **Circular deps**: Wrap with `forwardRef(() => OtherModule)` on both sides.
20
- 3. **Bull queues**: Import `BullQueueModule` (shared forRoot) + `BullModule.registerQueue({ name })` — never re-declare `forRoot`.
21
- 4. **Exports**: Only export what other modules actually need (service, guard, etc.).
22
- 5. **Registering in AppModule**: Add your module to `app.module.ts` imports array.
23
-
24
- ## Variant Section Markers
25
-
26
- Variant-specific code uses comment markers so the CLI can splice in additions:
27
-
28
- ```typescript
29
- // MULTI_TENANT_IMPORTS_START
30
- // MULTI_TENANT_IMPORTS_END
31
-
32
- // MULTI_TENANT_ENTITIES_START
33
- // MULTI_TENANT_ENTITIES_END
34
-
35
- // MULTI_TENANT_PROVIDERS_START
36
- // MULTI_TENANT_PROVIDERS_END
37
- ```
38
-
39
- Leave these markers in place even if empty — the CLI relies on them.
40
-
41
- ## Placement in AppModule
42
-
43
- `app.module.ts` already bootstraps global modules (TypeORM, Bull, Schedule, etc.).
44
- Your module only needs to import what it directly depends on.
@@ -1,18 +0,0 @@
1
- # Bull Queues
2
-
3
- LaunchFrame registers 5 Bull queues via `BullQueueModule` (`src/modules/bull/bull.module.ts`).
4
-
5
- | Queue name | Purpose |
6
- |--------------|----------------------------------------------|
7
- | `emails` | Transactional email sending via Resend |
8
- | `api` | Outbound HTTP API calls to third-party services |
9
- | `webhooks` | Processing inbound webhook payloads |
10
-
11
- ## Usage rules
12
-
13
- - **Import `BullQueueModule`** (not `BullModule.forRoot`) in your feature module — it re-uses the shared Redis connection.
14
- - Register the queue in your module with `BullModule.registerQueue({ name: 'queue-name' })`, imported from `bull.module.ts`.
15
- - Inject with `@InjectQueue('queue-name') private queue: Queue` from `@nestjs/bull`.
16
- - Default job options: `{ attempts: 3, backoff: { type: 'exponential', delay: 2000 } }`.
17
- - **Always `throw error`** in processor catch blocks — Bull needs the error to trigger retries.
18
- - Keep cron/scheduler methods lightweight: enqueue to Bull, do heavy work in the processor.
@@ -1,67 +0,0 @@
1
- # LaunchFrame Variants
2
-
3
- LaunchFrame generates projects in one of three variants. Each variant is a superset of the previous.
4
-
5
- ## Variant Overview
6
-
7
- ### Base (B2B, single-tenant)
8
- The simplest variant. One admin panel, one customer-facing portal, no multi-tenancy.
9
- - Single workspace per account
10
- - No `projectId` on entities
11
- - No `projects` module
12
- - Roles: `admin`, `user`
13
-
14
- ### Multi-tenant
15
- Extends Base by adding workspace/project isolation.
16
- - `projects` module: CRUD, ownership guards
17
- - `projectId: number` column on all domain entities
18
- - Project ownership guard on all project-scoped routes
19
- - Roles: `admin`, `user` (scoped to project)
20
-
21
- ### B2B2C
22
- Extends Base by adding a separate customer-facing experience (end-users of your customers).
23
- - Adds `customer` role
24
- - Adds `customers-portal` frontend service
25
- - Adds `@CustomerPortal()` route decorator for customer-only endpoints
26
- - B2B2C can also be combined with multi-tenancy
27
-
28
- ## Section Marker Syntax
29
-
30
- Source files use markers so the CLI can strip/inject variant-specific code blocks:
31
-
32
- ```
33
- // {{SECTION_NAME}}_START
34
- ... variant-specific code ...
35
- // {{SECTION_NAME}}_END
36
- ```
37
-
38
- Common section names:
39
- - `MULTI_TENANT_FIELDS` — `projectId` columns on entities
40
- - `MULTI_TENANT_GUARD` — project ownership guard imports/decorators
41
- - `CUSTOMER_PORTAL_ROUTES` — B2B2C customer route blocks
42
-
43
- The CLI strips sections that don't apply to the chosen variant and removes the markers from kept sections.
44
-
45
- ## Coding Guidelines per Variant
46
-
47
- ### When writing entities
48
- - **Base**: no `projectId`
49
- - **Multi-tenant**: add `@Column() projectId: number;` wrapped in `// MULTI_TENANT_FIELDS_START` / `_END`
50
-
51
- ### When writing controllers
52
- - **Multi-tenant**: add project ownership guard
53
- ```typescript
54
- // MULTI_TENANT_GUARD_START
55
- @UseGuards(ProjectOwnershipGuard)
56
- // MULTI_TENANT_GUARD_END
57
- ```
58
- - **B2B2C**: wrap customer-only routes with `@CustomerPortal()` and section markers
59
-
60
- ### When scaffolding modules
61
- - Use section markers for any variant-specific imports, providers, or route decorators
62
- - Keep the base code path clean — markers are additive
63
-
64
- ## Which Variant to Target
65
-
66
- When writing code for a LaunchFrame project, check the project's `VARIANT` env var or ask the user.
67
- Default assumption: **Base** (single-tenant B2B) unless told otherwise.
@@ -1,53 +0,0 @@
1
- # Webhook Architecture
2
-
3
- LaunchFrame uses a **receipt/processing separation** pattern for all inbound webhooks.
4
-
5
- ## Flow
6
-
7
- ```
8
- Webhook Provider → Controller (receipt) → WebhookLog (DB) → Bull queue → Processor (processing)
9
-
10
- Cron retries failed jobs
11
- ```
12
-
13
- ## 1. Controller (Receipt Layer)
14
-
15
- The controller saves the raw payload and returns 200 **immediately** — never process inline.
16
-
17
- ```typescript
18
- import { Controller, Post, Req, Res } from '@nestjs/common';
19
- import { Public } from '../auth/auth.decorator';
20
- import { UseGuards } from '@nestjs/common';
21
- import { PolarWebhookGuard } from './guards/polar-webhook.guard';
22
-
23
- @Controller('webhooks')
24
- export class WebhooksController {
25
- @Post('polar')
26
- @Public()
27
- @UseGuards(PolarWebhookGuard)
28
- async handlePolar(@Req() req: Request, @Res() res: Response): Promise<void> {
29
- // Save raw payload to WebhookLog, enqueue to 'webhooks' Bull queue
30
- res.status(200).send(); // Always return 200 immediately
31
- }
32
- }
33
- ```
34
-
35
- ## 2. WebhookLog Entity
36
-
37
- Fields: `provider` (enum: POLAR | PAYPAL | STRIPE), `eventType`, `webhookId`, `payload` (jsonb),
38
- `headers` (jsonb), `processed` (bool, default false), `retryCount` (int, default 0), `processingError`.
39
-
40
- ## 3. Processor (Processing Layer)
41
-
42
- A Bull processor on the `webhooks` queue handles the actual logic. Always `throw error` in catch blocks.
43
-
44
- ## 4. Retry Cron
45
-
46
- `CronService` runs `EVERY_HOUR`, fetches up to 100 `WebhookLog` records where `processed=false AND retryCount < 5`,
47
- and re-enqueues them to the `webhooks` Bull queue (which has its own 3-attempt exponential backoff).
48
- App-level max retries: **5** (enforced by the cron filter).
49
-
50
- ## Guards
51
-
52
- - `PolarWebhookGuard` — validates Polar webhook signature
53
- - Add equivalent guards for other providers (PayPal, Stripe) as needed