@appixar/xpg 1.0.0

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 (54) hide show
  1. package/dist/__tests__/core.test.d.ts +2 -0
  2. package/dist/__tests__/core.test.d.ts.map +1 -0
  3. package/dist/__tests__/core.test.js +228 -0
  4. package/dist/__tests__/core.test.js.map +1 -0
  5. package/dist/builder.d.ts +23 -0
  6. package/dist/builder.d.ts.map +1 -0
  7. package/dist/builder.js +262 -0
  8. package/dist/builder.js.map +1 -0
  9. package/dist/cli.d.ts +3 -0
  10. package/dist/cli.d.ts.map +1 -0
  11. package/dist/cli.js +119 -0
  12. package/dist/cli.js.map +1 -0
  13. package/dist/configLoader.d.ts +22 -0
  14. package/dist/configLoader.d.ts.map +1 -0
  15. package/dist/configLoader.js +97 -0
  16. package/dist/configLoader.js.map +1 -0
  17. package/dist/defaultNormalizer.d.ts +29 -0
  18. package/dist/defaultNormalizer.d.ts.map +1 -0
  19. package/dist/defaultNormalizer.js +124 -0
  20. package/dist/defaultNormalizer.js.map +1 -0
  21. package/dist/diffEngine.d.ts +21 -0
  22. package/dist/diffEngine.d.ts.map +1 -0
  23. package/dist/diffEngine.js +233 -0
  24. package/dist/diffEngine.js.map +1 -0
  25. package/dist/index.d.ts +11 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +15 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/logger.d.ts +22 -0
  30. package/dist/logger.d.ts.map +1 -0
  31. package/dist/logger.js +63 -0
  32. package/dist/logger.js.map +1 -0
  33. package/dist/pgService.d.ts +42 -0
  34. package/dist/pgService.d.ts.map +1 -0
  35. package/dist/pgService.js +219 -0
  36. package/dist/pgService.js.map +1 -0
  37. package/dist/schemaParser.d.ts +10 -0
  38. package/dist/schemaParser.d.ts.map +1 -0
  39. package/dist/schemaParser.js +118 -0
  40. package/dist/schemaParser.js.map +1 -0
  41. package/dist/sqlGenerator.d.ts +18 -0
  42. package/dist/sqlGenerator.d.ts.map +1 -0
  43. package/dist/sqlGenerator.js +104 -0
  44. package/dist/sqlGenerator.js.map +1 -0
  45. package/dist/typeDictionary.d.ts +2 -0
  46. package/dist/typeDictionary.d.ts.map +1 -0
  47. package/dist/typeDictionary.js +27 -0
  48. package/dist/typeDictionary.js.map +1 -0
  49. package/dist/types.d.ts +72 -0
  50. package/dist/types.d.ts.map +1 -0
  51. package/dist/types.js +5 -0
  52. package/dist/types.js.map +1 -0
  53. package/package.json +43 -0
  54. package/xpg.config.yml-sample +42 -0
package/dist/cli.js ADDED
@@ -0,0 +1,119 @@
1
+ #!/usr/bin/env node
2
+ // ─────────────────────────────────────────────
3
+ // x-postgres — CLI entry point
4
+ // ─────────────────────────────────────────────
5
+ import { Command } from 'commander';
6
+ import { writeFileSync, existsSync, mkdirSync } from 'node:fs';
7
+ import { resolve } from 'node:path';
8
+ import { up } from './builder.js';
9
+ import * as log from './logger.js';
10
+ const program = new Command();
11
+ program
12
+ .name('xpg')
13
+ .description('YAML-driven PostgreSQL schema management & migrations')
14
+ .version('1.0.0')
15
+ .option('--no-color', 'Disable colored output');
16
+ // ─── up command ───
17
+ program
18
+ .command('up')
19
+ .description('Run database migrations (create/update/drop tables from YAML schemas)')
20
+ .option('--create', 'Create database if it does not exist')
21
+ .option('--name <db>', 'Target specific database cluster by NAME')
22
+ .option('--tenant <key>', 'Target specific tenant key')
23
+ .option('--mute', 'Suppress all output')
24
+ .option('--dry', 'Dry run — show queries without executing')
25
+ .option('--drop-orphans', 'Drop tables that exist in DB but not in YAML')
26
+ .option('--config <path>', 'Path to config file (default: xpg.config.yml)')
27
+ .action(async (opts) => {
28
+ try {
29
+ await up({
30
+ create: opts.create,
31
+ name: opts.name,
32
+ tenant: opts.tenant,
33
+ mute: opts.mute,
34
+ dry: opts.dry,
35
+ dropOrphans: opts.dropOrphans,
36
+ config: opts.config,
37
+ });
38
+ }
39
+ catch (err) {
40
+ log.error(err.message);
41
+ process.exit(1);
42
+ }
43
+ });
44
+ // ─── init command ───
45
+ program
46
+ .command('init')
47
+ .description('Generate sample configuration files in the current directory')
48
+ .action(() => {
49
+ const configPath = resolve(process.cwd(), 'xpg.config.yml');
50
+ const dbDir = resolve(process.cwd(), 'database');
51
+ if (existsSync(configPath)) {
52
+ log.warn('xpg.config.yml already exists. Skipping.');
53
+ }
54
+ else {
55
+ writeFileSync(configPath, SAMPLE_CONFIG);
56
+ log.success('✓ Created xpg.config.yml');
57
+ }
58
+ if (!existsSync(dbDir)) {
59
+ mkdirSync(dbDir, { recursive: true });
60
+ writeFileSync(resolve(dbDir, 'example.yml'), SAMPLE_TABLE);
61
+ log.success('✓ Created database/example.yml');
62
+ }
63
+ else {
64
+ log.warn('database/ already exists. Skipping.');
65
+ }
66
+ log.say('\nEdit xpg.config.yml with your PostgreSQL credentials, then run:');
67
+ log.say(' npx xpg up', 'cyan');
68
+ });
69
+ program.parse();
70
+ // ─── Sample templates ───
71
+ const SAMPLE_CONFIG = `#────────────────────────────────────
72
+ # x-postgres configuration
73
+ #────────────────────────────────────
74
+ POSTGRES:
75
+ DB:
76
+ "main":
77
+ NAME: my_database
78
+ HOST: <ENV.DB_HOST>
79
+ USER: <ENV.DB_USER>
80
+ PASS: <ENV.DB_PASS>
81
+ PORT: <ENV.DB_PORT>
82
+ PREF: app_
83
+ PATH: [database]
84
+
85
+ CUSTOM_FIELDS:
86
+ "id":
87
+ Type: serial
88
+ Key: PRI
89
+ "str":
90
+ Type: varchar(64)
91
+ "text":
92
+ Type: text
93
+ "int":
94
+ Type: integer
95
+ "float":
96
+ Type: real
97
+ "date":
98
+ Type: timestamp
99
+ "email":
100
+ Type: varchar(128)
101
+ "phone":
102
+ Type: varchar(11)
103
+ "now":
104
+ Type: timestamp
105
+ Default: now()
106
+ "pid":
107
+ Type: varchar(12)
108
+ Key: UNI
109
+ Default: '"left"(md5((random())::text), 12)'
110
+ `;
111
+ const SAMPLE_TABLE = `# Example table definition
112
+ example_users:
113
+ user_id: id
114
+ user_name: str required
115
+ user_email: email unique index
116
+ user_status: str/32 default/active index
117
+ user_date_insert: now
118
+ `;
119
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import type { PostgresConfig, CustomFieldDef } from './types.js';
2
+ export interface LoadedConfig {
3
+ postgres: PostgresConfig['POSTGRES'];
4
+ customFields: Record<string, CustomFieldDef>;
5
+ /** Directory where the config file lives (used to resolve relative paths) */
6
+ configDir: string;
7
+ }
8
+ /**
9
+ * Load configuration.
10
+ *
11
+ * Resolution order:
12
+ * 1. `--config <path>` CLI argument → single file
13
+ * 2. `xpg.config.yml` in CWD
14
+ * 3. `config/postgres.yml` + `config/custom_fields.yml` (PHP-compatible layout)
15
+ */
16
+ export declare function loadConfig(configPath?: string): LoadedConfig;
17
+ /**
18
+ * Resolve database schema paths from config.
19
+ * Handles both absolute paths and paths relative to configDir.
20
+ */
21
+ export declare function resolveSchemaPath(pathEntry: string, configDir: string): string;
22
+ //# sourceMappingURL=configLoader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configLoader.d.ts","sourceRoot":"","sources":["../src/configLoader.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AA+BjE,MAAM,WAAW,YAAY;IACzB,QAAQ,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;IACrC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAC7C,6EAA6E;IAC7E,SAAS,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,YAAY,CA6C5D;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAG9E"}
@@ -0,0 +1,97 @@
1
+ // ─────────────────────────────────────────────
2
+ // x-postgres — Config loader
3
+ // ─────────────────────────────────────────────
4
+ // Loads xpg.config.yml (or separate postgres.yml + custom_fields.yml)
5
+ // with environment variable interpolation: <ENV.VAR> → process.env.VAR
6
+ import { readFileSync, existsSync } from 'node:fs';
7
+ import { resolve, dirname } from 'node:path';
8
+ import YAML from 'yaml';
9
+ /**
10
+ * Interpolate <ENV.VAR_NAME> placeholders with process.env values.
11
+ */
12
+ function interpolateEnv(raw) {
13
+ return raw.replace(/<ENV\.([A-Za-z_][A-Za-z0-9_]*)>/g, (_match, varName) => {
14
+ const val = process.env[varName];
15
+ if (val === undefined) {
16
+ console.warn(`[x-postgres] ⚠ ENV var "${varName}" is not defined — using empty string`);
17
+ }
18
+ return val ?? '';
19
+ });
20
+ }
21
+ /**
22
+ * Recursively walk a parsed YAML object and interpolate env vars in all string values.
23
+ */
24
+ function deepInterpolate(obj) {
25
+ if (typeof obj === 'string')
26
+ return interpolateEnv(obj);
27
+ if (Array.isArray(obj))
28
+ return obj.map(deepInterpolate);
29
+ if (obj !== null && typeof obj === 'object') {
30
+ const result = {};
31
+ for (const [k, v] of Object.entries(obj)) {
32
+ result[k] = deepInterpolate(v);
33
+ }
34
+ return result;
35
+ }
36
+ return obj;
37
+ }
38
+ /**
39
+ * Load configuration.
40
+ *
41
+ * Resolution order:
42
+ * 1. `--config <path>` CLI argument → single file
43
+ * 2. `xpg.config.yml` in CWD
44
+ * 3. `config/postgres.yml` + `config/custom_fields.yml` (PHP-compatible layout)
45
+ */
46
+ export function loadConfig(configPath) {
47
+ let raw = {};
48
+ let configDir = process.cwd();
49
+ if (configPath) {
50
+ const abs = resolve(configPath);
51
+ if (!existsSync(abs))
52
+ throw new Error(`Config file not found: ${abs}`);
53
+ raw = YAML.parse(readFileSync(abs, 'utf-8')) ?? {};
54
+ configDir = dirname(abs);
55
+ }
56
+ else if (existsSync(resolve(process.cwd(), 'xpg.config.yml'))) {
57
+ const abs = resolve(process.cwd(), 'xpg.config.yml');
58
+ raw = YAML.parse(readFileSync(abs, 'utf-8')) ?? {};
59
+ configDir = dirname(abs);
60
+ }
61
+ else {
62
+ // Try PHP-compatible layout
63
+ const pgPath = resolve(process.cwd(), 'config/postgres.yml');
64
+ const cfPath = resolve(process.cwd(), 'config/custom_fields.yml');
65
+ if (existsSync(pgPath)) {
66
+ raw = YAML.parse(readFileSync(pgPath, 'utf-8')) ?? {};
67
+ }
68
+ if (existsSync(cfPath)) {
69
+ const cfRaw = YAML.parse(readFileSync(cfPath, 'utf-8')) ?? {};
70
+ // Merge custom fields into POSTGRES block
71
+ if (cfRaw?.POSTGRES?.CUSTOM_FIELDS && raw?.POSTGRES) {
72
+ raw.POSTGRES.CUSTOM_FIELDS = cfRaw.POSTGRES.CUSTOM_FIELDS;
73
+ }
74
+ else if (cfRaw?.POSTGRES?.CUSTOM_FIELDS) {
75
+ raw.POSTGRES = { CUSTOM_FIELDS: cfRaw.POSTGRES.CUSTOM_FIELDS };
76
+ }
77
+ }
78
+ }
79
+ // Interpolate environment variables
80
+ raw = deepInterpolate(raw);
81
+ const postgres = raw.POSTGRES;
82
+ if (!postgres?.DB) {
83
+ throw new Error('Config error: POSTGRES.DB is missing. Please create xpg.config.yml or config/postgres.yml');
84
+ }
85
+ const customFields = postgres.CUSTOM_FIELDS ?? {};
86
+ return { postgres, customFields, configDir };
87
+ }
88
+ /**
89
+ * Resolve database schema paths from config.
90
+ * Handles both absolute paths and paths relative to configDir.
91
+ */
92
+ export function resolveSchemaPath(pathEntry, configDir) {
93
+ if (pathEntry.startsWith('/'))
94
+ return pathEntry;
95
+ return resolve(configDir, pathEntry);
96
+ }
97
+ //# sourceMappingURL=configLoader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configLoader.js","sourceRoot":"","sources":["../src/configLoader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Convert a raw default value from YAML into a safe SQL expression.
3
+ *
4
+ * Rules:
5
+ * - null/empty/"null" → null (no DEFAULT clause)
6
+ * - Numbers / booleans → as-is
7
+ * - SQL functions (ending with ")") or keywords (CURRENT_TIMESTAMP, etc.) → as-is
8
+ * - JSON/JSONB with {} or [] → cast with ::jsonb / ::json
9
+ * - UUID literals → single-quoted lowercase
10
+ * - Already single-quoted → as-is
11
+ * - Double-quoted → convert to single-quoted
12
+ * - Everything else → single-quoted string
13
+ */
14
+ export declare function normalizeDefaultSql(rawDefault: string | null | undefined, typeRealUpper: string): string | null;
15
+ /**
16
+ * Build the full DEFAULT clause for SQL, e.g. "DEFAULT 'value'"
17
+ */
18
+ export declare function buildDefaultClause(rawDefault: string | null | undefined, typeRealUpper: string): string;
19
+ /**
20
+ * Normalize a column_default value from Postgres for safe comparison.
21
+ *
22
+ * Examples:
23
+ * - "'0'::integer" → "0"
24
+ * - "true::boolean" → "true"
25
+ * - "now()" → "now()" (unchanged)
26
+ * - "nextval('tbl_id_seq'::regclass)" → kept as-is
27
+ */
28
+ export declare function normalizeDbDefaultForCompare(dbDefault: string | null | undefined): string;
29
+ //# sourceMappingURL=defaultNormalizer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaultNormalizer.d.ts","sourceRoot":"","sources":["../src/defaultNormalizer.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA8D/G;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,CAIvG;AAED;;;;;;;;GAQG;AACH,wBAAgB,4BAA4B,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAkCzF"}
@@ -0,0 +1,124 @@
1
+ // ─────────────────────────────────────────────
2
+ // x-postgres — Default value normalizer
3
+ // ─────────────────────────────────────────────
4
+ // Handles all logic for converting YAML default values to SQL,
5
+ // and normalizing database defaults for diff comparison.
6
+ /**
7
+ * Convert a raw default value from YAML into a safe SQL expression.
8
+ *
9
+ * Rules:
10
+ * - null/empty/"null" → null (no DEFAULT clause)
11
+ * - Numbers / booleans → as-is
12
+ * - SQL functions (ending with ")") or keywords (CURRENT_TIMESTAMP, etc.) → as-is
13
+ * - JSON/JSONB with {} or [] → cast with ::jsonb / ::json
14
+ * - UUID literals → single-quoted lowercase
15
+ * - Already single-quoted → as-is
16
+ * - Double-quoted → convert to single-quoted
17
+ * - Everything else → single-quoted string
18
+ */
19
+ export function normalizeDefaultSql(rawDefault, typeRealUpper) {
20
+ if (rawDefault === null || rawDefault === undefined)
21
+ return null;
22
+ let raw = String(rawDefault).trim();
23
+ if (raw === '')
24
+ return null;
25
+ // Explicit "null" → no default
26
+ if (raw.toLowerCase() === 'null')
27
+ return null;
28
+ // Strip "DEFAULT " prefix if user typed it
29
+ if (raw.toLowerCase().startsWith('default ')) {
30
+ raw = raw.substring(8).trim();
31
+ }
32
+ const upperRaw = raw.toUpperCase();
33
+ // SQL functions (ends with ")") or SQL keywords
34
+ if (/\)\s*$/.test(raw) || ['CURRENT_TIMESTAMP', 'CURRENT_DATE', 'CURRENT_TIME'].includes(upperRaw)) {
35
+ return raw;
36
+ }
37
+ // Boolean
38
+ if (['true', 'false'].includes(raw.toLowerCase())) {
39
+ return raw.toUpperCase();
40
+ }
41
+ // Numeric
42
+ if (/^-?\d+(\.\d+)?$/.test(raw)) {
43
+ return raw;
44
+ }
45
+ // JSON / JSONB
46
+ if (typeRealUpper.includes('JSONB') || typeRealUpper.includes('JSON')) {
47
+ const first = raw[0];
48
+ if (first === '{' || first === '[') {
49
+ const escaped = raw.replace(/'/g, "''");
50
+ if (typeRealUpper.includes('JSONB'))
51
+ return `'${escaped}'::jsonb`;
52
+ return `'${escaped}'::json`;
53
+ }
54
+ // Already advanced expression
55
+ return raw;
56
+ }
57
+ // UUID literal
58
+ if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(raw)) {
59
+ return `'${raw.toLowerCase()}'`;
60
+ }
61
+ // Already single-quoted
62
+ if (/^'(.*)'$/s.test(raw)) {
63
+ return raw;
64
+ }
65
+ // Double-quoted → convert
66
+ const dqMatch = raw.match(/^"(.*)"$/s);
67
+ if (dqMatch) {
68
+ raw = dqMatch[1];
69
+ }
70
+ // Default: wrap as string
71
+ const escaped = raw.replace(/'/g, "''");
72
+ return `'${escaped}'`;
73
+ }
74
+ /**
75
+ * Build the full DEFAULT clause for SQL, e.g. "DEFAULT 'value'"
76
+ */
77
+ export function buildDefaultClause(rawDefault, typeRealUpper) {
78
+ const sql = normalizeDefaultSql(rawDefault, typeRealUpper);
79
+ if (sql === null)
80
+ return '';
81
+ return `DEFAULT ${sql}`;
82
+ }
83
+ /**
84
+ * Normalize a column_default value from Postgres for safe comparison.
85
+ *
86
+ * Examples:
87
+ * - "'0'::integer" → "0"
88
+ * - "true::boolean" → "true"
89
+ * - "now()" → "now()" (unchanged)
90
+ * - "nextval('tbl_id_seq'::regclass)" → kept as-is
91
+ */
92
+ export function normalizeDbDefaultForCompare(dbDefault) {
93
+ let d = String(dbDefault ?? '').trim();
94
+ if (d === '')
95
+ return '';
96
+ d = d.replace(/\s+/g, ' ');
97
+ // Don't touch nextval (serial/sequence)
98
+ if (d.toLowerCase().includes('nextval('))
99
+ return d;
100
+ // Strip trailing ::type casts (handles multiword like "timestamp without time zone")
101
+ let prev = '';
102
+ while (prev !== d) {
103
+ prev = d;
104
+ const m = d.match(/^(.*)::[a-zA-Z][a-zA-Z0-9_ ]*$/);
105
+ if (m)
106
+ d = m[1].trim();
107
+ }
108
+ // Strip outer parentheses
109
+ const parenMatch = d.match(/^\((.*)\)$/);
110
+ if (parenMatch)
111
+ d = parenMatch[1].trim();
112
+ // Strip outer single quotes
113
+ const quoteMatch = d.match(/^'(.*)'$/s);
114
+ if (quoteMatch)
115
+ d = quoteMatch[1];
116
+ // Boolean normalize
117
+ if (['true', 'false'].includes(d.toLowerCase())) {
118
+ d = d.toLowerCase();
119
+ }
120
+ // Unescape double single-quotes
121
+ d = d.replace(/''/g, "'");
122
+ return d;
123
+ }
124
+ //# sourceMappingURL=defaultNormalizer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaultNormalizer.js","sourceRoot":"","sources":["../src/defaultNormalizer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import type { ParsedSchema, DbColumnInfo, QueuedQuery } from './types.js';
2
+ interface DbIndexRow {
3
+ indexname: string;
4
+ }
5
+ interface DbConstraintRow {
6
+ conname: string;
7
+ }
8
+ export interface DiffContext {
9
+ table: string;
10
+ schema: ParsedSchema;
11
+ currentColumns: Record<string, DbColumnInfo>;
12
+ existingIndexes: DbIndexRow[];
13
+ existingUniques: DbConstraintRow[];
14
+ mute: boolean;
15
+ }
16
+ /**
17
+ * Generate ALTER statements to bring a live table in sync with YAML schema.
18
+ */
19
+ export declare function generateUpdateTable(ctx: DiffContext): QueuedQuery[];
20
+ export {};
21
+ //# sourceMappingURL=diffEngine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diffEngine.d.ts","sourceRoot":"","sources":["../src/diffEngine.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAK1E,UAAU,UAAU;IAAG,SAAS,EAAE,MAAM,CAAA;CAAE;AAC1C,UAAU,eAAe;IAAG,OAAO,EAAE,MAAM,CAAA;CAAE;AAE7C,MAAM,WAAW,WAAW;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,YAAY,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAC7C,eAAe,EAAE,UAAU,EAAE,CAAC;IAC9B,eAAe,EAAE,eAAe,EAAE,CAAC;IACnC,IAAI,EAAE,OAAO,CAAC;CACjB;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,WAAW,GAAG,WAAW,EAAE,CAgOnE"}