@happyvertical/sql 0.74.8

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 (53) hide show
  1. package/AGENT.md +33 -0
  2. package/LICENSE +7 -0
  3. package/README.md +287 -0
  4. package/dist/backup/index.d.ts +119 -0
  5. package/dist/backup/index.d.ts.map +1 -0
  6. package/dist/backup/index.js +212 -0
  7. package/dist/backup/index.js.map +1 -0
  8. package/dist/backup/manifest.d.ts +51 -0
  9. package/dist/backup/manifest.d.ts.map +1 -0
  10. package/dist/chunks/duckdb-Djx8WKZE.js +924 -0
  11. package/dist/chunks/duckdb-Djx8WKZE.js.map +1 -0
  12. package/dist/chunks/index-DL-5nB7l.js +3468 -0
  13. package/dist/chunks/index-DL-5nB7l.js.map +1 -0
  14. package/dist/chunks/postgres-cli-Cr6bBZ3q.js +209 -0
  15. package/dist/chunks/postgres-cli-Cr6bBZ3q.js.map +1 -0
  16. package/dist/chunks/sqlite-DkCGjXmu.js +937 -0
  17. package/dist/chunks/sqlite-DkCGjXmu.js.map +1 -0
  18. package/dist/chunks/sqlite-native-B6q-0nLs.js +1129 -0
  19. package/dist/chunks/sqlite-native-B6q-0nLs.js.map +1 -0
  20. package/dist/cli/claude-context.d.ts +3 -0
  21. package/dist/cli/claude-context.d.ts.map +1 -0
  22. package/dist/cli/claude-context.js +21 -0
  23. package/dist/cli/claude-context.js.map +1 -0
  24. package/dist/doctor.d.ts +69 -0
  25. package/dist/doctor.d.ts.map +1 -0
  26. package/dist/duckdb.d.ts +9 -0
  27. package/dist/duckdb.d.ts.map +1 -0
  28. package/dist/index.d.ts +109 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.js +34 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/json.d.ts +30 -0
  33. package/dist/json.d.ts.map +1 -0
  34. package/dist/postgres-cli.d.ts +135 -0
  35. package/dist/postgres-cli.d.ts.map +1 -0
  36. package/dist/postgres.d.ts +77 -0
  37. package/dist/postgres.d.ts.map +1 -0
  38. package/dist/schema-manager.d.ts +86 -0
  39. package/dist/schema-manager.d.ts.map +1 -0
  40. package/dist/shared/alter-utils.d.ts +83 -0
  41. package/dist/shared/alter-utils.d.ts.map +1 -0
  42. package/dist/shared/duckdb-schema-utils.d.ts +86 -0
  43. package/dist/shared/duckdb-schema-utils.d.ts.map +1 -0
  44. package/dist/shared/types.d.ts +1016 -0
  45. package/dist/shared/types.d.ts.map +1 -0
  46. package/dist/shared/utils.d.ts +104 -0
  47. package/dist/shared/utils.d.ts.map +1 -0
  48. package/dist/sqlite-native.d.ts +4 -0
  49. package/dist/sqlite-native.d.ts.map +1 -0
  50. package/dist/sqlite.d.ts +54 -0
  51. package/dist/sqlite.d.ts.map +1 -0
  52. package/metadata.json +38 -0
  53. package/package.json +84 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../../src/backup/manifest.ts","../../src/backup/index.ts"],"sourcesContent":["/**\n * Backup manifest shape and helpers.\n *\n * The manifest is the source-of-truth for what's in a backup directory:\n * which database it came from, when, at what commit, and any\n * app-specific metadata that the caller chose to attach via the `extra`\n * field. Bumping `BACKUP_VERSION` is the way to evolve the shape — older\n * backups will fail `readBackupManifest` until upgraded.\n */\nimport { readFile, rename, writeFile } from 'node:fs/promises';\nimport { join, resolve } from 'node:path';\n\nexport const BACKUP_KIND = 'happyvertical-backup' as const;\nexport const BACKUP_VERSION = 1 as const;\nexport const DEFAULT_DUMP_FILE = 'database.dump';\nexport const DEFAULT_FILES_DIR = 'files';\nexport const DEFAULT_MANIFEST_FILE = 'manifest.json';\n\nexport interface BackupManifestDatabase {\n /** File name of the pg_dump archive within the backup directory. */\n dumpFile: string;\n /** Redacted URL the dump was taken from. */\n url: string;\n /** Caller-supplied diagnostic metadata (row counts, schema count, etc.). */\n metadata?: Record<string, unknown>;\n}\n\nexport interface BackupManifestFiles {\n /** Directory name within the backup that holds copied file storage. */\n directory: string;\n exported: boolean;\n count: number;\n bytes: number;\n reason?: string;\n /** Redacted provider config, if the caller chose to record it. */\n storageConfig?: unknown;\n}\n\nexport interface BackupManifest {\n kind: typeof BACKUP_KIND;\n version: typeof BACKUP_VERSION;\n timestamp: string;\n gitSha: string | null;\n /** Free-form label (e.g. 'current', 'prod', 'pre-migration'). */\n source: string;\n database: BackupManifestDatabase;\n files: BackupManifestFiles;\n /** Opaque slot for app-specific extension (resume publish status, etc.). */\n extra?: unknown;\n}\n\n/**\n * Read a backup manifest from `backupPath/manifest.json` and validate\n * that it matches this package's kind+version. Throws (with the path\n * included) on mismatch — that's almost always a real problem worth\n * surfacing, not silently tolerating.\n */\nexport async function readBackupManifest(\n backupPath: string,\n): Promise<BackupManifest> {\n const manifestPath = join(resolve(backupPath), DEFAULT_MANIFEST_FILE);\n const manifest = JSON.parse(\n await readFile(manifestPath, 'utf8'),\n ) as BackupManifest;\n if (manifest.kind !== BACKUP_KIND || manifest.version !== BACKUP_VERSION) {\n throw new Error(\n `Unsupported backup manifest at ${manifestPath} (kind=${manifest.kind}, version=${manifest.version}).`,\n );\n }\n return manifest;\n}\n\nexport async function writeBackupManifest(\n backupPath: string,\n manifest: BackupManifest,\n): Promise<void> {\n const targetPath = join(backupPath, DEFAULT_MANIFEST_FILE);\n const tempPath = join(\n backupPath,\n `.manifest.${process.pid}.${Date.now().toString(36)}.tmp`,\n );\n await writeFile(tempPath, `${JSON.stringify(manifest, null, 2)}\\n`, {\n mode: 0o600,\n });\n await rename(tempPath, targetPath);\n}\n\n/**\n * Filesystem-safe timestamp suitable for backup directory names — same\n * shape as `Date.toISOString()` but with `:` and `.` swapped to `-` so\n * the value is a valid path segment on Windows and friendly to shell\n * tab-completion everywhere else.\n */\nexport function timestampForBackup(date = new Date()): string {\n return date.toISOString().replace(/[:.]/g, '-');\n}\n","/**\n * Backup orchestration — composes the pg shell-out helpers in\n * `../postgres-cli` with caller-supplied hooks for file storage,\n * app-specific manifest extras, and post-restore steps.\n *\n * Decoupled from `@happyvertical/files` on purpose: callers that want a\n * file-storage backup pass an `onBackup` callback that does the copy\n * however they prefer (local cp, S3 sync, WebDAV walk). That keeps this\n * subpath usable by apps that only need the database half.\n */\nimport { execFile } from 'node:child_process';\nimport { access, mkdir, rm } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join, resolve } from 'node:path';\nimport {\n assertCanExportDatabase,\n assertCanImportDatabase,\n createPostgresDatabase,\n dropPostgresDatabase,\n dumpPostgresDatabase,\n redactDatabaseUrl,\n restorePostgresDatabase,\n} from '../postgres-cli';\nimport {\n BACKUP_KIND,\n BACKUP_VERSION,\n type BackupManifest,\n type BackupManifestFiles,\n DEFAULT_DUMP_FILE,\n DEFAULT_FILES_DIR,\n readBackupManifest,\n timestampForBackup,\n writeBackupManifest,\n} from './manifest';\n\nexport {\n BACKUP_KIND,\n BACKUP_VERSION,\n type BackupManifest,\n type BackupManifestDatabase,\n type BackupManifestFiles,\n DEFAULT_DUMP_FILE,\n DEFAULT_FILES_DIR,\n DEFAULT_MANIFEST_FILE,\n readBackupManifest,\n timestampForBackup,\n writeBackupManifest,\n} from './manifest';\n\nexport interface ExportBackupContext {\n backupPath: string;\n /**\n * The raw, credential-bearing database URL the caller passed to\n * `exportBackup`. The hook needs the raw form to open its own\n * connection, but **do not log this value verbatim** — it contains\n * credentials. Run it through `redactDatabaseUrl` (exported from\n * `@happyvertical/sql`) before including it in any output. The\n * manifest's `database.url` field is redacted; only `databaseUrl`\n * here is raw.\n */\n databaseUrl: string;\n /**\n * Local directory the caller should copy file storage into.\n * Already exists by the time `onBackup` runs.\n */\n filesDir: string;\n}\n\nexport interface ExportBackupResult<Extra = unknown> {\n /** Absolute path to the backup directory. */\n backupPath: string;\n /** The manifest that was written. */\n manifest: BackupManifest & { extra?: Extra };\n}\n\nexport interface ExportBackupOptions<Extra = unknown> {\n databaseUrl: string;\n /** Override the parent dir for the timestamped backup. Defaults to ~/.local/share/happyvertical/backups. */\n backupRoot?: string;\n /** Prefix the timestamp with this label (e.g. 'prod', 'pre-migration'). Defaults to 'backup'. */\n label?: string;\n /** Allow exporting from a non-local DB without --allow-production. */\n allowProduction?: boolean;\n /** Allow exporting from a local DB even though `prod: true` is set. */\n allowLocal?: boolean;\n /** Caller is running a prod-flavored export. */\n prod?: boolean;\n /** Skip the file-storage half entirely (no `onBackup` invoked). */\n skipFiles?: boolean;\n /**\n * Caller-supplied hook for backing up file storage. Receives the\n * already-created backup/files directory; whatever it returns is\n * recorded as `extra` on the manifest.\n */\n onBackup?: (ctx: ExportBackupContext) => Promise<Extra | void>;\n /**\n * Caller-supplied hook for filling the database half of the manifest\n * with diagnostic metadata (row counts, schema count, etc.) — runs\n * after the dump completes. As with `onBackup`, the `databaseUrl`\n * passed in is the raw credential-bearing form — redact via\n * `redactDatabaseUrl` before logging.\n */\n onDatabaseMetadata?: (ctx: {\n /** Raw URL — see warning on {@link ExportBackupContext.databaseUrl}. */\n databaseUrl: string;\n }) => Promise<Record<string, unknown>>;\n /** Recorded as `files.storageConfig` on the manifest after redaction by the caller. */\n filesStorageConfig?: unknown;\n /** Working dir used when discovering the git sha. Defaults to process.cwd(). */\n gitCwd?: string;\n}\n\nexport interface RestoreBackupContext {\n backupPath: string;\n /** Raw URL — see warning on {@link ExportBackupContext.databaseUrl}. */\n databaseUrl: string;\n filesDir: string;\n manifest: BackupManifest;\n}\n\nexport interface RestoreBackupOptions {\n databaseUrl: string;\n backupPath: string;\n allowProduction?: boolean;\n /** Skip the file-storage half (no `onRestore` invoked). */\n skipFiles?: boolean;\n /**\n * Caller-supplied hook for restoring file storage from\n * `backupPath/<files.directory>`. Runs after the pg_restore completes.\n */\n onRestore?: (ctx: RestoreBackupContext) => Promise<void>;\n}\n\nexport interface ResetLocalOptions\n extends Omit<RestoreBackupOptions, 'allowProduction'> {}\n\n/** Default root for backups. Override via $HAPPYVERTICAL_BACKUP_DIR or `backupRoot`. */\nexport function defaultBackupRoot(): string {\n return (\n process.env.HAPPYVERTICAL_BACKUP_DIR ??\n join(homedir(), '.local', 'share', 'happyvertical', 'backups')\n );\n}\n\n/**\n * Take a postgres dump and (optionally) a snapshot of file storage,\n * writing both plus a manifest into a fresh timestamped directory.\n *\n * Returns the directory path and the manifest. The manifest's `extra`\n * field carries whatever `onBackup` returned.\n *\n * **Failure model:** the function is all-or-nothing. If any step after\n * the initial directory creation throws — `dumpPostgresDatabase`,\n * `onBackup`, `onDatabaseMetadata`, `getGitSha`, or the final manifest\n * write — the partial backup directory is removed (best-effort)\n * before the original error is re-thrown. Callers therefore never see\n * a backup directory on disk without a complete manifest, which would\n * otherwise poison later `restoreBackup` calls with confusing\n * \"no manifest\" errors and leak a partial dump containing sensitive\n * data. If cleanup itself fails, that error is logged via\n * `console.warn` and swallowed so it doesn't mask the original.\n */\nexport async function exportBackup<Extra = unknown>(\n options: ExportBackupOptions<Extra>,\n): Promise<ExportBackupResult<Extra>> {\n assertCanExportDatabase(options.databaseUrl, options);\n\n const label = validateLabel(options.label ?? 'backup');\n const backupPath = await createUniqueBackupDir(\n options.backupRoot ?? defaultBackupRoot(),\n label,\n );\n\n try {\n const dumpPath = join(backupPath, DEFAULT_DUMP_FILE);\n await dumpPostgresDatabase(options.databaseUrl, dumpPath);\n\n const filesDir = join(backupPath, DEFAULT_FILES_DIR);\n let filesInfo: BackupManifestFiles = {\n directory: DEFAULT_FILES_DIR,\n exported: false,\n count: 0,\n bytes: 0,\n reason: 'Skipped by skipFiles.',\n storageConfig: options.filesStorageConfig,\n };\n let extra: Extra | undefined;\n\n if (!options.skipFiles && options.onBackup) {\n await mkdir(filesDir, { recursive: true, mode: 0o700 });\n const result = await options.onBackup({\n backupPath,\n databaseUrl: options.databaseUrl,\n filesDir,\n });\n if (result !== undefined && result !== null) extra = result as Extra;\n filesInfo = {\n directory: DEFAULT_FILES_DIR,\n exported: true,\n count: 0,\n bytes: 0,\n storageConfig: options.filesStorageConfig,\n };\n }\n\n const metadata = options.onDatabaseMetadata\n ? await options.onDatabaseMetadata({ databaseUrl: options.databaseUrl })\n : undefined;\n\n const manifest: BackupManifest & { extra?: Extra } = {\n kind: BACKUP_KIND,\n version: BACKUP_VERSION,\n timestamp: new Date().toISOString(),\n gitSha: await getGitSha(options.gitCwd),\n source: label,\n database: {\n dumpFile: DEFAULT_DUMP_FILE,\n url: redactDatabaseUrl(options.databaseUrl),\n metadata,\n },\n files: filesInfo,\n extra,\n };\n\n await writeBackupManifest(backupPath, manifest);\n return { backupPath, manifest };\n } catch (error) {\n // Partial state on disk would either confuse later restoreBackup\n // calls (no manifest → cryptic ENOENT) or leak sensitive dump\n // contents. Remove the directory; if cleanup fails, log but don't\n // mask the original error.\n try {\n await rm(backupPath, { recursive: true, force: true });\n } catch (cleanupError) {\n console.warn(\n `exportBackup: failed to clean up partial backup at ${backupPath}:`,\n cleanupError instanceof Error\n ? cleanupError.message\n : String(cleanupError),\n );\n }\n throw error;\n }\n}\n\n/**\n * Restore a backup directory into `databaseUrl` and, if `onRestore` is\n * provided, run the file-storage half. The caller controls everything\n * after the dump completes (file copy, migrations, doctor) via the hook.\n */\nexport async function restoreBackup(\n options: RestoreBackupOptions,\n): Promise<BackupManifest> {\n const backupPath = resolve(options.backupPath);\n const manifest = await readBackupManifest(backupPath);\n assertCanImportDatabase(options.databaseUrl, options);\n const dumpPath = join(\n backupPath,\n validateManifestPathSegment(\n manifest.database.dumpFile,\n 'database dump file',\n ),\n );\n const filesDir = join(\n backupPath,\n validateManifestPathSegment(manifest.files.directory, 'files directory'),\n );\n\n await restorePostgresDatabase(options.databaseUrl, dumpPath);\n\n // Skip `onRestore` for DB-only backups: if the backup was taken with\n // `skipFiles: true` or no `onBackup` hook, the manifest records\n // `files.exported: false` and the `files/` directory may not exist on\n // disk. Calling a generic restore hook anyway would either crash\n // after the database has already been restored, or — worse — let\n // the hook clear target file storage before discovering there's\n // nothing to restore from.\n if (!options.skipFiles && options.onRestore && manifest.files.exported) {\n await options.onRestore({\n backupPath,\n databaseUrl: options.databaseUrl,\n filesDir,\n manifest,\n });\n }\n\n return manifest;\n}\n\n/**\n * Drop-and-recreate the target database, then run `restoreBackup`.\n * Local-only: explicitly refuses non-local URLs without an escape hatch\n * (this is a destructive operation by design).\n */\nexport async function resetLocalDatabaseFromBackup(\n options: ResetLocalOptions,\n): Promise<BackupManifest> {\n assertCanImportDatabase(options.databaseUrl, { allowProduction: false });\n\n // Validate the backup BEFORE dropping the local database. A mistyped\n // backupPath, missing dump file, or corrupted manifest field would\n // otherwise destroy the existing database (drop + create) and only\n // then fail when restoreBackup tries to read those values — leaving\n // the developer with an empty local DB and a confusing ENOENT error.\n //\n // Validates every manifest field restoreBackup later relies on\n // (`database.dumpFile` AND `files.directory`), and verifies the dump\n // file actually exists on disk. The drop only happens if all of that\n // is sound.\n const backupPath = resolve(options.backupPath);\n const manifest = await readBackupManifest(backupPath);\n const dumpFile = validateManifestPathSegment(\n manifest.database.dumpFile,\n 'database dump file',\n );\n validateManifestPathSegment(manifest.files.directory, 'files directory');\n await access(join(backupPath, dumpFile));\n\n await dropPostgresDatabase(options.databaseUrl);\n await createPostgresDatabase(options.databaseUrl);\n return restoreBackup({ ...options, allowProduction: false });\n}\n\nasync function getGitSha(cwd: string = process.cwd()): Promise<string | null> {\n try {\n return await new Promise<string>((resolvePromise, reject) => {\n execFile('git', ['rev-parse', 'HEAD'], { cwd }, (error, stdout) => {\n if (error) {\n reject(error);\n return;\n }\n resolvePromise(stdout.trim());\n });\n });\n } catch {\n return null;\n }\n}\n\nfunction validateLabel(label: string): string {\n if (!label || label === '.' || label === '..' || /[\\\\/]/u.test(label)) {\n throw new Error(\n `Backup label \"${label}\" is invalid. Labels must be a single path-safe segment.`,\n );\n }\n return label;\n}\n\nfunction validateManifestPathSegment(value: string, field: string): string {\n if (!value || value === '.' || value === '..' || /[\\\\/]/u.test(value)) {\n throw new Error(\n `Backup manifest ${field} is invalid: \"${value}\". It must be a single path segment.`,\n );\n }\n return value;\n}\n\nasync function createUniqueBackupDir(\n backupRoot: string,\n label: string,\n): Promise<string> {\n const root = resolve(backupRoot);\n await mkdir(root, { recursive: true, mode: 0o700 });\n\n const baseName = `${label}-${timestampForBackup()}`;\n for (let attempt = 0; attempt < 64; attempt += 1) {\n const suffix = attempt === 0 ? '' : `-${attempt.toString(36)}`;\n const candidate = join(root, `${baseName}${suffix}`);\n try {\n await mkdir(candidate, { mode: 0o700 });\n return candidate;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'EEXIST') {\n continue;\n }\n throw error;\n }\n }\n\n throw new Error(\n `Failed to create a unique backup directory under ${root} for label \"${label}\".`,\n );\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ export declare const BACKUP_KIND: "happyvertical-backup";
2
+ export declare const BACKUP_VERSION: 1;
3
+ export declare const DEFAULT_DUMP_FILE = "database.dump";
4
+ export declare const DEFAULT_FILES_DIR = "files";
5
+ export declare const DEFAULT_MANIFEST_FILE = "manifest.json";
6
+ export interface BackupManifestDatabase {
7
+ /** File name of the pg_dump archive within the backup directory. */
8
+ dumpFile: string;
9
+ /** Redacted URL the dump was taken from. */
10
+ url: string;
11
+ /** Caller-supplied diagnostic metadata (row counts, schema count, etc.). */
12
+ metadata?: Record<string, unknown>;
13
+ }
14
+ export interface BackupManifestFiles {
15
+ /** Directory name within the backup that holds copied file storage. */
16
+ directory: string;
17
+ exported: boolean;
18
+ count: number;
19
+ bytes: number;
20
+ reason?: string;
21
+ /** Redacted provider config, if the caller chose to record it. */
22
+ storageConfig?: unknown;
23
+ }
24
+ export interface BackupManifest {
25
+ kind: typeof BACKUP_KIND;
26
+ version: typeof BACKUP_VERSION;
27
+ timestamp: string;
28
+ gitSha: string | null;
29
+ /** Free-form label (e.g. 'current', 'prod', 'pre-migration'). */
30
+ source: string;
31
+ database: BackupManifestDatabase;
32
+ files: BackupManifestFiles;
33
+ /** Opaque slot for app-specific extension (resume publish status, etc.). */
34
+ extra?: unknown;
35
+ }
36
+ /**
37
+ * Read a backup manifest from `backupPath/manifest.json` and validate
38
+ * that it matches this package's kind+version. Throws (with the path
39
+ * included) on mismatch — that's almost always a real problem worth
40
+ * surfacing, not silently tolerating.
41
+ */
42
+ export declare function readBackupManifest(backupPath: string): Promise<BackupManifest>;
43
+ export declare function writeBackupManifest(backupPath: string, manifest: BackupManifest): Promise<void>;
44
+ /**
45
+ * Filesystem-safe timestamp suitable for backup directory names — same
46
+ * shape as `Date.toISOString()` but with `:` and `.` swapped to `-` so
47
+ * the value is a valid path segment on Windows and friendly to shell
48
+ * tab-completion everywhere else.
49
+ */
50
+ export declare function timestampForBackup(date?: Date): string;
51
+ //# sourceMappingURL=manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/backup/manifest.ts"],"names":[],"mappings":"AAYA,eAAO,MAAM,WAAW,EAAG,sBAA+B,CAAC;AAC3D,eAAO,MAAM,cAAc,EAAG,CAAU,CAAC;AACzC,eAAO,MAAM,iBAAiB,kBAAkB,CAAC;AACjD,eAAO,MAAM,iBAAiB,UAAU,CAAC;AACzC,eAAO,MAAM,qBAAqB,kBAAkB,CAAC;AAErD,MAAM,WAAW,sBAAsB;IACrC,oEAAoE;IACpE,QAAQ,EAAE,MAAM,CAAC;IACjB,4CAA4C;IAC5C,GAAG,EAAE,MAAM,CAAC;IACZ,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,mBAAmB;IAClC,uEAAuE;IACvE,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kEAAkE;IAClE,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,OAAO,WAAW,CAAC;IACzB,OAAO,EAAE,OAAO,cAAc,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,iEAAiE;IACjE,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,sBAAsB,CAAC;IACjC,KAAK,EAAE,mBAAmB,CAAC;IAC3B,4EAA4E;IAC5E,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,cAAc,CAAC,CAWzB;AAED,wBAAsB,mBAAmB,CACvC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,cAAc,GACvB,OAAO,CAAC,IAAI,CAAC,CAUf;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,OAAa,GAAG,MAAM,CAE5D"}