@prisma-next/psl-printer
Advanced tools
| import type { StructuredError, StructuredErrorOptions } from '@prisma-next/utils/structured-error'; | ||
| import { structuredError } from '@prisma-next/utils/structured-error'; | ||
| export type ContractCode = `CONTRACT.${ContractSubcode}`; | ||
| type ContractSubcode = 'PACK_CONTRIBUTION_INVALID'; | ||
| export function contractError( | ||
| code: ContractCode, | ||
| message: string, | ||
| options?: StructuredErrorOptions, | ||
| ): StructuredError { | ||
| return structuredError(code, message, options); | ||
| } |
+41
-8
@@ -5,2 +5,8 @@ import { ifDefined } from "@prisma-next/utils/defined"; | ||
| import { blindCast } from "@prisma-next/utils/casts"; | ||
| import { structuredError } from "@prisma-next/utils/structured-error"; | ||
| //#region src/contract-errors.ts | ||
| function contractError(code, message, options) { | ||
| return structuredError(code, message, options); | ||
| } | ||
| //#endregion | ||
| //#region src/serialize-print-document.ts | ||
@@ -50,4 +56,12 @@ /** | ||
| const descriptor = blockDispatchMap.byKeyword.get(extensionBlock.keyword); | ||
| if (!descriptor) throw new Error(`No pslBlockDescriptors contribution registered for extension-contributed block keyword "${extensionBlock.keyword}". Provide a matching pslBlockDescriptors contribution to serializePrintDocument, or remove the block from the input AST.`); | ||
| if (descriptor.discriminator !== extensionBlock.kind) throw new Error(`The pslBlockDescriptors contribution for keyword "${extensionBlock.keyword}" owns discriminator "${descriptor.discriminator}", but the block carries kind "${extensionBlock.kind}". Provide a matching pslBlockDescriptors contribution to serializePrintDocument, or remove the block from the input AST.`); | ||
| if (!descriptor) throw contractError("CONTRACT.PACK_CONTRIBUTION_INVALID", `No pslBlockDescriptors contribution registered for extension-contributed block keyword "${extensionBlock.keyword}". Provide a matching pslBlockDescriptors contribution to serializePrintDocument, or remove the block from the input AST.`, { meta: { | ||
| reason: "block-descriptor-missing", | ||
| keyword: extensionBlock.keyword | ||
| } }); | ||
| if (descriptor.discriminator !== extensionBlock.kind) throw contractError("CONTRACT.PACK_CONTRIBUTION_INVALID", `The pslBlockDescriptors contribution for keyword "${extensionBlock.keyword}" owns discriminator "${descriptor.discriminator}", but the block carries kind "${extensionBlock.kind}". Provide a matching pslBlockDescriptors contribution to serializePrintDocument, or remove the block from the input AST.`, { meta: { | ||
| reason: "block-descriptor-kind-mismatch", | ||
| keyword: extensionBlock.keyword, | ||
| descriptorDiscriminator: descriptor.discriminator, | ||
| blockKind: extensionBlock.kind | ||
| } }); | ||
| const lines = [`${extensionBlock.keyword} ${extensionBlock.name} {`]; | ||
@@ -93,19 +107,31 @@ for (const [paramName, paramDescriptor] of Object.entries(descriptor.parameters)) { | ||
| case "ref": | ||
| if (paramValue.kind !== "ref") throw new Error(`Extension block parameter "${paramName}": descriptor is "ref" but AST node has kind "${paramValue.kind}"`); | ||
| if (paramValue.kind !== "ref") throw paramKindMismatchError(paramName, "ref", paramValue.kind); | ||
| return paramValue.identifier; | ||
| case "value": | ||
| if (paramValue.kind !== "value") throw new Error(`Extension block parameter "${paramName}": descriptor is "value" but AST node has kind "${paramValue.kind}"`); | ||
| if (paramValue.kind !== "value") throw paramKindMismatchError(paramName, "value", paramValue.kind); | ||
| return renderValueParam(paramValue.raw, descriptor.codecId, codecLookup, paramName); | ||
| case "option": | ||
| if (paramValue.kind !== "option") throw new Error(`Extension block parameter "${paramName}": descriptor is "option" but AST node has kind "${paramValue.kind}"`); | ||
| if (paramValue.kind !== "option") throw paramKindMismatchError(paramName, "option", paramValue.kind); | ||
| return paramValue.token; | ||
| case "list": | ||
| if (paramValue.kind !== "list") throw new Error(`Extension block parameter "${paramName}": descriptor is "list" but AST node has kind "${paramValue.kind}"`); | ||
| if (paramValue.kind !== "list") throw paramKindMismatchError(paramName, "list", paramValue.kind); | ||
| return `[${paramValue.items.map((item) => renderParamValue(item, descriptor.of, codecLookup, paramName)).join(", ")}]`; | ||
| } | ||
| } | ||
| function paramKindMismatchError(paramName, descriptorKind, valueKind) { | ||
| return contractError("CONTRACT.PACK_CONTRIBUTION_INVALID", `Extension block parameter "${paramName}": descriptor is "${descriptorKind}" but AST node has kind "${valueKind}"`, { meta: { | ||
| reason: "param-kind-mismatch", | ||
| paramName, | ||
| descriptorKind, | ||
| valueKind | ||
| } }); | ||
| } | ||
| function renderValueParam(raw, codecId, codecLookup, paramName) { | ||
| if (!codecLookup) return raw; | ||
| const codec = codecLookup.get(codecId); | ||
| if (!codec) throw new Error(`Extension block parameter "${paramName}": no codec registered for id "${codecId}"`); | ||
| if (!codec) throw contractError("CONTRACT.PACK_CONTRIBUTION_INVALID", `Extension block parameter "${paramName}": no codec registered for id "${codecId}"`, { meta: { | ||
| reason: "codec-unregistered", | ||
| paramName, | ||
| codecId | ||
| } }); | ||
| let parsedJson; | ||
@@ -115,3 +141,10 @@ try { | ||
| } catch (e) { | ||
| throw new Error(`Extension block parameter "${paramName}": codec "${codecId}" — raw literal is not valid JSON: ${String(e)}`); | ||
| throw contractError("CONTRACT.PACK_CONTRIBUTION_INVALID", `Extension block parameter "${paramName}": codec "${codecId}" — raw literal is not valid JSON: ${String(e)}`, { | ||
| meta: { | ||
| reason: "raw-literal-invalid-json", | ||
| paramName, | ||
| codecId | ||
| }, | ||
| cause: e | ||
| }); | ||
| } | ||
@@ -118,0 +151,0 @@ return JSON.stringify(codec.encodeJson(codec.decodeJson(blindCast(parsedJson)))); |
@@ -1,1 +0,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 {\n AuthoringPslBlockDescriptor,\n AuthoringPslBlockDescriptorNamespace,\n} from '@prisma-next/framework-components/authoring';\nimport { isAuthoringPslBlockDescriptor } from '@prisma-next/framework-components/authoring';\nimport type { CodecLookup } from '@prisma-next/framework-components/codec';\nimport type {\n PslBlockParam,\n PslExtensionBlock,\n PslExtensionBlockParamValue,\n} from '@prisma-next/framework-components/psl-ast';\nimport { UNSPECIFIED_PSL_NAMESPACE_ID } from '@prisma-next/framework-components/psl-ast';\nimport { blindCast } from '@prisma-next/utils/casts';\nimport type { PrintDocument, PrintNamespaceSection } from './print-document';\nimport type { PrinterField, PrinterNamedType } from './types';\n\n/**\n * Indent unit used for PSL block bodies and namespace nesting.\n */\nconst PSL_INDENT_UNIT = ' ';\n\n/**\n * Maps each block keyword to its descriptor, keyed by keyword rather than\n * discriminator because several keywords can share one discriminator but\n * each keyword resolves to exactly one descriptor.\n */\ninterface PslBlockDispatchMap {\n readonly byKeyword: ReadonlyMap<string, AuthoringPslBlockDescriptor>;\n}\n\nfunction buildPslBlockDispatchMap(\n namespace: AuthoringPslBlockDescriptorNamespace | undefined,\n): PslBlockDispatchMap {\n const byKeyword = new Map<string, AuthoringPslBlockDescriptor>();\n if (namespace) {\n collectBlockDescriptors(namespace, byKeyword);\n }\n return { byKeyword };\n}\n\nfunction collectBlockDescriptors(\n namespace: AuthoringPslBlockDescriptorNamespace,\n byKeyword: Map<string, AuthoringPslBlockDescriptor>,\n): void {\n for (const value of Object.values(namespace)) {\n if (isAuthoringPslBlockDescriptor(value)) {\n byKeyword.set(value.keyword, value);\n continue;\n }\n if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\n collectBlockDescriptors(value, byKeyword);\n }\n }\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 interface SerializePrintDocumentOptions {\n readonly pslBlockDescriptors?: AuthoringPslBlockDescriptorNamespace;\n readonly codecLookup?: CodecLookup;\n}\n\nexport function serializePrintDocument(\n doc: PrintDocument,\n options: SerializePrintDocumentOptions = {},\n): 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 blockDispatchMap = buildPslBlockDispatchMap(options.pslBlockDescriptors);\n\n for (const namespace of doc.namespaces) {\n const namespaceSections = serializeNamespaceContents(\n namespace,\n blockDispatchMap,\n options.codecLookup,\n );\n if (namespaceSections.length === 0) {\n continue;\n }\n if (namespace.name === UNSPECIFIED_PSL_NAMESPACE_ID) {\n // The parser-synthesised bucket exists for AST symmetry; printing it as\n // `namespace __unspecified__ { … }` would invent syntax the user never\n // wrote. Top-level declarations round-trip back to top-level output.\n sections.push(...namespaceSections);\n } else {\n sections.push(wrapNamespaceBlock(namespace.name, namespaceSections));\n }\n }\n\n return `${sections.join('\\n\\n')}\\n`;\n}\n\nfunction serializeNamespaceContents(\n namespace: PrintNamespaceSection,\n blockDispatchMap: PslBlockDispatchMap,\n codecLookup: CodecLookup | undefined,\n): string[] {\n const sections: string[] = [];\n for (const model of namespace.models) {\n sections.push(serializeModel(model));\n }\n for (const extensionBlock of namespace.extensionBlocks) {\n sections.push(serializeExtensionBlock(extensionBlock, blockDispatchMap, codecLookup));\n }\n return sections;\n}\n\nfunction serializeExtensionBlock(\n extensionBlock: PslExtensionBlock,\n blockDispatchMap: PslBlockDispatchMap,\n codecLookup: CodecLookup | undefined,\n): string {\n const descriptor = blockDispatchMap.byKeyword.get(extensionBlock.keyword);\n if (!descriptor) {\n throw new Error(\n `No pslBlockDescriptors contribution registered for extension-contributed block keyword \"${extensionBlock.keyword}\". Provide a matching pslBlockDescriptors contribution to serializePrintDocument, or remove the block from the input AST.`,\n );\n }\n if (descriptor.discriminator !== extensionBlock.kind) {\n throw new Error(\n `The pslBlockDescriptors contribution for keyword \"${extensionBlock.keyword}\" owns discriminator \"${descriptor.discriminator}\", but the block carries kind \"${extensionBlock.kind}\". Provide a matching pslBlockDescriptors contribution to serializePrintDocument, or remove the block from the input AST.`,\n );\n }\n const lines: string[] = [`${extensionBlock.keyword} ${extensionBlock.name} {`];\n for (const [paramName, paramDescriptor] of Object.entries(descriptor.parameters)) {\n const paramValue = extensionBlock.parameters[paramName];\n if (paramValue === undefined) {\n continue;\n }\n const rendered = renderParamValue(paramValue, paramDescriptor, codecLookup, paramName);\n lines.push(`${PSL_INDENT_UNIT}${paramName} = ${rendered}`);\n }\n if (descriptor.variadicParameters) {\n for (const [paramName, paramValue] of Object.entries(extensionBlock.parameters)) {\n if (Object.hasOwn(descriptor.parameters, paramName)) {\n continue;\n }\n lines.push(`${PSL_INDENT_UNIT}${renderVariadicParam(paramName, paramValue)}`);\n }\n }\n for (const attr of extensionBlock.blockAttributes ?? []) {\n const args = attr.args.map((arg) => arg.value).join(', ');\n lines.push(`${PSL_INDENT_UNIT}@@${attr.name}${args.length > 0 ? `(${args})` : ''}`);\n }\n lines.push('}');\n return lines.join('\\n');\n}\n\n/**\n * Renders one undeclared parameter of a `variadicParameters` block (e.g. a\n * `native_enum` member line). Variadic entries have no per-parameter\n * descriptor, so there is no codec to round-trip a `value` through — the raw\n * PSL literal is emitted verbatim. A `bare` entry is just its key.\n */\nfunction renderVariadicParam(paramName: string, paramValue: PslExtensionBlockParamValue): string {\n if (paramValue.kind === 'bare') {\n return paramName;\n }\n return `${paramName} = ${renderVariadicValue(paramValue)}`;\n}\n\nfunction renderVariadicValue(paramValue: PslExtensionBlockParamValue): string {\n switch (paramValue.kind) {\n case 'bare':\n return '';\n case 'value':\n return paramValue.raw;\n case 'ref':\n return paramValue.identifier;\n case 'option':\n return paramValue.token;\n case 'list':\n return `[${paramValue.items.map(renderVariadicValue).join(', ')}]`;\n }\n}\n\nfunction renderParamValue(\n paramValue: PslExtensionBlockParamValue,\n descriptor: PslBlockParam,\n codecLookup: CodecLookup | undefined,\n paramName: string,\n): string {\n switch (descriptor.kind) {\n case 'ref': {\n if (paramValue.kind !== 'ref') {\n throw new Error(\n `Extension block parameter \"${paramName}\": descriptor is \"ref\" but AST node has kind \"${paramValue.kind}\"`,\n );\n }\n return paramValue.identifier;\n }\n case 'value': {\n if (paramValue.kind !== 'value') {\n throw new Error(\n `Extension block parameter \"${paramName}\": descriptor is \"value\" but AST node has kind \"${paramValue.kind}\"`,\n );\n }\n return renderValueParam(paramValue.raw, descriptor.codecId, codecLookup, paramName);\n }\n case 'option': {\n if (paramValue.kind !== 'option') {\n throw new Error(\n `Extension block parameter \"${paramName}\": descriptor is \"option\" but AST node has kind \"${paramValue.kind}\"`,\n );\n }\n return paramValue.token;\n }\n case 'list': {\n if (paramValue.kind !== 'list') {\n throw new Error(\n `Extension block parameter \"${paramName}\": descriptor is \"list\" but AST node has kind \"${paramValue.kind}\"`,\n );\n }\n const items = paramValue.items.map((item) =>\n renderParamValue(item, descriptor.of, codecLookup, paramName),\n );\n return `[${items.join(', ')}]`;\n }\n }\n}\n\nfunction renderValueParam(\n raw: string,\n codecId: string,\n codecLookup: CodecLookup | undefined,\n paramName: string,\n): string {\n if (!codecLookup) {\n return raw;\n }\n const codec = codecLookup.get(codecId);\n if (!codec) {\n throw new Error(\n `Extension block parameter \"${paramName}\": no codec registered for id \"${codecId}\"`,\n );\n }\n let parsedJson: unknown;\n try {\n parsedJson = JSON.parse(raw);\n } catch (e) {\n throw new Error(\n `Extension block parameter \"${paramName}\": codec \"${codecId}\" — raw literal is not valid JSON: ${String(e)}`,\n );\n }\n return JSON.stringify(\n codec.encodeJson(\n codec.decodeJson(\n blindCast<Parameters<typeof codec.decodeJson>[0], 'JSON.parse output is JsonValue'>(\n parsedJson,\n ),\n ),\n ),\n );\n}\n\nfunction wrapNamespaceBlock(name: string, innerSections: readonly string[]): string {\n const indented = innerSections\n .map((section) =>\n section\n .split('\\n')\n .map((line) => (line.length > 0 ? ` ${line}` : line))\n .join('\\n'),\n )\n .join('\\n\\n');\n return `namespace ${name} {\\n${indented}\\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 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","import type {\n PslAttribute,\n PslAttributeArgument,\n PslDocumentAst,\n PslField,\n PslModel,\n PslNamedTypeDeclaration,\n PslTypeConstructorCall,\n} from '@prisma-next/framework-components/psl-ast';\nimport {\n flatPslModels,\n namespacePslExtensionBlocks,\n UNSPECIFIED_PSL_NAMESPACE_ID,\n} from '@prisma-next/framework-components/psl-ast';\nimport type { PrintDocument, PrintNamespaceSection } from './print-document';\nimport { escapePslString } from './serialize-print-document';\nimport type { 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 '// use prisma-next\\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 // FK dependencies are resolved across the whole document — a model in one\n // namespace can reference a model in another, and the topo-sort needs to\n // see every model to produce a stable order. After sorting, we re-bucket by\n // namespace so each block prints with its own models in topo order.\n const allModels = flatPslModels(ast);\n const modelNames = new Set(allModels.map((m) => m.name));\n const deps = buildModelFkDeps(allModels, modelNames);\n const sortedModels = topologicalSortModels(allModels, deps);\n\n const modelNamespaceIndex = new Map<string, string>();\n for (const namespace of ast.namespaces) {\n for (const model of namespace.models) {\n modelNamespaceIndex.set(model.name, namespace.name);\n }\n }\n\n const namedTypes: PrinterNamedType[] = ast.types\n ? ast.types.declarations.map(namedTypeDeclarationToPrinterNamedType)\n : [];\n\n const namespaceSections: PrintNamespaceSection[] = ast.namespaces.map((namespace) => {\n const namespaceModels = sortedModels.filter(\n (model) => modelNamespaceIndex.get(model.name) === namespace.name,\n );\n const printerModels = namespaceModels.map((m) => modelToPrinterModel(m));\n return {\n name: namespace.name,\n models: printerModels,\n extensionBlocks: namespacePslExtensionBlocks(namespace),\n };\n });\n\n // Ensure the synthesised `__unspecified__` bucket sorts first so top-level\n // declarations print before any `namespace { … }` blocks — matches what a\n // user would write by hand.\n namespaceSections.sort((a, b) => {\n if (a.name === b.name) return 0;\n if (a.name === UNSPECIFIED_PSL_NAMESPACE_ID) return -1;\n if (b.name === UNSPECIFIED_PSL_NAMESPACE_ID) return 1;\n return a.name.localeCompare(b.name);\n });\n\n return {\n headerComment: DEFAULT_AST_PRINT_HEADER,\n namedTypes,\n namespaces: namespaceSections,\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 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\n/**\n * Assembles the qualified type name string for a field type reference.\n *\n * Handles all four forms:\n * - `space:ns.Name` — typeContractSpaceId + typeNamespaceId + typeName\n * - `space:Name` — typeContractSpaceId + typeName (no namespace)\n * - `ns.Name` — typeNamespaceId + typeName (no space); fixes TML-2459 printer gap\n * - `Name` — typeName only (no qualifier)\n */\nfunction assembleQualifiedTypeName(field: PslField): string {\n const { typeName, typeNamespaceId, typeContractSpaceId } = field;\n const dotted = typeNamespaceId !== undefined ? `${typeNamespaceId}.${typeName}` : typeName;\n return typeContractSpaceId !== undefined ? `${typeContractSpaceId}:${dotted}` : dotted;\n}\n\nfunction fieldToPrinterField(field: PslField): PrinterField {\n // Assemble the qualified type identifier: `space:ns.Name` / `space:Name` / `ns.Name` / `Name`.\n // When a typeConstructor is present it takes precedence and carries no qualifier.\n // Line-wrap policy (pinned): keep the identifier on one line until the existing column limit —\n // no special wrap logic at `:` or `.` (project-spec open question; simplest readable default).\n const typeName =\n field.typeConstructor !== undefined\n ? formatTypeConstructor(field.typeConstructor)\n : assembleQualifiedTypeName(field);\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 { AuthoringPslBlockDescriptorNamespace } from '@prisma-next/framework-components/authoring';\nimport type { CodecLookup } from '@prisma-next/framework-components/codec';\nimport type { PslDocumentAst } from '@prisma-next/framework-components/psl-ast';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { astDocumentToPrintDocument } from './ast-to-print-document';\nimport { serializePrintDocument } from './serialize-print-document';\n\nexport type PslBlockDescriptorsNamespace = AuthoringPslBlockDescriptorNamespace;\n\nexport interface PrintPslOptions {\n /**\n * Extension-contributed PSL block descriptors, indexed by user-facing path.\n * Typically an `AssembledAuthoringContributions.pslBlockDescriptors` namespace\n * produced by `assembleAuthoringContributions`. Phase 2 of the printer indexes\n * into this namespace by each extension-contributed AST node's `kind`\n * discriminator and renders the block generically from the descriptor's\n * `parameters` map.\n *\n * When absent, an AST that contains extension-contributed blocks throws —\n * silently dropping blocks would lose user-authored content without a\n * diagnostic. ASTs that contain only framework-parsed blocks print without\n * any `pslBlockDescriptors` argument, which is what existing call sites do today.\n */\n readonly pslBlockDescriptors?: PslBlockDescriptorsNamespace;\n /**\n * Codec lookup used to print `value`-kind block parameters. The codec's JSON\n * medium (`encodeJson`/`decodeJson`) validates and normalizes each value param.\n * Required alongside `pslBlockDescriptors` when the AST contains `value`-kind\n * parameters. When absent, the raw PSL literal stored in the AST node is\n * emitted as-is.\n */\n readonly codecLookup?: CodecLookup;\n}\n\nexport function printPslFromAst(ast: PslDocumentAst, options: PrintPslOptions = {}): string {\n const doc = astDocumentToPrintDocument(ast);\n return serializePrintDocument(doc, {\n ...ifDefined('pslBlockDescriptors', options.pslBlockDescriptors),\n ...ifDefined('codecLookup', options.codecLookup),\n });\n}\n"],"mappings":"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"} | ||
| {"version":3,"file":"index.mjs","names":[],"sources":["../src/contract-errors.ts","../src/serialize-print-document.ts","../src/ast-to-print-document.ts","../src/print-psl.ts"],"sourcesContent":["import type { StructuredError, StructuredErrorOptions } from '@prisma-next/utils/structured-error';\nimport { structuredError } from '@prisma-next/utils/structured-error';\n\nexport type ContractCode = `CONTRACT.${ContractSubcode}`;\n\ntype ContractSubcode = 'PACK_CONTRIBUTION_INVALID';\n\nexport function contractError(\n code: ContractCode,\n message: string,\n options?: StructuredErrorOptions,\n): StructuredError {\n return structuredError(code, message, options);\n}\n","import type {\n AuthoringPslBlockDescriptor,\n AuthoringPslBlockDescriptorNamespace,\n} from '@prisma-next/framework-components/authoring';\nimport { isAuthoringPslBlockDescriptor } from '@prisma-next/framework-components/authoring';\nimport type { CodecLookup } from '@prisma-next/framework-components/codec';\nimport type {\n PslBlockParam,\n PslExtensionBlock,\n PslExtensionBlockParamValue,\n} from '@prisma-next/framework-components/psl-ast';\nimport { UNSPECIFIED_PSL_NAMESPACE_ID } from '@prisma-next/framework-components/psl-ast';\nimport { blindCast } from '@prisma-next/utils/casts';\nimport { contractError } from './contract-errors';\nimport type { PrintDocument, PrintNamespaceSection } from './print-document';\nimport type { PrinterField, PrinterNamedType } from './types';\n\n/**\n * Indent unit used for PSL block bodies and namespace nesting.\n */\nconst PSL_INDENT_UNIT = ' ';\n\n/**\n * Maps each block keyword to its descriptor, keyed by keyword rather than\n * discriminator because several keywords can share one discriminator but\n * each keyword resolves to exactly one descriptor.\n */\ninterface PslBlockDispatchMap {\n readonly byKeyword: ReadonlyMap<string, AuthoringPslBlockDescriptor>;\n}\n\nfunction buildPslBlockDispatchMap(\n namespace: AuthoringPslBlockDescriptorNamespace | undefined,\n): PslBlockDispatchMap {\n const byKeyword = new Map<string, AuthoringPslBlockDescriptor>();\n if (namespace) {\n collectBlockDescriptors(namespace, byKeyword);\n }\n return { byKeyword };\n}\n\nfunction collectBlockDescriptors(\n namespace: AuthoringPslBlockDescriptorNamespace,\n byKeyword: Map<string, AuthoringPslBlockDescriptor>,\n): void {\n for (const value of Object.values(namespace)) {\n if (isAuthoringPslBlockDescriptor(value)) {\n byKeyword.set(value.keyword, value);\n continue;\n }\n if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\n collectBlockDescriptors(value, byKeyword);\n }\n }\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 interface SerializePrintDocumentOptions {\n readonly pslBlockDescriptors?: AuthoringPslBlockDescriptorNamespace;\n readonly codecLookup?: CodecLookup;\n}\n\nexport function serializePrintDocument(\n doc: PrintDocument,\n options: SerializePrintDocumentOptions = {},\n): 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 blockDispatchMap = buildPslBlockDispatchMap(options.pslBlockDescriptors);\n\n for (const namespace of doc.namespaces) {\n const namespaceSections = serializeNamespaceContents(\n namespace,\n blockDispatchMap,\n options.codecLookup,\n );\n if (namespaceSections.length === 0) {\n continue;\n }\n if (namespace.name === UNSPECIFIED_PSL_NAMESPACE_ID) {\n // The parser-synthesised bucket exists for AST symmetry; printing it as\n // `namespace __unspecified__ { … }` would invent syntax the user never\n // wrote. Top-level declarations round-trip back to top-level output.\n sections.push(...namespaceSections);\n } else {\n sections.push(wrapNamespaceBlock(namespace.name, namespaceSections));\n }\n }\n\n return `${sections.join('\\n\\n')}\\n`;\n}\n\nfunction serializeNamespaceContents(\n namespace: PrintNamespaceSection,\n blockDispatchMap: PslBlockDispatchMap,\n codecLookup: CodecLookup | undefined,\n): string[] {\n const sections: string[] = [];\n for (const model of namespace.models) {\n sections.push(serializeModel(model));\n }\n for (const extensionBlock of namespace.extensionBlocks) {\n sections.push(serializeExtensionBlock(extensionBlock, blockDispatchMap, codecLookup));\n }\n return sections;\n}\n\nfunction serializeExtensionBlock(\n extensionBlock: PslExtensionBlock,\n blockDispatchMap: PslBlockDispatchMap,\n codecLookup: CodecLookup | undefined,\n): string {\n const descriptor = blockDispatchMap.byKeyword.get(extensionBlock.keyword);\n if (!descriptor) {\n throw contractError(\n 'CONTRACT.PACK_CONTRIBUTION_INVALID',\n `No pslBlockDescriptors contribution registered for extension-contributed block keyword \"${extensionBlock.keyword}\". Provide a matching pslBlockDescriptors contribution to serializePrintDocument, or remove the block from the input AST.`,\n { meta: { reason: 'block-descriptor-missing', keyword: extensionBlock.keyword } },\n );\n }\n if (descriptor.discriminator !== extensionBlock.kind) {\n throw contractError(\n 'CONTRACT.PACK_CONTRIBUTION_INVALID',\n `The pslBlockDescriptors contribution for keyword \"${extensionBlock.keyword}\" owns discriminator \"${descriptor.discriminator}\", but the block carries kind \"${extensionBlock.kind}\". Provide a matching pslBlockDescriptors contribution to serializePrintDocument, or remove the block from the input AST.`,\n {\n meta: {\n reason: 'block-descriptor-kind-mismatch',\n keyword: extensionBlock.keyword,\n descriptorDiscriminator: descriptor.discriminator,\n blockKind: extensionBlock.kind,\n },\n },\n );\n }\n const lines: string[] = [`${extensionBlock.keyword} ${extensionBlock.name} {`];\n for (const [paramName, paramDescriptor] of Object.entries(descriptor.parameters)) {\n const paramValue = extensionBlock.parameters[paramName];\n if (paramValue === undefined) {\n continue;\n }\n const rendered = renderParamValue(paramValue, paramDescriptor, codecLookup, paramName);\n lines.push(`${PSL_INDENT_UNIT}${paramName} = ${rendered}`);\n }\n if (descriptor.variadicParameters) {\n for (const [paramName, paramValue] of Object.entries(extensionBlock.parameters)) {\n if (Object.hasOwn(descriptor.parameters, paramName)) {\n continue;\n }\n lines.push(`${PSL_INDENT_UNIT}${renderVariadicParam(paramName, paramValue)}`);\n }\n }\n for (const attr of extensionBlock.blockAttributes ?? []) {\n const args = attr.args.map((arg) => arg.value).join(', ');\n lines.push(`${PSL_INDENT_UNIT}@@${attr.name}${args.length > 0 ? `(${args})` : ''}`);\n }\n lines.push('}');\n return lines.join('\\n');\n}\n\n/**\n * Renders one undeclared parameter of a `variadicParameters` block (e.g. a\n * `native_enum` member line). Variadic entries have no per-parameter\n * descriptor, so there is no codec to round-trip a `value` through — the raw\n * PSL literal is emitted verbatim. A `bare` entry is just its key.\n */\nfunction renderVariadicParam(paramName: string, paramValue: PslExtensionBlockParamValue): string {\n if (paramValue.kind === 'bare') {\n return paramName;\n }\n return `${paramName} = ${renderVariadicValue(paramValue)}`;\n}\n\nfunction renderVariadicValue(paramValue: PslExtensionBlockParamValue): string {\n switch (paramValue.kind) {\n case 'bare':\n return '';\n case 'value':\n return paramValue.raw;\n case 'ref':\n return paramValue.identifier;\n case 'option':\n return paramValue.token;\n case 'list':\n return `[${paramValue.items.map(renderVariadicValue).join(', ')}]`;\n }\n}\n\nfunction renderParamValue(\n paramValue: PslExtensionBlockParamValue,\n descriptor: PslBlockParam,\n codecLookup: CodecLookup | undefined,\n paramName: string,\n): string {\n switch (descriptor.kind) {\n case 'ref': {\n if (paramValue.kind !== 'ref') {\n throw paramKindMismatchError(paramName, 'ref', paramValue.kind);\n }\n return paramValue.identifier;\n }\n case 'value': {\n if (paramValue.kind !== 'value') {\n throw paramKindMismatchError(paramName, 'value', paramValue.kind);\n }\n return renderValueParam(paramValue.raw, descriptor.codecId, codecLookup, paramName);\n }\n case 'option': {\n if (paramValue.kind !== 'option') {\n throw paramKindMismatchError(paramName, 'option', paramValue.kind);\n }\n return paramValue.token;\n }\n case 'list': {\n if (paramValue.kind !== 'list') {\n throw paramKindMismatchError(paramName, 'list', paramValue.kind);\n }\n const items = paramValue.items.map((item) =>\n renderParamValue(item, descriptor.of, codecLookup, paramName),\n );\n return `[${items.join(', ')}]`;\n }\n }\n}\n\nfunction paramKindMismatchError(\n paramName: string,\n descriptorKind: PslBlockParam['kind'],\n valueKind: PslExtensionBlockParamValue['kind'],\n) {\n return contractError(\n 'CONTRACT.PACK_CONTRIBUTION_INVALID',\n `Extension block parameter \"${paramName}\": descriptor is \"${descriptorKind}\" but AST node has kind \"${valueKind}\"`,\n { meta: { reason: 'param-kind-mismatch', paramName, descriptorKind, valueKind } },\n );\n}\n\nfunction renderValueParam(\n raw: string,\n codecId: string,\n codecLookup: CodecLookup | undefined,\n paramName: string,\n): string {\n if (!codecLookup) {\n return raw;\n }\n const codec = codecLookup.get(codecId);\n if (!codec) {\n throw contractError(\n 'CONTRACT.PACK_CONTRIBUTION_INVALID',\n `Extension block parameter \"${paramName}\": no codec registered for id \"${codecId}\"`,\n { meta: { reason: 'codec-unregistered', paramName, codecId } },\n );\n }\n let parsedJson: unknown;\n try {\n parsedJson = JSON.parse(raw);\n } catch (e) {\n throw contractError(\n 'CONTRACT.PACK_CONTRIBUTION_INVALID',\n `Extension block parameter \"${paramName}\": codec \"${codecId}\" — raw literal is not valid JSON: ${String(e)}`,\n { meta: { reason: 'raw-literal-invalid-json', paramName, codecId }, cause: e },\n );\n }\n return JSON.stringify(\n codec.encodeJson(\n codec.decodeJson(\n blindCast<Parameters<typeof codec.decodeJson>[0], 'JSON.parse output is JsonValue'>(\n parsedJson,\n ),\n ),\n ),\n );\n}\n\nfunction wrapNamespaceBlock(name: string, innerSections: readonly string[]): string {\n const indented = innerSections\n .map((section) =>\n section\n .split('\\n')\n .map((line) => (line.length > 0 ? ` ${line}` : line))\n .join('\\n'),\n )\n .join('\\n\\n');\n return `namespace ${name} {\\n${indented}\\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 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","import type {\n PslAttribute,\n PslAttributeArgument,\n PslDocumentAst,\n PslField,\n PslModel,\n PslNamedTypeDeclaration,\n PslTypeConstructorCall,\n} from '@prisma-next/framework-components/psl-ast';\nimport {\n flatPslModels,\n namespacePslExtensionBlocks,\n UNSPECIFIED_PSL_NAMESPACE_ID,\n} from '@prisma-next/framework-components/psl-ast';\nimport type { PrintDocument, PrintNamespaceSection } from './print-document';\nimport { escapePslString } from './serialize-print-document';\nimport type { 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 '// use prisma-next\\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 // FK dependencies are resolved across the whole document — a model in one\n // namespace can reference a model in another, and the topo-sort needs to\n // see every model to produce a stable order. After sorting, we re-bucket by\n // namespace so each block prints with its own models in topo order.\n const allModels = flatPslModels(ast);\n const modelNames = new Set(allModels.map((m) => m.name));\n const deps = buildModelFkDeps(allModels, modelNames);\n const sortedModels = topologicalSortModels(allModels, deps);\n\n const modelNamespaceIndex = new Map<string, string>();\n for (const namespace of ast.namespaces) {\n for (const model of namespace.models) {\n modelNamespaceIndex.set(model.name, namespace.name);\n }\n }\n\n const namedTypes: PrinterNamedType[] = ast.types\n ? ast.types.declarations.map(namedTypeDeclarationToPrinterNamedType)\n : [];\n\n const namespaceSections: PrintNamespaceSection[] = ast.namespaces.map((namespace) => {\n const namespaceModels = sortedModels.filter(\n (model) => modelNamespaceIndex.get(model.name) === namespace.name,\n );\n const printerModels = namespaceModels.map((m) => modelToPrinterModel(m));\n return {\n name: namespace.name,\n models: printerModels,\n extensionBlocks: namespacePslExtensionBlocks(namespace),\n };\n });\n\n // Ensure the synthesised `__unspecified__` bucket sorts first so top-level\n // declarations print before any `namespace { … }` blocks — matches what a\n // user would write by hand.\n namespaceSections.sort((a, b) => {\n if (a.name === b.name) return 0;\n if (a.name === UNSPECIFIED_PSL_NAMESPACE_ID) return -1;\n if (b.name === UNSPECIFIED_PSL_NAMESPACE_ID) return 1;\n return a.name.localeCompare(b.name);\n });\n\n return {\n headerComment: DEFAULT_AST_PRINT_HEADER,\n namedTypes,\n namespaces: namespaceSections,\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 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\n/**\n * Assembles the qualified type name string for a field type reference.\n *\n * Handles all four forms:\n * - `space:ns.Name` — typeContractSpaceId + typeNamespaceId + typeName\n * - `space:Name` — typeContractSpaceId + typeName (no namespace)\n * - `ns.Name` — typeNamespaceId + typeName (no space); fixes TML-2459 printer gap\n * - `Name` — typeName only (no qualifier)\n */\nfunction assembleQualifiedTypeName(field: PslField): string {\n const { typeName, typeNamespaceId, typeContractSpaceId } = field;\n const dotted = typeNamespaceId !== undefined ? `${typeNamespaceId}.${typeName}` : typeName;\n return typeContractSpaceId !== undefined ? `${typeContractSpaceId}:${dotted}` : dotted;\n}\n\nfunction fieldToPrinterField(field: PslField): PrinterField {\n // Assemble the qualified type identifier: `space:ns.Name` / `space:Name` / `ns.Name` / `Name`.\n // When a typeConstructor is present it takes precedence and carries no qualifier.\n // Line-wrap policy (pinned): keep the identifier on one line until the existing column limit —\n // no special wrap logic at `:` or `.` (project-spec open question; simplest readable default).\n const typeName =\n field.typeConstructor !== undefined\n ? formatTypeConstructor(field.typeConstructor)\n : assembleQualifiedTypeName(field);\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 { AuthoringPslBlockDescriptorNamespace } from '@prisma-next/framework-components/authoring';\nimport type { CodecLookup } from '@prisma-next/framework-components/codec';\nimport type { PslDocumentAst } from '@prisma-next/framework-components/psl-ast';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { astDocumentToPrintDocument } from './ast-to-print-document';\nimport { serializePrintDocument } from './serialize-print-document';\n\nexport type PslBlockDescriptorsNamespace = AuthoringPslBlockDescriptorNamespace;\n\nexport interface PrintPslOptions {\n /**\n * Extension-contributed PSL block descriptors, indexed by user-facing path.\n * Typically an `AssembledAuthoringContributions.pslBlockDescriptors` namespace\n * produced by `assembleAuthoringContributions`. Phase 2 of the printer indexes\n * into this namespace by each extension-contributed AST node's `kind`\n * discriminator and renders the block generically from the descriptor's\n * `parameters` map.\n *\n * When absent, an AST that contains extension-contributed blocks throws —\n * silently dropping blocks would lose user-authored content without a\n * diagnostic. ASTs that contain only framework-parsed blocks print without\n * any `pslBlockDescriptors` argument, which is what existing call sites do today.\n */\n readonly pslBlockDescriptors?: PslBlockDescriptorsNamespace;\n /**\n * Codec lookup used to print `value`-kind block parameters. The codec's JSON\n * medium (`encodeJson`/`decodeJson`) validates and normalizes each value param.\n * Required alongside `pslBlockDescriptors` when the AST contains `value`-kind\n * parameters. When absent, the raw PSL literal stored in the AST node is\n * emitted as-is.\n */\n readonly codecLookup?: CodecLookup;\n}\n\nexport function printPslFromAst(ast: PslDocumentAst, options: PrintPslOptions = {}): string {\n const doc = astDocumentToPrintDocument(ast);\n return serializePrintDocument(doc, {\n ...ifDefined('pslBlockDescriptors', options.pslBlockDescriptors),\n ...ifDefined('codecLookup', options.codecLookup),\n });\n}\n"],"mappings":"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"} |
+7
-7
| { | ||
| "name": "@prisma-next/psl-printer", | ||
| "version": "0.16.0-dev.6", | ||
| "version": "0.16.0-dev.7", | ||
| "license": "Apache-2.0", | ||
@@ -9,10 +9,10 @@ "type": "module", | ||
| "dependencies": { | ||
| "@prisma-next/framework-components": "0.16.0-dev.6", | ||
| "@prisma-next/utils": "0.16.0-dev.6" | ||
| "@prisma-next/framework-components": "0.16.0-dev.7", | ||
| "@prisma-next/utils": "0.16.0-dev.7" | ||
| }, | ||
| "devDependencies": { | ||
| "@prisma-next/contract": "0.16.0-dev.6", | ||
| "@prisma-next/psl-parser": "0.16.0-dev.6", | ||
| "@prisma-next/tsconfig": "0.16.0-dev.6", | ||
| "@prisma-next/tsdown": "0.16.0-dev.6", | ||
| "@prisma-next/contract": "0.16.0-dev.7", | ||
| "@prisma-next/psl-parser": "0.16.0-dev.7", | ||
| "@prisma-next/tsconfig": "0.16.0-dev.7", | ||
| "@prisma-next/tsdown": "0.16.0-dev.7", | ||
| "@standard-schema/spec": "^1.1.0", | ||
@@ -19,0 +19,0 @@ "tsdown": "0.22.8", |
@@ -14,2 +14,3 @@ import type { | ||
| import { blindCast } from '@prisma-next/utils/casts'; | ||
| import { contractError } from './contract-errors'; | ||
| import type { PrintDocument, PrintNamespaceSection } from './print-document'; | ||
@@ -129,9 +130,20 @@ import type { PrinterField, PrinterNamedType } from './types'; | ||
| if (!descriptor) { | ||
| throw new Error( | ||
| throw contractError( | ||
| 'CONTRACT.PACK_CONTRIBUTION_INVALID', | ||
| `No pslBlockDescriptors contribution registered for extension-contributed block keyword "${extensionBlock.keyword}". Provide a matching pslBlockDescriptors contribution to serializePrintDocument, or remove the block from the input AST.`, | ||
| { meta: { reason: 'block-descriptor-missing', keyword: extensionBlock.keyword } }, | ||
| ); | ||
| } | ||
| if (descriptor.discriminator !== extensionBlock.kind) { | ||
| throw new Error( | ||
| throw contractError( | ||
| 'CONTRACT.PACK_CONTRIBUTION_INVALID', | ||
| `The pslBlockDescriptors contribution for keyword "${extensionBlock.keyword}" owns discriminator "${descriptor.discriminator}", but the block carries kind "${extensionBlock.kind}". Provide a matching pslBlockDescriptors contribution to serializePrintDocument, or remove the block from the input AST.`, | ||
| { | ||
| meta: { | ||
| reason: 'block-descriptor-kind-mismatch', | ||
| keyword: extensionBlock.keyword, | ||
| descriptorDiscriminator: descriptor.discriminator, | ||
| blockKind: extensionBlock.kind, | ||
| }, | ||
| }, | ||
| ); | ||
@@ -201,5 +213,3 @@ } | ||
| if (paramValue.kind !== 'ref') { | ||
| throw new Error( | ||
| `Extension block parameter "${paramName}": descriptor is "ref" but AST node has kind "${paramValue.kind}"`, | ||
| ); | ||
| throw paramKindMismatchError(paramName, 'ref', paramValue.kind); | ||
| } | ||
@@ -210,5 +220,3 @@ return paramValue.identifier; | ||
| if (paramValue.kind !== 'value') { | ||
| throw new Error( | ||
| `Extension block parameter "${paramName}": descriptor is "value" but AST node has kind "${paramValue.kind}"`, | ||
| ); | ||
| throw paramKindMismatchError(paramName, 'value', paramValue.kind); | ||
| } | ||
@@ -219,5 +227,3 @@ return renderValueParam(paramValue.raw, descriptor.codecId, codecLookup, paramName); | ||
| if (paramValue.kind !== 'option') { | ||
| throw new Error( | ||
| `Extension block parameter "${paramName}": descriptor is "option" but AST node has kind "${paramValue.kind}"`, | ||
| ); | ||
| throw paramKindMismatchError(paramName, 'option', paramValue.kind); | ||
| } | ||
@@ -228,5 +234,3 @@ return paramValue.token; | ||
| if (paramValue.kind !== 'list') { | ||
| throw new Error( | ||
| `Extension block parameter "${paramName}": descriptor is "list" but AST node has kind "${paramValue.kind}"`, | ||
| ); | ||
| throw paramKindMismatchError(paramName, 'list', paramValue.kind); | ||
| } | ||
@@ -241,2 +245,14 @@ const items = paramValue.items.map((item) => | ||
| function paramKindMismatchError( | ||
| paramName: string, | ||
| descriptorKind: PslBlockParam['kind'], | ||
| valueKind: PslExtensionBlockParamValue['kind'], | ||
| ) { | ||
| return contractError( | ||
| 'CONTRACT.PACK_CONTRIBUTION_INVALID', | ||
| `Extension block parameter "${paramName}": descriptor is "${descriptorKind}" but AST node has kind "${valueKind}"`, | ||
| { meta: { reason: 'param-kind-mismatch', paramName, descriptorKind, valueKind } }, | ||
| ); | ||
| } | ||
| function renderValueParam( | ||
@@ -253,4 +269,6 @@ raw: string, | ||
| if (!codec) { | ||
| throw new Error( | ||
| throw contractError( | ||
| 'CONTRACT.PACK_CONTRIBUTION_INVALID', | ||
| `Extension block parameter "${paramName}": no codec registered for id "${codecId}"`, | ||
| { meta: { reason: 'codec-unregistered', paramName, codecId } }, | ||
| ); | ||
@@ -262,4 +280,6 @@ } | ||
| } catch (e) { | ||
| throw new Error( | ||
| throw contractError( | ||
| 'CONTRACT.PACK_CONTRIBUTION_INVALID', | ||
| `Extension block parameter "${paramName}": codec "${codecId}" — raw literal is not valid JSON: ${String(e)}`, | ||
| { meta: { reason: 'raw-literal-invalid-json', paramName, codecId }, cause: e }, | ||
| ); | ||
@@ -266,0 +286,0 @@ } |
97114
4.28%14
7.69%1121
6.05%+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed