@memberjunction/db-auto-doc 6.2.0-edge.2 → 6.2.0-edge.4
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 +105 -3
- package/dist/commands/export.d.ts +8 -0
- package/dist/commands/export.d.ts.map +1 -1
- package/dist/commands/export.js +53 -2
- package/dist/commands/export.js.map +1 -1
- package/dist/commands/init.js +1 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/core/AnalysisOrchestrator.d.ts +5 -0
- package/dist/core/AnalysisOrchestrator.d.ts.map +1 -1
- package/dist/core/AnalysisOrchestrator.js +32 -0
- package/dist/core/AnalysisOrchestrator.js.map +1 -1
- package/dist/drivers/BaseAutoDocDriver.d.ts +6 -1
- package/dist/drivers/BaseAutoDocDriver.d.ts.map +1 -1
- package/dist/drivers/BaseAutoDocDriver.js.map +1 -1
- package/dist/drivers/IndexCatalog.d.ts +54 -0
- package/dist/drivers/IndexCatalog.d.ts.map +1 -0
- package/dist/drivers/IndexCatalog.js +145 -0
- package/dist/drivers/IndexCatalog.js.map +1 -0
- package/dist/drivers/MySQLDriver.d.ts +5 -1
- package/dist/drivers/MySQLDriver.d.ts.map +1 -1
- package/dist/drivers/MySQLDriver.js +15 -6
- package/dist/drivers/MySQLDriver.js.map +1 -1
- package/dist/drivers/PostgreSQLDriver.d.ts +5 -1
- package/dist/drivers/PostgreSQLDriver.d.ts.map +1 -1
- package/dist/drivers/PostgreSQLDriver.js +11 -0
- package/dist/drivers/PostgreSQLDriver.js.map +1 -1
- package/dist/drivers/SQLServerDriver.d.ts +5 -1
- package/dist/drivers/SQLServerDriver.d.ts.map +1 -1
- package/dist/drivers/SQLServerDriver.js +11 -0
- package/dist/drivers/SQLServerDriver.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/plugins/BaseAutoDocPlugin.d.ts +33 -0
- package/dist/plugins/BaseAutoDocPlugin.d.ts.map +1 -0
- package/dist/plugins/BaseAutoDocPlugin.js +70 -0
- package/dist/plugins/BaseAutoDocPlugin.js.map +1 -0
- package/dist/plugins/PluginManager.d.ts +54 -0
- package/dist/plugins/PluginManager.d.ts.map +1 -0
- package/dist/plugins/PluginManager.js +115 -0
- package/dist/plugins/PluginManager.js.map +1 -0
- package/dist/plugins/builtins.d.ts +13 -0
- package/dist/plugins/builtins.d.ts.map +1 -0
- package/dist/plugins/builtins.js +12 -0
- package/dist/plugins/builtins.js.map +1 -0
- package/dist/plugins/index-advisor/IndexAdvisorPlugin.d.ts +25 -0
- package/dist/plugins/index-advisor/IndexAdvisorPlugin.d.ts.map +1 -0
- package/dist/plugins/index-advisor/IndexAdvisorPlugin.js +128 -0
- package/dist/plugins/index-advisor/IndexAdvisorPlugin.js.map +1 -0
- package/dist/plugins/index-advisor/IndexAdvisorTypes.d.ts +111 -0
- package/dist/plugins/index-advisor/IndexAdvisorTypes.d.ts.map +1 -0
- package/dist/plugins/index-advisor/IndexAdvisorTypes.js +5 -0
- package/dist/plugins/index-advisor/IndexAdvisorTypes.js.map +1 -0
- package/dist/plugins/index-advisor/IndexLLMReview.d.ts +48 -0
- package/dist/plugins/index-advisor/IndexLLMReview.d.ts.map +1 -0
- package/dist/plugins/index-advisor/IndexLLMReview.js +181 -0
- package/dist/plugins/index-advisor/IndexLLMReview.js.map +1 -0
- package/dist/plugins/index-advisor/IndexMigrationGenerator.d.ts +28 -0
- package/dist/plugins/index-advisor/IndexMigrationGenerator.d.ts.map +1 -0
- package/dist/plugins/index-advisor/IndexMigrationGenerator.js +98 -0
- package/dist/plugins/index-advisor/IndexMigrationGenerator.js.map +1 -0
- package/dist/plugins/index-advisor/IndexRules.d.ts +19 -0
- package/dist/plugins/index-advisor/IndexRules.d.ts.map +1 -0
- package/dist/plugins/index-advisor/IndexRules.js +154 -0
- package/dist/plugins/index-advisor/IndexRules.js.map +1 -0
- package/dist/plugins/index.d.ts +13 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +13 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/plugins/types.d.ts +82 -0
- package/dist/plugins/types.d.ts.map +1 -0
- package/dist/plugins/types.js +9 -0
- package/dist/plugins/types.js.map +1 -0
- package/dist/types/config.d.ts +6 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js +0 -3
- package/dist/types/config.js.map +1 -1
- package/dist/types/driver.d.ts +30 -0
- package/dist/types/driver.d.ts.map +1 -1
- package/dist/types/driver.js +5 -1
- package/dist/types/driver.js.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/state.d.ts +9 -0
- package/dist/types/state.d.ts.map +1 -1
- package/dist/utils/config-loader.js +1 -1
- package/dist/utils/config-loader.js.map +1 -1
- package/package.json +5 -5
- package/prompts/index-review.md +60 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for the Index Advisor plugin's options and its section of the state file.
|
|
3
|
+
*/
|
|
4
|
+
import type { AutoDocIndex } from '../../types/driver.js';
|
|
5
|
+
/** Where a proposal came from. */
|
|
6
|
+
export type IndexProposalSource = 'FKCoverage' | 'SoftPrimaryKey' | 'LLM';
|
|
7
|
+
/** A proposed new index. */
|
|
8
|
+
export interface IndexProposal {
|
|
9
|
+
SchemaName: string;
|
|
10
|
+
TableName: string;
|
|
11
|
+
/** Key columns, in index order. */
|
|
12
|
+
Columns: string[];
|
|
13
|
+
/** Non-key covering columns (SQL Server / PostgreSQL INCLUDE). */
|
|
14
|
+
IncludeColumns: string[];
|
|
15
|
+
Source: IndexProposalSource;
|
|
16
|
+
Reason: string;
|
|
17
|
+
/** 1 (low) – 100 (high); larger tables score higher. */
|
|
18
|
+
Priority: number;
|
|
19
|
+
Evidence: IndexProposalEvidence;
|
|
20
|
+
}
|
|
21
|
+
export interface IndexProposalEvidence {
|
|
22
|
+
RowCount: number;
|
|
23
|
+
/** Distinct values of the leading column, when column statistics are available. */
|
|
24
|
+
LeadingColumnDistinctCount?: number;
|
|
25
|
+
/** `schema.table.column` the foreign key points at (FK coverage proposals). */
|
|
26
|
+
References?: string;
|
|
27
|
+
/** Confidence of the discovered key that motivated the proposal (0–100). */
|
|
28
|
+
KeyConfidence?: number;
|
|
29
|
+
/** Whether the motivating key is declared in the database or was discovered. */
|
|
30
|
+
KeyOrigin?: 'declared' | 'discovered';
|
|
31
|
+
}
|
|
32
|
+
/** An existing index whose job another index already does. Reported only; never dropped. */
|
|
33
|
+
export interface RedundantIndex {
|
|
34
|
+
SchemaName: string;
|
|
35
|
+
TableName: string;
|
|
36
|
+
IndexName: string;
|
|
37
|
+
CoveredBy: string;
|
|
38
|
+
Reason: string;
|
|
39
|
+
}
|
|
40
|
+
/** Index Advisor options from the config file (`plugins[].Options`). */
|
|
41
|
+
export interface IndexAdvisorOptions {
|
|
42
|
+
/** Tables with fewer rows get no proposals. Default 1000. */
|
|
43
|
+
MinRowCount: number;
|
|
44
|
+
/** Also index discovered (not just declared) foreign keys. Default true. */
|
|
45
|
+
IncludeDiscoveredFKs: boolean;
|
|
46
|
+
/** Minimum confidence for a discovered key to be used. Default 90. */
|
|
47
|
+
KeyMinConfidence: number;
|
|
48
|
+
/** Run the LLM review layer. Default true. */
|
|
49
|
+
LLMReview: boolean;
|
|
50
|
+
/** Most tables the LLM review looks at (largest first). Default 40. */
|
|
51
|
+
MaxLLMTables: number;
|
|
52
|
+
/** PostgreSQL: emit CREATE INDEX CONCURRENTLY (no write lock while building; runs outside a transaction). Default true. */
|
|
53
|
+
Concurrently: boolean;
|
|
54
|
+
}
|
|
55
|
+
/** The plugin's section of the state file (`state.plugins.IndexAdvisor.Data`). */
|
|
56
|
+
export interface IndexAdvisorData {
|
|
57
|
+
AnalyzedAt: string;
|
|
58
|
+
Options: IndexAdvisorOptions;
|
|
59
|
+
Existing: AutoDocIndex[];
|
|
60
|
+
Proposed: IndexProposal[];
|
|
61
|
+
Redundant: RedundantIndex[];
|
|
62
|
+
/** Schemas whose existing indexes could not be read (proposals for them are skipped). */
|
|
63
|
+
SkippedSchemas: {
|
|
64
|
+
SchemaName: string;
|
|
65
|
+
Error: string;
|
|
66
|
+
}[];
|
|
67
|
+
/** What the LLM review did. Absent when the review was off. */
|
|
68
|
+
LLMReview?: IndexLLMReviewSummary;
|
|
69
|
+
}
|
|
70
|
+
/** Outcome of the LLM review layer. */
|
|
71
|
+
export interface IndexLLMReviewSummary {
|
|
72
|
+
Model: string;
|
|
73
|
+
TablesReviewed: number;
|
|
74
|
+
TokensUsed: number;
|
|
75
|
+
/** Rule-based proposals the review removed, with its reason. */
|
|
76
|
+
Dropped: {
|
|
77
|
+
Proposal: IndexProposal;
|
|
78
|
+
Reason: string;
|
|
79
|
+
}[];
|
|
80
|
+
/** LLM suggestions that failed validation and were not applied. */
|
|
81
|
+
Rejected: {
|
|
82
|
+
TableName: string;
|
|
83
|
+
Columns: string[];
|
|
84
|
+
Why: string;
|
|
85
|
+
}[];
|
|
86
|
+
/** Tables whose review call failed; their rule-based proposals were kept unchanged. */
|
|
87
|
+
Errors: {
|
|
88
|
+
TableName: string;
|
|
89
|
+
Error: string;
|
|
90
|
+
}[];
|
|
91
|
+
}
|
|
92
|
+
/** One proposal decision the LLM returns. `Proposal` is the 1-based number shown in the prompt. */
|
|
93
|
+
export interface IndexReviewDecision {
|
|
94
|
+
Proposal: number;
|
|
95
|
+
Action: 'keep' | 'drop' | 'modify';
|
|
96
|
+
Columns?: string[];
|
|
97
|
+
IncludeColumns?: string[];
|
|
98
|
+
Reason: string;
|
|
99
|
+
}
|
|
100
|
+
/** A new index the LLM suggests. */
|
|
101
|
+
export interface IndexReviewAddition {
|
|
102
|
+
Columns: string[];
|
|
103
|
+
IncludeColumns?: string[];
|
|
104
|
+
Reason: string;
|
|
105
|
+
}
|
|
106
|
+
/** The LLM's full response for one table. */
|
|
107
|
+
export interface IndexReviewResponse {
|
|
108
|
+
Decisions: IndexReviewDecision[];
|
|
109
|
+
Additions: IndexReviewAddition[];
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=IndexAdvisorTypes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IndexAdvisorTypes.d.ts","sourceRoot":"","sources":["../../../src/plugins/index-advisor/IndexAdvisorTypes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IndexAdvisorTypes.js","sourceRoot":"","sources":["../../../src/plugins/index-advisor/IndexAdvisorTypes.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM review layer for the Index Advisor.
|
|
3
|
+
*
|
|
4
|
+
* Per table, the model sees the rule-based proposals plus the context DBAutoDoc built (column
|
|
5
|
+
* statistics, descriptions, existing indexes, sample queries) and may keep, drop or modify each
|
|
6
|
+
* proposal and suggest new ones. The model returns structured decisions, never SQL; every
|
|
7
|
+
* suggestion is validated deterministically before it is applied.
|
|
8
|
+
*/
|
|
9
|
+
import { PromptEngine } from '../../prompts/PromptEngine.js';
|
|
10
|
+
import type { AIConfig } from '../../types/config.js';
|
|
11
|
+
import type { DatabaseDocumentation, TableDefinition } from '../../types/state.js';
|
|
12
|
+
import type { AutoDocIndex } from '../../types/driver.js';
|
|
13
|
+
import type { IndexAdvisorData, IndexLLMReviewSummary, IndexProposal, IndexReviewResponse } from './IndexAdvisorTypes.js';
|
|
14
|
+
/** A table chosen for review. */
|
|
15
|
+
export interface IndexReviewTarget {
|
|
16
|
+
SchemaName: string;
|
|
17
|
+
Table: TableDefinition;
|
|
18
|
+
}
|
|
19
|
+
/** Result of applying one table's review response. */
|
|
20
|
+
export interface IndexReviewApplyResult {
|
|
21
|
+
Proposals: IndexProposal[];
|
|
22
|
+
Dropped: IndexLLMReviewSummary['Dropped'];
|
|
23
|
+
Rejected: IndexLLMReviewSummary['Rejected'];
|
|
24
|
+
}
|
|
25
|
+
export declare class IndexLLMReviewer {
|
|
26
|
+
private promptEngine;
|
|
27
|
+
private ai;
|
|
28
|
+
private log;
|
|
29
|
+
constructor(promptEngine: PromptEngine, ai: AIConfig, log: (message: string) => void);
|
|
30
|
+
/** Creates a reviewer with its own prompt engine over the package's prompt templates. */
|
|
31
|
+
static Create(ai: AIConfig, log: (message: string) => void): Promise<IndexLLMReviewer>;
|
|
32
|
+
/** Reviews the selected tables and returns the revised proposal list plus a summary. */
|
|
33
|
+
Review(state: DatabaseDocumentation, data: IndexAdvisorData, platform: string): Promise<{
|
|
34
|
+
Proposals: IndexProposal[];
|
|
35
|
+
Summary: IndexLLMReviewSummary;
|
|
36
|
+
}>;
|
|
37
|
+
private reviewTable;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Tables worth reviewing: large enough, and either carrying a rule-based proposal or used by a
|
|
41
|
+
* sample query. Largest first, capped at MaxLLMTables.
|
|
42
|
+
*/
|
|
43
|
+
export declare function SelectReviewTables(state: DatabaseDocumentation, data: IndexAdvisorData): IndexReviewTarget[];
|
|
44
|
+
/** Template context for `prompts/index-review.md`. */
|
|
45
|
+
export declare function BuildReviewContext(state: DatabaseDocumentation, target: IndexReviewTarget, proposals: IndexProposal[], existing: AutoDocIndex[], platform: string): object;
|
|
46
|
+
/** Applies one table's review response, validating every change. Pure; no LLM involved. */
|
|
47
|
+
export declare function ApplyReviewResponse(target: IndexReviewTarget, proposals: IndexProposal[], existing: AutoDocIndex[], response: IndexReviewResponse): IndexReviewApplyResult;
|
|
48
|
+
//# sourceMappingURL=IndexLLMReview.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IndexLLMReview.d.ts","sourceRoot":"","sources":["../../../src/plugins/index-advisor/IndexLLMReview.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAC7D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,KAAK,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACnF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAE1D,OAAO,KAAK,EACV,gBAAgB,EAChB,qBAAqB,EACrB,aAAa,EACb,mBAAmB,EACpB,MAAM,wBAAwB,CAAC;AAUhC,iCAAiC;AACjC,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,eAAe,CAAC;CACxB;AAED,sDAAsD;AACtD,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,aAAa,EAAE,CAAC;IAC3B,OAAO,EAAE,qBAAqB,CAAC,SAAS,CAAC,CAAC;IAC1C,QAAQ,EAAE,qBAAqB,CAAC,UAAU,CAAC,CAAC;CAC7C;AAED,qBAAa,gBAAgB;IAEzB,OAAO,CAAC,YAAY;IACpB,OAAO,CAAC,EAAE;IACV,OAAO,CAAC,GAAG;gBAFH,YAAY,EAAE,YAAY,EAC1B,EAAE,EAAE,QAAQ,EACZ,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI;IAGxC,yFAAyF;WACrE,MAAM,CAAC,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAOnG,wFAAwF;IAC3E,MAAM,CACjB,KAAK,EAAE,qBAAqB,EAC5B,IAAI,EAAE,gBAAgB,EACtB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC;QAAE,SAAS,EAAE,aAAa,EAAE,CAAC;QAAC,OAAO,EAAE,qBAAqB,CAAA;KAAE,CAAC;YAY5D,WAAW;CA6B1B;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,qBAAqB,EAAE,IAAI,EAAE,gBAAgB,GAAG,iBAAiB,EAAE,CAY5G;AAED,sDAAsD;AACtD,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,qBAAqB,EAC5B,MAAM,EAAE,iBAAiB,EACzB,SAAS,EAAE,aAAa,EAAE,EAC1B,QAAQ,EAAE,YAAY,EAAE,EACxB,QAAQ,EAAE,MAAM,GACf,MAAM,CAqBR;AAED,2FAA2F;AAC3F,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,iBAAiB,EACzB,SAAS,EAAE,aAAa,EAAE,EAC1B,QAAQ,EAAE,YAAY,EAAE,EACxB,QAAQ,EAAE,mBAAmB,GAC5B,sBAAsB,CAaxB"}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM review layer for the Index Advisor.
|
|
3
|
+
*
|
|
4
|
+
* Per table, the model sees the rule-based proposals plus the context DBAutoDoc built (column
|
|
5
|
+
* statistics, descriptions, existing indexes, sample queries) and may keep, drop or modify each
|
|
6
|
+
* proposal and suggest new ones. The model returns structured decisions, never SQL; every
|
|
7
|
+
* suggestion is validated deterministically before it is applied.
|
|
8
|
+
*/
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
import { PromptEngine } from '../../prompts/PromptEngine.js';
|
|
12
|
+
import { IsCovered, IsPoorKeyColumn, IndexPriority } from './IndexRules.js';
|
|
13
|
+
const MAX_KEY_COLUMNS = 5;
|
|
14
|
+
const MAX_INCLUDE_COLUMNS = 10;
|
|
15
|
+
const MAX_SAMPLE_QUERIES = 3;
|
|
16
|
+
const MAX_SQL_CHARS = 1500;
|
|
17
|
+
const MODEL_OVERRIDE_KEY = 'indexAdvisor';
|
|
18
|
+
const lower = (s) => s.toLowerCase();
|
|
19
|
+
export class IndexLLMReviewer {
|
|
20
|
+
constructor(promptEngine, ai, log) {
|
|
21
|
+
this.promptEngine = promptEngine;
|
|
22
|
+
this.ai = ai;
|
|
23
|
+
this.log = log;
|
|
24
|
+
}
|
|
25
|
+
/** Creates a reviewer with its own prompt engine over the package's prompt templates. */
|
|
26
|
+
static async Create(ai, log) {
|
|
27
|
+
const promptsDir = path.join(path.dirname(fileURLToPath(import.meta.url)), '../../../prompts');
|
|
28
|
+
const engine = new PromptEngine(ai, promptsDir);
|
|
29
|
+
await engine.Initialize();
|
|
30
|
+
return new IndexLLMReviewer(engine, ai, log);
|
|
31
|
+
}
|
|
32
|
+
/** Reviews the selected tables and returns the revised proposal list plus a summary. */
|
|
33
|
+
async Review(state, data, platform) {
|
|
34
|
+
const override = this.ai.modelOverrides?.[MODEL_OVERRIDE_KEY];
|
|
35
|
+
const summary = {
|
|
36
|
+
Model: override?.model ?? this.ai.model, TablesReviewed: 0, TokensUsed: 0, Dropped: [], Rejected: [], Errors: []
|
|
37
|
+
};
|
|
38
|
+
let proposals = [...data.Proposed];
|
|
39
|
+
for (const target of SelectReviewTables(state, data)) {
|
|
40
|
+
proposals = await this.reviewTable(state, data, target, platform, proposals, summary);
|
|
41
|
+
}
|
|
42
|
+
return { Proposals: proposals.sort((a, b) => b.Priority - a.Priority), Summary: summary };
|
|
43
|
+
}
|
|
44
|
+
async reviewTable(state, data, target, platform, proposals, summary) {
|
|
45
|
+
const isThisTable = (p) => sameTable(p, target);
|
|
46
|
+
const tableProposals = proposals.filter(isThisTable);
|
|
47
|
+
const existing = data.Existing.filter((ix) => sameTable(ix, target));
|
|
48
|
+
const override = this.ai.modelOverrides?.[MODEL_OVERRIDE_KEY];
|
|
49
|
+
const result = await this.promptEngine.ExecutePrompt('index-review', BuildReviewContext(state, target, tableProposals, existing, platform), { responseFormat: 'JSON', temperature: override?.temperature ?? 0.1, maxTokens: override?.maxTokens ?? this.ai.maxTokens, modelOverride: override?.model, effortLevelOverride: override?.effortLevel });
|
|
50
|
+
summary.TablesReviewed++;
|
|
51
|
+
summary.TokensUsed += result.tokensUsed;
|
|
52
|
+
if (!result.success || !result.result) {
|
|
53
|
+
summary.Errors.push({ TableName: target.Table.name, Error: result.errorMessage ?? 'empty response' });
|
|
54
|
+
this.log(`Index Advisor review failed for ${target.SchemaName}.${target.Table.name} (keeping rule-based proposals)`);
|
|
55
|
+
return proposals;
|
|
56
|
+
}
|
|
57
|
+
const applied = ApplyReviewResponse(target, tableProposals, existing, result.result);
|
|
58
|
+
summary.Dropped.push(...applied.Dropped);
|
|
59
|
+
summary.Rejected.push(...applied.Rejected);
|
|
60
|
+
return [...proposals.filter((p) => !isThisTable(p)), ...applied.Proposals];
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Tables worth reviewing: large enough, and either carrying a rule-based proposal or used by a
|
|
65
|
+
* sample query. Largest first, capped at MaxLLMTables.
|
|
66
|
+
*/
|
|
67
|
+
export function SelectReviewTables(state, data) {
|
|
68
|
+
const wanted = new Set([
|
|
69
|
+
...data.Proposed.map((p) => tableId(p.SchemaName, p.TableName)),
|
|
70
|
+
...(state.sampleQueries?.queries ?? []).flatMap((q) => [...q.primaryEntities, ...q.relatedEntities].map((e) => tableId(e.schema, e.table)))
|
|
71
|
+
]);
|
|
72
|
+
const skipped = new Set(data.SkippedSchemas.map((s) => lower(s.SchemaName)));
|
|
73
|
+
return state.schemas
|
|
74
|
+
.filter((s) => !skipped.has(lower(s.name)))
|
|
75
|
+
.flatMap((s) => s.tables.map((t) => ({ SchemaName: s.name, Table: t })))
|
|
76
|
+
.filter((t) => t.Table.rowCount >= data.Options.MinRowCount && wanted.has(tableId(t.SchemaName, t.Table.name)))
|
|
77
|
+
.sort((a, b) => b.Table.rowCount - a.Table.rowCount)
|
|
78
|
+
.slice(0, data.Options.MaxLLMTables);
|
|
79
|
+
}
|
|
80
|
+
/** Template context for `prompts/index-review.md`. */
|
|
81
|
+
export function BuildReviewContext(state, target, proposals, existing, platform) {
|
|
82
|
+
const t = target.Table;
|
|
83
|
+
return {
|
|
84
|
+
platform,
|
|
85
|
+
table: { schema: target.SchemaName, name: t.name, rowCount: t.rowCount, description: t.description ?? '' },
|
|
86
|
+
columns: t.columns.map((c) => ({
|
|
87
|
+
name: c.name,
|
|
88
|
+
dataType: c.dataType,
|
|
89
|
+
distinctCount: c.statistics?.distinctCount ?? null,
|
|
90
|
+
nullPercentage: c.statistics ? Math.round(c.statistics.nullPercentage) : null,
|
|
91
|
+
description: (c.description ?? '').slice(0, 160)
|
|
92
|
+
})),
|
|
93
|
+
existing: existing.map((ix) => ({
|
|
94
|
+
name: ix.IndexName, columns: ix.Columns, include: ix.IncludeColumns, isUnique: ix.IsUnique, isPrimaryKey: ix.IsPrimaryKey, filter: ix.FilterDefinition ?? ''
|
|
95
|
+
})),
|
|
96
|
+
proposals: proposals.map((p) => ({ columns: p.Columns, reason: p.Reason })),
|
|
97
|
+
sampleQueries: (state.sampleQueries?.queries ?? [])
|
|
98
|
+
.filter((q) => [...q.primaryEntities, ...q.relatedEntities].some((e) => tableId(e.schema, e.table) === tableId(target.SchemaName, t.name)))
|
|
99
|
+
.slice(0, MAX_SAMPLE_QUERIES)
|
|
100
|
+
.map((q) => ({ name: q.name, sql: q.sqlQuery.slice(0, MAX_SQL_CHARS) }))
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/** Applies one table's review response, validating every change. Pure; no LLM involved. */
|
|
104
|
+
export function ApplyReviewResponse(target, proposals, existing, response) {
|
|
105
|
+
const out = { Proposals: [], Dropped: [], Rejected: [] };
|
|
106
|
+
const decisions = new Map((Array.isArray(response.Decisions) ? response.Decisions : []).map((d) => [d.Proposal, d]));
|
|
107
|
+
proposals.forEach((proposal, i) => applyDecision(target, proposal, decisions.get(i + 1), existing, out));
|
|
108
|
+
for (const addition of Array.isArray(response.Additions) ? response.Additions : []) {
|
|
109
|
+
const candidate = {
|
|
110
|
+
SchemaName: target.SchemaName, TableName: target.Table.name, Columns: addition.Columns ?? [], IncludeColumns: addition.IncludeColumns ?? [],
|
|
111
|
+
Source: 'LLM', Reason: addition.Reason ?? '', Priority: Math.max(1, IndexPriority(target.Table.rowCount) - 5),
|
|
112
|
+
Evidence: { RowCount: target.Table.rowCount }
|
|
113
|
+
};
|
|
114
|
+
acceptIfValid(target, candidate, existing, out);
|
|
115
|
+
}
|
|
116
|
+
return out;
|
|
117
|
+
}
|
|
118
|
+
function applyDecision(target, proposal, decision, existing, out) {
|
|
119
|
+
if (decision?.Action === 'drop' && decision.Reason) {
|
|
120
|
+
out.Dropped.push({ Proposal: proposal, Reason: decision.Reason });
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (decision?.Action === 'modify') {
|
|
124
|
+
const modified = {
|
|
125
|
+
...proposal,
|
|
126
|
+
Columns: decision.Columns ?? proposal.Columns,
|
|
127
|
+
IncludeColumns: decision.IncludeColumns ?? proposal.IncludeColumns,
|
|
128
|
+
Reason: `${proposal.Reason} Adjusted by review: ${decision.Reason}`
|
|
129
|
+
};
|
|
130
|
+
if (acceptIfValid(target, modified, existing, out)) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// keep, no decision, or an invalid modification: the rule-based proposal stands
|
|
135
|
+
acceptIfValid(target, proposal, existing, out);
|
|
136
|
+
}
|
|
137
|
+
/** Validates a proposal against the table and what is already planned; records rejections. */
|
|
138
|
+
function acceptIfValid(target, p, existing, out) {
|
|
139
|
+
const why = validate(target, p, existing, out.Proposals);
|
|
140
|
+
if (why) {
|
|
141
|
+
out.Rejected.push({ TableName: target.Table.name, Columns: p.Columns, Why: why });
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
out.Proposals.push({ ...p, Columns: canonical(target.Table, p.Columns), IncludeColumns: canonical(target.Table, p.IncludeColumns) });
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
function validate(target, p, existing, planned) {
|
|
148
|
+
const columns = p.Columns ?? [];
|
|
149
|
+
const include = p.IncludeColumns ?? [];
|
|
150
|
+
const unknown = [...columns, ...include].filter((c) => !findColumn(target.Table, c));
|
|
151
|
+
if (columns.length === 0 || columns.length > MAX_KEY_COLUMNS)
|
|
152
|
+
return `needs 1–${MAX_KEY_COLUMNS} key columns`;
|
|
153
|
+
if (include.length > MAX_INCLUDE_COLUMNS)
|
|
154
|
+
return `too many INCLUDE columns`;
|
|
155
|
+
if (unknown.length)
|
|
156
|
+
return `unknown column(s): ${unknown.join(', ')}`;
|
|
157
|
+
if (new Set(columns.map(lower)).size !== columns.length)
|
|
158
|
+
return 'repeats a key column';
|
|
159
|
+
if (include.some((c) => columns.some((k) => lower(k) === lower(c))))
|
|
160
|
+
return 'INCLUDE repeats a key column';
|
|
161
|
+
if (IsPoorKeyColumn(findColumn(target.Table, columns[0])))
|
|
162
|
+
return `leading column ${columns[0]} is a poor index key`;
|
|
163
|
+
if (IsCovered(columns, existing))
|
|
164
|
+
return 'an existing index already starts with these columns';
|
|
165
|
+
if (planned.some((q) => q.Columns.length === columns.length && q.Columns.every((c, i) => lower(c) === lower(columns[i]))))
|
|
166
|
+
return 'duplicates another proposal';
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
function findColumn(table, name) {
|
|
170
|
+
return table.columns.find((c) => lower(c.name) === lower(name));
|
|
171
|
+
}
|
|
172
|
+
function canonical(table, names) {
|
|
173
|
+
return names.map((n) => findColumn(table, n)?.name ?? n);
|
|
174
|
+
}
|
|
175
|
+
function sameTable(item, target) {
|
|
176
|
+
return lower(item.SchemaName) === lower(target.SchemaName) && lower(item.TableName) === lower(target.Table.name);
|
|
177
|
+
}
|
|
178
|
+
function tableId(schema, table) {
|
|
179
|
+
return `${lower(schema)}.${lower(table)}`;
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=IndexLLMReview.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IndexLLMReview.js","sourceRoot":"","sources":["../../../src/plugins/index-advisor/IndexLLMReview.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turns Index Advisor proposals into a dialect-specific, Flyway-style SQL migration.
|
|
3
|
+
*/
|
|
4
|
+
import type { AutoDocExportFile, AutoDocExporterOptions } from '../types.js';
|
|
5
|
+
import type { IndexAdvisorData, IndexProposal } from './IndexAdvisorTypes.js';
|
|
6
|
+
type Provider = AutoDocExporterOptions['Provider'];
|
|
7
|
+
export declare class IndexMigrationGenerator {
|
|
8
|
+
/** Returns the migration file (plus a Flyway `.conf` when PostgreSQL CONCURRENTLY is on), or nothing when there are no proposals. */
|
|
9
|
+
Generate(data: IndexAdvisorData, options: AutoDocExporterOptions): AutoDocExportFile[];
|
|
10
|
+
/**
|
|
11
|
+
* Index name for a proposal: IX_<table>_<columns>, shortened with a hash suffix when too long.
|
|
12
|
+
* PostgreSQL index names must be unique per schema, and the readable part is lossy (Order.Line_ID and
|
|
13
|
+
* Order_Line.ID both give IX_Order_Line_ID), so PostgreSQL names always carry a hash of the exact table and columns.
|
|
14
|
+
*/
|
|
15
|
+
static IndexName(proposal: Pick<IndexProposal, 'TableName' | 'Columns'>, provider: Provider): string;
|
|
16
|
+
/** Flattens text onto one line so it cannot end a `--` comment and become executable SQL. */
|
|
17
|
+
static CommentText(text: string): string;
|
|
18
|
+
/** yyyyMMddHHmm (UTC) from an ISO timestamp. */
|
|
19
|
+
static Timestamp(iso: string): string;
|
|
20
|
+
private buildScript;
|
|
21
|
+
private statement;
|
|
22
|
+
/** Quotes an identifier for the platform, doubling any embedded quote character. */
|
|
23
|
+
static Quote(identifier: string, provider: Provider): string;
|
|
24
|
+
/** 8-hex-digit FNV-1a hash, used to keep shortened index names unique. */
|
|
25
|
+
private static hash;
|
|
26
|
+
}
|
|
27
|
+
export {};
|
|
28
|
+
//# sourceMappingURL=IndexMigrationGenerator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IndexMigrationGenerator.d.ts","sourceRoot":"","sources":["../../../src/plugins/index-advisor/IndexMigrationGenerator.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAC7E,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAE9E,KAAK,QAAQ,GAAG,sBAAsB,CAAC,UAAU,CAAC,CAAC;AAOnD,qBAAa,uBAAuB;IAClC,qIAAqI;IAC9H,QAAQ,CAAC,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,sBAAsB,GAAG,iBAAiB,EAAE;IAc7F;;;;OAIG;WACW,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,aAAa,EAAE,WAAW,GAAG,SAAS,CAAC,EAAE,QAAQ,EAAE,QAAQ,GAAG,MAAM;IAa3G,6FAA6F;WAC/E,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAI/C,gDAAgD;WAClC,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAM5C,OAAO,CAAC,WAAW;IAWnB,OAAO,CAAC,SAAS;IAmBjB,oFAAoF;WACtE,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,MAAM;IAWnE,0EAA0E;IAC1E,OAAO,CAAC,MAAM,CAAC,IAAI;CAQpB"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turns Index Advisor proposals into a dialect-specific, Flyway-style SQL migration.
|
|
3
|
+
*/
|
|
4
|
+
/** Maximum identifier length per platform. */
|
|
5
|
+
const MAX_IDENTIFIER = { sqlserver: 128, postgresql: 63, mysql: 64 };
|
|
6
|
+
const BASE_NAME = 'dbautodoc_indexes';
|
|
7
|
+
export class IndexMigrationGenerator {
|
|
8
|
+
/** Returns the migration file (plus a Flyway `.conf` when PostgreSQL CONCURRENTLY is on), or nothing when there are no proposals. */
|
|
9
|
+
Generate(data, options) {
|
|
10
|
+
if (data.Proposed.length === 0) {
|
|
11
|
+
return [];
|
|
12
|
+
}
|
|
13
|
+
const concurrently = options.Provider === 'postgresql' && data.Options.Concurrently;
|
|
14
|
+
const fileName = `${options.FileNamePrefix ?? `V${IndexMigrationGenerator.Timestamp(data.AnalyzedAt)}__`}${BASE_NAME}.sql`;
|
|
15
|
+
const files = [{ FileName: fileName, Content: this.buildScript(data, options.Provider, concurrently) }];
|
|
16
|
+
if (concurrently) {
|
|
17
|
+
// CREATE INDEX CONCURRENTLY cannot run inside a transaction; tell Flyway to run this script without one.
|
|
18
|
+
files.push({ FileName: `${fileName}.conf`, Content: 'executeInTransaction=false\n' });
|
|
19
|
+
}
|
|
20
|
+
return files;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Index name for a proposal: IX_<table>_<columns>, shortened with a hash suffix when too long.
|
|
24
|
+
* PostgreSQL index names must be unique per schema, and the readable part is lossy (Order.Line_ID and
|
|
25
|
+
* Order_Line.ID both give IX_Order_Line_ID), so PostgreSQL names always carry a hash of the exact table and columns.
|
|
26
|
+
*/
|
|
27
|
+
static IndexName(proposal, provider) {
|
|
28
|
+
const raw = `IX_${proposal.TableName}_${proposal.Columns.join('_')}`.replace(/[^A-Za-z0-9_]/g, '_');
|
|
29
|
+
const max = MAX_IDENTIFIER[provider];
|
|
30
|
+
if (provider === 'postgresql') {
|
|
31
|
+
const suffix = IndexMigrationGenerator.hash(JSON.stringify([proposal.TableName, proposal.Columns]));
|
|
32
|
+
return `${raw.slice(0, max - 9)}_${suffix}`;
|
|
33
|
+
}
|
|
34
|
+
if (raw.length <= max) {
|
|
35
|
+
return raw;
|
|
36
|
+
}
|
|
37
|
+
return `${raw.slice(0, max - 9)}_${IndexMigrationGenerator.hash(raw)}`;
|
|
38
|
+
}
|
|
39
|
+
/** Flattens text onto one line so it cannot end a `--` comment and become executable SQL. */
|
|
40
|
+
static CommentText(text) {
|
|
41
|
+
return text.replace(/[\r\n\u0085\u2028\u2029]+/g, ' ');
|
|
42
|
+
}
|
|
43
|
+
/** yyyyMMddHHmm (UTC) from an ISO timestamp. */
|
|
44
|
+
static Timestamp(iso) {
|
|
45
|
+
const d = new Date(iso);
|
|
46
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
47
|
+
return `${d.getUTCFullYear()}${pad(d.getUTCMonth() + 1)}${pad(d.getUTCDate())}${pad(d.getUTCHours())}${pad(d.getUTCMinutes())}`;
|
|
48
|
+
}
|
|
49
|
+
buildScript(data, provider, concurrently) {
|
|
50
|
+
const header = [
|
|
51
|
+
`-- Index migration generated by DBAutoDoc Index Advisor (${data.AnalyzedAt}).`,
|
|
52
|
+
`-- Platform: ${provider}. ${data.Proposed.length} proposed index(es), highest priority first.`,
|
|
53
|
+
'-- Review before applying. Each index is created only if it does not already exist.',
|
|
54
|
+
provider === 'mysql' ? '-- MySQL has no CREATE INDEX IF NOT EXISTS; re-running this script fails on indexes that already exist.' : ''
|
|
55
|
+
].filter((l) => l.length > 0);
|
|
56
|
+
const statements = data.Proposed.map((p) => `-- ${p.Source}: ${IndexMigrationGenerator.CommentText(p.Reason)}\n${this.statement(p, provider, concurrently)}`);
|
|
57
|
+
return `${header.join('\n')}\n\n${statements.join('\n\n')}\n`;
|
|
58
|
+
}
|
|
59
|
+
statement(p, provider, concurrently) {
|
|
60
|
+
const name = IndexMigrationGenerator.IndexName(p, provider);
|
|
61
|
+
const q = (id) => IndexMigrationGenerator.Quote(id, provider);
|
|
62
|
+
const table = `${q(p.SchemaName)}.${q(p.TableName)}`;
|
|
63
|
+
const columns = p.Columns.map(q).join(', ');
|
|
64
|
+
const include = p.IncludeColumns.length && provider !== 'mysql' ? ` INCLUDE (${p.IncludeColumns.map(q).join(', ')})` : '';
|
|
65
|
+
switch (provider) {
|
|
66
|
+
case 'sqlserver':
|
|
67
|
+
return [
|
|
68
|
+
`IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'${name}' AND object_id = OBJECT_ID(N'${table.replace(/'/g, "''")}'))`,
|
|
69
|
+
` CREATE NONCLUSTERED INDEX ${q(name)} ON ${table} (${columns})${include};`
|
|
70
|
+
].join('\n');
|
|
71
|
+
case 'postgresql':
|
|
72
|
+
return `CREATE INDEX ${concurrently ? 'CONCURRENTLY ' : ''}IF NOT EXISTS ${q(name)} ON ${table} (${columns})${include};`;
|
|
73
|
+
case 'mysql':
|
|
74
|
+
return `CREATE INDEX ${q(name)} ON ${table} (${columns});`;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/** Quotes an identifier for the platform, doubling any embedded quote character. */
|
|
78
|
+
static Quote(identifier, provider) {
|
|
79
|
+
switch (provider) {
|
|
80
|
+
case 'sqlserver':
|
|
81
|
+
return `[${identifier.replace(/]/g, ']]')}]`;
|
|
82
|
+
case 'postgresql':
|
|
83
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
84
|
+
case 'mysql':
|
|
85
|
+
return `\`${identifier.replace(/`/g, '``')}\``;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/** 8-hex-digit FNV-1a hash, used to keep shortened index names unique. */
|
|
89
|
+
static hash(value) {
|
|
90
|
+
let h = 0x811c9dc5;
|
|
91
|
+
for (let i = 0; i < value.length; i++) {
|
|
92
|
+
h ^= value.charCodeAt(i);
|
|
93
|
+
h = Math.imul(h, 0x01000193);
|
|
94
|
+
}
|
|
95
|
+
return (h >>> 0).toString(16).padStart(8, '0');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=IndexMigrationGenerator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IndexMigrationGenerator.js","sourceRoot":"","sources":["../../../src/plugins/index-advisor/IndexMigrationGenerator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic index rules. Pure functions over the state file and the existing indexes,
|
|
3
|
+
* so they can be tested without a database.
|
|
4
|
+
*/
|
|
5
|
+
import type { DatabaseDocumentation, TableDefinition, ColumnDefinition } from '../../types/state.js';
|
|
6
|
+
import type { AutoDocIndex } from '../../types/driver.js';
|
|
7
|
+
import type { IndexAdvisorOptions, IndexProposal, RedundantIndex } from './IndexAdvisorTypes.js';
|
|
8
|
+
/** Proposes indexes for uncovered foreign keys and discovered primary keys. */
|
|
9
|
+
export declare function ProposeIndexes(state: DatabaseDocumentation, existing: AutoDocIndex[], options: IndexAdvisorOptions): IndexProposal[];
|
|
10
|
+
/** Flags existing indexes whose key columns are a prefix of (or equal to) another index's. */
|
|
11
|
+
export declare function FindRedundantIndexes(existing: AutoDocIndex[]): RedundantIndex[];
|
|
12
|
+
/** True when an index on `table` already starts with exactly these columns. */
|
|
13
|
+
export declare function IsCovered(columns: string[], tableIndexes: AutoDocIndex[]): boolean;
|
|
14
|
+
/** Larger tables benefit most: 1k rows → ~30, 100k → ~50, 10M → ~70, capped at 100. */
|
|
15
|
+
export declare function IndexPriority(rowCount: number): number;
|
|
16
|
+
/** True for columns that make poor leading index keys (large/unstructured types, ≤2 distinct values). */
|
|
17
|
+
export declare function IsPoorKeyColumn(column: ColumnDefinition): boolean;
|
|
18
|
+
export declare function FindTable(state: DatabaseDocumentation, schemaName: string, tableName: string): TableDefinition | undefined;
|
|
19
|
+
//# sourceMappingURL=IndexRules.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IndexRules.d.ts","sourceRoot":"","sources":["../../../src/plugins/index-advisor/IndexRules.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACrG,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,KAAK,EAAE,mBAAmB,EAAE,aAAa,EAAyB,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAiBxH,+EAA+E;AAC/E,wBAAgB,cAAc,CAC5B,KAAK,EAAE,qBAAqB,EAC5B,QAAQ,EAAE,YAAY,EAAE,EACxB,OAAO,EAAE,mBAAmB,GAC3B,aAAa,EAAE,CAajB;AAED,8FAA8F;AAC9F,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG,cAAc,EAAE,CAiB/E;AAED,+EAA+E;AAC/E,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,YAAY,EAAE,YAAY,EAAE,GAAG,OAAO,CAElF;AAkFD,uFAAuF;AACvF,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED,yGAAyG;AACzG,wBAAgB,eAAe,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAGjE;AAiCD,wBAAgB,SAAS,CAAC,KAAK,EAAE,qBAAqB,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAI1H"}
|