@prisma-next/psl-printer 0.5.0-dev.8 → 0.5.0-dev.81

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["DEFAULT_FUNCTION_ATTRIBUTES: Readonly<Record<string, string>>","name: string","REFERENTIAL_ACTION_PSL: Record<string, string>","models: PrinterModel[]","enums: Array<{ name: string; mapName: string | undefined; values: readonly string[] }>","sections: string[]","fields: PrinterField[]","attributes: string[]","comment: string | undefined","relAttributes: string[]","parts: string[]","modelAttributes: string[]","registry: NamedTypeRegistry","result: PrinterModel[]","lines: string[]","validated: Record<string, PslPrintableSqlTable>","validated: Record<string, PslPrintableSqlColumn>"],"sources":["../src/default-mapping.ts","../src/name-transforms.ts","../src/relation-inference.ts","../src/print-psl.ts","../src/schema-validation.ts"],"sourcesContent":["import type { ColumnDefault } from '@prisma-next/contract/types';\n\nconst DEFAULT_FUNCTION_ATTRIBUTES: Readonly<Record<string, string>> = {\n 'autoincrement()': '@default(autoincrement())',\n 'now()': '@default(now())',\n};\n\nexport interface DefaultMappingOptions {\n readonly functionAttributes?: Readonly<Record<string, string>>;\n readonly fallbackFunctionAttribute?: ((expression: string) => string | undefined) | undefined;\n}\n\n/**\n * Result of mapping a ColumnDefault to a PSL @default expression.\n */\nexport type DefaultMappingResult = { readonly attribute: string } | { readonly comment: string };\n\n/**\n * Maps a normalized ColumnDefault to a PSL @default(...) attribute string,\n * or a comment for unrecognized expressions.\n */\nexport function mapDefault(\n columnDefault: ColumnDefault,\n options?: DefaultMappingOptions,\n): DefaultMappingResult {\n switch (columnDefault.kind) {\n case 'literal':\n return { attribute: `@default(${formatLiteralValue(columnDefault.value)})` };\n case 'function': {\n const attribute =\n options?.functionAttributes?.[columnDefault.expression] ??\n DEFAULT_FUNCTION_ATTRIBUTES[columnDefault.expression] ??\n options?.fallbackFunctionAttribute?.(columnDefault.expression);\n return attribute\n ? { attribute }\n : { comment: `// Raw default: ${columnDefault.expression.replace(/[\\r\\n]+/g, ' ')}` };\n }\n }\n}\n\n/**\n * Formats a literal value for use in @default(...).\n */\nfunction formatLiteralValue(value: unknown): string {\n if (value === null) {\n return 'null';\n }\n\n switch (typeof value) {\n case 'boolean':\n case 'number':\n return String(value);\n case 'string':\n return quoteString(value);\n default:\n return quoteString(JSON.stringify(value));\n }\n}\n\nfunction quoteString(str: string): string {\n return `\"${escapeString(str)}\"`;\n}\n\nfunction escapeString(str: string): string {\n return JSON.stringify(str).slice(1, -1);\n}\n","/**\n * PSL reserved words that cannot be used as identifiers without escaping.\n */\nconst PSL_RESERVED_WORDS = new Set(['model', 'enum', 'types', 'type', 'generator', 'datasource']);\n\nconst IDENTIFIER_PART_PATTERN = /[A-Za-z0-9]+/g;\n\ntype NameResult = {\n readonly name: string;\n readonly map?: string;\n};\n\n/**\n * Checks whether normalization needs to split or sanitize the identifier.\n */\nfunction hasSeparators(input: string): boolean {\n return /[^A-Za-z0-9]/.test(input);\n}\n\nfunction extractIdentifierParts(input: string): string[] {\n return input.match(IDENTIFIER_PART_PATTERN) ?? [];\n}\n\nfunction createSyntheticIdentifier(input: string): string {\n let hash = 2166136261;\n\n for (const char of input) {\n hash ^= char.codePointAt(0) ?? 0;\n hash = Math.imul(hash, 16777619);\n }\n\n return `x${(hash >>> 0).toString(16)}`;\n}\n\nfunction sanitizeIdentifierCharacters(input: string): string {\n const sanitized = input.replace(/[^\\w]/g, '');\n return sanitized.length > 0 ? sanitized : createSyntheticIdentifier(input);\n}\n\nfunction capitalize(word: string): string {\n return word.charAt(0).toUpperCase() + word.slice(1);\n}\n\n/**\n * Converts a normalized identifier to PascalCase.\n */\nfunction snakeToPascalCase(input: string): string {\n const parts = extractIdentifierParts(input);\n if (parts.length === 0) {\n return capitalize(sanitizeIdentifierCharacters(input));\n }\n return parts.map(capitalize).join('');\n}\n\n/**\n * Converts a normalized identifier to camelCase.\n */\nfunction snakeToCamelCase(input: string): string {\n const parts = extractIdentifierParts(input);\n if (parts.length === 0) {\n return sanitizeIdentifierCharacters(input);\n }\n const [firstPart = input, ...rest] = parts;\n return firstPart.charAt(0).toLowerCase() + firstPart.slice(1) + rest.map(capitalize).join('');\n}\n\n/**\n * Checks if a name needs escaping (reserved word or starts with digit).\n */\nfunction needsEscaping(name: string): boolean {\n return PSL_RESERVED_WORDS.has(name.toLowerCase()) || /^\\d/.test(name);\n}\n\n/**\n * Escapes a name by prefixing with underscore.\n */\nfunction escapeName(name: string): string {\n return `_${name}`;\n}\n\nfunction escapeIfNeeded(name: string): string {\n return needsEscaping(name) ? escapeName(name) : name;\n}\n\n/**\n * Converts a database table name to a PSL model name.\n * snake_case → PascalCase, with @@map(\"db_name\") when the name was transformed.\n * Names that are already PascalCase (no separators, start with uppercase) are kept as-is.\n */\nexport function toModelName(tableName: string): NameResult {\n let name: string;\n\n if (hasSeparators(tableName)) {\n name = snakeToPascalCase(tableName);\n } else {\n // Ensure first character is uppercase\n name = tableName.charAt(0).toUpperCase() + tableName.slice(1);\n }\n\n if (needsEscaping(name)) {\n const escaped = escapeName(name);\n return { name: escaped, map: tableName };\n }\n\n if (name !== tableName) {\n return { name, map: tableName };\n }\n\n return { name };\n}\n\n/**\n * Converts a database column name to a PSL field name.\n * snake_case → camelCase, with @map(\"db_col\") when the name was transformed.\n * Names that are already camelCase (no separators, start with lowercase) are kept as-is.\n */\nexport function toFieldName(columnName: string): NameResult {\n let name: string;\n\n if (hasSeparators(columnName)) {\n name = snakeToCamelCase(columnName);\n } else {\n // Ensure first character is lowercase\n name = columnName.charAt(0).toLowerCase() + columnName.slice(1);\n }\n\n if (needsEscaping(name)) {\n const escaped = escapeName(name);\n return { name: escaped, map: columnName };\n }\n\n if (name !== columnName) {\n return { name, map: columnName };\n }\n\n return { name };\n}\n\n/**\n * Converts a Postgres enum type name to a PSL enum name.\n * snake_case → PascalCase, with @@map when transformed.\n */\nexport function toEnumName(pgTypeName: string): NameResult {\n let name: string;\n\n if (hasSeparators(pgTypeName)) {\n name = snakeToPascalCase(pgTypeName);\n } else {\n name = pgTypeName.charAt(0).toUpperCase() + pgTypeName.slice(1);\n }\n\n if (needsEscaping(name)) {\n const escaped = escapeName(name);\n return { name: escaped, map: pgTypeName };\n }\n\n if (name !== pgTypeName) {\n return { name, map: pgTypeName };\n }\n\n return { name };\n}\n\n/**\n * Simple English pluralization for back-relation field names.\n * Handles: s→ses, y→ies, default→s\n */\nexport function pluralize(word: string): string {\n if (\n word.endsWith('s') ||\n word.endsWith('x') ||\n word.endsWith('z') ||\n word.endsWith('ch') ||\n word.endsWith('sh')\n ) {\n return `${word}es`;\n }\n if (word.endsWith('y') && !/[aeiou]y$/i.test(word)) {\n return `${word.slice(0, -1)}ies`;\n }\n return `${word}s`;\n}\n\n/**\n * Derives a relation field name from FK column names.\n *\n * For single-column FKs: strip _id/Id suffix, camelCase the result.\n * For composite FKs: use the referenced table name (lowercased, camelCased).\n */\nexport function deriveRelationFieldName(\n fkColumns: readonly string[],\n referencedTableName: string,\n): string {\n if (fkColumns.length === 1) {\n const [col = referencedTableName] = fkColumns;\n // Strip common FK suffixes\n const stripped = col.replace(/_id$/i, '').replace(/Id$/, '');\n\n if (stripped.length > 0 && stripped !== col) {\n return escapeIfNeeded(snakeToCamelCase(stripped));\n }\n // If stripping didn't change anything, use the referenced table name\n return escapeIfNeeded(snakeToCamelCase(referencedTableName));\n }\n\n // Composite FK: use referenced table name\n return escapeIfNeeded(snakeToCamelCase(referencedTableName));\n}\n\n/**\n * Derives a back-relation field name.\n * For 1:N: pluralize the child model name (lowercased first char).\n * For 1:1: lowercase first char of child model name.\n */\nexport function deriveBackRelationFieldName(childModelName: string, isOneToOne: boolean): string {\n const base = childModelName.charAt(0).toLowerCase() + childModelName.slice(1);\n return isOneToOne ? base : pluralize(base);\n}\n\n/**\n * Converts a column name to a named type name for the types block.\n * E.g., column \"email\" with type \"character varying(255)\" → \"Email\"\n */\nexport function toNamedTypeName(columnName: string): string {\n let name: string;\n\n if (hasSeparators(columnName)) {\n name = snakeToPascalCase(columnName);\n } else {\n name = columnName.charAt(0).toUpperCase() + columnName.slice(1);\n }\n\n return escapeIfNeeded(name);\n}\n","import type { SqlForeignKeyIR } from '@prisma-next/sql-schema-ir/types';\nimport { deriveBackRelationFieldName, deriveRelationFieldName, pluralize } from './name-transforms';\nimport type { PslPrintableSqlTable } from './schema-validation';\nimport type { RelationField } from './types';\n\n/**\n * Default referential actions — when the FK uses these, we omit them from the PSL output.\n */\nconst DEFAULT_ON_DELETE = 'noAction';\nconst DEFAULT_ON_UPDATE = 'noAction';\n\n/**\n * Maps SqlReferentialAction to PSL-compatible casing.\n */\nconst REFERENTIAL_ACTION_PSL: Record<string, string> = {\n noAction: 'NoAction',\n restrict: 'Restrict',\n cascade: 'Cascade',\n setNull: 'SetNull',\n setDefault: 'SetDefault',\n};\n\nexport type InferredRelations = {\n /** Relation fields keyed by table name → array of fields to add */\n readonly relationsByTable: ReadonlyMap<string, readonly RelationField[]>;\n};\n\n/**\n * Infers relation fields from foreign keys across all tables.\n *\n * For each FK:\n * 1. Creates a relation field on the child table (the table with the FK)\n * 2. Creates a back-relation field on the parent table (the referenced table)\n * 3. Detects 1:1 vs 1:N cardinality\n * 4. Handles multiple FKs to the same parent (named relations)\n * 5. Handles self-referencing FKs\n */\nexport function inferRelations(\n tables: Record<string, PslPrintableSqlTable>,\n modelNameMap: ReadonlyMap<string, string>,\n): InferredRelations {\n const relationsByTable = new Map<string, RelationField[]>();\n\n // Track FK count from each child table to each parent table, for disambiguation\n const fkCountByPair = new Map<string, number>();\n for (const table of Object.values(tables)) {\n for (const fk of table.foreignKeys) {\n const pairKey = `${table.name}→${fk.referencedTable}`;\n fkCountByPair.set(pairKey, (fkCountByPair.get(pairKey) ?? 0) + 1);\n }\n }\n\n // Track which field names are used per table (including existing columns) for collision avoidance\n const usedFieldNames = new Map<string, Set<string>>();\n for (const table of Object.values(tables)) {\n const names = new Set<string>();\n for (const col of Object.values(table.columns)) {\n names.add(col.name);\n }\n usedFieldNames.set(table.name, names);\n }\n\n for (const table of Object.values(tables)) {\n for (const fk of table.foreignKeys) {\n const childTableName = table.name;\n const parentTableName = fk.referencedTable;\n const childUsed = usedFieldNames.get(childTableName) as Set<string>;\n const childModelName = modelNameMap.get(childTableName) ?? childTableName;\n const parentModelName = modelNameMap.get(parentTableName) ?? parentTableName;\n const pairKey = `${childTableName}→${parentTableName}`;\n const isSelfRelation = childTableName === parentTableName;\n const needsRelationName = (fkCountByPair.get(pairKey) as number) > 1 || isSelfRelation;\n\n // Determine cardinality\n const isOneToOne = detectOneToOne(fk, table);\n\n // Child table: relation field (e.g., author User @relation(...))\n const childRelFieldName = resolveUniqueFieldName(\n deriveRelationFieldName(fk.columns, parentTableName),\n childUsed,\n parentModelName,\n );\n const relationName = needsRelationName\n ? deriveRelationName(fk, childRelFieldName, parentModelName, isSelfRelation)\n : undefined;\n const childOptional = fk.columns.some(\n (columnName) => table.columns[columnName]?.nullable ?? false,\n );\n\n const childRelField = buildChildRelationField(\n childRelFieldName,\n parentModelName,\n fk,\n childOptional,\n relationName,\n );\n\n addRelationField(relationsByTable, childTableName, childRelField);\n childUsed.add(childRelFieldName);\n\n // Parent table: back-relation field (e.g., posts Post[])\n const parentUsed = usedFieldNames.get(parentTableName) ?? new Set();\n usedFieldNames.set(parentTableName, parentUsed);\n\n const backRelFieldName = resolveUniqueFieldName(\n deriveBackRelationFieldName(childModelName, isOneToOne),\n parentUsed,\n childModelName,\n );\n\n const backRelField: RelationField = {\n fieldName: backRelFieldName,\n typeName: childModelName,\n optional: isOneToOne,\n list: !isOneToOne,\n relationName,\n };\n\n addRelationField(relationsByTable, parentTableName, backRelField);\n parentUsed.add(backRelFieldName);\n }\n }\n\n return { relationsByTable };\n}\n\n/**\n * Detects whether a FK represents a 1:1 relationship.\n * A FK is 1:1 if:\n * - The FK columns exactly match the table's PK columns, OR\n * - The FK columns exactly match a unique constraint\n */\nfunction detectOneToOne(fk: SqlForeignKeyIR, table: PslPrintableSqlTable): boolean {\n const fkCols = [...fk.columns].sort();\n\n // FK columns == PK columns → 1:1\n if (table.primaryKey) {\n const pkCols = [...table.primaryKey.columns].sort();\n if (pkCols.length === fkCols.length && pkCols.every((c, i) => c === fkCols[i])) {\n return true;\n }\n }\n\n // FK columns == unique columns → 1:1\n for (const unique of table.uniques) {\n const uniqueCols = [...unique.columns].sort();\n if (uniqueCols.length === fkCols.length && uniqueCols.every((c, i) => c === fkCols[i])) {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Derives a relation name for disambiguation.\n * Uses the FK constraint name if available, otherwise generates one from the relation shape.\n */\nfunction deriveRelationName(\n fk: SqlForeignKeyIR,\n childRelationFieldName: string,\n parentModelName: string,\n isSelfRelation: boolean,\n): string {\n if (fk.name) {\n return fk.name;\n }\n if (isSelfRelation) {\n return `${childRelationFieldName.charAt(0).toUpperCase() + childRelationFieldName.slice(1)}${pluralize(parentModelName)}`;\n }\n return fk.columns.join('_');\n}\n\n/**\n * Builds a child-side relation field with @relation attributes.\n */\nfunction buildChildRelationField(\n fieldName: string,\n parentModelName: string,\n fk: SqlForeignKeyIR,\n optional: boolean,\n relationName?: string,\n): RelationField {\n const onDelete = fk.onDelete && fk.onDelete !== DEFAULT_ON_DELETE ? fk.onDelete : undefined;\n const onUpdate = fk.onUpdate && fk.onUpdate !== DEFAULT_ON_UPDATE ? fk.onUpdate : undefined;\n\n return {\n fieldName,\n typeName: parentModelName,\n referencedTableName: fk.referencedTable,\n optional,\n list: false,\n relationName,\n fkName: fk.name,\n fields: fk.columns,\n references: fk.referencedColumns,\n onDelete: onDelete ? REFERENTIAL_ACTION_PSL[onDelete] : undefined,\n onUpdate: onUpdate ? REFERENTIAL_ACTION_PSL[onUpdate] : undefined,\n };\n}\n\n/**\n * Resolves a unique field name by appending the model name if there's a collision.\n */\nfunction resolveUniqueFieldName(\n desired: string,\n usedNames: ReadonlySet<string>,\n fallbackSuffix: string,\n): string {\n if (!usedNames.has(desired)) {\n return desired;\n }\n\n // Try appending the model name\n const withSuffix = `${desired}${fallbackSuffix}`;\n if (!usedNames.has(withSuffix)) {\n return withSuffix;\n }\n\n // Last resort: append a number\n let counter = 2;\n while (usedNames.has(`${desired}${counter}`)) {\n counter++;\n }\n return `${desired}${counter}`;\n}\n\nfunction addRelationField(\n map: Map<string, RelationField[]>,\n tableName: string,\n field: RelationField,\n): void {\n const existing = map.get(tableName);\n if (existing) {\n existing.push(field);\n } else {\n map.set(tableName, [field]);\n }\n}\n","import type { ColumnDefault } from '@prisma-next/contract/types';\nimport { mapDefault } from './default-mapping';\nimport { toEnumName, toFieldName, toModelName, toNamedTypeName } from './name-transforms';\nimport { inferRelations } from './relation-inference';\nimport type {\n PrintableSqlColumnDefault,\n PslPrintableSqlSchemaIR,\n PslPrintableSqlTable,\n} from './schema-validation';\nimport type {\n EnumInfo,\n PrinterField,\n PrinterModel,\n PrinterNamedType,\n PslNativeTypeAttribute,\n PslPrinterOptions,\n RelationField,\n} from './types';\n\nconst DEFAULT_HEADER = '// This file was introspected from the database. Do not edit manually.';\n\n/**\n * Escapes a string for use inside a PSL double-quoted context (e.g., @map(\"...\"), @relation(name: \"...\")).\n * Prevents malformed PSL when database identifiers contain `\"` or newlines.\n */\nfunction escapePslString(value: string): string {\n return value\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/\"/g, '\\\\\"')\n .replace(/\\n/g, '\\\\n')\n .replace(/\\r/g, '\\\\r');\n}\n\ntype ResolvedColumnFieldName = {\n readonly fieldName: string;\n readonly fieldMap?: string | undefined;\n};\n\ntype TableColumnFieldNameMap = ReadonlyMap<string, ResolvedColumnFieldName>;\n\ntype NamedTypeRegistry = {\n readonly entriesByKey: Map<string, PrinterNamedType>;\n readonly usedNames: Set<string>;\n};\n\ntype TopLevelNameResult = {\n readonly name: string;\n readonly map?: string | undefined;\n};\n\nconst PSL_IDENTIFIER_PATTERN = /^[A-Za-z_]\\w*$/;\nconst ENUM_MEMBER_RESERVED_WORDS = new Set([\n 'datasource',\n 'default',\n 'enum',\n 'generator',\n 'model',\n 'type',\n 'types',\n]);\nconst PSL_SCALAR_TYPE_NAMES = new Set([\n 'String',\n 'Boolean',\n 'Int',\n 'BigInt',\n 'Float',\n 'Decimal',\n 'DateTime',\n 'Json',\n 'Bytes',\n]);\n\n/**\n * Converts a SqlSchemaIR to a PSL (Prisma Schema Language) string.\n *\n * The output follows PSL formatting conventions:\n * - Header comment\n * - `types` block (if parameterized types exist)\n * - `enum` blocks (alphabetical)\n * - `model` blocks (topologically sorted by FK deps, alphabetical fallback)\n *\n * @param schemaIR - The introspected schema IR\n * @param options - Printer configuration (type map, header)\n * @returns A valid PSL string\n */\nexport function printPsl(schemaIR: PslPrintableSqlSchemaIR, options: PslPrinterOptions): string {\n const { typeMap, header, defaultMapping, enumInfo, parseRawDefault } = options;\n const headerComment = header ?? DEFAULT_HEADER;\n\n const emptyEnumInfo: EnumInfo = {\n typeNames: new Set<string>(),\n definitions: new Map<string, readonly string[]>(),\n };\n const { typeNames: enumTypeNames, definitions: enumDefinitions } = enumInfo ?? emptyEnumInfo;\n\n // Build model name mapping (db table name → PSL model name)\n const modelNames = buildTopLevelNameMap(\n Object.keys(schemaIR.tables),\n toModelName,\n 'model',\n 'table',\n );\n\n // Build enum name mapping (db type name → PSL enum name)\n const enumNames = buildTopLevelNameMap(enumTypeNames, toEnumName, 'enum', 'enum type');\n assertNoCrossKindNameCollisions(modelNames, enumNames);\n\n const modelNameMap = new Map(\n [...modelNames].map(([tableName, result]) => [tableName, result.name]),\n );\n const enumNameMap = new Map(\n [...enumNames].map(([pgTypeName, result]) => [pgTypeName, result.name]),\n );\n const reservedNamedTypeNames = createReservedNamedTypeNames(modelNames, enumNames);\n\n const fieldNamesByTable = buildFieldNamesByTable(schemaIR.tables);\n\n // Infer relations from foreign keys\n const { relationsByTable } = inferRelations(schemaIR.tables, modelNameMap);\n\n // Collect named types for the types block\n const namedTypes = seedNamedTypeRegistry(schemaIR, typeMap, enumNameMap, reservedNamedTypeNames);\n\n // Process tables into models\n const models: PrinterModel[] = [];\n for (const table of Object.values(schemaIR.tables)) {\n const model = processTable(\n table,\n typeMap,\n enumNameMap,\n fieldNamesByTable,\n namedTypes,\n defaultMapping,\n parseRawDefault,\n relationsByTable.get(table.name) ?? [],\n );\n models.push(model);\n }\n\n // Process enums\n const enums: Array<{ name: string; mapName: string | undefined; values: readonly string[] }> = [];\n for (const [pgTypeName, values] of enumDefinitions) {\n const enumName = enumNames.get(pgTypeName) as TopLevelNameResult;\n enums.push({ name: enumName.name, mapName: enumName.map, values });\n }\n\n // Sort enums alphabetically\n enums.sort((a, b) => a.name.localeCompare(b.name));\n\n // Sort models topologically by FK dependencies\n const sortedModels = topologicalSort(models, schemaIR.tables, modelNameMap);\n\n // Serialize\n const sections: string[] = [];\n\n // Header\n sections.push(headerComment);\n\n // Types block\n const namedTypeEntries = [...namedTypes.entriesByKey.values()].sort((a, b) =>\n a.name.localeCompare(b.name),\n );\n if (namedTypeEntries.length > 0) {\n sections.push(serializeTypesBlock(namedTypeEntries));\n }\n\n // Enum blocks\n for (const e of enums) {\n sections.push(serializeEnum(e));\n }\n\n // Model blocks\n for (const model of sortedModels) {\n sections.push(serializeModel(model));\n }\n\n return `${sections.join('\\n\\n')}\\n`;\n}\n\n/**\n * Processes a SQL table into a PrinterModel.\n */\nfunction processTable(\n table: PslPrintableSqlTable,\n typeMap: PslPrinterOptions['typeMap'],\n enumNameMap: ReadonlyMap<string, string>,\n fieldNamesByTable: ReadonlyMap<string, TableColumnFieldNameMap>,\n namedTypes: NamedTypeRegistry,\n defaultMapping: PslPrinterOptions['defaultMapping'],\n rawDefaultParser: PslPrinterOptions['parseRawDefault'],\n relationFields: readonly RelationField[],\n): PrinterModel {\n const { name: modelName, map: mapName } = toModelName(table.name);\n const fieldNameMap = fieldNamesByTable.get(table.name);\n\n const pkColumns = new Set(table.primaryKey?.columns ?? []);\n const isSinglePk = pkColumns.size === 1;\n const singlePkConstraintName = isSinglePk ? table.primaryKey?.name : undefined;\n\n // Build lookup for unique single-column constraints.\n const uniqueColumns = new Map<string, string | undefined>();\n for (const unique of table.uniques) {\n if (unique.columns.length === 1) {\n const [columnName = ''] = unique.columns;\n const existingConstraintName = uniqueColumns.get(columnName);\n if (!uniqueColumns.has(columnName) || (existingConstraintName === undefined && unique.name)) {\n uniqueColumns.set(columnName, unique.name);\n }\n }\n }\n\n // Process columns into fields\n const fields: PrinterField[] = [];\n const columnEntries = Object.values(table.columns);\n\n for (const column of columnEntries) {\n const resolvedField = fieldNameMap?.get(column.name);\n const fieldName = resolvedField?.fieldName ?? toFieldName(column.name).name;\n const fieldMap = resolvedField?.fieldMap;\n\n // Resolve type\n const resolution = typeMap.resolve(column.nativeType, table.annotations);\n\n if ('unsupported' in resolution) {\n // Unsupported type\n fields.push({\n name: fieldName,\n typeName: `Unsupported(\"${escapePslString(resolution.nativeType)}\")`,\n optional: column.nullable,\n list: false,\n attributes: fieldMap ? [`@map(\"${escapePslString(fieldMap)}\")`] : [],\n mapName: fieldMap ?? undefined,\n isId: false,\n isRelation: false,\n isUnsupported: true,\n });\n continue;\n }\n\n // Check if this is an enum type\n let typeName = resolution.pslType;\n const enumPslName = enumNameMap.get(column.nativeType);\n if (enumPslName) {\n typeName = enumPslName;\n }\n\n // Preserve non-default native storage shapes via named types.\n if (resolution.nativeTypeAttribute && !enumPslName) {\n typeName = resolveNamedTypeName(namedTypes, resolution);\n }\n\n // Build attributes\n const attributes: string[] = [];\n const isId = isSinglePk && pkColumns.has(column.name);\n if (isId) {\n attributes.push(formatFieldConstraintAttribute('@id', singlePkConstraintName));\n }\n\n // Default value\n let comment: string | undefined;\n if (column.default !== undefined) {\n const parsed = parseDefaultIfNeeded(column.default, column.nativeType, rawDefaultParser);\n if (parsed) {\n const result = mapDefault(parsed, defaultMapping);\n if ('attribute' in result) {\n attributes.push(result.attribute);\n } else {\n comment = result.comment;\n }\n }\n }\n\n // Unique\n const uniqueConstraintName = uniqueColumns.get(column.name);\n if (uniqueConstraintName !== undefined || uniqueColumns.has(column.name)) {\n if (!isId) {\n attributes.push(formatFieldConstraintAttribute('@unique', uniqueConstraintName));\n }\n }\n\n // Map\n if (fieldMap) {\n attributes.push(`@map(\"${escapePslString(fieldMap)}\")`);\n }\n\n fields.push({\n name: fieldName,\n typeName,\n optional: column.nullable,\n list: false,\n attributes,\n mapName: fieldMap ?? undefined,\n isId,\n isRelation: false,\n isUnsupported: false,\n comment,\n });\n }\n\n // Add relation fields\n const usedFieldNames = new Set(fields.map((field) => field.name));\n for (const rel of relationFields) {\n const relationFieldName = createUniqueFieldName(rel.fieldName, usedFieldNames);\n const relAttributes: string[] = [];\n\n if (rel.fields && rel.references) {\n const parts: string[] = [];\n if (rel.relationName) {\n parts.push(`name: \"${escapePslString(rel.relationName)}\"`);\n }\n parts.push(\n `fields: [${rel.fields\n .map((fieldName) => resolveColumnFieldName(fieldNamesByTable, table.name, fieldName))\n .join(', ')}]`,\n );\n parts.push(\n `references: [${rel.references\n .map((fieldName) =>\n resolveColumnFieldName(fieldNamesByTable, rel.referencedTableName ?? '', fieldName),\n )\n .join(', ')}]`,\n );\n if (rel.onDelete) {\n parts.push(`onDelete: ${rel.onDelete}`);\n }\n if (rel.onUpdate) {\n parts.push(`onUpdate: ${rel.onUpdate}`);\n }\n if (rel.fkName) {\n parts.push(`map: \"${escapePslString(rel.fkName)}\"`);\n }\n relAttributes.push(`@relation(${parts.join(', ')})`);\n } else if (rel.relationName) {\n relAttributes.push(`@relation(name: \"${escapePslString(rel.relationName)}\")`);\n }\n\n fields.push({\n name: relationFieldName,\n typeName: rel.typeName,\n optional: rel.optional,\n list: rel.list,\n attributes: relAttributes,\n isId: false,\n isRelation: true,\n isUnsupported: false,\n });\n usedFieldNames.add(relationFieldName);\n }\n\n // Model-level attributes\n const modelAttributes: string[] = [];\n\n // Composite PK\n if (table.primaryKey && table.primaryKey.columns.length > 1) {\n const pkFieldNames = table.primaryKey.columns.map((columnName) =>\n resolveColumnFieldName(fieldNamesByTable, table.name, columnName),\n );\n modelAttributes.push(\n formatModelConstraintAttribute('@@id', pkFieldNames, table.primaryKey.name),\n );\n }\n\n // Composite unique constraints\n for (const unique of table.uniques) {\n if (unique.columns.length > 1) {\n const fieldNames = unique.columns.map((columnName) =>\n resolveColumnFieldName(fieldNamesByTable, table.name, columnName),\n );\n modelAttributes.push(formatModelConstraintAttribute('@@unique', fieldNames, unique.name));\n }\n }\n\n // Indexes (non-unique only; unique indexes are handled by @@unique)\n for (const index of table.indexes) {\n if (!index.unique) {\n const fieldNames = index.columns.map((columnName) =>\n resolveColumnFieldName(fieldNamesByTable, table.name, columnName),\n );\n modelAttributes.push(formatModelConstraintAttribute('@@index', fieldNames, index.name));\n }\n }\n\n // @@map\n if (mapName) {\n modelAttributes.push(`@@map(\"${escapePslString(mapName)}\")`);\n }\n\n // Table without PK warning\n const tableComment = !table.primaryKey\n ? '// WARNING: This table has no primary key in the database'\n : undefined;\n\n return {\n name: modelName,\n mapName: mapName ?? undefined,\n fields,\n modelAttributes,\n comment: tableComment,\n };\n}\n\nfunction parseDefaultIfNeeded(\n value: PrintableSqlColumnDefault,\n nativeType: string | undefined,\n rawDefaultParser: PslPrinterOptions['parseRawDefault'],\n): ColumnDefault | undefined {\n if (typeof value === 'string') {\n return rawDefaultParser ? rawDefaultParser(value, nativeType) : undefined;\n }\n return value;\n}\n\nfunction formatFieldConstraintAttribute(\n attribute: '@id' | '@unique',\n constraintName?: string,\n): string {\n return constraintName ? `${attribute}(map: \"${escapePslString(constraintName)}\")` : attribute;\n}\n\nfunction formatModelConstraintAttribute(\n attribute: '@@id' | '@@unique' | '@@index',\n fields: readonly string[],\n constraintName?: string,\n): string {\n const parts = [`[${fields.join(', ')}]`];\n if (constraintName) {\n parts.push(`map: \"${escapePslString(constraintName)}\"`);\n }\n return `${attribute}(${parts.join(', ')})`;\n}\n\nfunction buildFieldNamesByTable(\n tables: Record<string, PslPrintableSqlTable>,\n): ReadonlyMap<string, TableColumnFieldNameMap> {\n const fieldNamesByTable = new Map<string, TableColumnFieldNameMap>();\n\n for (const table of Object.values(tables)) {\n const columns = Object.values(table.columns).map((column, index) => {\n const { name, map } = toFieldName(column.name);\n return {\n columnName: column.name,\n desiredFieldName: name,\n fieldMap: map,\n index,\n };\n });\n\n const assignmentOrder = [...columns].sort((left, right) => {\n const mapComparison =\n Number(left.fieldMap !== undefined) - Number(right.fieldMap !== undefined);\n if (mapComparison !== 0) {\n return mapComparison;\n }\n return left.index - right.index;\n });\n\n const usedFieldNames = new Set<string>();\n const tableFieldNames = new Map<string, ResolvedColumnFieldName>();\n\n for (const column of assignmentOrder) {\n const fieldName = createUniqueFieldName(column.desiredFieldName, usedFieldNames);\n usedFieldNames.add(fieldName);\n tableFieldNames.set(column.columnName, {\n fieldName,\n fieldMap: column.fieldMap,\n });\n }\n\n fieldNamesByTable.set(table.name, tableFieldNames);\n }\n\n return fieldNamesByTable;\n}\n\nfunction resolveColumnFieldName(\n fieldNamesByTable: ReadonlyMap<string, TableColumnFieldNameMap>,\n tableName: string,\n columnName: string,\n): string {\n return (\n fieldNamesByTable.get(tableName)?.get(columnName)?.fieldName ?? toFieldName(columnName).name\n );\n}\n\nfunction createUniqueFieldName(desiredName: string, usedFieldNames: ReadonlySet<string>): string {\n if (!usedFieldNames.has(desiredName)) {\n return desiredName;\n }\n\n let counter = 2;\n while (usedFieldNames.has(`${desiredName}${counter}`)) {\n counter++;\n }\n return `${desiredName}${counter}`;\n}\n\nfunction buildTopLevelNameMap(\n sources: Iterable<string>,\n normalize: (source: string) => TopLevelNameResult,\n kind: 'model' | 'enum',\n sourceKind: 'table' | 'enum type',\n): Map<string, TopLevelNameResult> {\n const results = new Map<string, TopLevelNameResult>();\n const normalizedToSources = new Map<string, string[]>();\n\n for (const source of sources) {\n const normalized = normalize(source);\n results.set(source, normalized);\n normalizedToSources.set(normalized.name, [\n ...(normalizedToSources.get(normalized.name) ?? []),\n source,\n ]);\n }\n\n const duplicates = [...normalizedToSources.entries()].filter(\n ([, conflictingSources]) => conflictingSources.length > 1,\n );\n if (duplicates.length > 0) {\n const details = duplicates.map(\n ([normalizedName, conflictingSources]) =>\n `- ${kind} \"${normalizedName}\" from ${sourceKind}s ${conflictingSources\n .map((source) => `\"${source}\"`)\n .join(', ')}`,\n );\n throw new Error(`PSL ${kind} name collisions detected:\\n${details.join('\\n')}`);\n }\n\n return results;\n}\n\nfunction assertNoCrossKindNameCollisions(\n modelNames: ReadonlyMap<string, TopLevelNameResult>,\n enumNames: ReadonlyMap<string, TopLevelNameResult>,\n): void {\n const enumSourceByName = new Map([...enumNames].map(([source, result]) => [result.name, source]));\n\n const collisions = [...modelNames.entries()]\n .map(([tableName, result]) => {\n const enumSource = enumSourceByName.get(result.name);\n return enumSource\n ? `- identifier \"${result.name}\" from table \"${tableName}\" collides with enum type \"${enumSource}\"`\n : undefined;\n })\n .filter((detail): detail is string => detail !== undefined);\n\n if (collisions.length > 0) {\n throw new Error(`PSL top-level name collisions detected:\\n${collisions.join('\\n')}`);\n }\n}\n\nfunction createReservedNamedTypeNames(\n modelNames: ReadonlyMap<string, TopLevelNameResult>,\n enumNames: ReadonlyMap<string, TopLevelNameResult>,\n): Set<string> {\n const reservedNames = new Set<string>(PSL_SCALAR_TYPE_NAMES);\n\n for (const result of modelNames.values()) {\n reservedNames.add(result.name);\n }\n\n for (const result of enumNames.values()) {\n reservedNames.add(result.name);\n }\n\n return reservedNames;\n}\n\nfunction seedNamedTypeRegistry(\n schemaIR: PslPrintableSqlSchemaIR,\n typeMap: PslPrinterOptions['typeMap'],\n enumNameMap: ReadonlyMap<string, string>,\n reservedNames: ReadonlySet<string>,\n): NamedTypeRegistry {\n const seeds = new Map<\n string,\n {\n readonly baseType: string;\n readonly desiredName: string;\n readonly attributes: readonly string[];\n }\n >();\n\n for (const tableName of Object.keys(schemaIR.tables).sort()) {\n const table = schemaIR.tables[tableName];\n if (!table) {\n continue;\n }\n\n for (const columnName of Object.keys(table.columns).sort()) {\n const column = table.columns[columnName];\n if (!column) {\n continue;\n }\n\n const resolution = typeMap.resolve(column.nativeType, table.annotations);\n if (\n 'unsupported' in resolution ||\n enumNameMap.has(column.nativeType) ||\n !resolution.nativeTypeAttribute\n ) {\n continue;\n }\n\n const signatureKey = createNamedTypeSignatureKey(resolution);\n if (!seeds.has(signatureKey)) {\n seeds.set(signatureKey, {\n baseType: resolution.pslType,\n desiredName: toNamedTypeName(column.name),\n attributes: [renderNativeTypeAttribute(resolution.nativeTypeAttribute)],\n });\n }\n }\n }\n\n const registry: NamedTypeRegistry = {\n entriesByKey: new Map<string, PrinterNamedType>(),\n usedNames: new Set<string>(reservedNames),\n };\n\n const sortedSeeds = [...seeds.entries()].sort((left, right) => {\n const desiredNameComparison = left[1].desiredName.localeCompare(right[1].desiredName);\n if (desiredNameComparison !== 0) {\n return desiredNameComparison;\n }\n return left[0].localeCompare(right[0]);\n });\n\n for (const [signatureKey, seed] of sortedSeeds) {\n const name = createUniqueFieldName(seed.desiredName, registry.usedNames);\n registry.entriesByKey.set(signatureKey, {\n name,\n baseType: seed.baseType,\n attributes: seed.attributes,\n });\n registry.usedNames.add(name);\n }\n\n return registry;\n}\n\nfunction resolveNamedTypeName(\n registry: NamedTypeRegistry,\n resolution: {\n readonly pslType: string;\n readonly nativeType: string;\n readonly typeParams?: Record<string, unknown>;\n readonly nativeTypeAttribute?: PslNativeTypeAttribute;\n },\n): string {\n const key = createNamedTypeSignatureKey(resolution);\n const existing = registry.entriesByKey.get(key);\n if (existing) {\n return existing.name;\n }\n\n throw new Error(`Named type registry was not seeded for native type \"${resolution.nativeType}\"`);\n}\n\nfunction createNamedTypeSignatureKey(resolution: {\n readonly pslType: string;\n readonly nativeType: string;\n readonly typeParams?: Record<string, unknown>;\n readonly nativeTypeAttribute?: PslNativeTypeAttribute;\n}): string {\n return JSON.stringify({\n baseType: resolution.pslType,\n nativeTypeAttribute: resolution.nativeTypeAttribute\n ? {\n name: resolution.nativeTypeAttribute.name,\n args: resolution.nativeTypeAttribute.args ?? null,\n }\n : null,\n });\n}\n\nfunction isNormalizedEnumMemberReservedWord(value: string): boolean {\n return ENUM_MEMBER_RESERVED_WORDS.has(value.toLowerCase());\n}\n\nfunction normalizeEnumMemberName(value: string, usedNames: ReadonlySet<string>): string {\n const desiredName =\n PSL_IDENTIFIER_PATTERN.test(value) && !isNormalizedEnumMemberReservedWord(value)\n ? value\n : createNormalizedEnumMemberBaseName(value);\n\n return createUniqueFieldName(desiredName, usedNames);\n}\n\nfunction createNormalizedEnumMemberBaseName(value: string): string {\n const tokens = value.match(/[A-Za-z0-9]+/g)?.map((token) => token.toLowerCase()) ?? [];\n let normalized = tokens[0] ?? 'value';\n\n for (const token of tokens.slice(1)) {\n normalized += token.charAt(0).toUpperCase() + token.slice(1);\n }\n\n if (isNormalizedEnumMemberReservedWord(normalized) || /^\\d/.test(normalized)) {\n normalized = `_${normalized}`;\n }\n\n return normalized;\n}\n\n/**\n * Topologically sorts models by FK dependencies.\n * Parent tables (those referenced by FKs) come before child tables.\n * Alphabetical fallback for cycles.\n */\nfunction topologicalSort(\n models: PrinterModel[],\n tables: Record<string, PslPrintableSqlTable>,\n modelNameMap: ReadonlyMap<string, string>,\n): PrinterModel[] {\n const modelByName = new Map<string, PrinterModel>();\n for (const model of models) {\n modelByName.set(model.name, model);\n }\n\n // Build adjacency: model name → set of model names it depends on (via FK)\n const deps = new Map<string, Set<string>>();\n const tableToModel = new Map<string, string>();\n for (const tableName of Object.keys(tables)) {\n const modelName = modelNameMap.get(tableName) as string;\n tableToModel.set(tableName, modelName);\n deps.set(modelName, new Set());\n }\n\n for (const [tableName, table] of Object.entries(tables)) {\n const modelName = tableToModel.get(tableName) as string;\n for (const fk of table.foreignKeys) {\n const refModelName = tableToModel.get(fk.referencedTable);\n if (refModelName && refModelName !== modelName) {\n (deps.get(modelName) as Set<string>).add(refModelName);\n }\n }\n }\n\n // DFS-based topological sort with cycle detection\n const result: PrinterModel[] = [];\n const visited = new Set<string>();\n const visiting = new Set<string>();\n\n // Sort model names alphabetically for deterministic output\n const sortedNames = [...deps.keys()].sort();\n\n function visit(name: string): void {\n if (visited.has(name)) return;\n if (visiting.has(name)) return; // Cycle — break it (alphabetical order handles it)\n visiting.add(name);\n\n // Visit dependencies first (parent tables before child tables)\n const sortedDeps = [...(deps.get(name) as Set<string>)].sort();\n for (const dep of sortedDeps) {\n visit(dep);\n }\n\n visiting.delete(name);\n visited.add(name);\n result.push(modelByName.get(name) as PrinterModel);\n }\n\n for (const name of sortedNames) {\n visit(name);\n }\n\n return result;\n}\n\n// ============================================================================\n// Serialization\n// ============================================================================\n\n/**\n * Serializes the `types` block.\n */\nfunction serializeTypesBlock(namedTypes: readonly PrinterNamedType[]): string {\n const lines = ['types {'];\n for (const nt of namedTypes) {\n const attrStr = nt.attributes.length > 0 ? ` ${nt.attributes.join(' ')}` : '';\n lines.push(` ${nt.name} = ${nt.baseType}${attrStr}`);\n }\n lines.push('}');\n return lines.join('\\n');\n}\n\nfunction renderNativeTypeAttribute(attribute: PslNativeTypeAttribute): string {\n if (!attribute.args || attribute.args.length === 0) {\n return `@${attribute.name}`;\n }\n return `@${attribute.name}(${attribute.args.join(', ')})`;\n}\n\n/**\n * Serializes an enum block.\n */\nfunction serializeEnum(e: {\n name: string;\n mapName?: string | undefined;\n values: readonly string[];\n}): string {\n const lines = [`enum ${e.name} {`];\n const usedNames = new Set<string>();\n for (const value of e.values) {\n const memberName = normalizeEnumMemberName(value, usedNames);\n lines.push(` ${memberName}`);\n usedNames.add(memberName);\n }\n if (e.mapName) {\n lines.push('');\n lines.push(` @@map(\"${escapePslString(e.mapName)}\")`);\n }\n lines.push('}');\n return lines.join('\\n');\n}\n\n/**\n * Serializes a model block with column-aligned fields.\n */\nfunction serializeModel(model: PrinterModel): string {\n const lines: string[] = [];\n\n if (model.comment) {\n lines.push(model.comment);\n }\n lines.push(`model ${model.name} {`);\n\n // Separate fields into groups:\n // 1. @id fields first\n // 2. Scalar fields (non-id, non-relation) in original order\n // 3. Relation fields\n const idFields = model.fields.filter((f) => f.isId);\n const scalarFields = model.fields.filter((f) => !f.isId && !f.isRelation);\n const relationFields = model.fields.filter((f) => f.isRelation);\n\n const allOrderedFields = [...idFields, ...scalarFields, ...relationFields];\n\n if (allOrderedFields.length > 0) {\n // Calculate column widths for alignment\n const maxNameLen = Math.max(...allOrderedFields.map((f) => f.name.length));\n const maxTypeLen = Math.max(...allOrderedFields.map((f) => formatFieldType(f).length));\n\n for (const field of allOrderedFields) {\n const typePart = formatFieldType(field);\n const paddedName = field.name.padEnd(maxNameLen);\n const paddedType = typePart.padEnd(maxTypeLen);\n\n if (field.comment) {\n lines.push(` ${field.comment}`);\n }\n\n const attrStr = field.attributes.length > 0 ? ` ${field.attributes.join(' ')}` : '';\n lines.push(` ${paddedName} ${paddedType}${attrStr}`.trimEnd());\n }\n }\n\n // Model-level attributes (blank line before if there are fields)\n if (model.modelAttributes.length > 0) {\n if (allOrderedFields.length > 0) {\n lines.push('');\n }\n for (const attr of model.modelAttributes) {\n lines.push(` ${attr}`);\n }\n }\n\n lines.push('}');\n return lines.join('\\n');\n}\n\n/**\n * Formats a field type string with optional/list modifiers.\n */\nfunction formatFieldType(field: PrinterField): string {\n let type = field.typeName;\n if (field.list) {\n type += '[]';\n } else if (field.optional) {\n type += '?';\n }\n return type;\n}\n","import type { ColumnDefault } from '@prisma-next/contract/types';\nimport type {\n DependencyIR,\n PrimaryKey,\n SqlAnnotations,\n SqlForeignKeyIR,\n SqlIndexIR,\n SqlReferentialAction,\n SqlSchemaIR,\n SqlUniqueIR,\n} from '@prisma-next/sql-schema-ir/types';\n\nconst REFERENTIAL_ACTIONS = new Set<SqlReferentialAction>([\n 'noAction',\n 'restrict',\n 'cascade',\n 'setNull',\n 'setDefault',\n]);\n\nexport type PrintableSqlColumnDefault = string | ColumnDefault;\n\ntype ColumnDefaultLiteralValue = Extract<ColumnDefault, { readonly kind: 'literal' }>['value'];\n\nexport type PslPrintableSqlColumn = {\n readonly name: string;\n readonly nativeType: string;\n readonly nullable: boolean;\n readonly default?: PrintableSqlColumnDefault;\n readonly annotations?: SqlAnnotations;\n};\n\nexport type PslPrintableSqlTable = {\n readonly name: string;\n readonly columns: Record<string, PslPrintableSqlColumn>;\n readonly primaryKey?: PrimaryKey;\n readonly foreignKeys: readonly SqlForeignKeyIR[];\n readonly uniques: readonly SqlUniqueIR[];\n readonly indexes: readonly SqlIndexIR[];\n readonly annotations?: SqlAnnotations;\n};\n\nexport type PslPrintableSqlSchemaIR = Omit<SqlSchemaIR, 'tables'> & {\n readonly tables: Record<string, PslPrintableSqlTable>;\n};\n\nexport function validatePrintableSqlSchemaIR(value: unknown): PslPrintableSqlSchemaIR {\n const root = expectRecord(value, 'schema');\n\n return {\n tables: validateTables(root['tables'], 'schema.tables'),\n dependencies: validateDependencies(root['dependencies'], 'schema.dependencies'),\n ...ifDefined('annotations', validateAnnotations(root['annotations'], 'schema.annotations')),\n };\n}\n\nfunction validateTables(value: unknown, path: string): Record<string, PslPrintableSqlTable> {\n const tables = expectRecord(value, path);\n const validated: Record<string, PslPrintableSqlTable> = {};\n\n for (const [tableName, tableValue] of Object.entries(tables)) {\n const tablePath = `${path}.${tableName}`;\n const table = expectRecord(tableValue, tablePath);\n\n validated[tableName] = {\n name: expectString(table['name'], `${tablePath}.name`),\n columns: validateColumns(table['columns'], `${tablePath}.columns`),\n foreignKeys: validateForeignKeys(table['foreignKeys'], `${tablePath}.foreignKeys`),\n uniques: validateUniques(table['uniques'], `${tablePath}.uniques`),\n indexes: validateIndexes(table['indexes'], `${tablePath}.indexes`),\n ...ifDefined(\n 'primaryKey',\n validatePrimaryKey(table['primaryKey'], `${tablePath}.primaryKey`),\n ),\n ...ifDefined(\n 'annotations',\n validateAnnotations(table['annotations'], `${tablePath}.annotations`),\n ),\n };\n }\n\n return validated;\n}\n\nfunction validateColumns(value: unknown, path: string): Record<string, PslPrintableSqlColumn> {\n const columns = expectRecord(value, path);\n const validated: Record<string, PslPrintableSqlColumn> = {};\n\n for (const [columnName, columnValue] of Object.entries(columns)) {\n const columnPath = `${path}.${columnName}`;\n const column = expectRecord(columnValue, columnPath);\n\n validated[columnName] = {\n name: expectString(column['name'], `${columnPath}.name`),\n nativeType: expectString(column['nativeType'], `${columnPath}.nativeType`),\n nullable: expectBoolean(column['nullable'], `${columnPath}.nullable`),\n ...ifDefined('default', validateColumnDefault(column['default'], `${columnPath}.default`)),\n ...ifDefined(\n 'annotations',\n validateAnnotations(column['annotations'], `${columnPath}.annotations`),\n ),\n };\n }\n\n return validated;\n}\n\nfunction validatePrimaryKey(value: unknown, path: string): PrimaryKey | undefined {\n if (value === undefined) {\n return undefined;\n }\n\n const primaryKey = expectRecord(value, path);\n return {\n columns: validateStringArray(primaryKey['columns'], `${path}.columns`),\n ...ifDefined('name', validateOptionalString(primaryKey['name'], `${path}.name`)),\n };\n}\n\nfunction validateForeignKeys(value: unknown, path: string): readonly SqlForeignKeyIR[] {\n const foreignKeys = expectArray(value, path);\n return foreignKeys.map((foreignKey, index) => {\n const foreignKeyPath = `${path}[${index}]`;\n const record = expectRecord(foreignKey, foreignKeyPath);\n\n return {\n columns: validateStringArray(record['columns'], `${foreignKeyPath}.columns`),\n referencedTable: expectString(record['referencedTable'], `${foreignKeyPath}.referencedTable`),\n referencedColumns: validateStringArray(\n record['referencedColumns'],\n `${foreignKeyPath}.referencedColumns`,\n ),\n ...ifDefined('name', validateOptionalString(record['name'], `${foreignKeyPath}.name`)),\n ...ifDefined(\n 'onDelete',\n validateReferentialAction(record['onDelete'], `${foreignKeyPath}.onDelete`),\n ),\n ...ifDefined(\n 'onUpdate',\n validateReferentialAction(record['onUpdate'], `${foreignKeyPath}.onUpdate`),\n ),\n ...ifDefined(\n 'annotations',\n validateAnnotations(record['annotations'], `${foreignKeyPath}.annotations`),\n ),\n };\n });\n}\n\nfunction validateUniques(value: unknown, path: string): readonly SqlUniqueIR[] {\n const uniques = expectArray(value, path);\n return uniques.map((uniqueValue, index) => {\n const uniquePath = `${path}[${index}]`;\n const unique = expectRecord(uniqueValue, uniquePath);\n\n return {\n columns: validateStringArray(unique['columns'], `${uniquePath}.columns`),\n ...ifDefined('name', validateOptionalString(unique['name'], `${uniquePath}.name`)),\n ...ifDefined(\n 'annotations',\n validateAnnotations(unique['annotations'], `${uniquePath}.annotations`),\n ),\n };\n });\n}\n\nfunction validateIndexes(value: unknown, path: string): readonly SqlIndexIR[] {\n const indexes = expectArray(value, path);\n return indexes.map((indexValue, index) => {\n const indexPath = `${path}[${index}]`;\n const record = expectRecord(indexValue, indexPath);\n\n return {\n columns: validateStringArray(record['columns'], `${indexPath}.columns`),\n unique: expectBoolean(record['unique'], `${indexPath}.unique`),\n ...ifDefined('name', validateOptionalString(record['name'], `${indexPath}.name`)),\n ...ifDefined(\n 'annotations',\n validateAnnotations(record['annotations'], `${indexPath}.annotations`),\n ),\n };\n });\n}\n\nfunction validateDependencies(value: unknown, path: string): readonly DependencyIR[] {\n const dependencies = expectArray(value, path);\n return dependencies.map((dependencyValue, index) => {\n const dependencyPath = `${path}[${index}]`;\n const dependency = expectRecord(dependencyValue, dependencyPath);\n\n return {\n id: expectString(dependency['id'], `${dependencyPath}.id`),\n };\n });\n}\n\nfunction validateAnnotations(value: unknown, path: string): SqlAnnotations | undefined {\n if (value === undefined) {\n return undefined;\n }\n\n return expectRecord(value, path);\n}\n\nfunction validateColumnDefault(\n value: unknown,\n path: string,\n): PrintableSqlColumnDefault | undefined {\n if (value === undefined) {\n return undefined;\n }\n\n if (typeof value === 'string') {\n return value;\n }\n\n const columnDefault = expectRecord(value, path);\n const kind = expectString(columnDefault['kind'], `${path}.kind`);\n\n if (kind === 'literal') {\n if (!Object.hasOwn(columnDefault, 'value')) {\n throw new Error(`${path}.value must be present for literal defaults`);\n }\n return {\n kind: 'literal',\n value: columnDefault['value'] as ColumnDefaultLiteralValue,\n };\n }\n\n if (kind === 'function') {\n return {\n kind: 'function',\n expression: expectString(columnDefault['expression'], `${path}.expression`),\n };\n }\n\n throw new Error(`${path}.kind must be \"literal\" or \"function\"`);\n}\n\nfunction validateReferentialAction(value: unknown, path: string): SqlReferentialAction | undefined {\n if (value === undefined) {\n return undefined;\n }\n\n const action = expectString(value, path) as SqlReferentialAction;\n if (!REFERENTIAL_ACTIONS.has(action)) {\n throw new Error(\n `${path} must be one of ${[...REFERENTIAL_ACTIONS].map((item) => `\"${item}\"`).join(', ')}`,\n );\n }\n return action;\n}\n\nfunction validateStringArray(value: unknown, path: string): readonly string[] {\n const items = expectArray(value, path);\n return items.map((item, index) => expectString(item, `${path}[${index}]`));\n}\n\nfunction validateOptionalString(value: unknown, path: string): string | undefined {\n if (value === undefined) {\n return undefined;\n }\n\n return expectString(value, path);\n}\n\nfunction expectRecord(value: unknown, path: string): Record<string, unknown> {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new Error(`${path} must be an object`);\n }\n\n return value as Record<string, unknown>;\n}\n\nfunction expectArray(value: unknown, path: string): readonly unknown[] {\n if (!Array.isArray(value)) {\n throw new Error(`${path} must be an array`);\n }\n\n return value;\n}\n\nfunction expectString(value: unknown, path: string): string {\n if (typeof value !== 'string') {\n throw new Error(`${path} must be a string`);\n }\n\n return value;\n}\n\nfunction expectBoolean(value: unknown, path: string): boolean {\n if (typeof value !== 'boolean') {\n throw new Error(`${path} must be a boolean`);\n }\n\n return value;\n}\n\nfunction ifDefined<TKey extends string, TValue>(\n key: TKey,\n value: TValue | undefined,\n): { readonly [K in TKey]: TValue } | Record<string, never> {\n if (value === undefined) {\n return {};\n }\n\n return { [key]: value } as { readonly [K in TKey]: TValue };\n}\n"],"mappings":";AAEA,MAAMA,8BAAgE;CACpE,mBAAmB;CACnB,SAAS;CACV;;;;;AAgBD,SAAgB,WACd,eACA,SACsB;AACtB,SAAQ,cAAc,MAAtB;EACE,KAAK,UACH,QAAO,EAAE,WAAW,YAAY,mBAAmB,cAAc,MAAM,CAAC,IAAI;EAC9E,KAAK,YAAY;GACf,MAAM,YACJ,SAAS,qBAAqB,cAAc,eAC5C,4BAA4B,cAAc,eAC1C,SAAS,4BAA4B,cAAc,WAAW;AAChE,UAAO,YACH,EAAE,WAAW,GACb,EAAE,SAAS,mBAAmB,cAAc,WAAW,QAAQ,YAAY,IAAI,IAAI;;;;;;;AAQ7F,SAAS,mBAAmB,OAAwB;AAClD,KAAI,UAAU,KACZ,QAAO;AAGT,SAAQ,OAAO,OAAf;EACE,KAAK;EACL,KAAK,SACH,QAAO,OAAO,MAAM;EACtB,KAAK,SACH,QAAO,YAAY,MAAM;EAC3B,QACE,QAAO,YAAY,KAAK,UAAU,MAAM,CAAC;;;AAI/C,SAAS,YAAY,KAAqB;AACxC,QAAO,IAAI,aAAa,IAAI,CAAC;;AAG/B,SAAS,aAAa,KAAqB;AACzC,QAAO,KAAK,UAAU,IAAI,CAAC,MAAM,GAAG,GAAG;;;;;;;;AC7DzC,MAAM,qBAAqB,IAAI,IAAI;CAAC;CAAS;CAAQ;CAAS;CAAQ;CAAa;CAAa,CAAC;AAEjG,MAAM,0BAA0B;;;;AAUhC,SAAS,cAAc,OAAwB;AAC7C,QAAO,eAAe,KAAK,MAAM;;AAGnC,SAAS,uBAAuB,OAAyB;AACvD,QAAO,MAAM,MAAM,wBAAwB,IAAI,EAAE;;AAGnD,SAAS,0BAA0B,OAAuB;CACxD,IAAI,OAAO;AAEX,MAAK,MAAM,QAAQ,OAAO;AACxB,UAAQ,KAAK,YAAY,EAAE,IAAI;AAC/B,SAAO,KAAK,KAAK,MAAM,SAAS;;AAGlC,QAAO,KAAK,SAAS,GAAG,SAAS,GAAG;;AAGtC,SAAS,6BAA6B,OAAuB;CAC3D,MAAM,YAAY,MAAM,QAAQ,UAAU,GAAG;AAC7C,QAAO,UAAU,SAAS,IAAI,YAAY,0BAA0B,MAAM;;AAG5E,SAAS,WAAW,MAAsB;AACxC,QAAO,KAAK,OAAO,EAAE,CAAC,aAAa,GAAG,KAAK,MAAM,EAAE;;;;;AAMrD,SAAS,kBAAkB,OAAuB;CAChD,MAAM,QAAQ,uBAAuB,MAAM;AAC3C,KAAI,MAAM,WAAW,EACnB,QAAO,WAAW,6BAA6B,MAAM,CAAC;AAExD,QAAO,MAAM,IAAI,WAAW,CAAC,KAAK,GAAG;;;;;AAMvC,SAAS,iBAAiB,OAAuB;CAC/C,MAAM,QAAQ,uBAAuB,MAAM;AAC3C,KAAI,MAAM,WAAW,EACnB,QAAO,6BAA6B,MAAM;CAE5C,MAAM,CAAC,YAAY,OAAO,GAAG,QAAQ;AACrC,QAAO,UAAU,OAAO,EAAE,CAAC,aAAa,GAAG,UAAU,MAAM,EAAE,GAAG,KAAK,IAAI,WAAW,CAAC,KAAK,GAAG;;;;;AAM/F,SAAS,cAAc,MAAuB;AAC5C,QAAO,mBAAmB,IAAI,KAAK,aAAa,CAAC,IAAI,MAAM,KAAK,KAAK;;;;;AAMvE,SAAS,WAAW,MAAsB;AACxC,QAAO,IAAI;;AAGb,SAAS,eAAe,MAAsB;AAC5C,QAAO,cAAc,KAAK,GAAG,WAAW,KAAK,GAAG;;;;;;;AAQlD,SAAgB,YAAY,WAA+B;CACzD,IAAIC;AAEJ,KAAI,cAAc,UAAU,CAC1B,QAAO,kBAAkB,UAAU;KAGnC,QAAO,UAAU,OAAO,EAAE,CAAC,aAAa,GAAG,UAAU,MAAM,EAAE;AAG/D,KAAI,cAAc,KAAK,CAErB,QAAO;EAAE,MADO,WAAW,KAAK;EACR,KAAK;EAAW;AAG1C,KAAI,SAAS,UACX,QAAO;EAAE;EAAM,KAAK;EAAW;AAGjC,QAAO,EAAE,MAAM;;;;;;;AAQjB,SAAgB,YAAY,YAAgC;CAC1D,IAAIA;AAEJ,KAAI,cAAc,WAAW,CAC3B,QAAO,iBAAiB,WAAW;KAGnC,QAAO,WAAW,OAAO,EAAE,CAAC,aAAa,GAAG,WAAW,MAAM,EAAE;AAGjE,KAAI,cAAc,KAAK,CAErB,QAAO;EAAE,MADO,WAAW,KAAK;EACR,KAAK;EAAY;AAG3C,KAAI,SAAS,WACX,QAAO;EAAE;EAAM,KAAK;EAAY;AAGlC,QAAO,EAAE,MAAM;;;;;;AAOjB,SAAgB,WAAW,YAAgC;CACzD,IAAIA;AAEJ,KAAI,cAAc,WAAW,CAC3B,QAAO,kBAAkB,WAAW;KAEpC,QAAO,WAAW,OAAO,EAAE,CAAC,aAAa,GAAG,WAAW,MAAM,EAAE;AAGjE,KAAI,cAAc,KAAK,CAErB,QAAO;EAAE,MADO,WAAW,KAAK;EACR,KAAK;EAAY;AAG3C,KAAI,SAAS,WACX,QAAO;EAAE;EAAM,KAAK;EAAY;AAGlC,QAAO,EAAE,MAAM;;;;;;AAOjB,SAAgB,UAAU,MAAsB;AAC9C,KACE,KAAK,SAAS,IAAI,IAClB,KAAK,SAAS,IAAI,IAClB,KAAK,SAAS,IAAI,IAClB,KAAK,SAAS,KAAK,IACnB,KAAK,SAAS,KAAK,CAEnB,QAAO,GAAG,KAAK;AAEjB,KAAI,KAAK,SAAS,IAAI,IAAI,CAAC,aAAa,KAAK,KAAK,CAChD,QAAO,GAAG,KAAK,MAAM,GAAG,GAAG,CAAC;AAE9B,QAAO,GAAG,KAAK;;;;;;;;AASjB,SAAgB,wBACd,WACA,qBACQ;AACR,KAAI,UAAU,WAAW,GAAG;EAC1B,MAAM,CAAC,MAAM,uBAAuB;EAEpC,MAAM,WAAW,IAAI,QAAQ,SAAS,GAAG,CAAC,QAAQ,OAAO,GAAG;AAE5D,MAAI,SAAS,SAAS,KAAK,aAAa,IACtC,QAAO,eAAe,iBAAiB,SAAS,CAAC;AAGnD,SAAO,eAAe,iBAAiB,oBAAoB,CAAC;;AAI9D,QAAO,eAAe,iBAAiB,oBAAoB,CAAC;;;;;;;AAQ9D,SAAgB,4BAA4B,gBAAwB,YAA6B;CAC/F,MAAM,OAAO,eAAe,OAAO,EAAE,CAAC,aAAa,GAAG,eAAe,MAAM,EAAE;AAC7E,QAAO,aAAa,OAAO,UAAU,KAAK;;;;;;AAO5C,SAAgB,gBAAgB,YAA4B;CAC1D,IAAIA;AAEJ,KAAI,cAAc,WAAW,CAC3B,QAAO,kBAAkB,WAAW;KAEpC,QAAO,WAAW,OAAO,EAAE,CAAC,aAAa,GAAG,WAAW,MAAM,EAAE;AAGjE,QAAO,eAAe,KAAK;;;;;;;;AChO7B,MAAM,oBAAoB;AAC1B,MAAM,oBAAoB;;;;AAK1B,MAAMC,yBAAiD;CACrD,UAAU;CACV,UAAU;CACV,SAAS;CACT,SAAS;CACT,YAAY;CACb;;;;;;;;;;;AAiBD,SAAgB,eACd,QACA,cACmB;CACnB,MAAM,mCAAmB,IAAI,KAA8B;CAG3D,MAAM,gCAAgB,IAAI,KAAqB;AAC/C,MAAK,MAAM,SAAS,OAAO,OAAO,OAAO,CACvC,MAAK,MAAM,MAAM,MAAM,aAAa;EAClC,MAAM,UAAU,GAAG,MAAM,KAAK,GAAG,GAAG;AACpC,gBAAc,IAAI,UAAU,cAAc,IAAI,QAAQ,IAAI,KAAK,EAAE;;CAKrE,MAAM,iCAAiB,IAAI,KAA0B;AACrD,MAAK,MAAM,SAAS,OAAO,OAAO,OAAO,EAAE;EACzC,MAAM,wBAAQ,IAAI,KAAa;AAC/B,OAAK,MAAM,OAAO,OAAO,OAAO,MAAM,QAAQ,CAC5C,OAAM,IAAI,IAAI,KAAK;AAErB,iBAAe,IAAI,MAAM,MAAM,MAAM;;AAGvC,MAAK,MAAM,SAAS,OAAO,OAAO,OAAO,CACvC,MAAK,MAAM,MAAM,MAAM,aAAa;EAClC,MAAM,iBAAiB,MAAM;EAC7B,MAAM,kBAAkB,GAAG;EAC3B,MAAM,YAAY,eAAe,IAAI,eAAe;EACpD,MAAM,iBAAiB,aAAa,IAAI,eAAe,IAAI;EAC3D,MAAM,kBAAkB,aAAa,IAAI,gBAAgB,IAAI;EAC7D,MAAM,UAAU,GAAG,eAAe,GAAG;EACrC,MAAM,iBAAiB,mBAAmB;EAC1C,MAAM,oBAAqB,cAAc,IAAI,QAAQ,GAAc,KAAK;EAGxE,MAAM,aAAa,eAAe,IAAI,MAAM;EAG5C,MAAM,oBAAoB,uBACxB,wBAAwB,GAAG,SAAS,gBAAgB,EACpD,WACA,gBACD;EACD,MAAM,eAAe,oBACjB,mBAAmB,IAAI,mBAAmB,iBAAiB,eAAe,GAC1E;AAaJ,mBAAiB,kBAAkB,gBARb,wBACpB,mBACA,iBACA,IAPoB,GAAG,QAAQ,MAC9B,eAAe,MAAM,QAAQ,aAAa,YAAY,MACxD,EAOC,aACD,CAEgE;AACjE,YAAU,IAAI,kBAAkB;EAGhC,MAAM,aAAa,eAAe,IAAI,gBAAgB,oBAAI,IAAI,KAAK;AACnE,iBAAe,IAAI,iBAAiB,WAAW;EAE/C,MAAM,mBAAmB,uBACvB,4BAA4B,gBAAgB,WAAW,EACvD,YACA,eACD;AAUD,mBAAiB,kBAAkB,iBARC;GAClC,WAAW;GACX,UAAU;GACV,UAAU;GACV,MAAM,CAAC;GACP;GACD,CAEgE;AACjE,aAAW,IAAI,iBAAiB;;AAIpC,QAAO,EAAE,kBAAkB;;;;;;;;AAS7B,SAAS,eAAe,IAAqB,OAAsC;CACjF,MAAM,SAAS,CAAC,GAAG,GAAG,QAAQ,CAAC,MAAM;AAGrC,KAAI,MAAM,YAAY;EACpB,MAAM,SAAS,CAAC,GAAG,MAAM,WAAW,QAAQ,CAAC,MAAM;AACnD,MAAI,OAAO,WAAW,OAAO,UAAU,OAAO,OAAO,GAAG,MAAM,MAAM,OAAO,GAAG,CAC5E,QAAO;;AAKX,MAAK,MAAM,UAAU,MAAM,SAAS;EAClC,MAAM,aAAa,CAAC,GAAG,OAAO,QAAQ,CAAC,MAAM;AAC7C,MAAI,WAAW,WAAW,OAAO,UAAU,WAAW,OAAO,GAAG,MAAM,MAAM,OAAO,GAAG,CACpF,QAAO;;AAIX,QAAO;;;;;;AAOT,SAAS,mBACP,IACA,wBACA,iBACA,gBACQ;AACR,KAAI,GAAG,KACL,QAAO,GAAG;AAEZ,KAAI,eACF,QAAO,GAAG,uBAAuB,OAAO,EAAE,CAAC,aAAa,GAAG,uBAAuB,MAAM,EAAE,GAAG,UAAU,gBAAgB;AAEzH,QAAO,GAAG,QAAQ,KAAK,IAAI;;;;;AAM7B,SAAS,wBACP,WACA,iBACA,IACA,UACA,cACe;CACf,MAAM,WAAW,GAAG,YAAY,GAAG,aAAa,oBAAoB,GAAG,WAAW;CAClF,MAAM,WAAW,GAAG,YAAY,GAAG,aAAa,oBAAoB,GAAG,WAAW;AAElF,QAAO;EACL;EACA,UAAU;EACV,qBAAqB,GAAG;EACxB;EACA,MAAM;EACN;EACA,QAAQ,GAAG;EACX,QAAQ,GAAG;EACX,YAAY,GAAG;EACf,UAAU,WAAW,uBAAuB,YAAY;EACxD,UAAU,WAAW,uBAAuB,YAAY;EACzD;;;;;AAMH,SAAS,uBACP,SACA,WACA,gBACQ;AACR,KAAI,CAAC,UAAU,IAAI,QAAQ,CACzB,QAAO;CAIT,MAAM,aAAa,GAAG,UAAU;AAChC,KAAI,CAAC,UAAU,IAAI,WAAW,CAC5B,QAAO;CAIT,IAAI,UAAU;AACd,QAAO,UAAU,IAAI,GAAG,UAAU,UAAU,CAC1C;AAEF,QAAO,GAAG,UAAU;;AAGtB,SAAS,iBACP,KACA,WACA,OACM;CACN,MAAM,WAAW,IAAI,IAAI,UAAU;AACnC,KAAI,SACF,UAAS,KAAK,MAAM;KAEpB,KAAI,IAAI,WAAW,CAAC,MAAM,CAAC;;;;;ACzN/B,MAAM,iBAAiB;;;;;AAMvB,SAAS,gBAAgB,OAAuB;AAC9C,QAAO,MACJ,QAAQ,OAAO,OAAO,CACtB,QAAQ,MAAM,OAAM,CACpB,QAAQ,OAAO,MAAM,CACrB,QAAQ,OAAO,MAAM;;AAoB1B,MAAM,yBAAyB;AAC/B,MAAM,6BAA6B,IAAI,IAAI;CACzC;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AACF,MAAM,wBAAwB,IAAI,IAAI;CACpC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;;;;;;;;;;;;;;AAeF,SAAgB,SAAS,UAAmC,SAAoC;CAC9F,MAAM,EAAE,SAAS,QAAQ,gBAAgB,UAAU,oBAAoB;CACvE,MAAM,gBAAgB,UAAU;CAMhC,MAAM,EAAE,WAAW,eAAe,aAAa,oBAAoB,YAJnC;EAC9B,2BAAW,IAAI,KAAa;EAC5B,6BAAa,IAAI,KAAgC;EAClD;CAID,MAAM,aAAa,qBACjB,OAAO,KAAK,SAAS,OAAO,EAC5B,aACA,SACA,QACD;CAGD,MAAM,YAAY,qBAAqB,eAAe,YAAY,QAAQ,YAAY;AACtF,iCAAgC,YAAY,UAAU;CAEtD,MAAM,eAAe,IAAI,IACvB,CAAC,GAAG,WAAW,CAAC,KAAK,CAAC,WAAW,YAAY,CAAC,WAAW,OAAO,KAAK,CAAC,CACvE;CACD,MAAM,cAAc,IAAI,IACtB,CAAC,GAAG,UAAU,CAAC,KAAK,CAAC,YAAY,YAAY,CAAC,YAAY,OAAO,KAAK,CAAC,CACxE;CACD,MAAM,yBAAyB,6BAA6B,YAAY,UAAU;CAElF,MAAM,oBAAoB,uBAAuB,SAAS,OAAO;CAGjE,MAAM,EAAE,qBAAqB,eAAe,SAAS,QAAQ,aAAa;CAG1E,MAAM,aAAa,sBAAsB,UAAU,SAAS,aAAa,uBAAuB;CAGhG,MAAMC,SAAyB,EAAE;AACjC,MAAK,MAAM,SAAS,OAAO,OAAO,SAAS,OAAO,EAAE;EAClD,MAAM,QAAQ,aACZ,OACA,SACA,aACA,mBACA,YACA,gBACA,iBACA,iBAAiB,IAAI,MAAM,KAAK,IAAI,EAAE,CACvC;AACD,SAAO,KAAK,MAAM;;CAIpB,MAAMC,QAAyF,EAAE;AACjG,MAAK,MAAM,CAAC,YAAY,WAAW,iBAAiB;EAClD,MAAM,WAAW,UAAU,IAAI,WAAW;AAC1C,QAAM,KAAK;GAAE,MAAM,SAAS;GAAM,SAAS,SAAS;GAAK;GAAQ,CAAC;;AAIpE,OAAM,MAAM,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,KAAK,CAAC;CAGlD,MAAM,eAAe,gBAAgB,QAAQ,SAAS,QAAQ,aAAa;CAG3E,MAAMC,WAAqB,EAAE;AAG7B,UAAS,KAAK,cAAc;CAG5B,MAAM,mBAAmB,CAAC,GAAG,WAAW,aAAa,QAAQ,CAAC,CAAC,MAAM,GAAG,MACtE,EAAE,KAAK,cAAc,EAAE,KAAK,CAC7B;AACD,KAAI,iBAAiB,SAAS,EAC5B,UAAS,KAAK,oBAAoB,iBAAiB,CAAC;AAItD,MAAK,MAAM,KAAK,MACd,UAAS,KAAK,cAAc,EAAE,CAAC;AAIjC,MAAK,MAAM,SAAS,aAClB,UAAS,KAAK,eAAe,MAAM,CAAC;AAGtC,QAAO,GAAG,SAAS,KAAK,OAAO,CAAC;;;;;AAMlC,SAAS,aACP,OACA,SACA,aACA,mBACA,YACA,gBACA,kBACA,gBACc;CACd,MAAM,EAAE,MAAM,WAAW,KAAK,YAAY,YAAY,MAAM,KAAK;CACjE,MAAM,eAAe,kBAAkB,IAAI,MAAM,KAAK;CAEtD,MAAM,YAAY,IAAI,IAAI,MAAM,YAAY,WAAW,EAAE,CAAC;CAC1D,MAAM,aAAa,UAAU,SAAS;CACtC,MAAM,yBAAyB,aAAa,MAAM,YAAY,OAAO;CAGrE,MAAM,gCAAgB,IAAI,KAAiC;AAC3D,MAAK,MAAM,UAAU,MAAM,QACzB,KAAI,OAAO,QAAQ,WAAW,GAAG;EAC/B,MAAM,CAAC,aAAa,MAAM,OAAO;EACjC,MAAM,yBAAyB,cAAc,IAAI,WAAW;AAC5D,MAAI,CAAC,cAAc,IAAI,WAAW,IAAK,2BAA2B,UAAa,OAAO,KACpF,eAAc,IAAI,YAAY,OAAO,KAAK;;CAMhD,MAAMC,SAAyB,EAAE;CACjC,MAAM,gBAAgB,OAAO,OAAO,MAAM,QAAQ;AAElD,MAAK,MAAM,UAAU,eAAe;EAClC,MAAM,gBAAgB,cAAc,IAAI,OAAO,KAAK;EACpD,MAAM,YAAY,eAAe,aAAa,YAAY,OAAO,KAAK,CAAC;EACvE,MAAM,WAAW,eAAe;EAGhC,MAAM,aAAa,QAAQ,QAAQ,OAAO,YAAY,MAAM,YAAY;AAExE,MAAI,iBAAiB,YAAY;AAE/B,UAAO,KAAK;IACV,MAAM;IACN,UAAU,gBAAgB,gBAAgB,WAAW,WAAW,CAAC;IACjE,UAAU,OAAO;IACjB,MAAM;IACN,YAAY,WAAW,CAAC,SAAS,gBAAgB,SAAS,CAAC,IAAI,GAAG,EAAE;IACpE,SAAS,YAAY;IACrB,MAAM;IACN,YAAY;IACZ,eAAe;IAChB,CAAC;AACF;;EAIF,IAAI,WAAW,WAAW;EAC1B,MAAM,cAAc,YAAY,IAAI,OAAO,WAAW;AACtD,MAAI,YACF,YAAW;AAIb,MAAI,WAAW,uBAAuB,CAAC,YACrC,YAAW,qBAAqB,YAAY,WAAW;EAIzD,MAAMC,aAAuB,EAAE;EAC/B,MAAM,OAAO,cAAc,UAAU,IAAI,OAAO,KAAK;AACrD,MAAI,KACF,YAAW,KAAK,+BAA+B,OAAO,uBAAuB,CAAC;EAIhF,IAAIC;AACJ,MAAI,OAAO,YAAY,QAAW;GAChC,MAAM,SAAS,qBAAqB,OAAO,SAAS,OAAO,YAAY,iBAAiB;AACxF,OAAI,QAAQ;IACV,MAAM,SAAS,WAAW,QAAQ,eAAe;AACjD,QAAI,eAAe,OACjB,YAAW,KAAK,OAAO,UAAU;QAEjC,WAAU,OAAO;;;EAMvB,MAAM,uBAAuB,cAAc,IAAI,OAAO,KAAK;AAC3D,MAAI,yBAAyB,UAAa,cAAc,IAAI,OAAO,KAAK,EACtE;OAAI,CAAC,KACH,YAAW,KAAK,+BAA+B,WAAW,qBAAqB,CAAC;;AAKpF,MAAI,SACF,YAAW,KAAK,SAAS,gBAAgB,SAAS,CAAC,IAAI;AAGzD,SAAO,KAAK;GACV,MAAM;GACN;GACA,UAAU,OAAO;GACjB,MAAM;GACN;GACA,SAAS,YAAY;GACrB;GACA,YAAY;GACZ,eAAe;GACf;GACD,CAAC;;CAIJ,MAAM,iBAAiB,IAAI,IAAI,OAAO,KAAK,UAAU,MAAM,KAAK,CAAC;AACjE,MAAK,MAAM,OAAO,gBAAgB;EAChC,MAAM,oBAAoB,sBAAsB,IAAI,WAAW,eAAe;EAC9E,MAAMC,gBAA0B,EAAE;AAElC,MAAI,IAAI,UAAU,IAAI,YAAY;GAChC,MAAMC,QAAkB,EAAE;AAC1B,OAAI,IAAI,aACN,OAAM,KAAK,UAAU,gBAAgB,IAAI,aAAa,CAAC,GAAG;AAE5D,SAAM,KACJ,YAAY,IAAI,OACb,KAAK,cAAc,uBAAuB,mBAAmB,MAAM,MAAM,UAAU,CAAC,CACpF,KAAK,KAAK,CAAC,GACf;AACD,SAAM,KACJ,gBAAgB,IAAI,WACjB,KAAK,cACJ,uBAAuB,mBAAmB,IAAI,uBAAuB,IAAI,UAAU,CACpF,CACA,KAAK,KAAK,CAAC,GACf;AACD,OAAI,IAAI,SACN,OAAM,KAAK,aAAa,IAAI,WAAW;AAEzC,OAAI,IAAI,SACN,OAAM,KAAK,aAAa,IAAI,WAAW;AAEzC,OAAI,IAAI,OACN,OAAM,KAAK,SAAS,gBAAgB,IAAI,OAAO,CAAC,GAAG;AAErD,iBAAc,KAAK,aAAa,MAAM,KAAK,KAAK,CAAC,GAAG;aAC3C,IAAI,aACb,eAAc,KAAK,oBAAoB,gBAAgB,IAAI,aAAa,CAAC,IAAI;AAG/E,SAAO,KAAK;GACV,MAAM;GACN,UAAU,IAAI;GACd,UAAU,IAAI;GACd,MAAM,IAAI;GACV,YAAY;GACZ,MAAM;GACN,YAAY;GACZ,eAAe;GAChB,CAAC;AACF,iBAAe,IAAI,kBAAkB;;CAIvC,MAAMC,kBAA4B,EAAE;AAGpC,KAAI,MAAM,cAAc,MAAM,WAAW,QAAQ,SAAS,GAAG;EAC3D,MAAM,eAAe,MAAM,WAAW,QAAQ,KAAK,eACjD,uBAAuB,mBAAmB,MAAM,MAAM,WAAW,CAClE;AACD,kBAAgB,KACd,+BAA+B,QAAQ,cAAc,MAAM,WAAW,KAAK,CAC5E;;AAIH,MAAK,MAAM,UAAU,MAAM,QACzB,KAAI,OAAO,QAAQ,SAAS,GAAG;EAC7B,MAAM,aAAa,OAAO,QAAQ,KAAK,eACrC,uBAAuB,mBAAmB,MAAM,MAAM,WAAW,CAClE;AACD,kBAAgB,KAAK,+BAA+B,YAAY,YAAY,OAAO,KAAK,CAAC;;AAK7F,MAAK,MAAM,SAAS,MAAM,QACxB,KAAI,CAAC,MAAM,QAAQ;EACjB,MAAM,aAAa,MAAM,QAAQ,KAAK,eACpC,uBAAuB,mBAAmB,MAAM,MAAM,WAAW,CAClE;AACD,kBAAgB,KAAK,+BAA+B,WAAW,YAAY,MAAM,KAAK,CAAC;;AAK3F,KAAI,QACF,iBAAgB,KAAK,UAAU,gBAAgB,QAAQ,CAAC,IAAI;CAI9D,MAAM,eAAe,CAAC,MAAM,aACxB,8DACA;AAEJ,QAAO;EACL,MAAM;EACN,SAAS,WAAW;EACpB;EACA;EACA,SAAS;EACV;;AAGH,SAAS,qBACP,OACA,YACA,kBAC2B;AAC3B,KAAI,OAAO,UAAU,SACnB,QAAO,mBAAmB,iBAAiB,OAAO,WAAW,GAAG;AAElE,QAAO;;AAGT,SAAS,+BACP,WACA,gBACQ;AACR,QAAO,iBAAiB,GAAG,UAAU,SAAS,gBAAgB,eAAe,CAAC,MAAM;;AAGtF,SAAS,+BACP,WACA,QACA,gBACQ;CACR,MAAM,QAAQ,CAAC,IAAI,OAAO,KAAK,KAAK,CAAC,GAAG;AACxC,KAAI,eACF,OAAM,KAAK,SAAS,gBAAgB,eAAe,CAAC,GAAG;AAEzD,QAAO,GAAG,UAAU,GAAG,MAAM,KAAK,KAAK,CAAC;;AAG1C,SAAS,uBACP,QAC8C;CAC9C,MAAM,oCAAoB,IAAI,KAAsC;AAEpE,MAAK,MAAM,SAAS,OAAO,OAAO,OAAO,EAAE;EAWzC,MAAM,kBAAkB,CAAC,GAVT,OAAO,OAAO,MAAM,QAAQ,CAAC,KAAK,QAAQ,UAAU;GAClE,MAAM,EAAE,MAAM,QAAQ,YAAY,OAAO,KAAK;AAC9C,UAAO;IACL,YAAY,OAAO;IACnB,kBAAkB;IAClB,UAAU;IACV;IACD;IACD,CAEkC,CAAC,MAAM,MAAM,UAAU;GACzD,MAAM,gBACJ,OAAO,KAAK,aAAa,OAAU,GAAG,OAAO,MAAM,aAAa,OAAU;AAC5E,OAAI,kBAAkB,EACpB,QAAO;AAET,UAAO,KAAK,QAAQ,MAAM;IAC1B;EAEF,MAAM,iCAAiB,IAAI,KAAa;EACxC,MAAM,kCAAkB,IAAI,KAAsC;AAElE,OAAK,MAAM,UAAU,iBAAiB;GACpC,MAAM,YAAY,sBAAsB,OAAO,kBAAkB,eAAe;AAChF,kBAAe,IAAI,UAAU;AAC7B,mBAAgB,IAAI,OAAO,YAAY;IACrC;IACA,UAAU,OAAO;IAClB,CAAC;;AAGJ,oBAAkB,IAAI,MAAM,MAAM,gBAAgB;;AAGpD,QAAO;;AAGT,SAAS,uBACP,mBACA,WACA,YACQ;AACR,QACE,kBAAkB,IAAI,UAAU,EAAE,IAAI,WAAW,EAAE,aAAa,YAAY,WAAW,CAAC;;AAI5F,SAAS,sBAAsB,aAAqB,gBAA6C;AAC/F,KAAI,CAAC,eAAe,IAAI,YAAY,CAClC,QAAO;CAGT,IAAI,UAAU;AACd,QAAO,eAAe,IAAI,GAAG,cAAc,UAAU,CACnD;AAEF,QAAO,GAAG,cAAc;;AAG1B,SAAS,qBACP,SACA,WACA,MACA,YACiC;CACjC,MAAM,0BAAU,IAAI,KAAiC;CACrD,MAAM,sCAAsB,IAAI,KAAuB;AAEvD,MAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,aAAa,UAAU,OAAO;AACpC,UAAQ,IAAI,QAAQ,WAAW;AAC/B,sBAAoB,IAAI,WAAW,MAAM,CACvC,GAAI,oBAAoB,IAAI,WAAW,KAAK,IAAI,EAAE,EAClD,OACD,CAAC;;CAGJ,MAAM,aAAa,CAAC,GAAG,oBAAoB,SAAS,CAAC,CAAC,QACnD,GAAG,wBAAwB,mBAAmB,SAAS,EACzD;AACD,KAAI,WAAW,SAAS,GAAG;EACzB,MAAM,UAAU,WAAW,KACxB,CAAC,gBAAgB,wBAChB,KAAK,KAAK,IAAI,eAAe,SAAS,WAAW,IAAI,mBAClD,KAAK,WAAW,IAAI,OAAO,GAAG,CAC9B,KAAK,KAAK,GAChB;AACD,QAAM,IAAI,MAAM,OAAO,KAAK,8BAA8B,QAAQ,KAAK,KAAK,GAAG;;AAGjF,QAAO;;AAGT,SAAS,gCACP,YACA,WACM;CACN,MAAM,mBAAmB,IAAI,IAAI,CAAC,GAAG,UAAU,CAAC,KAAK,CAAC,QAAQ,YAAY,CAAC,OAAO,MAAM,OAAO,CAAC,CAAC;CAEjG,MAAM,aAAa,CAAC,GAAG,WAAW,SAAS,CAAC,CACzC,KAAK,CAAC,WAAW,YAAY;EAC5B,MAAM,aAAa,iBAAiB,IAAI,OAAO,KAAK;AACpD,SAAO,aACH,iBAAiB,OAAO,KAAK,gBAAgB,UAAU,6BAA6B,WAAW,KAC/F;GACJ,CACD,QAAQ,WAA6B,WAAW,OAAU;AAE7D,KAAI,WAAW,SAAS,EACtB,OAAM,IAAI,MAAM,4CAA4C,WAAW,KAAK,KAAK,GAAG;;AAIxF,SAAS,6BACP,YACA,WACa;CACb,MAAM,gBAAgB,IAAI,IAAY,sBAAsB;AAE5D,MAAK,MAAM,UAAU,WAAW,QAAQ,CACtC,eAAc,IAAI,OAAO,KAAK;AAGhC,MAAK,MAAM,UAAU,UAAU,QAAQ,CACrC,eAAc,IAAI,OAAO,KAAK;AAGhC,QAAO;;AAGT,SAAS,sBACP,UACA,SACA,aACA,eACmB;CACnB,MAAM,wBAAQ,IAAI,KAOf;AAEH,MAAK,MAAM,aAAa,OAAO,KAAK,SAAS,OAAO,CAAC,MAAM,EAAE;EAC3D,MAAM,QAAQ,SAAS,OAAO;AAC9B,MAAI,CAAC,MACH;AAGF,OAAK,MAAM,cAAc,OAAO,KAAK,MAAM,QAAQ,CAAC,MAAM,EAAE;GAC1D,MAAM,SAAS,MAAM,QAAQ;AAC7B,OAAI,CAAC,OACH;GAGF,MAAM,aAAa,QAAQ,QAAQ,OAAO,YAAY,MAAM,YAAY;AACxE,OACE,iBAAiB,cACjB,YAAY,IAAI,OAAO,WAAW,IAClC,CAAC,WAAW,oBAEZ;GAGF,MAAM,eAAe,4BAA4B,WAAW;AAC5D,OAAI,CAAC,MAAM,IAAI,aAAa,CAC1B,OAAM,IAAI,cAAc;IACtB,UAAU,WAAW;IACrB,aAAa,gBAAgB,OAAO,KAAK;IACzC,YAAY,CAAC,0BAA0B,WAAW,oBAAoB,CAAC;IACxE,CAAC;;;CAKR,MAAMC,WAA8B;EAClC,8BAAc,IAAI,KAA+B;EACjD,WAAW,IAAI,IAAY,cAAc;EAC1C;CAED,MAAM,cAAc,CAAC,GAAG,MAAM,SAAS,CAAC,CAAC,MAAM,MAAM,UAAU;EAC7D,MAAM,wBAAwB,KAAK,GAAG,YAAY,cAAc,MAAM,GAAG,YAAY;AACrF,MAAI,0BAA0B,EAC5B,QAAO;AAET,SAAO,KAAK,GAAG,cAAc,MAAM,GAAG;GACtC;AAEF,MAAK,MAAM,CAAC,cAAc,SAAS,aAAa;EAC9C,MAAM,OAAO,sBAAsB,KAAK,aAAa,SAAS,UAAU;AACxE,WAAS,aAAa,IAAI,cAAc;GACtC;GACA,UAAU,KAAK;GACf,YAAY,KAAK;GAClB,CAAC;AACF,WAAS,UAAU,IAAI,KAAK;;AAG9B,QAAO;;AAGT,SAAS,qBACP,UACA,YAMQ;CACR,MAAM,MAAM,4BAA4B,WAAW;CACnD,MAAM,WAAW,SAAS,aAAa,IAAI,IAAI;AAC/C,KAAI,SACF,QAAO,SAAS;AAGlB,OAAM,IAAI,MAAM,uDAAuD,WAAW,WAAW,GAAG;;AAGlG,SAAS,4BAA4B,YAK1B;AACT,QAAO,KAAK,UAAU;EACpB,UAAU,WAAW;EACrB,qBAAqB,WAAW,sBAC5B;GACE,MAAM,WAAW,oBAAoB;GACrC,MAAM,WAAW,oBAAoB,QAAQ;GAC9C,GACD;EACL,CAAC;;AAGJ,SAAS,mCAAmC,OAAwB;AAClE,QAAO,2BAA2B,IAAI,MAAM,aAAa,CAAC;;AAG5D,SAAS,wBAAwB,OAAe,WAAwC;AAMtF,QAAO,sBAJL,uBAAuB,KAAK,MAAM,IAAI,CAAC,mCAAmC,MAAM,GAC5E,QACA,mCAAmC,MAAM,EAEL,UAAU;;AAGtD,SAAS,mCAAmC,OAAuB;CACjE,MAAM,SAAS,MAAM,MAAM,gBAAgB,EAAE,KAAK,UAAU,MAAM,aAAa,CAAC,IAAI,EAAE;CACtF,IAAI,aAAa,OAAO,MAAM;AAE9B,MAAK,MAAM,SAAS,OAAO,MAAM,EAAE,CACjC,eAAc,MAAM,OAAO,EAAE,CAAC,aAAa,GAAG,MAAM,MAAM,EAAE;AAG9D,KAAI,mCAAmC,WAAW,IAAI,MAAM,KAAK,WAAW,CAC1E,cAAa,IAAI;AAGnB,QAAO;;;;;;;AAQT,SAAS,gBACP,QACA,QACA,cACgB;CAChB,MAAM,8BAAc,IAAI,KAA2B;AACnD,MAAK,MAAM,SAAS,OAClB,aAAY,IAAI,MAAM,MAAM,MAAM;CAIpC,MAAM,uBAAO,IAAI,KAA0B;CAC3C,MAAM,+BAAe,IAAI,KAAqB;AAC9C,MAAK,MAAM,aAAa,OAAO,KAAK,OAAO,EAAE;EAC3C,MAAM,YAAY,aAAa,IAAI,UAAU;AAC7C,eAAa,IAAI,WAAW,UAAU;AACtC,OAAK,IAAI,2BAAW,IAAI,KAAK,CAAC;;AAGhC,MAAK,MAAM,CAAC,WAAW,UAAU,OAAO,QAAQ,OAAO,EAAE;EACvD,MAAM,YAAY,aAAa,IAAI,UAAU;AAC7C,OAAK,MAAM,MAAM,MAAM,aAAa;GAClC,MAAM,eAAe,aAAa,IAAI,GAAG,gBAAgB;AACzD,OAAI,gBAAgB,iBAAiB,UACnC,CAAC,KAAK,IAAI,UAAU,CAAiB,IAAI,aAAa;;;CAM5D,MAAMC,SAAyB,EAAE;CACjC,MAAM,0BAAU,IAAI,KAAa;CACjC,MAAM,2BAAW,IAAI,KAAa;CAGlC,MAAM,cAAc,CAAC,GAAG,KAAK,MAAM,CAAC,CAAC,MAAM;CAE3C,SAAS,MAAM,MAAoB;AACjC,MAAI,QAAQ,IAAI,KAAK,CAAE;AACvB,MAAI,SAAS,IAAI,KAAK,CAAE;AACxB,WAAS,IAAI,KAAK;EAGlB,MAAM,aAAa,CAAC,GAAI,KAAK,IAAI,KAAK,CAAiB,CAAC,MAAM;AAC9D,OAAK,MAAM,OAAO,WAChB,OAAM,IAAI;AAGZ,WAAS,OAAO,KAAK;AACrB,UAAQ,IAAI,KAAK;AACjB,SAAO,KAAK,YAAY,IAAI,KAAK,CAAiB;;AAGpD,MAAK,MAAM,QAAQ,YACjB,OAAM,KAAK;AAGb,QAAO;;;;;AAUT,SAAS,oBAAoB,YAAiD;CAC5E,MAAM,QAAQ,CAAC,UAAU;AACzB,MAAK,MAAM,MAAM,YAAY;EAC3B,MAAM,UAAU,GAAG,WAAW,SAAS,IAAI,IAAI,GAAG,WAAW,KAAK,IAAI,KAAK;AAC3E,QAAM,KAAK,KAAK,GAAG,KAAK,KAAK,GAAG,WAAW,UAAU;;AAEvD,OAAM,KAAK,IAAI;AACf,QAAO,MAAM,KAAK,KAAK;;AAGzB,SAAS,0BAA0B,WAA2C;AAC5E,KAAI,CAAC,UAAU,QAAQ,UAAU,KAAK,WAAW,EAC/C,QAAO,IAAI,UAAU;AAEvB,QAAO,IAAI,UAAU,KAAK,GAAG,UAAU,KAAK,KAAK,KAAK,CAAC;;;;;AAMzD,SAAS,cAAc,GAIZ;CACT,MAAM,QAAQ,CAAC,QAAQ,EAAE,KAAK,IAAI;CAClC,MAAM,4BAAY,IAAI,KAAa;AACnC,MAAK,MAAM,SAAS,EAAE,QAAQ;EAC5B,MAAM,aAAa,wBAAwB,OAAO,UAAU;AAC5D,QAAM,KAAK,KAAK,aAAa;AAC7B,YAAU,IAAI,WAAW;;AAE3B,KAAI,EAAE,SAAS;AACb,QAAM,KAAK,GAAG;AACd,QAAM,KAAK,YAAY,gBAAgB,EAAE,QAAQ,CAAC,IAAI;;AAExD,OAAM,KAAK,IAAI;AACf,QAAO,MAAM,KAAK,KAAK;;;;;AAMzB,SAAS,eAAe,OAA6B;CACnD,MAAMC,QAAkB,EAAE;AAE1B,KAAI,MAAM,QACR,OAAM,KAAK,MAAM,QAAQ;AAE3B,OAAM,KAAK,SAAS,MAAM,KAAK,IAAI;CAMnC,MAAM,WAAW,MAAM,OAAO,QAAQ,MAAM,EAAE,KAAK;CACnD,MAAM,eAAe,MAAM,OAAO,QAAQ,MAAM,CAAC,EAAE,QAAQ,CAAC,EAAE,WAAW;CACzE,MAAM,iBAAiB,MAAM,OAAO,QAAQ,MAAM,EAAE,WAAW;CAE/D,MAAM,mBAAmB;EAAC,GAAG;EAAU,GAAG;EAAc,GAAG;EAAe;AAE1E,KAAI,iBAAiB,SAAS,GAAG;EAE/B,MAAM,aAAa,KAAK,IAAI,GAAG,iBAAiB,KAAK,MAAM,EAAE,KAAK,OAAO,CAAC;EAC1E,MAAM,aAAa,KAAK,IAAI,GAAG,iBAAiB,KAAK,MAAM,gBAAgB,EAAE,CAAC,OAAO,CAAC;AAEtF,OAAK,MAAM,SAAS,kBAAkB;GACpC,MAAM,WAAW,gBAAgB,MAAM;GACvC,MAAM,aAAa,MAAM,KAAK,OAAO,WAAW;GAChD,MAAM,aAAa,SAAS,OAAO,WAAW;AAE9C,OAAI,MAAM,QACR,OAAM,KAAK,KAAK,MAAM,UAAU;GAGlC,MAAM,UAAU,MAAM,WAAW,SAAS,IAAI,IAAI,MAAM,WAAW,KAAK,IAAI,KAAK;AACjF,SAAM,KAAK,KAAK,WAAW,GAAG,aAAa,UAAU,SAAS,CAAC;;;AAKnE,KAAI,MAAM,gBAAgB,SAAS,GAAG;AACpC,MAAI,iBAAiB,SAAS,EAC5B,OAAM,KAAK,GAAG;AAEhB,OAAK,MAAM,QAAQ,MAAM,gBACvB,OAAM,KAAK,KAAK,OAAO;;AAI3B,OAAM,KAAK,IAAI;AACf,QAAO,MAAM,KAAK,KAAK;;;;;AAMzB,SAAS,gBAAgB,OAA6B;CACpD,IAAI,OAAO,MAAM;AACjB,KAAI,MAAM,KACR,SAAQ;UACC,MAAM,SACf,SAAQ;AAEV,QAAO;;;;;ACn2BT,MAAM,sBAAsB,IAAI,IAA0B;CACxD;CACA;CACA;CACA;CACA;CACD,CAAC;AA4BF,SAAgB,6BAA6B,OAAyC;CACpF,MAAM,OAAO,aAAa,OAAO,SAAS;AAE1C,QAAO;EACL,QAAQ,eAAe,KAAK,WAAW,gBAAgB;EACvD,cAAc,qBAAqB,KAAK,iBAAiB,sBAAsB;EAC/E,GAAG,UAAU,eAAe,oBAAoB,KAAK,gBAAgB,qBAAqB,CAAC;EAC5F;;AAGH,SAAS,eAAe,OAAgB,MAAoD;CAC1F,MAAM,SAAS,aAAa,OAAO,KAAK;CACxC,MAAMC,YAAkD,EAAE;AAE1D,MAAK,MAAM,CAAC,WAAW,eAAe,OAAO,QAAQ,OAAO,EAAE;EAC5D,MAAM,YAAY,GAAG,KAAK,GAAG;EAC7B,MAAM,QAAQ,aAAa,YAAY,UAAU;AAEjD,YAAU,aAAa;GACrB,MAAM,aAAa,MAAM,SAAS,GAAG,UAAU,OAAO;GACtD,SAAS,gBAAgB,MAAM,YAAY,GAAG,UAAU,UAAU;GAClE,aAAa,oBAAoB,MAAM,gBAAgB,GAAG,UAAU,cAAc;GAClF,SAAS,gBAAgB,MAAM,YAAY,GAAG,UAAU,UAAU;GAClE,SAAS,gBAAgB,MAAM,YAAY,GAAG,UAAU,UAAU;GAClE,GAAG,UACD,cACA,mBAAmB,MAAM,eAAe,GAAG,UAAU,aAAa,CACnE;GACD,GAAG,UACD,eACA,oBAAoB,MAAM,gBAAgB,GAAG,UAAU,cAAc,CACtE;GACF;;AAGH,QAAO;;AAGT,SAAS,gBAAgB,OAAgB,MAAqD;CAC5F,MAAM,UAAU,aAAa,OAAO,KAAK;CACzC,MAAMC,YAAmD,EAAE;AAE3D,MAAK,MAAM,CAAC,YAAY,gBAAgB,OAAO,QAAQ,QAAQ,EAAE;EAC/D,MAAM,aAAa,GAAG,KAAK,GAAG;EAC9B,MAAM,SAAS,aAAa,aAAa,WAAW;AAEpD,YAAU,cAAc;GACtB,MAAM,aAAa,OAAO,SAAS,GAAG,WAAW,OAAO;GACxD,YAAY,aAAa,OAAO,eAAe,GAAG,WAAW,aAAa;GAC1E,UAAU,cAAc,OAAO,aAAa,GAAG,WAAW,WAAW;GACrE,GAAG,UAAU,WAAW,sBAAsB,OAAO,YAAY,GAAG,WAAW,UAAU,CAAC;GAC1F,GAAG,UACD,eACA,oBAAoB,OAAO,gBAAgB,GAAG,WAAW,cAAc,CACxE;GACF;;AAGH,QAAO;;AAGT,SAAS,mBAAmB,OAAgB,MAAsC;AAChF,KAAI,UAAU,OACZ;CAGF,MAAM,aAAa,aAAa,OAAO,KAAK;AAC5C,QAAO;EACL,SAAS,oBAAoB,WAAW,YAAY,GAAG,KAAK,UAAU;EACtE,GAAG,UAAU,QAAQ,uBAAuB,WAAW,SAAS,GAAG,KAAK,OAAO,CAAC;EACjF;;AAGH,SAAS,oBAAoB,OAAgB,MAA0C;AAErF,QADoB,YAAY,OAAO,KAAK,CACzB,KAAK,YAAY,UAAU;EAC5C,MAAM,iBAAiB,GAAG,KAAK,GAAG,MAAM;EACxC,MAAM,SAAS,aAAa,YAAY,eAAe;AAEvD,SAAO;GACL,SAAS,oBAAoB,OAAO,YAAY,GAAG,eAAe,UAAU;GAC5E,iBAAiB,aAAa,OAAO,oBAAoB,GAAG,eAAe,kBAAkB;GAC7F,mBAAmB,oBACjB,OAAO,sBACP,GAAG,eAAe,oBACnB;GACD,GAAG,UAAU,QAAQ,uBAAuB,OAAO,SAAS,GAAG,eAAe,OAAO,CAAC;GACtF,GAAG,UACD,YACA,0BAA0B,OAAO,aAAa,GAAG,eAAe,WAAW,CAC5E;GACD,GAAG,UACD,YACA,0BAA0B,OAAO,aAAa,GAAG,eAAe,WAAW,CAC5E;GACD,GAAG,UACD,eACA,oBAAoB,OAAO,gBAAgB,GAAG,eAAe,cAAc,CAC5E;GACF;GACD;;AAGJ,SAAS,gBAAgB,OAAgB,MAAsC;AAE7E,QADgB,YAAY,OAAO,KAAK,CACzB,KAAK,aAAa,UAAU;EACzC,MAAM,aAAa,GAAG,KAAK,GAAG,MAAM;EACpC,MAAM,SAAS,aAAa,aAAa,WAAW;AAEpD,SAAO;GACL,SAAS,oBAAoB,OAAO,YAAY,GAAG,WAAW,UAAU;GACxE,GAAG,UAAU,QAAQ,uBAAuB,OAAO,SAAS,GAAG,WAAW,OAAO,CAAC;GAClF,GAAG,UACD,eACA,oBAAoB,OAAO,gBAAgB,GAAG,WAAW,cAAc,CACxE;GACF;GACD;;AAGJ,SAAS,gBAAgB,OAAgB,MAAqC;AAE5E,QADgB,YAAY,OAAO,KAAK,CACzB,KAAK,YAAY,UAAU;EACxC,MAAM,YAAY,GAAG,KAAK,GAAG,MAAM;EACnC,MAAM,SAAS,aAAa,YAAY,UAAU;AAElD,SAAO;GACL,SAAS,oBAAoB,OAAO,YAAY,GAAG,UAAU,UAAU;GACvE,QAAQ,cAAc,OAAO,WAAW,GAAG,UAAU,SAAS;GAC9D,GAAG,UAAU,QAAQ,uBAAuB,OAAO,SAAS,GAAG,UAAU,OAAO,CAAC;GACjF,GAAG,UACD,eACA,oBAAoB,OAAO,gBAAgB,GAAG,UAAU,cAAc,CACvE;GACF;GACD;;AAGJ,SAAS,qBAAqB,OAAgB,MAAuC;AAEnF,QADqB,YAAY,OAAO,KAAK,CACzB,KAAK,iBAAiB,UAAU;EAClD,MAAM,iBAAiB,GAAG,KAAK,GAAG,MAAM;AAGxC,SAAO,EACL,IAAI,aAHa,aAAa,iBAAiB,eAAe,CAGlC,OAAO,GAAG,eAAe,KAAK,EAC3D;GACD;;AAGJ,SAAS,oBAAoB,OAAgB,MAA0C;AACrF,KAAI,UAAU,OACZ;AAGF,QAAO,aAAa,OAAO,KAAK;;AAGlC,SAAS,sBACP,OACA,MACuC;AACvC,KAAI,UAAU,OACZ;AAGF,KAAI,OAAO,UAAU,SACnB,QAAO;CAGT,MAAM,gBAAgB,aAAa,OAAO,KAAK;CAC/C,MAAM,OAAO,aAAa,cAAc,SAAS,GAAG,KAAK,OAAO;AAEhE,KAAI,SAAS,WAAW;AACtB,MAAI,CAAC,OAAO,OAAO,eAAe,QAAQ,CACxC,OAAM,IAAI,MAAM,GAAG,KAAK,6CAA6C;AAEvE,SAAO;GACL,MAAM;GACN,OAAO,cAAc;GACtB;;AAGH,KAAI,SAAS,WACX,QAAO;EACL,MAAM;EACN,YAAY,aAAa,cAAc,eAAe,GAAG,KAAK,aAAa;EAC5E;AAGH,OAAM,IAAI,MAAM,GAAG,KAAK,uCAAuC;;AAGjE,SAAS,0BAA0B,OAAgB,MAAgD;AACjG,KAAI,UAAU,OACZ;CAGF,MAAM,SAAS,aAAa,OAAO,KAAK;AACxC,KAAI,CAAC,oBAAoB,IAAI,OAAO,CAClC,OAAM,IAAI,MACR,GAAG,KAAK,kBAAkB,CAAC,GAAG,oBAAoB,CAAC,KAAK,SAAS,IAAI,KAAK,GAAG,CAAC,KAAK,KAAK,GACzF;AAEH,QAAO;;AAGT,SAAS,oBAAoB,OAAgB,MAAiC;AAE5E,QADc,YAAY,OAAO,KAAK,CACzB,KAAK,MAAM,UAAU,aAAa,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,CAAC;;AAG5E,SAAS,uBAAuB,OAAgB,MAAkC;AAChF,KAAI,UAAU,OACZ;AAGF,QAAO,aAAa,OAAO,KAAK;;AAGlC,SAAS,aAAa,OAAgB,MAAuC;AAC3E,KAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,MAAM,QAAQ,MAAM,CACrE,OAAM,IAAI,MAAM,GAAG,KAAK,oBAAoB;AAG9C,QAAO;;AAGT,SAAS,YAAY,OAAgB,MAAkC;AACrE,KAAI,CAAC,MAAM,QAAQ,MAAM,CACvB,OAAM,IAAI,MAAM,GAAG,KAAK,mBAAmB;AAG7C,QAAO;;AAGT,SAAS,aAAa,OAAgB,MAAsB;AAC1D,KAAI,OAAO,UAAU,SACnB,OAAM,IAAI,MAAM,GAAG,KAAK,mBAAmB;AAG7C,QAAO;;AAGT,SAAS,cAAc,OAAgB,MAAuB;AAC5D,KAAI,OAAO,UAAU,UACnB,OAAM,IAAI,MAAM,GAAG,KAAK,oBAAoB;AAG9C,QAAO;;AAGT,SAAS,UACP,KACA,OAC0D;AAC1D,KAAI,UAAU,OACZ,QAAO,EAAE;AAGX,QAAO,GAAG,MAAM,OAAO"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/serialize-print-document.ts","../src/ast-to-print-document.ts","../src/print-psl.ts"],"sourcesContent":["import type { PrintDocument } from './print-document';\nimport type { PrinterEnumValue, PrinterField, PrinterNamedType } from './types';\n\nconst PSL_IDENTIFIER_PATTERN = /^[A-Za-z_]\\w*$/;\nconst ENUM_MEMBER_RESERVED_WORDS = new Set([\n 'datasource',\n 'default',\n 'enum',\n 'generator',\n 'model',\n 'type',\n 'types',\n]);\n\nexport function escapePslString(value: string): string {\n return value\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/\"/g, '\\\\\"')\n .replace(/\\n/g, '\\\\n')\n .replace(/\\r/g, '\\\\r');\n}\n\nexport function serializePrintDocument(doc: PrintDocument): string {\n const sections: string[] = [];\n\n sections.push(doc.headerComment);\n\n const namedTypeEntries = [...doc.namedTypes].sort((a, b) => a.name.localeCompare(b.name));\n if (namedTypeEntries.length > 0) {\n sections.push(serializeTypesBlock(namedTypeEntries));\n }\n\n const enumsSorted = [...doc.enums].sort((a, b) => a.name.localeCompare(b.name));\n for (const e of enumsSorted) {\n sections.push(serializeEnum(e));\n }\n\n for (const model of doc.models) {\n sections.push(serializeModel(model));\n }\n\n return `${sections.join('\\n\\n')}\\n`;\n}\n\nfunction serializeTypesBlock(namedTypes: readonly PrinterNamedType[]): string {\n const lines = ['types {'];\n for (const nt of namedTypes) {\n const attrStr = nt.attributes.length > 0 ? ` ${nt.attributes.join(' ')}` : '';\n lines.push(` ${nt.name} = ${nt.baseType}${attrStr}`);\n }\n lines.push('}');\n return lines.join('\\n');\n}\n\nfunction serializeEnum(e: {\n name: string;\n mapName?: string | undefined;\n values: readonly PrinterEnumValue[];\n}): string {\n const lines = [`enum ${e.name} {`];\n const usedNames = new Set<string>();\n for (const value of e.values) {\n const memberName = normalizeEnumMemberName(value.name, usedNames);\n // Emit a per-member `@map(\"...\")` whenever the printed identifier differs\n // from the original storage label (e.g. PostgreSQL enum labels with\n // hyphens that get normalised to camelCase, reserved words that get\n // `_`-prefixed, or names that collide and get a numeric suffix), or when\n // the AST carried an explicit `mapName` from a parsed source. Without\n // this, parsing the emitted PSL would lose the original storage label and\n // a subsequent `contract emit` would talk to the wrong DB enum value.\n const explicitMap = value.mapName;\n const storageLabel =\n explicitMap !== undefined ? explicitMap : memberName !== value.name ? value.name : undefined;\n if (storageLabel !== undefined) {\n lines.push(` ${memberName} @map(\"${escapePslString(storageLabel)}\")`);\n } else {\n lines.push(` ${memberName}`);\n }\n usedNames.add(memberName);\n }\n if (e.mapName) {\n lines.push('');\n lines.push(` @@map(\"${escapePslString(e.mapName)}\")`);\n }\n lines.push('}');\n return lines.join('\\n');\n}\n\nfunction serializeModel(model: import('./types').PrinterModel): string {\n const lines: string[] = [];\n\n if (model.comment) {\n lines.push(model.comment);\n }\n lines.push(`model ${model.name} {`);\n\n const idFields = model.fields.filter((f) => f.isId);\n const scalarFields = model.fields.filter((f) => !f.isId && !f.isRelation);\n const relationFields = model.fields.filter((f) => f.isRelation);\n\n const allOrderedFields = [...idFields, ...scalarFields, ...relationFields];\n\n if (allOrderedFields.length > 0) {\n const maxNameLen = Math.max(...allOrderedFields.map((f) => f.name.length));\n const maxTypeLen = Math.max(...allOrderedFields.map((f) => formatFieldType(f).length));\n\n for (const field of allOrderedFields) {\n const typePart = formatFieldType(field);\n const paddedName = field.name.padEnd(maxNameLen);\n const paddedType = typePart.padEnd(maxTypeLen);\n\n if (field.comment) {\n lines.push(` ${field.comment}`);\n }\n\n const attrStr = field.attributes.length > 0 ? ` ${field.attributes.join(' ')}` : '';\n lines.push(` ${paddedName} ${paddedType}${attrStr}`.trimEnd());\n }\n }\n\n if (model.modelAttributes.length > 0) {\n if (allOrderedFields.length > 0) {\n lines.push('');\n }\n for (const attr of model.modelAttributes) {\n lines.push(` ${attr}`);\n }\n }\n\n lines.push('}');\n return lines.join('\\n');\n}\n\nfunction formatFieldType(field: PrinterField): string {\n let type = field.typeName;\n if (field.list) {\n type += '[]';\n } else if (field.optional) {\n type += '?';\n }\n return type;\n}\n\nfunction createUniqueFieldName(desiredName: string, usedFieldNames: ReadonlySet<string>): string {\n if (!usedFieldNames.has(desiredName)) {\n return desiredName;\n }\n\n let counter = 2;\n while (usedFieldNames.has(`${desiredName}${counter}`)) {\n counter++;\n }\n return `${desiredName}${counter}`;\n}\n\nfunction isNormalizedEnumMemberReservedWord(value: string): boolean {\n return ENUM_MEMBER_RESERVED_WORDS.has(value.toLowerCase());\n}\n\nfunction normalizeEnumMemberName(value: string, usedNames: ReadonlySet<string>): string {\n const desiredName =\n PSL_IDENTIFIER_PATTERN.test(value) && !isNormalizedEnumMemberReservedWord(value)\n ? value\n : createNormalizedEnumMemberBaseName(value);\n\n return createUniqueFieldName(desiredName, usedNames);\n}\n\nfunction createNormalizedEnumMemberBaseName(value: string): string {\n const tokens = value.match(/[A-Za-z0-9]+/g)?.map((token) => token.toLowerCase()) ?? [];\n let normalized = tokens[0] ?? 'value';\n\n for (const token of tokens.slice(1)) {\n normalized += token.charAt(0).toUpperCase() + token.slice(1);\n }\n\n if (isNormalizedEnumMemberReservedWord(normalized) || /^\\d/.test(normalized)) {\n normalized = `_${normalized}`;\n }\n\n return normalized;\n}\n","import type {\n PslAttribute,\n PslAttributeArgument,\n PslDocumentAst,\n PslEnum,\n PslField,\n PslModel,\n PslNamedTypeDeclaration,\n PslTypeConstructorCall,\n} from '@prisma-next/framework-components/psl-ast';\nimport type { PrintDocument } from './print-document';\nimport { escapePslString } from './serialize-print-document';\nimport type { PrinterEnum, PrinterField, PrinterModel, PrinterNamedType } from './types';\n\n// `contract infer` produces a starting-point PSL contract from a live database\n// schema; the user is expected to edit it (rename models/fields, tighten types,\n// add `@id` where introspection couldn't infer one, etc.) and then run\n// `contract emit` to produce the canonical artifacts. The header invites that\n// workflow rather than warning against it.\nconst DEFAULT_AST_PRINT_HEADER =\n '// Contract inferred from the live database schema. Edit as needed, then run `prisma-next contract emit`.';\n\nexport function astDocumentToPrintDocument(ast: PslDocumentAst): PrintDocument {\n const modelNames = new Set(ast.models.map((m) => m.name));\n const deps = buildModelFkDeps(ast.models, modelNames);\n const sortedModels = topologicalSortModels(ast.models, deps);\n\n const namedTypes: PrinterNamedType[] = ast.types\n ? ast.types.declarations.map(namedTypeDeclarationToPrinterNamedType)\n : [];\n\n const enums: PrinterEnum[] = ast.enums.map(enumToPrinterEnum);\n\n const printerModels = sortedModels.map((m) => modelToPrinterModel(m));\n\n return {\n headerComment: DEFAULT_AST_PRINT_HEADER,\n namedTypes,\n enums: enums.map((e) => ({\n name: e.name,\n mapName: e.mapName,\n values: e.values,\n })),\n models: printerModels,\n };\n}\n\nexport function renderPslAttribute(attr: PslAttribute): string {\n const prefix = attr.target === 'model' || attr.target === 'enum' ? '@@' : '@';\n if (attr.args.length === 0) {\n return `${prefix}${attr.name}`;\n }\n const inner = attr.args.map(renderAttributeArgument).join(', ');\n return `${prefix}${attr.name}(${inner})`;\n}\n\nfunction renderAttributeArgument(arg: PslAttributeArgument): string {\n if (arg.kind === 'positional') {\n return arg.value;\n }\n return `${arg.name}: ${arg.value}`;\n}\n\nfunction namedTypeDeclarationToPrinterNamedType(decl: PslNamedTypeDeclaration): PrinterNamedType {\n const base =\n decl.baseType ??\n (decl.typeConstructor !== undefined ? formatTypeConstructor(decl.typeConstructor) : '');\n const attributes = decl.attributes.map(renderPslAttribute);\n return {\n name: decl.name,\n baseType: base,\n attributes,\n };\n}\n\nfunction formatTypeConstructor(tc: PslTypeConstructorCall): string {\n const path = tc.path.join('.');\n if (tc.args.length === 0) {\n return path;\n }\n return `${path}(${tc.args.map(renderAttributeArgument).join(', ')})`;\n}\n\nfunction enumToPrinterEnum(en: PslEnum): PrinterEnum {\n let mapName: string | undefined;\n for (const a of en.attributes) {\n if (a.name === 'map' && a.target === 'enum') {\n const quoted = getPositionalStringArg(a, 0);\n if (quoted !== undefined) {\n mapName = quoted;\n }\n }\n }\n return {\n name: en.name,\n mapName,\n values: en.values.map((v) => ({\n name: v.name,\n ...(v.mapName !== undefined ? { mapName: v.mapName } : {}),\n })),\n };\n}\n\nfunction getPositionalStringArg(attr: PslAttribute, index: number): string | undefined {\n const positional = attr.args.filter((a) => a.kind === 'positional');\n const raw = positional[index]?.value.trim();\n if (!raw) return undefined;\n const m = raw.match(/^(['\"])(.*)\\1$/);\n if (!m) return undefined;\n return unescapePslString(m[2] as string);\n}\n\n/**\n * Inverse of `escapePslString`. The parser stores quoted-literal arguments with\n * their PSL escape sequences (`\\\\`, `\\\"`, `\\n`, `\\r`) intact; when we round-trip\n * a value through `getPositionalStringArg` and re-render via `escapePslString`,\n * we must decode it once on extraction to avoid double-escaping the same\n * sequences on output.\n */\nfunction unescapePslString(value: string): string {\n let result = '';\n for (let i = 0; i < value.length; i++) {\n const ch = value.charCodeAt(i);\n if (ch !== 0x5c /* '\\\\' */ || i + 1 >= value.length) {\n result += value[i];\n continue;\n }\n const next = value[i + 1];\n if (next === '\\\\' || next === '\"' || next === \"'\") {\n result += next;\n } else if (next === 'n') {\n result += '\\n';\n } else if (next === 'r') {\n result += '\\r';\n } else {\n result += '\\\\';\n result += next;\n }\n i++;\n }\n return result;\n}\n\nfunction modelToPrinterModel(model: PslModel): PrinterModel {\n let mapName: string | undefined;\n const modelAttrStrings: string[] = [];\n\n for (const a of model.attributes) {\n if (a.name === 'map' && a.target === 'model') {\n mapName = getPositionalStringArg(a, 0) ?? mapName;\n continue;\n }\n modelAttrStrings.push(renderPslAttribute(a));\n }\n\n if (mapName !== undefined) {\n modelAttrStrings.push(`@@map(\"${escapePslString(mapName)}\")`);\n }\n\n const printerFields = model.fields.map((f) => fieldToPrinterField(f));\n\n return {\n name: model.name,\n mapName,\n fields: printerFields,\n modelAttributes: modelAttrStrings,\n comment: model.comment,\n };\n}\n\nfunction fieldToPrinterField(field: PslField): PrinterField {\n const typeName =\n field.typeConstructor !== undefined\n ? formatTypeConstructor(field.typeConstructor)\n : field.typeName;\n\n let mapName: string | undefined;\n const attrStrings: string[] = [];\n\n for (const a of field.attributes) {\n if (a.name === 'map' && a.target === 'field') {\n mapName = getPositionalStringArg(a, 0) ?? mapName;\n continue;\n }\n attrStrings.push(renderPslAttribute(a));\n }\n\n if (mapName !== undefined) {\n attrStrings.push(`@map(\"${escapePslString(mapName)}\")`);\n }\n\n const isRelation = field.attributes.some((a) => a.name === 'relation' && a.target === 'field');\n\n const isUnsupported = typeName.startsWith('Unsupported(');\n\n const isId = field.attributes.some((a) => a.name === 'id' && a.target === 'field');\n\n return {\n name: field.name,\n typeName,\n optional: field.optional,\n list: field.list,\n attributes: attrStrings,\n mapName,\n isId,\n isRelation,\n isUnsupported,\n comment: undefined,\n };\n}\n\nfunction buildModelFkDeps(\n models: readonly PslModel[],\n modelNames: ReadonlySet<string>,\n): Map<string, Set<string>> {\n const deps = new Map<string, Set<string>>();\n for (const m of models) {\n deps.set(m.name, new Set());\n }\n\n for (const m of models) {\n for (const field of m.fields) {\n const refModel = relationReferencedModel(field, modelNames);\n if (!refModel || refModel === m.name) continue;\n if (!hasFullRelation(field)) continue;\n (deps.get(m.name) as Set<string>).add(refModel);\n }\n }\n\n return deps;\n}\n\nfunction hasFullRelation(field: PslField): boolean {\n const rel = field.attributes.find((a) => a.name === 'relation' && a.target === 'field');\n if (!rel) return false;\n const named = Object.fromEntries(\n rel.args\n .filter(\n (a): a is import('@prisma-next/framework-components/psl-ast').PslAttributeNamedArgument =>\n a.kind === 'named',\n )\n .map((a) => [a.name, a.value.trim()]),\n );\n return named['fields'] !== undefined && named['references'] !== undefined;\n}\n\nfunction relationReferencedModel(\n field: PslField,\n modelNames: ReadonlySet<string>,\n): string | undefined {\n const head = field.typeConstructor?.path[0];\n const raw = head ?? field.typeName.replace(/\\?$/, '').replace(/\\[\\]$/, '');\n if (raw.length === 0) {\n return undefined;\n }\n return modelNames.has(raw) ? raw : undefined;\n}\n\nfunction topologicalSortModels(\n models: readonly PslModel[],\n deps: ReadonlyMap<string, Set<string>>,\n): PslModel[] {\n const byName = new Map(models.map((m) => [m.name, m]));\n const result: PslModel[] = [];\n const visited = new Set<string>();\n const visiting = new Set<string>();\n\n const sortedNames = [...deps.keys()].sort();\n\n function visit(name: string): void {\n if (visited.has(name)) return;\n if (visiting.has(name)) return;\n visiting.add(name);\n\n const sortedDeps = [...(deps.get(name) ?? new Set())].sort();\n for (const dep of sortedDeps) {\n visit(dep);\n }\n\n visiting.delete(name);\n visited.add(name);\n const model = byName.get(name);\n if (model) {\n result.push(model);\n }\n }\n\n for (const name of sortedNames) {\n visit(name);\n }\n\n return result;\n}\n","import type { PslDocumentAst } from '@prisma-next/framework-components/psl-ast';\nimport { astDocumentToPrintDocument } from './ast-to-print-document';\nimport { serializePrintDocument } from './serialize-print-document';\n\nexport function printPslFromAst(ast: PslDocumentAst): string {\n const doc = astDocumentToPrintDocument(ast);\n return serializePrintDocument(doc);\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "name": "@prisma-next/psl-printer",
3
- "version": "0.5.0-dev.8",
3
+ "version": "0.5.0-dev.81",
4
+ "license": "Apache-2.0",
4
5
  "type": "module",
5
6
  "sideEffects": false,
6
- "description": "PSL printer: converts SqlSchemaIR to Prisma Schema Language (.prisma) files",
7
+ "description": "PSL printer: deterministic Prisma Schema Language (.prisma) output from a PslDocumentAst",
7
8
  "dependencies": {
8
- "@prisma-next/contract": "0.5.0-dev.8",
9
- "@prisma-next/sql-schema-ir": "0.5.0-dev.8",
10
- "@prisma-next/utils": "0.5.0-dev.8"
9
+ "@prisma-next/framework-components": "0.5.0-dev.81"
11
10
  },
12
11
  "devDependencies": {
13
- "tsdown": "0.18.4",
12
+ "tsdown": "0.22.0",
14
13
  "typescript": "5.9.3",
15
- "vitest": "4.0.17",
14
+ "vitest": "4.1.5",
15
+ "@prisma-next/psl-parser": "0.5.0-dev.81",
16
16
  "@prisma-next/tsconfig": "0.0.0",
17
17
  "@prisma-next/tsdown": "0.0.0"
18
18
  },
@@ -25,10 +25,6 @@
25
25
  "types": "./dist/index.d.mts",
26
26
  "import": "./dist/index.mjs"
27
27
  },
