forge-sql-orm 2.1.16 → 2.1.17
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/README.md +190 -1
- package/dist/async/PrintQueryConsumer.d.ts +98 -0
- package/dist/async/PrintQueryConsumer.d.ts.map +1 -0
- package/dist/async/PrintQueryConsumer.js +89 -0
- package/dist/async/PrintQueryConsumer.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/utils/forgeDriverProxy.js +7 -5
- package/dist/utils/forgeDriverProxy.js.map +1 -1
- package/dist/utils/metadataContextUtils.d.ts +44 -4
- package/dist/utils/metadataContextUtils.d.ts.map +1 -1
- package/dist/utils/metadataContextUtils.js +102 -19
- package/dist/utils/metadataContextUtils.js.map +1 -1
- package/dist/utils/sqlUtils.d.ts +2 -1
- package/dist/utils/sqlUtils.d.ts.map +1 -1
- package/dist/utils/sqlUtils.js +47 -25
- package/dist/utils/sqlUtils.js.map +1 -1
- package/package.json +13 -12
- package/src/async/PrintQueryConsumer.ts +114 -0
- package/src/index.ts +1 -0
- package/src/utils/forgeDriverProxy.ts +9 -9
- package/src/utils/metadataContextUtils.ts +120 -26
- package/src/utils/sqlUtils.ts +74 -35
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
- ✅ **Custom Drizzle Driver** for direct integration with @forge/sql
|
|
24
24
|
- ✅ **Local Cache System (Level 1)** for in-memory query optimization within single resolver invocation scope
|
|
25
25
|
- ✅ **Global Cache System (Level 2)** with cross-invocation caching, automatic cache invalidation and context-aware operations (using [@forge/kvs](https://developer.atlassian.com/platform/forge/storage-reference/storage-api-custom-entities/) )
|
|
26
|
-
- ✅ **Performance Monitoring**: Query execution metrics and analysis capabilities with automatic error analysis for timeout and OOM errors,
|
|
26
|
+
- ✅ **Performance Monitoring**: Query execution metrics and analysis capabilities with automatic error analysis for timeout and OOM errors, scheduled slow query monitoring with execution plans, and async query degradation analysis for non-blocking performance monitoring
|
|
27
27
|
- ✅ **Type-Safe Query Building**: Write SQL queries with full TypeScript support
|
|
28
28
|
- ✅ **Supports complex SQL queries** with joins and filtering using Drizzle ORM
|
|
29
29
|
- ✅ **Advanced Query Methods**: `selectFrom()`, `selectDistinctFrom()`, `selectCacheableFrom()`, `selectDistinctCacheableFrom()` for all-column queries with field aliasing
|
|
@@ -2806,6 +2806,7 @@ All options are **optional**. If not specified, default values are used. You can
|
|
|
2806
2806
|
| `topQueries` | `number` | `1` | Number of top slowest queries to analyze when `mode` is `'TopSlowest'` |
|
|
2807
2807
|
| `showSlowestPlans` | `boolean` | `true` | Whether to show execution plans for slowest queries in TopSlowest mode. If `false`, only SQL and execution time are printed |
|
|
2808
2808
|
| `normalizeQuery` | `boolean` | `true` | Whether to normalize SQL queries by replacing parameter values with `?` placeholders. Set to `false` to disable normalization if it causes issues with complex queries |
|
|
2809
|
+
| `asyncQueueName` | `string` | `""` | Queue name for async processing. If provided, query analysis will be queued for background processing instead of running synchronously. Requires consumer configuration in `manifest.yml` |
|
|
2809
2810
|
|
|
2810
2811
|
**Examples:**
|
|
2811
2812
|
|
|
@@ -2891,6 +2892,194 @@ resolver.define("fetch", async (req: Request) => {
|
|
|
2891
2892
|
|
|
2892
2893
|
> **💡 Tip**: When multiple resolvers are running concurrently, their query data may also appear in `printQueriesWithPlan()` analysis when using SummaryTable mode, as it queries the global `CLUSTER_STATEMENTS_SUMMARY` table.
|
|
2893
2894
|
|
|
2895
|
+
### Async Query Degradation Analysis
|
|
2896
|
+
|
|
2897
|
+
Forge-SQL-ORM supports asynchronous processing of query degradation analysis, allowing you to offload performance analysis to a background queue. This is particularly useful for production environments where you want to avoid blocking resolver responses while still capturing detailed performance metrics.
|
|
2898
|
+
|
|
2899
|
+
#### Key Features
|
|
2900
|
+
|
|
2901
|
+
- **Non-Blocking Analysis**: Query analysis runs asynchronously without blocking resolver responses
|
|
2902
|
+
- **Automatic Fallback**: Falls back to synchronous execution if async queue fails
|
|
2903
|
+
- **Log Correlation**: Job IDs help correlate resolver logs with consumer logs
|
|
2904
|
+
- **Queue-Based Processing**: Uses Forge's event queue system for reliable processing
|
|
2905
|
+
- **Configurable Timeout**: Customizable timeout for event queuing (default: 1200ms)
|
|
2906
|
+
|
|
2907
|
+
#### Basic Setup
|
|
2908
|
+
|
|
2909
|
+
**1. Configure consumer in `manifest.yml`:**
|
|
2910
|
+
|
|
2911
|
+
```yaml
|
|
2912
|
+
modules:
|
|
2913
|
+
consumer:
|
|
2914
|
+
- key: print-degradation-queries
|
|
2915
|
+
queue: degradationQueue
|
|
2916
|
+
function: handlerAsyncDegradation
|
|
2917
|
+
|
|
2918
|
+
function:
|
|
2919
|
+
- key: handlerAsyncDegradation
|
|
2920
|
+
handler: index.handlerAsyncDegradation
|
|
2921
|
+
```
|
|
2922
|
+
|
|
2923
|
+
**2. Create the handler function:**
|
|
2924
|
+
|
|
2925
|
+
```typescript
|
|
2926
|
+
import { AsyncEvent } from "@forge/events";
|
|
2927
|
+
import { printDegradationQueriesConsumer } from "forge-sql-orm";
|
|
2928
|
+
import { FORGE_SQL_ORM } from "./utils/forgeSqlOrmUtils";
|
|
2929
|
+
|
|
2930
|
+
export const handlerAsyncDegradation = (event: AsyncEvent) => {
|
|
2931
|
+
return printDegradationQueriesConsumer(FORGE_SQL_ORM, event);
|
|
2932
|
+
};
|
|
2933
|
+
```
|
|
2934
|
+
|
|
2935
|
+
**3. Enable async processing in resolver:**
|
|
2936
|
+
|
|
2937
|
+
```typescript
|
|
2938
|
+
resolver.define("fetch", async (req: Request) => {
|
|
2939
|
+
return await FORGE_SQL_ORM.executeWithMetadata(
|
|
2940
|
+
async () => {
|
|
2941
|
+
// ... your queries ...
|
|
2942
|
+
return await SQL_QUERY;
|
|
2943
|
+
},
|
|
2944
|
+
async (totalDbExecutionTime, totalResponseSize, printQueries) => {
|
|
2945
|
+
if (totalDbExecutionTime > 800) {
|
|
2946
|
+
await printQueries(); // Will queue for async processing
|
|
2947
|
+
}
|
|
2948
|
+
},
|
|
2949
|
+
{ asyncQueueName: "degradationQueue" }, // Enable async processing
|
|
2950
|
+
);
|
|
2951
|
+
});
|
|
2952
|
+
```
|
|
2953
|
+
|
|
2954
|
+
#### Configuration Options
|
|
2955
|
+
|
|
2956
|
+
The `asyncQueueName` option enables async processing:
|
|
2957
|
+
|
|
2958
|
+
| Option | Type | Default | Description |
|
|
2959
|
+
| ---------------- | -------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
2960
|
+
| `asyncQueueName` | `string` | `""` | Queue name for async processing. If provided, query analysis will be queued instead of running synchronously. If empty or not provided, runs synchronously |
|
|
2961
|
+
|
|
2962
|
+
#### How It Works
|
|
2963
|
+
|
|
2964
|
+
1. **Resolver Execution**: When `printQueriesWithPlan()` is called with `asyncQueueName` configured:
|
|
2965
|
+
- Creates an event payload with query statistics and metadata
|
|
2966
|
+
- Sends event to the specified queue with a timeout (default: 1200ms)
|
|
2967
|
+
- Logs a warning message with Job ID for correlation
|
|
2968
|
+
- Returns immediately without waiting for analysis
|
|
2969
|
+
|
|
2970
|
+
2. **Async Processing**: The consumer function (`handlerAsyncDegradation`):
|
|
2971
|
+
- Receives the event from the queue
|
|
2972
|
+
- Logs processing start with Job ID
|
|
2973
|
+
- Executes query analysis (TopSlowest or SummaryTable mode)
|
|
2974
|
+
- Prints execution plans and performance metrics
|
|
2975
|
+
|
|
2976
|
+
3. **Fallback Behavior**: If queue push fails or times out:
|
|
2977
|
+
- Falls back to synchronous execution automatically
|
|
2978
|
+
- Logs a warning message
|
|
2979
|
+
- Analysis still completes, just synchronously
|
|
2980
|
+
|
|
2981
|
+
#### Log Correlation
|
|
2982
|
+
|
|
2983
|
+
Both resolver and consumer logs include Job IDs to help you correlate related events:
|
|
2984
|
+
|
|
2985
|
+
**Resolver log (when event is queued):**
|
|
2986
|
+
|
|
2987
|
+
```
|
|
2988
|
+
WARN [Performance Analysis] Query degradation event queued for async processing | Job ID: abc-123 | Total DB time: 3531ms | Queries: 3 | Look for consumer log with jobId: abc-123
|
|
2989
|
+
```
|
|
2990
|
+
|
|
2991
|
+
**Consumer log (when event is processed):**
|
|
2992
|
+
|
|
2993
|
+
```
|
|
2994
|
+
WARN [Performance Analysis] Processing query degradation event | Job ID: abc-123 | Total DB time: 3531ms | Queries: 3 | Started: 2025-12-15T18:12:34.251Z
|
|
2995
|
+
WARN SQL: SELECT ... | Time: 3514 ms
|
|
2996
|
+
Plan:
|
|
2997
|
+
Projection_7 | task:root | ...
|
|
2998
|
+
```
|
|
2999
|
+
|
|
3000
|
+
**To find all related logs:**
|
|
3001
|
+
|
|
3002
|
+
- Search logs for: `"Job ID: abc-123"`
|
|
3003
|
+
- This will show both the queuing event and the processing event
|
|
3004
|
+
|
|
3005
|
+
#### Example: Complete Setup
|
|
3006
|
+
|
|
3007
|
+
**manifest.yml:**
|
|
3008
|
+
|
|
3009
|
+
```yaml
|
|
3010
|
+
modules:
|
|
3011
|
+
consumer:
|
|
3012
|
+
- key: print-degradation-queries
|
|
3013
|
+
queue: degradationQueue
|
|
3014
|
+
function: handlerAsyncDegradation
|
|
3015
|
+
|
|
3016
|
+
function:
|
|
3017
|
+
- key: handlerAsyncDegradation
|
|
3018
|
+
handler: index.handlerAsyncDegradation
|
|
3019
|
+
```
|
|
3020
|
+
|
|
3021
|
+
**index.ts:**
|
|
3022
|
+
|
|
3023
|
+
```typescript
|
|
3024
|
+
import { AsyncEvent } from "@forge/events";
|
|
3025
|
+
import { printDegradationQueriesConsumer } from "forge-sql-orm";
|
|
3026
|
+
import { FORGE_SQL_ORM } from "./utils/forgeSqlOrmUtils";
|
|
3027
|
+
|
|
3028
|
+
// Consumer handler
|
|
3029
|
+
export const handlerAsyncDegradation = (event: AsyncEvent) => {
|
|
3030
|
+
return printDegradationQueriesConsumer(FORGE_SQL_ORM, event);
|
|
3031
|
+
};
|
|
3032
|
+
|
|
3033
|
+
// Resolver with async analysis
|
|
3034
|
+
resolver.define("fetch", async (req: Request) => {
|
|
3035
|
+
return await FORGE_SQL_ORM.executeWithMetadata(
|
|
3036
|
+
async () => {
|
|
3037
|
+
const users = await FORGE_SQL_ORM.selectFrom(demoUsers);
|
|
3038
|
+
const orders = await FORGE_SQL_ORM.selectFrom(demoOrders);
|
|
3039
|
+
return { users, orders };
|
|
3040
|
+
},
|
|
3041
|
+
async (totalDbExecutionTime, totalResponseSize, printQueries) => {
|
|
3042
|
+
const threshold = 800; // ms baseline
|
|
3043
|
+
|
|
3044
|
+
if (totalDbExecutionTime > threshold) {
|
|
3045
|
+
console.warn(`[Performance Warning] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
|
|
3046
|
+
await printQueries(); // Queued for async processing
|
|
3047
|
+
}
|
|
3048
|
+
},
|
|
3049
|
+
{
|
|
3050
|
+
asyncQueueName: "degradationQueue", // Enable async processing
|
|
3051
|
+
mode: "TopSlowest",
|
|
3052
|
+
topQueries: 1,
|
|
3053
|
+
},
|
|
3054
|
+
);
|
|
3055
|
+
});
|
|
3056
|
+
```
|
|
3057
|
+
|
|
3058
|
+
#### Benefits
|
|
3059
|
+
|
|
3060
|
+
- **Non-Blocking**: Resolver responses are not delayed by query analysis
|
|
3061
|
+
- **Production Ready**: Suitable for production environments where performance is critical
|
|
3062
|
+
- **Reliable**: Automatic fallback ensures analysis always completes
|
|
3063
|
+
- **Traceable**: Job IDs enable easy log correlation
|
|
3064
|
+
- **Scalable**: Queue-based processing handles high load scenarios
|
|
3065
|
+
|
|
3066
|
+
#### When to Use Async Processing
|
|
3067
|
+
|
|
3068
|
+
**Use async processing when:**
|
|
3069
|
+
|
|
3070
|
+
- You're in a production environment
|
|
3071
|
+
- Resolver response time is critical
|
|
3072
|
+
- You want to avoid blocking user requests
|
|
3073
|
+
- You need detailed analysis but can process it later
|
|
3074
|
+
|
|
3075
|
+
**Use synchronous processing when:**
|
|
3076
|
+
|
|
3077
|
+
- You're in development/debugging
|
|
3078
|
+
- You need immediate analysis results
|
|
3079
|
+
- You want simpler setup (no queue configuration)
|
|
3080
|
+
|
|
3081
|
+
> **💡 Tip**: The async queue name must match the queue name configured in your `manifest.yml` consumer section. If the queue doesn't exist or the event fails to send, the system automatically falls back to synchronous execution.
|
|
3082
|
+
|
|
2894
3083
|
### Slow Query Monitoring
|
|
2895
3084
|
|
|
2896
3085
|
Forge-SQL-ORM provides a scheduler trigger (`slowQuerySchedulerTrigger`) that automatically monitors and analyzes slow queries on an hourly basis. This trigger queries TiDB's slow query log system table and provides detailed performance information including SQL query text, memory usage, execution time, and execution plans.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { AsyncEvent } from "@forge/events";
|
|
2
|
+
import { MetadataQueryOptions, Statistic } from "../utils/metadataContextUtils";
|
|
3
|
+
import { ForgeSqlOperation } from "../core/ForgeSQLQueryBuilder";
|
|
4
|
+
/**
|
|
5
|
+
* Event payload for async query degradation analysis.
|
|
6
|
+
* Contains query performance statistics and metadata for analysis.
|
|
7
|
+
*/
|
|
8
|
+
export type AsyncEventPrintQuery = {
|
|
9
|
+
/** Total database execution time across all queries in milliseconds */
|
|
10
|
+
totalDbExecutionTime: number;
|
|
11
|
+
/** Total response size across all queries in bytes */
|
|
12
|
+
totalResponseSize: number;
|
|
13
|
+
/** Timestamp when the query execution started */
|
|
14
|
+
beginTime: Date;
|
|
15
|
+
/** Query analysis options with all fields set to defaults if not provided */
|
|
16
|
+
options: Required<MetadataQueryOptions>;
|
|
17
|
+
/** Array of query statistics including SQL, parameters, and execution metadata */
|
|
18
|
+
statistics: Statistic[];
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Consumer function for processing async query degradation events.
|
|
22
|
+
*
|
|
23
|
+
* This function is called by the Forge event system when a query degradation event
|
|
24
|
+
* is received from the queue. It processes the event and prints query performance
|
|
25
|
+
* analysis including execution plans for slow queries.
|
|
26
|
+
*
|
|
27
|
+
* The function logs a warning message with job ID, total DB time, query count, and
|
|
28
|
+
* start time to help correlate with the original resolver/service logs.
|
|
29
|
+
*
|
|
30
|
+
* @param forgeSQLORM - The ForgeSQL operation instance for database access
|
|
31
|
+
* @param event - The async event containing query degradation data
|
|
32
|
+
* @returns Promise that resolves when query analysis is complete
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```yaml
|
|
36
|
+
* # manifest.yml - Configure consumer for async query degradation analysis
|
|
37
|
+
* modules:
|
|
38
|
+
* consumer:
|
|
39
|
+
* - key: print-degradation-queries
|
|
40
|
+
* queue: degradationQueue
|
|
41
|
+
* function: handlerAsyncDegradation
|
|
42
|
+
* function:
|
|
43
|
+
* - key: handlerAsyncDegradation
|
|
44
|
+
* handler: index.handlerAsyncDegradation
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* // index.ts - Handler function that processes async events
|
|
50
|
+
* import { AsyncEvent } from "@forge/events";
|
|
51
|
+
* import { printDegradationQueriesConsumer } from "forge-sql-orm";
|
|
52
|
+
* import { FORGE_SQL_ORM } from "./utils/forgeSqlOrmUtils";
|
|
53
|
+
*
|
|
54
|
+
* export const handlerAsyncDegradation = (event: AsyncEvent) => {
|
|
55
|
+
* return printDegradationQueriesConsumer(FORGE_SQL_ORM, event);
|
|
56
|
+
* };
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```typescript
|
|
61
|
+
* // Using async queue in resolver - Enable async processing
|
|
62
|
+
* resolver.define("fetch", async (req: Request) => {
|
|
63
|
+
* return await FORGE_SQL_ORM.executeWithMetadata(
|
|
64
|
+
* async () => {
|
|
65
|
+
* // ... your queries ...
|
|
66
|
+
* return await SQL_QUERY;
|
|
67
|
+
* },
|
|
68
|
+
* async (totalDbExecutionTime, totalResponseSize, printQueries) => {
|
|
69
|
+
* if (totalDbExecutionTime > 800) {
|
|
70
|
+
* await printQueries(); // Will queue for async processing
|
|
71
|
+
* }
|
|
72
|
+
* },
|
|
73
|
+
* { asyncQueueName: "degradationQueue" } // Enable async processing
|
|
74
|
+
* );
|
|
75
|
+
* });
|
|
76
|
+
* ```
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```typescript
|
|
80
|
+
* // Log correlation - How to find related logs
|
|
81
|
+
* //
|
|
82
|
+
* // 1. In resolver/service log, you'll see:
|
|
83
|
+
* // [Performance Analysis] Query degradation event queued for async processing | Job ID: abc-123 | ...
|
|
84
|
+
* //
|
|
85
|
+
* // 2. In consumer log (handlerAsyncDegradation), search for the same Job ID:
|
|
86
|
+
* // [Performance Analysis] Processing query degradation event | Job ID: abc-123 | ...
|
|
87
|
+
* //
|
|
88
|
+
* // 3. To find all related logs, search logs for: "Job ID: abc-123"
|
|
89
|
+
* // This will show both the queuing event and the processing event
|
|
90
|
+
* //
|
|
91
|
+
* // Example log flow:
|
|
92
|
+
* // WARN resolver: [Performance Analysis] Query degradation event queued... | Job ID: abc-123
|
|
93
|
+
* // WARN handlerAsyncDegradation: [Performance Analysis] Processing query degradation event | Job ID: abc-123
|
|
94
|
+
* // WARN handlerAsyncDegradation: SQL: SELECT ... | Time: 3514 ms
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
export declare function printDegradationQueriesConsumer(forgeSQLORM: ForgeSqlOperation, event: AsyncEvent): Promise<void>;
|
|
98
|
+
//# sourceMappingURL=PrintQueryConsumer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PrintQueryConsumer.d.ts","sourceRoot":"","sources":["../../src/async/PrintQueryConsumer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EACL,oBAAoB,EAEpB,SAAS,EACV,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAEjE;;;GAGG;AACH,MAAM,MAAM,oBAAoB,GAAG;IACjC,uEAAuE;IACvE,oBAAoB,EAAE,MAAM,CAAC;IAC7B,sDAAsD;IACtD,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iDAAiD;IACjD,SAAS,EAAE,IAAI,CAAC;IAChB,6EAA6E;IAC7E,OAAO,EAAE,QAAQ,CAAC,oBAAoB,CAAC,CAAC;IACxC,kFAAkF;IAClF,UAAU,EAAE,SAAS,EAAE,CAAC;CACzB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4EG;AACH,wBAAsB,+BAA+B,CACnD,WAAW,EAAE,iBAAiB,EAC9B,KAAK,EAAE,UAAU,GAChB,OAAO,CAAC,IAAI,CAAC,CAQf"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.printDegradationQueriesConsumer = printDegradationQueriesConsumer;
|
|
4
|
+
const metadataContextUtils_1 = require("../utils/metadataContextUtils");
|
|
5
|
+
/**
|
|
6
|
+
* Consumer function for processing async query degradation events.
|
|
7
|
+
*
|
|
8
|
+
* This function is called by the Forge event system when a query degradation event
|
|
9
|
+
* is received from the queue. It processes the event and prints query performance
|
|
10
|
+
* analysis including execution plans for slow queries.
|
|
11
|
+
*
|
|
12
|
+
* The function logs a warning message with job ID, total DB time, query count, and
|
|
13
|
+
* start time to help correlate with the original resolver/service logs.
|
|
14
|
+
*
|
|
15
|
+
* @param forgeSQLORM - The ForgeSQL operation instance for database access
|
|
16
|
+
* @param event - The async event containing query degradation data
|
|
17
|
+
* @returns Promise that resolves when query analysis is complete
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```yaml
|
|
21
|
+
* # manifest.yml - Configure consumer for async query degradation analysis
|
|
22
|
+
* modules:
|
|
23
|
+
* consumer:
|
|
24
|
+
* - key: print-degradation-queries
|
|
25
|
+
* queue: degradationQueue
|
|
26
|
+
* function: handlerAsyncDegradation
|
|
27
|
+
* function:
|
|
28
|
+
* - key: handlerAsyncDegradation
|
|
29
|
+
* handler: index.handlerAsyncDegradation
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```typescript
|
|
34
|
+
* // index.ts - Handler function that processes async events
|
|
35
|
+
* import { AsyncEvent } from "@forge/events";
|
|
36
|
+
* import { printDegradationQueriesConsumer } from "forge-sql-orm";
|
|
37
|
+
* import { FORGE_SQL_ORM } from "./utils/forgeSqlOrmUtils";
|
|
38
|
+
*
|
|
39
|
+
* export const handlerAsyncDegradation = (event: AsyncEvent) => {
|
|
40
|
+
* return printDegradationQueriesConsumer(FORGE_SQL_ORM, event);
|
|
41
|
+
* };
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```typescript
|
|
46
|
+
* // Using async queue in resolver - Enable async processing
|
|
47
|
+
* resolver.define("fetch", async (req: Request) => {
|
|
48
|
+
* return await FORGE_SQL_ORM.executeWithMetadata(
|
|
49
|
+
* async () => {
|
|
50
|
+
* // ... your queries ...
|
|
51
|
+
* return await SQL_QUERY;
|
|
52
|
+
* },
|
|
53
|
+
* async (totalDbExecutionTime, totalResponseSize, printQueries) => {
|
|
54
|
+
* if (totalDbExecutionTime > 800) {
|
|
55
|
+
* await printQueries(); // Will queue for async processing
|
|
56
|
+
* }
|
|
57
|
+
* },
|
|
58
|
+
* { asyncQueueName: "degradationQueue" } // Enable async processing
|
|
59
|
+
* );
|
|
60
|
+
* });
|
|
61
|
+
* ```
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```typescript
|
|
65
|
+
* // Log correlation - How to find related logs
|
|
66
|
+
* //
|
|
67
|
+
* // 1. In resolver/service log, you'll see:
|
|
68
|
+
* // [Performance Analysis] Query degradation event queued for async processing | Job ID: abc-123 | ...
|
|
69
|
+
* //
|
|
70
|
+
* // 2. In consumer log (handlerAsyncDegradation), search for the same Job ID:
|
|
71
|
+
* // [Performance Analysis] Processing query degradation event | Job ID: abc-123 | ...
|
|
72
|
+
* //
|
|
73
|
+
* // 3. To find all related logs, search logs for: "Job ID: abc-123"
|
|
74
|
+
* // This will show both the queuing event and the processing event
|
|
75
|
+
* //
|
|
76
|
+
* // Example log flow:
|
|
77
|
+
* // WARN resolver: [Performance Analysis] Query degradation event queued... | Job ID: abc-123
|
|
78
|
+
* // WARN handlerAsyncDegradation: [Performance Analysis] Processing query degradation event | Job ID: abc-123
|
|
79
|
+
* // WARN handlerAsyncDegradation: SQL: SELECT ... | Time: 3514 ms
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
async function printDegradationQueriesConsumer(forgeSQLORM, event) {
|
|
83
|
+
const body = event.body;
|
|
84
|
+
body.beginTime = new Date(body.beginTime);
|
|
85
|
+
// eslint-disable-next-line no-console
|
|
86
|
+
console.warn(`[Performance Analysis] Processing query degradation event | Job ID: ${event.jobId} | Total DB time: ${body.totalDbExecutionTime}ms | Queries: ${body.statistics.length} | Started: ${body.beginTime.toISOString()}`);
|
|
87
|
+
await (0, metadataContextUtils_1.printDegradationQueries)(forgeSQLORM, body);
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=PrintQueryConsumer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PrintQueryConsumer.js","sourceRoot":"","sources":["../../src/async/PrintQueryConsumer.ts"],"names":[],"mappings":";;AAsGA,0EAWC;AAhHD,wEAIuC;AAoBvC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4EG;AACI,KAAK,UAAU,+BAA+B,CACnD,WAA8B,EAC9B,KAAiB;IAEjB,MAAM,IAAI,GAAyB,KAAK,CAAC,IAA4B,CAAC;IACtE,IAAI,CAAC,SAAS,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC1C,sCAAsC;IACtC,OAAO,CAAC,IAAI,CACV,uEAAuE,KAAK,CAAC,KAAK,qBAAqB,IAAI,CAAC,oBAAoB,iBAAiB,IAAI,CAAC,UAAU,CAAC,MAAM,eAAe,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE,EAAE,CACrN,CAAC;IACF,MAAM,IAAA,8CAAuB,EAAC,WAAW,EAAE,IAAI,CAAC,CAAC;AACnD,CAAC"}
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAE7C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,iCAAiC,CAAC;AAChD,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,eAAe,CAAC;AAC9B,cAAc,4CAA4C,CAAC;AAC3D,cAAc,qBAAqB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAE7C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,iCAAiC,CAAC;AAChD,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,eAAe,CAAC;AAC9B,cAAc,4CAA4C,CAAC;AAC3D,cAAc,qBAAqB,CAAC;AACpC,cAAc,4BAA4B,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -28,4 +28,5 @@ __exportStar(require("./utils/forgeDriver"), exports);
|
|
|
28
28
|
__exportStar(require("./webtriggers"), exports);
|
|
29
29
|
__exportStar(require("./lib/drizzle/extensions/additionalActions"), exports);
|
|
30
30
|
__exportStar(require("./core/SystemTables"), exports);
|
|
31
|
+
__exportStar(require("./async/PrintQueryConsumer"), exports);
|
|
31
32
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA,kDAA6C;AAApC,uHAAA,OAAO,OAAA;AAEhB,8DAA4C;AAC5C,gEAA8C;AAC9C,kEAAgD;AAChD,mDAAiC;AACjC,sDAAoC;AACpC,gDAA8B;AAC9B,6EAA2D;AAC3D,sDAAoC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA,kDAA6C;AAApC,uHAAA,OAAO,OAAA;AAEhB,8DAA4C;AAC5C,gEAA8C;AAC9C,kEAAgD;AAChD,mDAAiC;AACjC,sDAAoC;AACpC,gDAA8B;AAC9B,6EAA2D;AAC3D,sDAAoC;AACpC,6DAA2C"}
|
|
@@ -40,6 +40,11 @@ function createForgeDriverProxy(forgeSqlOperation, options, logRawSqlQuery) {
|
|
|
40
40
|
const isTimeoutError = error.code === QUERY_ERROR_CODES.TIMEOUT;
|
|
41
41
|
const isOutOfMemoryError = error?.context?.debug?.errno === QUERY_ERROR_CODES.OUT_OF_MEMORY_ERRNO;
|
|
42
42
|
if (isTimeoutError || isOutOfMemoryError) {
|
|
43
|
+
// Wait for CLUSTER_STATEMENTS_SUMMARY to be populated with our failed query data
|
|
44
|
+
await new Promise((resolve) => setTimeout(resolve, STATEMENTS_SUMMARY_DELAY_MS));
|
|
45
|
+
const queryEndTime = Date.now();
|
|
46
|
+
const queryDuration = queryEndTime - queryStartTime;
|
|
47
|
+
let errorType = "TIMEOUT";
|
|
43
48
|
if (isTimeoutError) {
|
|
44
49
|
// eslint-disable-next-line no-console
|
|
45
50
|
console.error(` TIMEOUT detected - Query exceeded time limit`);
|
|
@@ -47,13 +52,10 @@ function createForgeDriverProxy(forgeSqlOperation, options, logRawSqlQuery) {
|
|
|
47
52
|
else {
|
|
48
53
|
// eslint-disable-next-line no-console
|
|
49
54
|
console.error(`OUT OF MEMORY detected - Query exceeded memory limit`);
|
|
55
|
+
errorType = "OOM";
|
|
50
56
|
}
|
|
51
|
-
// Wait for CLUSTER_STATEMENTS_SUMMARY to be populated with our failed query data
|
|
52
|
-
await new Promise((resolve) => setTimeout(resolve, STATEMENTS_SUMMARY_DELAY_MS));
|
|
53
|
-
const queryEndTime = Date.now();
|
|
54
|
-
const queryDuration = queryEndTime - queryStartTime;
|
|
55
57
|
// Analyze the failed query using CLUSTER_STATEMENTS_SUMMARY
|
|
56
|
-
await (0, sqlUtils_1.
|
|
58
|
+
await (0, sqlUtils_1.handleErrorsWithPlan)(forgeSqlOperation, queryDuration, errorType);
|
|
57
59
|
}
|
|
58
60
|
// Log SQL error details if requested
|
|
59
61
|
if (logRawSqlQuery) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"forgeDriverProxy.js","sourceRoot":"","sources":["../../src/utils/forgeDriverProxy.ts"],"names":[],"mappings":";;AAyBA,wDA8DC;AAvFD,+CAA4C;AAC5C,yCAAsD;AAEtD,yCAAkD;AAElD;;GAEG;AACH,MAAM,iBAAiB,GAAG;IACxB,OAAO,EAAE,mBAAmB;IAC5B,mBAAmB,EAAE,IAAI;CACjB,CAAC;AAEX;;GAEG;AACH,MAAM,2BAA2B,GAAG,GAAG,CAAC;AAExC;;;;;;GAMG;AACH,SAAgB,sBAAsB,CACpC,iBAAoC,EACpC,OAAkB,EAClB,cAAwB;IAExB,OAAO,KAAK,EACV,KAAa,EACb,MAAa,EACb,MAAyB,EAKxB,EAAE;QACH,kCAAkC;QAClC,MAAM,aAAa,GAAG,IAAA,yBAAc,EAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QAErD,IAAI,OAAO,IAAI,cAAc,IAAI,aAAa,KAAK,KAAK,EAAE,CAAC;YACzD,sCAAsC;YACtC,OAAO,CAAC,KAAK,CAAC,uBAAuB,aAAa,EAAE,CAAC,CAAC;QACxD,CAAC;QAED,MAAM,cAAc,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAElC,IAAI,CAAC;YACH,wCAAwC;YACxC,OAAO,MAAM,IAAA,yBAAW,EAAC,aAAa,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;QAC1D,CAAC;QAAC,OAAO,KAAU,EAAE,CAAC;YACpB,4EAA4E;YAC5E,MAAM,cAAc,GAAG,KAAK,CAAC,IAAI,KAAK,iBAAiB,CAAC,OAAO,CAAC;YAChE,MAAM,kBAAkB,GACtB,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,KAAK,iBAAiB,CAAC,mBAAmB,CAAC;YAEzE,IAAI,cAAc,IAAI,kBAAkB,EAAE,CAAC;gBACzC,IAAI,
|
|
1
|
+
{"version":3,"file":"forgeDriverProxy.js","sourceRoot":"","sources":["../../src/utils/forgeDriverProxy.ts"],"names":[],"mappings":";;AAyBA,wDA8DC;AAvFD,+CAA4C;AAC5C,yCAAsD;AAEtD,yCAAkD;AAElD;;GAEG;AACH,MAAM,iBAAiB,GAAG;IACxB,OAAO,EAAE,mBAAmB;IAC5B,mBAAmB,EAAE,IAAI;CACjB,CAAC;AAEX;;GAEG;AACH,MAAM,2BAA2B,GAAG,GAAG,CAAC;AAExC;;;;;;GAMG;AACH,SAAgB,sBAAsB,CACpC,iBAAoC,EACpC,OAAkB,EAClB,cAAwB;IAExB,OAAO,KAAK,EACV,KAAa,EACb,MAAa,EACb,MAAyB,EAKxB,EAAE;QACH,kCAAkC;QAClC,MAAM,aAAa,GAAG,IAAA,yBAAc,EAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QAErD,IAAI,OAAO,IAAI,cAAc,IAAI,aAAa,KAAK,KAAK,EAAE,CAAC;YACzD,sCAAsC;YACtC,OAAO,CAAC,KAAK,CAAC,uBAAuB,aAAa,EAAE,CAAC,CAAC;QACxD,CAAC;QAED,MAAM,cAAc,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAElC,IAAI,CAAC;YACH,wCAAwC;YACxC,OAAO,MAAM,IAAA,yBAAW,EAAC,aAAa,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;QAC1D,CAAC;QAAC,OAAO,KAAU,EAAE,CAAC;YACpB,4EAA4E;YAC5E,MAAM,cAAc,GAAG,KAAK,CAAC,IAAI,KAAK,iBAAiB,CAAC,OAAO,CAAC;YAChE,MAAM,kBAAkB,GACtB,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,KAAK,iBAAiB,CAAC,mBAAmB,CAAC;YAEzE,IAAI,cAAc,IAAI,kBAAkB,EAAE,CAAC;gBACzC,iFAAiF;gBACjF,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,2BAA2B,CAAC,CAAC,CAAC;gBAEjF,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;gBAChC,MAAM,aAAa,GAAG,YAAY,GAAG,cAAc,CAAC;gBACpD,IAAI,SAAS,GAAsB,SAAS,CAAC;gBAC7C,IAAI,cAAc,EAAE,CAAC;oBACnB,sCAAsC;oBACtC,OAAO,CAAC,KAAK,CAAC,+CAA+C,CAAC,CAAC;gBACjE,CAAC;qBAAM,CAAC;oBACN,sCAAsC;oBACtC,OAAO,CAAC,KAAK,CAAC,sDAAsD,CAAC,CAAC;oBACtE,SAAS,GAAG,KAAK,CAAC;gBACpB,CAAC;gBACD,4DAA4D;gBAC5D,MAAM,IAAA,+BAAoB,EAAC,iBAAiB,EAAE,aAAa,EAAE,SAAS,CAAC,CAAC;YAC1E,CAAC;YAED,qCAAqC;YACrC,IAAI,cAAc,EAAE,CAAC;gBACnB,sCAAsC;gBACtC,OAAO,CAAC,KAAK,CAAC,oBAAoB,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;YACtE,CAAC;YAED,8BAA8B;YAC9B,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
2
|
import { ForgeSQLMetadata } from "./forgeDriver";
|
|
3
3
|
import { ForgeSqlOperation } from "../core/ForgeSQLQueryBuilder";
|
|
4
|
-
|
|
4
|
+
import { AsyncEventPrintQuery } from "../async/PrintQueryConsumer";
|
|
5
|
+
export type Statistic = {
|
|
5
6
|
query: string;
|
|
6
7
|
params: unknown[];
|
|
7
8
|
metadata: ForgeSQLMetadata;
|
|
@@ -13,6 +14,7 @@ export type MetadataQueryOptions = {
|
|
|
13
14
|
topQueries?: number;
|
|
14
15
|
showSlowestPlans?: boolean;
|
|
15
16
|
normalizeQuery?: boolean;
|
|
17
|
+
asyncQueueName?: string;
|
|
16
18
|
};
|
|
17
19
|
export type MetadataQueryContext = {
|
|
18
20
|
totalDbExecutionTime: number;
|
|
@@ -26,15 +28,53 @@ export type MetadataQueryContext = {
|
|
|
26
28
|
export declare const metadataQueryContext: AsyncLocalStorage<MetadataQueryContext>;
|
|
27
29
|
/**
|
|
28
30
|
* Saves query metadata to the current context and sets up the printQueriesWithPlan function.
|
|
31
|
+
*
|
|
32
|
+
* This function accumulates query statistics in the async context. When printQueriesWithPlan
|
|
33
|
+
* is called, it can either:
|
|
34
|
+
* - Queue the analysis for async processing (if asyncQueueName is provided)
|
|
35
|
+
* - Execute the analysis synchronously (fallback or if asyncQueueName is not set)
|
|
36
|
+
*
|
|
37
|
+
* For async processing, the function sends an event to the specified queue with a timeout.
|
|
38
|
+
* If the event cannot be sent within the timeout, it falls back to synchronous execution.
|
|
39
|
+
*
|
|
29
40
|
* @param stringQuery - The SQL query string
|
|
30
|
-
* @param params - Query parameters
|
|
31
|
-
* @param metadata - Query execution metadata
|
|
41
|
+
* @param params - Query parameters used in the query
|
|
42
|
+
* @param metadata - Query execution metadata including execution time and response size
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```typescript
|
|
46
|
+
* await FORGE_SQL_ORM.executeWithMetadata(
|
|
47
|
+
* async () => {
|
|
48
|
+
* // ... queries ...
|
|
49
|
+
* },
|
|
50
|
+
* async (totalDbExecutionTime, totalResponseSize, printQueries) => {
|
|
51
|
+
* if (totalDbExecutionTime > threshold) {
|
|
52
|
+
* await printQueries(); // Will use async queue if configured
|
|
53
|
+
* }
|
|
54
|
+
* },
|
|
55
|
+
* { asyncQueueName: "degradationQueue" }
|
|
56
|
+
* );
|
|
57
|
+
* ```
|
|
32
58
|
*/
|
|
33
59
|
export declare function saveMetaDataToContext(stringQuery: string, params: unknown[], metadata: ForgeSQLMetadata): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* Prints query degradation analysis for the provided event payload.
|
|
62
|
+
*
|
|
63
|
+
* This function processes query degradation events (either from async queue or synchronous call).
|
|
64
|
+
* It first attempts to use summary tables (CLUSTER_STATEMENTS_SUMMARY) if configured and within
|
|
65
|
+
* the time window. Otherwise, it falls back to printing execution plans for the top slowest queries.
|
|
66
|
+
*
|
|
67
|
+
* @param forgeSQLORM - The ForgeSQL operation instance for database access
|
|
68
|
+
* @param params - The async event payload containing query statistics, options, and metadata
|
|
69
|
+
* @returns Promise that resolves when query analysis is complete
|
|
70
|
+
*
|
|
71
|
+
* @see printPlansUsingSummaryTables - For summary table analysis
|
|
72
|
+
* @see printTopQueriesPlans - For top slowest queries analysis
|
|
73
|
+
*/
|
|
74
|
+
export declare function printDegradationQueries(forgeSQLORM: ForgeSqlOperation, params: AsyncEventPrintQuery): Promise<void>;
|
|
34
75
|
/**
|
|
35
76
|
* Gets the latest metadata from the current context.
|
|
36
77
|
* @returns The current metadata context or undefined if not in a context
|
|
37
78
|
*/
|
|
38
79
|
export declare function getLastestMetadata(): Promise<MetadataQueryContext | undefined>;
|
|
39
|
-
export {};
|
|
40
80
|
//# sourceMappingURL=metadataContextUtils.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metadataContextUtils.d.ts","sourceRoot":"","sources":["../../src/utils/metadataContextUtils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"metadataContextUtils.d.ts","sourceRoot":"","sources":["../../src/utils/metadataContextUtils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAKjE,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAKnE,MAAM,MAAM,SAAS,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,EAAE,CAAC;IAAC,QAAQ,EAAE,gBAAgB,CAAA;CAAE,CAAC;AAEzF,MAAM,MAAM,aAAa,GAAG,YAAY,GAAG,cAAc,CAAC;AAE1D,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,oBAAoB,EAAE,MAAM,CAAC;IAC7B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,IAAI,CAAC;IAChB,OAAO,CAAC,EAAE,oBAAoB,CAAC;IAC/B,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,oBAAoB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1C,WAAW,EAAE,iBAAiB,CAAC;CAChC,CAAC;AAEF,eAAO,MAAM,oBAAoB,yCAAgD,CAAC;AA8OlF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAsB,qBAAqB,CACzC,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,OAAO,EAAE,EACjB,QAAQ,EAAE,gBAAgB,GACzB,OAAO,CAAC,IAAI,CAAC,CA8Df;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,uBAAuB,CAC3C,WAAW,EAAE,iBAAiB,EAC9B,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,IAAI,CAAC,CASf;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,oBAAoB,GAAG,SAAS,CAAC,CAEpF"}
|