@breadstone/archipel-mcp 0.0.10 → 0.0.13
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/data/guides/ai-text-generation.md +361 -0
- package/data/guides/analytics-and-error-tracking.md +189 -0
- package/data/guides/authentication-and-authorization.md +657 -0
- package/data/guides/blob-storage.md +242 -0
- package/data/guides/caching.md +255 -0
- package/data/guides/cryptography-and-otp.md +240 -0
- package/data/guides/database-setup.md +546 -0
- package/data/guides/document-generation.md +174 -0
- package/data/guides/email-delivery.md +196 -0
- package/data/guides/esigning-integration.md +231 -0
- package/data/guides/getting-started.md +351 -0
- package/data/guides/implementing-ports.md +317 -0
- package/data/guides/index.md +62 -0
- package/data/guides/mcp-server.md +222 -0
- package/data/guides/openapi-and-feature-discovery.md +266 -0
- package/data/guides/payments-and-feature-gating.md +244 -0
- package/data/guides/resource-management.md +352 -0
- package/data/guides/telemetry-and-observability.md +190 -0
- package/data/guides/testing.md +319 -0
- package/data/guides/tsdoc-guidelines.md +45 -0
- package/data/packages/platform-database/api/Class.DatabaseModule.md +3 -5
- package/data/packages/platform-database/api/Class.DatabaseService.md +5 -4
- package/data/packages/platform-database/api/Class.PrismaService.md +8 -7
- package/data/packages/platform-database/api/Interface.IDatabaseModuleConfig.md +13 -1
- package/data/packages/platform-database/api/Interface.IPrismaServiceOptions.md +22 -0
- package/data/packages/platform-database/api/Variable.DATABASE_MODULE_CONFIG.md +14 -0
- package/data/packages/platform-database/api/Variable.DB_ACCELERATE_URL.md +14 -0
- package/data/packages/platform-database/api/Variable.DB_AUTO_CONNECTION.md +14 -0
- package/data/packages/platform-database/api/Variable.DB_OPTIMIZE.md +14 -0
- package/data/packages/platform-database/api/Variable.DB_OPTIMIZE_API_KEY.md +14 -0
- package/data/packages/platform-database/api/Variable.PLATFORM_DATABASE_CONFIG_ENTRIES.md +14 -0
- package/data/packages/platform-database/api/index.md +13 -1
- package/data/packages/platform-database/index.md +41 -18
- package/data/packages/platform-openapi/api/Class.SwaggerFeatureDiscovery.md +10 -6
- package/data/packages/platform-telemetry/api/Class.NoopTelemetryFacade.md +10 -10
- package/data/packages/platform-telemetry/api/Interface.ITelemetryFacade.md +1 -1
- package/package.json +1 -1
- package/src/GuidesLoader.d.ts +13 -0
- package/src/GuidesLoader.js +81 -0
- package/src/main.js +36 -198
- package/src/models/IGuideDoc.d.ts +15 -0
- package/src/models/IGuideDoc.js +3 -0
- package/src/tools/registerGetConfigPatternTool.d.ts +5 -0
- package/src/tools/registerGetConfigPatternTool.js +15 -0
- package/src/tools/registerGetDtoPatternTool.d.ts +5 -0
- package/src/tools/registerGetDtoPatternTool.js +15 -0
- package/src/tools/registerGetErrorHandlingPatternTool.d.ts +5 -0
- package/src/tools/registerGetErrorHandlingPatternTool.js +15 -0
- package/src/tools/registerGetGuardPatternTool.d.ts +5 -0
- package/src/tools/registerGetGuardPatternTool.js +15 -0
- package/src/tools/registerGetGuideTool.d.ts +6 -0
- package/src/tools/registerGetGuideTool.js +32 -0
- package/src/tools/registerGetMappingPatternTool.d.ts +5 -0
- package/src/tools/registerGetMappingPatternTool.js +34 -0
- package/src/tools/registerGetModulePatternTool.d.ts +5 -0
- package/src/tools/registerGetModulePatternTool.js +29 -0
- package/src/tools/registerGetPackageDocTool.d.ts +6 -0
- package/src/tools/registerGetPackageDocTool.js +43 -0
- package/src/tools/registerGetQueryPatternTool.d.ts +5 -0
- package/src/tools/registerGetQueryPatternTool.js +29 -0
- package/src/tools/registerGetRepositoryPatternTool.d.ts +5 -0
- package/src/tools/registerGetRepositoryPatternTool.js +29 -0
- package/src/tools/registerGetTestingPatternTool.d.ts +5 -0
- package/src/tools/registerGetTestingPatternTool.js +15 -0
- package/src/tools/registerListGuidesTool.d.ts +6 -0
- package/src/tools/registerListGuidesTool.js +22 -0
- package/src/tools/registerListPackagesTool.d.ts +6 -0
- package/src/tools/registerListPackagesTool.js +20 -0
- package/src/tools/registerSearchDocsTool.d.ts +6 -0
- package/src/tools/registerSearchDocsTool.js +35 -0
- package/src/tools/registerSearchGuidesTool.d.ts +6 -0
- package/src/tools/registerSearchGuidesTool.js +28 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Payments & Feature Gating
|
|
3
|
+
description: Integrate payment providers (Stripe, Paddle, LemonSqueezy, Mollie) and gate features by subscription tier.
|
|
4
|
+
order: 9
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Payments & Feature Gating
|
|
8
|
+
|
|
9
|
+
This guide covers payment integration with `platform-payments`: choosing a payment provider, handling webhooks, gating features by subscription tier, and tracking feature usage.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
yarn add @breadstone/archipel-platform-payments
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Choose a Payment Provider
|
|
22
|
+
|
|
23
|
+
`platform-payments` normalizes payment operations across multiple providers. You implement the `PaymentClientPort` for your chosen provider:
|
|
24
|
+
|
|
25
|
+
| Provider | What it provides |
|
|
26
|
+
| ---------------- | --------------------------------------- |
|
|
27
|
+
| **Stripe** | Cards, subscriptions, invoices |
|
|
28
|
+
| **Paddle** | SaaS billing, tax handling |
|
|
29
|
+
| **LemonSqueezy** | Digital products, memberships |
|
|
30
|
+
| **Mollie** | European payment methods, subscriptions |
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Module Registration
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
import { Module } from '@nestjs/common';
|
|
38
|
+
import { PaymentModule } from '@breadstone/archipel-platform-payments';
|
|
39
|
+
import { StripePaymentClient } from './adapters/StripePaymentClient';
|
|
40
|
+
import { PrismaFeatureAccessAdapter } from './adapters/PrismaFeatureAccessAdapter';
|
|
41
|
+
import { STRIPE_CONFIG_ENTRIES } from './env';
|
|
42
|
+
|
|
43
|
+
@Module({
|
|
44
|
+
imports: [
|
|
45
|
+
PaymentModule.register({
|
|
46
|
+
paymentClient: StripePaymentClient,
|
|
47
|
+
featureAccess: PrismaFeatureAccessAdapter,
|
|
48
|
+
configEntries: [...STRIPE_CONFIG_ENTRIES],
|
|
49
|
+
}),
|
|
50
|
+
],
|
|
51
|
+
})
|
|
52
|
+
export class AppModule {}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Implementing the Payment Client Port
|
|
58
|
+
|
|
59
|
+
The `PaymentClientPort` defines the contract between the library and your payment provider. Implement it for your chosen provider:
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
import { Injectable } from '@nestjs/common';
|
|
63
|
+
import {
|
|
64
|
+
PaymentClientPort,
|
|
65
|
+
type INormalizedCheckoutSession,
|
|
66
|
+
type INormalizedSubscription,
|
|
67
|
+
type INormalizedWebhookEvent,
|
|
68
|
+
} from '@breadstone/archipel-platform-payments';
|
|
69
|
+
|
|
70
|
+
@Injectable()
|
|
71
|
+
export class StripePaymentClient extends PaymentClientPort {
|
|
72
|
+
public async createCheckoutSession(params: Record<string, unknown>): Promise<INormalizedCheckoutSession> {
|
|
73
|
+
// Use Stripe SDK to create a checkout session
|
|
74
|
+
// Return a normalized result
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
public async getSubscription(subscriptionId: string): Promise<INormalizedSubscription | null> {
|
|
78
|
+
// Fetch subscription details from Stripe
|
|
79
|
+
// Return normalized subscription data
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
public async handleWebhook(payload: Buffer, signature: string): Promise<INormalizedWebhookEvent> {
|
|
83
|
+
// Verify webhook signature and normalize the event
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
The library uses **normalized models** (`INormalizedSubscription`, `INormalizedCheckoutSession`, `INormalizedWebhookEvent`) so your application code works identically regardless of the payment provider.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Feature Gating
|
|
93
|
+
|
|
94
|
+
The `FeatureAccessPort` lets you restrict functionality based on subscription tiers.
|
|
95
|
+
|
|
96
|
+
### Step 1: Implement the Port
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
import { Injectable } from '@nestjs/common';
|
|
100
|
+
import { FeatureAccessPort } from '@breadstone/archipel-platform-payments';
|
|
101
|
+
import { PrismaService } from '@breadstone/archipel-platform-database';
|
|
102
|
+
|
|
103
|
+
@Injectable()
|
|
104
|
+
export class PrismaFeatureAccessAdapter extends FeatureAccessPort {
|
|
105
|
+
private readonly _prisma: PrismaService;
|
|
106
|
+
|
|
107
|
+
constructor(prisma: PrismaService) {
|
|
108
|
+
super();
|
|
109
|
+
this._prisma = prisma;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
public async hasAccess(userId: string, feature: string): Promise<boolean> {
|
|
113
|
+
const subscription = await this._prisma.subscription.findFirst({
|
|
114
|
+
where: { userId, status: 'active' },
|
|
115
|
+
include: { plan: true },
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
if (!subscription) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return subscription.plan.features.includes(feature);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
public async recordUsage(userId: string, feature: string): Promise<void> {
|
|
126
|
+
await this._prisma.featureUsage.create({
|
|
127
|
+
data: { userId, feature, usedAt: new Date() },
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
public async getUsageCount(userId: string, feature: string, since: Date): Promise<number> {
|
|
132
|
+
return this._prisma.featureUsage.count({
|
|
133
|
+
where: { userId, feature, usedAt: { gte: since } },
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Step 2: Use the Feature Guard
|
|
140
|
+
|
|
141
|
+
The `FeatureGuard` decorator restricts endpoints to users with access to specific features:
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
import { Controller, Post, UseGuards } from '@nestjs/common';
|
|
145
|
+
import { JwtAuthGuard } from '@breadstone/archipel-platform-authentication';
|
|
146
|
+
import { FeatureGuard } from '@breadstone/archipel-platform-payments';
|
|
147
|
+
|
|
148
|
+
@Controller('exports')
|
|
149
|
+
@UseGuards(JwtAuthGuard)
|
|
150
|
+
export class ExportController {
|
|
151
|
+
@Post('pdf')
|
|
152
|
+
@FeatureGuard('export:pdf')
|
|
153
|
+
public async exportPdf(): Promise<void> {
|
|
154
|
+
// Only accessible to users with 'export:pdf' feature
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
@Post('csv')
|
|
158
|
+
@FeatureGuard('export:csv')
|
|
159
|
+
public async exportCsv(): Promise<void> {
|
|
160
|
+
// Only accessible to users with 'export:csv' feature
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Step 3: Track Usage with Interceptor
|
|
166
|
+
|
|
167
|
+
`FeatureUsageInterceptor` automatically records feature usage after a successful request:
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
import { Controller, Post, UseGuards, UseInterceptors } from '@nestjs/common';
|
|
171
|
+
import { JwtAuthGuard } from '@breadstone/archipel-platform-authentication';
|
|
172
|
+
import { FeatureGuard, FeatureUsageInterceptor } from '@breadstone/archipel-platform-payments';
|
|
173
|
+
|
|
174
|
+
@Controller('ai')
|
|
175
|
+
@UseGuards(JwtAuthGuard)
|
|
176
|
+
export class AiController {
|
|
177
|
+
@Post('generate')
|
|
178
|
+
@FeatureGuard('ai:generate')
|
|
179
|
+
@UseInterceptors(FeatureUsageInterceptor)
|
|
180
|
+
public async generate(): Promise<string> {
|
|
181
|
+
// Usage is recorded automatically after success
|
|
182
|
+
return 'generated content';
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Handling Webhooks
|
|
190
|
+
|
|
191
|
+
Payment providers send webhook events for subscription changes, payment failures, etc. Create a dedicated webhook controller:
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
import { Controller, Post, Req, Headers, RawBodyRequest } from '@nestjs/common';
|
|
195
|
+
import { PaymentClientPort } from '@breadstone/archipel-platform-payments';
|
|
196
|
+
import { Request } from 'express';
|
|
197
|
+
|
|
198
|
+
@Controller('webhooks/payments')
|
|
199
|
+
export class PaymentWebhookController {
|
|
200
|
+
private readonly _paymentClient: PaymentClientPort;
|
|
201
|
+
|
|
202
|
+
constructor(paymentClient: PaymentClientPort) {
|
|
203
|
+
this._paymentClient = paymentClient;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
@Post()
|
|
207
|
+
public async handleWebhook(
|
|
208
|
+
@Req() request: RawBodyRequest<Request>,
|
|
209
|
+
@Headers('stripe-signature') signature: string,
|
|
210
|
+
): Promise<void> {
|
|
211
|
+
const event = await this._paymentClient.handleWebhook(request.rawBody!, signature);
|
|
212
|
+
|
|
213
|
+
switch (event.type) {
|
|
214
|
+
case 'subscription.created':
|
|
215
|
+
case 'subscription.updated':
|
|
216
|
+
// Update subscription in your database
|
|
217
|
+
break;
|
|
218
|
+
case 'subscription.deleted':
|
|
219
|
+
// Handle cancellation
|
|
220
|
+
break;
|
|
221
|
+
case 'payment.failed':
|
|
222
|
+
// Notify user, retry logic
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Best Practices
|
|
232
|
+
|
|
233
|
+
1. **Define feature keys as constants.** Never use inline strings for feature identifiers.
|
|
234
|
+
2. **Verify webhook signatures.** Always validate the webhook payload before processing.
|
|
235
|
+
3. **Use normalized models.** Don't leak provider-specific types into your business logic.
|
|
236
|
+
4. **Implement idempotent webhook handlers.** Webhooks can be delivered multiple times.
|
|
237
|
+
5. **Separate feature gating from billing logic.** The `FeatureAccessPort` should only check access, not manage subscriptions.
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## Next Steps
|
|
242
|
+
|
|
243
|
+
- See the [Authentication & Authorization](/guides/authentication-and-authorization) guide for securing payment endpoints
|
|
244
|
+
- Browse the [platform-payments API reference](/packages/platform-payments/api/) for complete method signatures
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Resource Management
|
|
3
|
+
description: Register and load files (templates, assets, configs) from disk, blob storage, or memory using the strategy-based ResourceManager.
|
|
4
|
+
order: 17
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Resource Management
|
|
8
|
+
|
|
9
|
+
This guide covers working with `ResourceManager` from `platform-core`: registering file strategies, loading resources at runtime, and using the built-in caching layer. If your application ships email templates, HTML pages, CSS/JS assets, or any other file-based content, this is how you make it available to services.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
`ResourceManager` is part of `platform-core` — no extra package needed.
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
yarn add @breadstone/archipel-platform-core
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Overview: Strategy Pattern
|
|
24
|
+
|
|
25
|
+
`ResourceManager` does not load files directly. It delegates to one or more **strategies**, tried in priority order. The first strategy that can resolve the key wins.
|
|
26
|
+
|
|
27
|
+
| Strategy | Source | Sync | Async | When to use |
|
|
28
|
+
| -------------------------- | ------------------ | ---- | ----- | -------------------------------------- |
|
|
29
|
+
| `FileResourceStrategy` | Local filesystem | Yes | Yes | Templates, assets bundled with the app |
|
|
30
|
+
| `BlobResourceStrategy` | Cloud blob storage | No | Yes | User-uploaded templates, dynamic files |
|
|
31
|
+
| `EmbeddedResourceStrategy` | In-memory map | Yes | Yes | Fallback content, test fixtures |
|
|
32
|
+
|
|
33
|
+
Strategies are evaluated in the order you provide them. A typical setup:
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
FileResourceStrategy → BlobResourceStrategy → EmbeddedResourceStrategy
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
If the file exists on disk, it's served immediately. If not, blob storage is checked. If that also misses, the embedded fallback is returned. If nothing matches, a `ResourceNotFoundError` is thrown.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Module Registration
|
|
44
|
+
|
|
45
|
+
`ResourceModule` is a global module. Register it once in your root module with `forRoot()`:
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
import { Module } from '@nestjs/common';
|
|
49
|
+
import { join } from 'node:path';
|
|
50
|
+
import { ResourceModule, FileResourceStrategy, EmbeddedResourceStrategy } from '@breadstone/archipel-platform-core';
|
|
51
|
+
|
|
52
|
+
@Module({
|
|
53
|
+
imports: [
|
|
54
|
+
ResourceModule.forRoot({
|
|
55
|
+
strategies: [
|
|
56
|
+
new FileResourceStrategy({
|
|
57
|
+
basePaths: [
|
|
58
|
+
join(__dirname, 'assets'),
|
|
59
|
+
join(__dirname, '..', 'node_modules', '@breadstone', 'archipel-platform-core', 'assets'),
|
|
60
|
+
join(__dirname, '..', 'node_modules', '@breadstone', 'archipel-platform-openapi', 'assets'),
|
|
61
|
+
join(__dirname, '..', 'node_modules', '@breadstone', 'archipel-platform-mailing', 'assets'),
|
|
62
|
+
],
|
|
63
|
+
includeSubfolders: true,
|
|
64
|
+
}),
|
|
65
|
+
new EmbeddedResourceStrategy({
|
|
66
|
+
'fallback.html': '<html><body>Service unavailable</body></html>',
|
|
67
|
+
}),
|
|
68
|
+
],
|
|
69
|
+
debug: false,
|
|
70
|
+
}),
|
|
71
|
+
],
|
|
72
|
+
})
|
|
73
|
+
export class AppModule {}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Configuration Options
|
|
77
|
+
|
|
78
|
+
| Option | Type | Default | Description |
|
|
79
|
+
| ------------ | ---------------------------------------- | ------- | ------------------------------------------------ |
|
|
80
|
+
| `strategies` | `IResourceStrategy[]` | — | Strategies in priority order. Required. |
|
|
81
|
+
| `cache` | `ILayeredCache<string, IResourceResult>` | — | Optional cache instance from `platform-caching`. |
|
|
82
|
+
| `cacheTtl` | `number` (seconds) | `3600` | TTL for cached resources. |
|
|
83
|
+
| `debug` | `boolean` | `false` | Log all available resources on startup. |
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Loading Resources
|
|
88
|
+
|
|
89
|
+
Inject `ResourceManager` and call its load methods. Every method has a `try*` variant that returns `undefined` instead of throwing.
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { Injectable } from '@nestjs/common';
|
|
93
|
+
import { ResourceManager } from '@breadstone/archipel-platform-core';
|
|
94
|
+
|
|
95
|
+
@Injectable()
|
|
96
|
+
export class InvoiceService {
|
|
97
|
+
private readonly _resourceManager: ResourceManager;
|
|
98
|
+
|
|
99
|
+
constructor(resourceManager: ResourceManager) {
|
|
100
|
+
this._resourceManager = resourceManager;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
public async generateInvoice(): Promise<string> {
|
|
104
|
+
// Load an HTML template as string
|
|
105
|
+
const template = await this._resourceManager.loadAsStringAsync('invoice-template.html');
|
|
106
|
+
|
|
107
|
+
// ... fill template with data
|
|
108
|
+
return template;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Method Reference
|
|
114
|
+
|
|
115
|
+
| Method | Returns | Throws on miss | Notes |
|
|
116
|
+
| ------------------------- | --------------------------------------- | -------------- | ----------------------------- |
|
|
117
|
+
| `load(key)` | `IResourceResult` | Yes | Sync |
|
|
118
|
+
| `tryLoad(key)` | `IResourceResult \| undefined` | No | Sync |
|
|
119
|
+
| `loadAsync(key)` | `Promise<IResourceResult>` | Yes | Required for blob strategy |
|
|
120
|
+
| `tryLoadAsync(key)` | `Promise<IResourceResult \| undefined>` | No | |
|
|
121
|
+
| `loadAsString(key, enc?)` | `string` | Yes | Defaults to `utf-8` |
|
|
122
|
+
| `loadAsStringAsync(key)` | `Promise<string>` | Yes | |
|
|
123
|
+
| `loadAsBuffer(key)` | `Buffer \| undefined` | No | |
|
|
124
|
+
| `loadAsBufferAsync(key)` | `Promise<Buffer \| undefined>` | No | |
|
|
125
|
+
| `exists(key)` | `boolean` | — | Sync |
|
|
126
|
+
| `existsAsync(key)` | `Promise<boolean>` | — | |
|
|
127
|
+
| `invalidate(key)` | `void` | — | Clears cache for key |
|
|
128
|
+
| `invalidateAsync(key)` | `Promise<void>` | — | |
|
|
129
|
+
| `whatDoIHave()` | `void` | — | Logs all resources to console |
|
|
130
|
+
|
|
131
|
+
### The `IResourceResult` Object
|
|
132
|
+
|
|
133
|
+
When you use the full `load()` / `tryLoad()` methods you get rich metadata alongside the content:
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
interface IResourceResult {
|
|
137
|
+
content: Buffer;
|
|
138
|
+
metadata: IResourceMetadata;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
interface IResourceMetadata {
|
|
142
|
+
key: string; // Resource identifier you requested
|
|
143
|
+
path: string; // Resolved file / blob path
|
|
144
|
+
mimeType: string; // e.g. 'text/html', 'application/pdf'
|
|
145
|
+
size: number; // Size in bytes
|
|
146
|
+
encoding?: string; // e.g. 'utf-8'
|
|
147
|
+
source: string; // Strategy name that resolved it (e.g. 'file')
|
|
148
|
+
cachedAt?: Date; // Present when served from cache
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Strategy Details
|
|
155
|
+
|
|
156
|
+
### FileResourceStrategy
|
|
157
|
+
|
|
158
|
+
Searches one or more base paths on the filesystem. Each path is tried in order. If `includeSubfolders` is enabled, a recursive glob search runs as fallback when the direct lookup misses.
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
new FileResourceStrategy({
|
|
162
|
+
basePaths: [join(__dirname, 'assets'), join(__dirname, 'templates')],
|
|
163
|
+
includeSubfolders: true,
|
|
164
|
+
defaultEncoding: 'utf-8',
|
|
165
|
+
});
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
The key you pass to `load()` is the **filename** (e.g. `'invoice-template.html'`). The strategy joins it with each base path and checks if the file exists.
|
|
169
|
+
|
|
170
|
+
### BlobResourceStrategy
|
|
171
|
+
|
|
172
|
+
Loads from cloud blob storage via a minimal `IBlobServiceAdapter` interface. This is **async-only** — calling `load()` (sync) will throw.
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
import { BlobResourceStrategy, IBlobServiceAdapter } from '@breadstone/archipel-platform-core';
|
|
176
|
+
|
|
177
|
+
const blobAdapter: IBlobServiceAdapter = {
|
|
178
|
+
async downloadFile(key: string) {
|
|
179
|
+
// Your blob storage client logic here
|
|
180
|
+
return { data: buffer, contentType: 'text/html' };
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
new BlobResourceStrategy(blobAdapter, {
|
|
185
|
+
keyPrefix: 'templates/',
|
|
186
|
+
});
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
With `keyPrefix: 'templates/'`, a call to `loadAsync('invoice.html')` resolves to the blob key `templates/invoice.html`.
|
|
190
|
+
|
|
191
|
+
### EmbeddedResourceStrategy
|
|
192
|
+
|
|
193
|
+
Stores resources as plain strings in memory. Useful for fallback content or test fixtures.
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
new EmbeddedResourceStrategy({
|
|
197
|
+
'maintenance.html': '<h1>We are down for maintenance</h1>',
|
|
198
|
+
'default-logo.svg': '<svg>...</svg>',
|
|
199
|
+
});
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
You can also register resources dynamically after construction:
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
const embedded = new EmbeddedResourceStrategy();
|
|
206
|
+
embedded.register('dynamic-content.txt', 'Hello World');
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Real-World Examples
|
|
212
|
+
|
|
213
|
+
### Email Templates
|
|
214
|
+
|
|
215
|
+
`platform-mailing` uses `ResourceManager` to load HTML email templates:
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
// In MailModule.ts — factory creates the strategy
|
|
219
|
+
ResourceModule.forRoot({
|
|
220
|
+
strategies: [
|
|
221
|
+
new FileResourceStrategy({
|
|
222
|
+
basePaths: [
|
|
223
|
+
join(__dirname, 'assets'), // App-level overrides
|
|
224
|
+
join(__dirname, '..', 'node_modules', '@breadstone', 'archipel-platform-mailing', 'assets'),
|
|
225
|
+
],
|
|
226
|
+
}),
|
|
227
|
+
],
|
|
228
|
+
});
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Templates like `AuthVerify.html`, `AuthRegister.html`, and `AuthForgotPassword.html` are loaded by key:
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
const template = await this._resourceManager.loadAsStringAsync('AuthVerify.html');
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
By listing your own `assets/` first, you can override built-in templates without modifying the library.
|
|
238
|
+
|
|
239
|
+
### Swagger UI Assets
|
|
240
|
+
|
|
241
|
+
`platform-openapi` loads its theme files (`swagger.css`, `swagger.js`, `swagger.html`) through the same mechanism:
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
const css = this._resourceManager.loadAsString('swagger.css');
|
|
245
|
+
const js = this._resourceManager.loadAsString('swagger.js');
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Application Index Page
|
|
249
|
+
|
|
250
|
+
`platform-core`'s `HostService` loads `index.html` as a template for the root route:
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
const html = await this._resourceManager.loadAsStringAsync('index.html');
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## Caching
|
|
259
|
+
|
|
260
|
+
Pass a cache instance from `platform-caching` to avoid repeated file reads or blob downloads:
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
import { LayeredCacheFactory } from '@breadstone/archipel-platform-caching';
|
|
264
|
+
|
|
265
|
+
ResourceModule.forRoot({
|
|
266
|
+
strategies: [
|
|
267
|
+
new FileResourceStrategy({ basePaths: [...] }),
|
|
268
|
+
],
|
|
269
|
+
cache: LayeredCacheFactory.create<string, IResourceResult>({
|
|
270
|
+
maxSize: 100,
|
|
271
|
+
}),
|
|
272
|
+
cacheTtl: 1800, // 30 minutes
|
|
273
|
+
})
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
- On load: cache is checked first. On hit, the resource is returned immediately.
|
|
277
|
+
- On miss: strategies are tried, and the result is cached for future requests.
|
|
278
|
+
- Use `invalidate(key)` or `invalidateAsync(key)` to clear a cached resource (e.g. after an upload replaces a template).
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## Debugging
|
|
283
|
+
|
|
284
|
+
Enable `debug: true` in the module config to have `ResourceManager` call `whatDoIHave()` at startup. This logs every resource available through every strategy — helpful when a resource isn't found and you need to verify the base paths.
|
|
285
|
+
|
|
286
|
+
```
|
|
287
|
+
[ResourceManager] ResourceManager initialized with 2 strategies: file, embedded
|
|
288
|
+
[FileResourceStrategy] Available resources:
|
|
289
|
+
- index.html (text/html, 2.4 KB) → /app/dist/assets/index.html
|
|
290
|
+
- swagger.css (text/css, 1.1 KB) → /app/dist/assets/swagger.css
|
|
291
|
+
...
|
|
292
|
+
[EmbeddedResourceStrategy] Available resources:
|
|
293
|
+
- fallback.html (text/html, 52 B) → embedded
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## Writing a Custom Strategy
|
|
299
|
+
|
|
300
|
+
Implement `IResourceStrategy` to support a new source (e.g. a database, S3 directly, or a remote API):
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
import { IResourceStrategy, IResourceResult } from '@breadstone/archipel-platform-core';
|
|
304
|
+
|
|
305
|
+
export class DatabaseResourceStrategy implements IResourceStrategy {
|
|
306
|
+
public get name(): string {
|
|
307
|
+
return 'database';
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
public load(key: string): IResourceResult | undefined {
|
|
311
|
+
// Sync lookup — return undefined if not found
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
public async loadAsync(key: string): Promise<IResourceResult | undefined> {
|
|
315
|
+
// Async lookup — return undefined if not found
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
public exists(key: string): boolean {
|
|
319
|
+
// ...
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
public async existsAsync(key: string): Promise<boolean> {
|
|
323
|
+
// ...
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
public whatDoIHave(): void {
|
|
327
|
+
// Log available resources for debugging
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
Then add it to the strategies array:
|
|
333
|
+
|
|
334
|
+
```typescript
|
|
335
|
+
ResourceModule.forRoot({
|
|
336
|
+
strategies: [
|
|
337
|
+
new FileResourceStrategy({ basePaths: [...] }),
|
|
338
|
+
new DatabaseResourceStrategy(prisma),
|
|
339
|
+
],
|
|
340
|
+
})
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
## Checklist
|
|
346
|
+
|
|
347
|
+
- [ ] `ResourceModule.forRoot()` registered in the root module
|
|
348
|
+
- [ ] Base paths include library asset directories for all used packages
|
|
349
|
+
- [ ] App-level overrides listed **before** library paths in `basePaths`
|
|
350
|
+
- [ ] Async methods used when `BlobResourceStrategy` is in the chain
|
|
351
|
+
- [ ] Cache configured for production (avoids repeated disk/network reads)
|
|
352
|
+
- [ ] `debug: true` used during development to verify resource availability
|