28
- "./postgres": {
29
- "types": "./dist/postgres.d.mts",
30
- "import": "./dist/postgres.mjs"
31
- },
32
28
  "./package.json": "./package.json"
33
29
  },
34
30
  "main": "./dist/index.mjs",
@@ -0,0 +1,293 @@
1
+ import type {
2
+ PslAttribute,
3
+ PslAttributeArgument,
4
+ PslDocumentAst,
5
+ PslEnum,
6
+ PslField,
7
+ PslModel,
8
+ PslNamedTypeDeclaration,
9
+ PslTypeConstructorCall,
10
+ } from '@prisma-next/framework-components/psl-ast';
11
+ import type { PrintDocument } from './print-document';
12
+ import { escapePslString } from './serialize-print-document';
13
+ import type { PrinterEnum, PrinterField, PrinterModel, PrinterNamedType } from './types';
14
+
15
+ // `contract infer` produces a starting-point PSL contract from a live database
16
+ // schema; the user is expected to edit it (rename models/fields, tighten types,
17
+ // add `@id` where introspection couldn't infer one, etc.) and then run
18
+ // `contract emit` to produce the canonical artifacts. The header invites that
19
+ // workflow rather than warning against it.
20
+ const DEFAULT_AST_PRINT_HEADER =
21
+ '// Contract inferred from the live database schema. Edit as needed, then run `prisma-next contract emit`.';
22
+
23
+ export function astDocumentToPrintDocument(ast: PslDocumentAst): PrintDocument {
24
+ const modelNames = new Set(ast.models.map((m) => m.name));
25
+ const deps = buildModelFkDeps(ast.models, modelNames);
26
+ const sortedModels = topologicalSortModels(ast.models, deps);
27
+
28
+ const namedTypes: PrinterNamedType[] = ast.types
29
+ ? ast.types.declarations.map(namedTypeDeclarationToPrinterNamedType)
30
+ : [];
31
+
32
+ const enums: PrinterEnum[] = ast.enums.map(enumToPrinterEnum);
33
+
34
+ const printerModels = sortedModels.map((m) => modelToPrinterModel(m));
35
+
36
+ return {
37
+ headerComment: DEFAULT_AST_PRINT_HEADER,
38
+ namedTypes,
39
+ enums: enums.map((e) => ({
40
+ name: e.name,
41
+ mapName: e.mapName,
42
+ values: e.values,
43
+ })),
44
+ models: printerModels,
45
+ };
46
+ }
47
+
48
+ export function renderPslAttribute(attr: PslAttribute): string {
49
+ const prefix = attr.target === 'model' || attr.target === 'enum' ? '@@' : '@';
50
+ if (attr.args.length === 0) {
51
+ return `${prefix}${attr.name}`;
52
+ }
53
+ const inner = attr.args.map(renderAttributeArgument).join(', ');
54
+ return `${prefix}${attr.name}(${inner})`;
55
+ }
56
+
57
+ function renderAttributeArgument(arg: PslAttributeArgument): string {
58
+ if (arg.kind === 'positional') {
59
+ return arg.value;
60
+ }
61
+ return `${arg.name}: ${arg.value}`;
62
+ }
63
+
64
+ function namedTypeDeclarationToPrinterNamedType(decl: PslNamedTypeDeclaration): PrinterNamedType {
65
+ const base =
66
+ decl.baseType ??
67
+ (decl.typeConstructor !== undefined ? formatTypeConstructor(decl.typeConstructor) : '');
68
+ const attributes = decl.attributes.map(renderPslAttribute);
69
+ return {
70
+ name: decl.name,
71
+ baseType: base,
72
+ attributes,
73
+ };
74
+ }
75
+
76
+ function formatTypeConstructor(tc: PslTypeConstructorCall): string {
77
+ const path = tc.path.join('.');
78
+ if (tc.args.length === 0) {
79
+ return path;
80
+ }
81
+ return `${path}(${tc.args.map(renderAttributeArgument).join(', ')})`;
82
+ }
83
+
84
+ function enumToPrinterEnum(en: PslEnum): PrinterEnum {
85
+ let mapName: string | undefined;
86
+ for (const a of en.attributes) {
87
+ if (a.name === 'map' && a.target === 'enum') {
88
+ const quoted = getPositionalStringArg(a, 0);
89
+ if (quoted !== undefined) {
90
+ mapName = quoted;
91
+ }
92
+ }
93
+ }
94
+ return {
95
+ name: en.name,
96
+ mapName,
97
+ values: en.values.map((v) => ({
98
+ name: v.name,
99
+ ...(v.mapName !== undefined ? { mapName: v.mapName } : {}),
100
+ })),
101
+ };
102
+ }
103
+
104
+ function getPositionalStringArg(attr: PslAttribute, index: number): string | undefined {
105
+ const positional = attr.args.filter((a) => a.kind === 'positional');
106
+ const raw = positional[index]?.value.trim();
107
+ if (!raw) return undefined;
108
+ const m = raw.match(/^(['"])(.*)\1$/);
109
+ if (!m) return undefined;
110
+ return unescapePslString(m[2] as string);
111
+ }
112
+
113
+ /**
114
+ * Inverse of `escapePslString`. The parser stores quoted-literal arguments with
115
+ * their PSL escape sequences (`\\`, `\"`, `\n`, `\r`) intact; when we round-trip
116
+ * a value through `getPositionalStringArg` and re-render via `escapePslString`,
117
+ * we must decode it once on extraction to avoid double-escaping the same
118
+ * sequences on output.
119
+ */
120
+ function unescapePslString(value: string): string {
121
+ let result = '';
122
+ for (let i = 0; i < value.length; i++) {
123
+ const ch = value.charCodeAt(i);
124
+ if (ch !== 0x5c /* '\\' */ || i + 1 >= value.length) {
125
+ result += value[i];
126
+ continue;
127
+ }
128
+ const next = value[i + 1];
129
+ if (next === '\\' || next === '"' || next === "'") {
130
+ result += next;
131
+ } else if (next === 'n') {
132
+ result += '\n';
133
+ } else if (next === 'r') {
134
+ result += '\r';
135
+ } else {
136
+ result += '\\';
137
+ result += next;
138
+ }
139
+ i++;
140
+ }
141
+ return result;
142
+ }
143
+
144
+ function modelToPrinterModel(model: PslModel): PrinterModel {
145
+ let mapName: string | undefined;
146
+ const modelAttrStrings: string[] = [];
147
+
148
+ for (const a of model.attributes) {
149
+ if (a.name === 'map' && a.target === 'model') {
150
+ mapName = getPositionalStringArg(a, 0) ?? mapName;
151
+ continue;
152
+ }
153
+ modelAttrStrings.push(renderPslAttribute(a));
154
+ }
155
+
156
+ if (mapName !== undefined) {
157
+ modelAttrStrings.push(`@@map("${escapePslString(mapName)}")`);
158
+ }
159
+
160
+ const printerFields = model.fields.map((f) => fieldToPrinterField(f));
161
+
162
+ return {
163
+ name: model.name,
164
+ mapName,
165
+ fields: printerFields,
166
+ modelAttributes: modelAttrStrings,
167
+ comment: model.comment,
168
+ };
169
+ }
170
+
171
+ function fieldToPrinterField(field: PslField): PrinterField {
172
+ const typeName =
173
+ field.typeConstructor !== undefined
174
+ ? formatTypeConstructor(field.typeConstructor)
175
+ : field.typeName;
176
+
177
+ let mapName: string | undefined;
178
+ const attrStrings: string[] = [];
179
+
180
+ for (const a of field.attributes) {
181
+ if (a.name === 'map' && a.target === 'field') {
182
+ mapName = getPositionalStringArg(a, 0) ?? mapName;
183
+ continue;
184
+ }
185
+ attrStrings.push(renderPslAttribute(a));
186
+ }
187
+
188
+ if (mapName !== undefined) {
189
+ attrStrings.push(`@map("${escapePslString(mapName)}")`);
190
+ }
191
+
192
+ const isRelation = field.attributes.some((a) => a.name === 'relation' && a.target === 'field');
193
+
194
+ const isUnsupported = typeName.startsWith('Unsupported(');
195
+
196
+ const isId = field.attributes.some((a) => a.name === 'id' && a.target === 'field');
197
+
198
+ return {
199
+ name: field.name,
200
+ typeName,
201
+ optional: field.optional,
202
+ list: field.list,
203
+ attributes: attrStrings,
204
+ mapName,
205
+ isId,
206
+ isRelation,
207
+ isUnsupported,
208
+ comment: undefined,
209
+ };
210
+ }
211
+
212
+ function buildModelFkDeps(
213
+ models: readonly PslModel[],
214
+ modelNames: ReadonlySet<string>,
215
+ ): Map<string, Set<string>> {
216
+ const deps = new Map<string, Set<string>>();
217
+ for (const m of models) {
218
+ deps.set(m.name, new Set());
219
+ }
220
+
221
+ for (const m of models) {
222
+ for (const field of m.fields) {
223
+ const refModel = relationReferencedModel(field, modelNames);
224
+ if (!refModel || refModel === m.name) continue;
225
+ if (!hasFullRelation(field)) continue;
226
+ (deps.get(m.name) as Set<string>).add(refModel);
227
+ }
228
+ }
229
+
230
+ return deps;
231
+ }
232
+
233
+ function hasFullRelation(field: PslField): boolean {
234
+ const rel = field.attributes.find((a) => a.name === 'relation' && a.target === 'field');
235
+ if (!rel) return false;
236
+ const named = Object.fromEntries(
237
+ rel.args
238
+ .filter(
239
+ (a): a is import('@prisma-next/framework-components/psl-ast').PslAttributeNamedArgument =>
240
+ a.kind === 'named',
241
+ )
242
+ .map((a) => [a.name, a.value.trim()]),
243
+ );
244
+ return named['fields'] !== undefined && named['references'] !== undefined;
245
+ }
246
+
247
+ function relationReferencedModel(
248
+ field: PslField,
249
+ modelNames: ReadonlySet<string>,
250
+ ): string | undefined {
251
+ const head = field.typeConstructor?.path[0];
252
+ const raw = head ?? field.typeName.replace(/\?$/, '').replace(/\[\]$/, '');
253
+ if (raw.length === 0) {
254
+ return undefined;
255
+ }
256
+ return modelNames.has(raw) ? raw : undefined;
257
+ }
258
+
259
+ function topologicalSortModels(
260
+ models: readonly PslModel[],
261
+ deps: ReadonlyMap<string, Set<string>>,
262
+ ): PslModel[] {
263
+ const byName = new Map(models.map((m) => [m.name, m]));
264
+ const result: PslModel[] = [];
265
+ const visited = new Set<string>();
266
+ const visiting = new Set<string>();
267
+
268
+ const sortedNames = [...deps.keys()].sort();
269
+
270
+ function visit(name: string): void {
271
+ if (visited.has(name)) return;
272
+ if (visiting.has(name)) return;
273
+ visiting.add(name);
274
+
275
+ const sortedDeps = [...(deps.get(name) ?? new Set())].sort();
276
+ for (const dep of sortedDeps) {
277
+ visit(dep);
278
+ }
279
+
280
+ visiting.delete(name);
281
+ visited.add(name);
282
+ const model = byName.get(name);
283
+ if (model) {
284
+ result.push(model);
285
+ }
286
+ }
287
+
288
+ for (const name of sortedNames) {
289
+ visit(name);
290
+ }
291
+
292
+ return result;
293
+ }
@@ -1,8 +1 @@
1
- export { printPsl } from '../print-psl';
2
- export type {
3
- PslPrintableSqlColumn,
4
- PslPrintableSqlSchemaIR,
5
- PslPrintableSqlTable,
6
- } from '../schema-validation';
7
- export { validatePrintableSqlSchemaIR } from '../schema-validation';
8
- export type { EnumInfo, PslPrinterOptions, PslTypeMap, PslTypeResolution } from '../types';
1
+ export { printPslFromAst as printPsl } from '../print-psl';
@@ -0,0 +1,14 @@
1
+ import type { PrinterEnumValue, PrinterModel, PrinterNamedType } from './types';
2
+
3
+ export type PrintEnumSection = {
4
+ readonly name: string;
5
+ readonly mapName?: string | undefined;
6
+ readonly values: readonly PrinterEnumValue[];
7
+ };
8
+
9
+ export type PrintDocument = {
10
+ readonly headerComment: string;
11
+ readonly namedTypes: readonly PrinterNamedType[];
12
+ readonly enums: readonly PrintEnumSection[];
13
+ readonly models: readonly PrinterModel[];
14
+ };