@breadstone/archipel-mcp 0.0.10 → 0.0.11

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.
Files changed (56) hide show
  1. package/data/guides/ai-text-generation.md +361 -0
  2. package/data/guides/analytics-and-error-tracking.md +189 -0
  3. package/data/guides/authentication-and-authorization.md +657 -0
  4. package/data/guides/blob-storage.md +242 -0
  5. package/data/guides/caching.md +255 -0
  6. package/data/guides/cryptography-and-otp.md +240 -0
  7. package/data/guides/document-generation.md +174 -0
  8. package/data/guides/email-delivery.md +196 -0
  9. package/data/guides/esigning-integration.md +231 -0
  10. package/data/guides/getting-started.md +351 -0
  11. package/data/guides/implementing-ports.md +317 -0
  12. package/data/guides/index.md +61 -0
  13. package/data/guides/mcp-server.md +222 -0
  14. package/data/guides/openapi-and-feature-discovery.md +266 -0
  15. package/data/guides/payments-and-feature-gating.md +244 -0
  16. package/data/guides/resource-management.md +352 -0
  17. package/data/guides/telemetry-and-observability.md +190 -0
  18. package/data/guides/testing.md +319 -0
  19. package/data/guides/tsdoc-guidelines.md +45 -0
  20. package/data/packages/platform-openapi/api/Class.SwaggerFeatureDiscovery.md +10 -6
  21. package/package.json +1 -1
  22. package/src/GuidesLoader.d.ts +13 -0
  23. package/src/GuidesLoader.js +81 -0
  24. package/src/main.js +36 -198
  25. package/src/models/IGuideDoc.d.ts +15 -0
  26. package/src/models/IGuideDoc.js +3 -0
  27. package/src/tools/registerGetConfigPatternTool.d.ts +5 -0
  28. package/src/tools/registerGetConfigPatternTool.js +15 -0
  29. package/src/tools/registerGetDtoPatternTool.d.ts +5 -0
  30. package/src/tools/registerGetDtoPatternTool.js +15 -0
  31. package/src/tools/registerGetErrorHandlingPatternTool.d.ts +5 -0
  32. package/src/tools/registerGetErrorHandlingPatternTool.js +15 -0
  33. package/src/tools/registerGetGuardPatternTool.d.ts +5 -0
  34. package/src/tools/registerGetGuardPatternTool.js +15 -0
  35. package/src/tools/registerGetGuideTool.d.ts +6 -0
  36. package/src/tools/registerGetGuideTool.js +32 -0
  37. package/src/tools/registerGetMappingPatternTool.d.ts +5 -0
  38. package/src/tools/registerGetMappingPatternTool.js +34 -0
  39. package/src/tools/registerGetModulePatternTool.d.ts +5 -0
  40. package/src/tools/registerGetModulePatternTool.js +29 -0
  41. package/src/tools/registerGetPackageDocTool.d.ts +6 -0
  42. package/src/tools/registerGetPackageDocTool.js +43 -0
  43. package/src/tools/registerGetQueryPatternTool.d.ts +5 -0
  44. package/src/tools/registerGetQueryPatternTool.js +29 -0
  45. package/src/tools/registerGetRepositoryPatternTool.d.ts +5 -0
  46. package/src/tools/registerGetRepositoryPatternTool.js +29 -0
  47. package/src/tools/registerGetTestingPatternTool.d.ts +5 -0
  48. package/src/tools/registerGetTestingPatternTool.js +15 -0
  49. package/src/tools/registerListGuidesTool.d.ts +6 -0
  50. package/src/tools/registerListGuidesTool.js +22 -0
  51. package/src/tools/registerListPackagesTool.d.ts +6 -0
  52. package/src/tools/registerListPackagesTool.js +20 -0
  53. package/src/tools/registerSearchDocsTool.d.ts +6 -0
  54. package/src/tools/registerSearchDocsTool.js +35 -0
  55. package/src/tools/registerSearchGuidesTool.d.ts +6 -0
  56. package/src/tools/registerSearchGuidesTool.js +28 -0
@@ -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
@@ -0,0 +1,190 @@
1
+ ---
2
+ title: Telemetry & Observability
3
+ description: Add OpenTelemetry-based metrics, tracing, and structured logging to your NestJS application.
4
+ order: 10
5
+ ---
6
+
7
+ # Telemetry & Observability
8
+
9
+ This guide covers adding observability to your application with `platform-telemetry`: setup, metrics recording, distributed tracing, structured logging, and graceful shutdown.
10
+
11
+ ---
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ yarn add @breadstone/archipel-platform-telemetry
17
+ ```
18
+
19
+ ---
20
+
21
+ ## Module Registration
22
+
23
+ ```typescript
24
+ import { Module } from '@nestjs/common';
25
+ import { TelemetryModule } from '@breadstone/archipel-platform-telemetry';
26
+
27
+ @Module({
28
+ imports: [
29
+ TelemetryModule.register({
30
+ enabled: true,
31
+ interceptor: {
32
+ operation: true, // auto-instrument controller operations
33
+ },
34
+ }),
35
+ ],
36
+ })
37
+ export class AppModule {}
38
+ ```
39
+
40
+ When `enabled` is `false`, the module provides a no-op facade (`NoopTelemetryFacade`) that silently discards all telemetry calls. This means you can instrument your code once and toggle telemetry via configuration.
41
+
42
+ ---
43
+
44
+ ## Configuration
45
+
46
+ ```env
47
+ TELEMETRY_ENABLED=true
48
+ TELEMETRY_SERVICE_NAME=my-api
49
+ TELEMETRY_EXPORTER_ENDPOINT=https://otel-collector.example.com:4318
50
+ ```
51
+
52
+ ---
53
+
54
+ ## Recording Metrics
55
+
56
+ Use `TelemetryFacade` to record application metrics:
57
+
58
+ ```typescript
59
+ import { Injectable } from '@nestjs/common';
60
+ import { TelemetryFacade } from '@breadstone/archipel-platform-telemetry';
61
+
62
+ @Injectable()
63
+ export class OrderService {
64
+ private readonly _telemetry: TelemetryFacade;
65
+
66
+ constructor(telemetry: TelemetryFacade) {
67
+ this._telemetry = telemetry;
68
+ }
69
+
70
+ public async createOrder(userId: string): Promise<void> {
71
+ const start = Date.now();
72
+
73
+ try {
74
+ // ... business logic
75
+
76
+ this._telemetry.recordCounter('domain_command_total', 1, {
77
+ command: 'create_order',
78
+ status: 'success',
79
+ });
80
+ } catch (error) {
81
+ this._telemetry.recordCounter('domain_command_total', 1, {
82
+ command: 'create_order',
83
+ status: 'failure',
84
+ });
85
+ throw error;
86
+ } finally {
87
+ this._telemetry.recordHistogram('order_processing_duration_ms', Date.now() - start, {
88
+ command: 'create_order',
89
+ });
90
+ }
91
+ }
92
+ }
93
+ ```
94
+
95
+ ### Recommended Baseline Metrics
96
+
97
+ | Metric | Type | Labels |
98
+ | ----------------------------------------- | --------- | ----------------------------- |
99
+ | `db_query_duration_ms` | Histogram | `model`, `operation` |
100
+ | `cache_hits_total` / `cache_misses_total` | Counter | `cache` |
101
+ | `domain_command_total` | Counter | `command`, `status` |
102
+ | `external_http_request_duration_ms` | Histogram | `service`, `action`, `status` |
103
+
104
+ ---
105
+
106
+ ## Structured Logging
107
+
108
+ `TelemetryLoggerService` provides structured logging with automatic correlation:
109
+
110
+ ```typescript
111
+ import { Injectable } from '@nestjs/common';
112
+ import { TelemetryLoggerService } from '@breadstone/archipel-platform-telemetry';
113
+
114
+ @Injectable()
115
+ export class PaymentService {
116
+ private readonly _logger: TelemetryLoggerService;
117
+
118
+ constructor(logger: TelemetryLoggerService) {
119
+ this._logger = logger;
120
+ }
121
+
122
+ public async processPayment(orderId: string): Promise<void> {
123
+ this._logger.log('Processing payment', { orderId });
124
+
125
+ try {
126
+ // ... payment logic
127
+ this._logger.log('Payment processed successfully', { orderId });
128
+ } catch (error) {
129
+ this._logger.error('Payment processing failed', { orderId, error });
130
+ throw error;
131
+ }
132
+ }
133
+ }
134
+ ```
135
+
136
+ All log entries automatically include `requestId` for request-level correlation.
137
+
138
+ ---
139
+
140
+ ## Automatic Operation Instrumentation
141
+
142
+ When you set `interceptor.operation: true`, every controller method is automatically traced. The interceptor records:
143
+
144
+ - Operation name (controller + method)
145
+ - Duration
146
+ - HTTP status code
147
+ - Error details on failure
148
+
149
+ This gives you endpoint-level performance visibility without manual instrumentation.
150
+
151
+ ---
152
+
153
+ ## Rule Engine
154
+
155
+ `TelemetryRuleEngine` lets you configure sampling and filtering rules:
156
+
157
+ ```typescript
158
+ TelemetryModule.register({
159
+ enabled: true,
160
+ interceptor: { operation: true },
161
+ rules: [
162
+ { pattern: '/health', action: 'drop' }, // Don't trace health checks
163
+ { pattern: '/api/v1/**', action: 'sample', rate: 0.1 }, // Sample 10% of API traffic
164
+ ],
165
+ }),
166
+ ```
167
+
168
+ This prevents noisy endpoints (health checks, readiness probes) from overwhelming your telemetry backend.
169
+
170
+ ---
171
+
172
+ ## Graceful Shutdown
173
+
174
+ The module registers a shutdown hook that flushes all pending telemetry data before the process exits. This ensures you don't lose metrics or traces during deployments or scaling events.
175
+
176
+ ---
177
+
178
+ ## Development Tips
179
+
180
+ - Set `TELEMETRY_ENABLED=false` in local development to avoid telemetry overhead.
181
+ - Use the `log` exporter during development to see traces in the console.
182
+ - Add metrics incrementally — start with the baseline metrics and expand as you identify bottlenecks.
183
+ - Include `requestId` in all log entries. The telemetry module handles this automatically for HTTP requests.
184
+
185
+ ---
186
+
187
+ ## Next Steps
188
+
189
+ - Browse the [platform-telemetry API reference](/packages/platform-telemetry/api/) for complete method signatures
190
+ - See the [Testing](/guides/testing) guide for verifying telemetry calls in unit tests