@prisma-next/sql-contract 0.16.0-dev.31 → 0.16.0-dev.33

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":"contract-errors-CPZcBw8G.mjs","names":[],"sources":["../src/contract-errors.ts"],"sourcesContent":["import type { StructuredError, StructuredErrorOptions } from '@prisma-next/utils/structured-error';\nimport { structuredError } from '@prisma-next/utils/structured-error';\n\ntype ContractCode = `CONTRACT.${ContractSubcode}`;\n\ntype ContractSubcode = 'PACK_CONTRIBUTION_INVALID' | 'TABLE_AMBIGUOUS' | 'VALIDATION_FAILED';\n\nexport function contractError(\n code: ContractCode,\n message: string,\n options?: StructuredErrorOptions,\n): StructuredError {\n return structuredError(code, message, options);\n}\n"],"mappings":";;AAOA,SAAgB,cACd,MACA,SACA,SACiB;CACjB,OAAO,gBAAgB,MAAM,SAAS,OAAO;AAC/C"}
1
+ {"version":3,"file":"contract-errors-CPZcBw8G.mjs","names":[],"sources":["../src/contract-errors.ts"],"sourcesContent":["import type { StructuredError, StructuredErrorOptions } from '@prisma-next/utils/structured-error';\nimport { structuredError } from '@prisma-next/utils/structured-error';\n\ntype ContractCode = `CONTRACT.${ContractSubcode}`;\n\ntype ContractSubcode =\n | 'ARGUMENT_INVALID'\n | 'PACK_CONTRIBUTION_INVALID'\n | 'TABLE_AMBIGUOUS'\n | 'VALIDATION_FAILED';\n\nexport function contractError(\n code: ContractCode,\n message: string,\n options?: StructuredErrorOptions,\n): StructuredError {\n return structuredError(code, message, options);\n}\n"],"mappings":";;AAWA,SAAgB,cACd,MACA,SACA,SACiB;CACjB,OAAO,gBAAgB,MAAM,SAAS,OAAO;AAC/C"}
@@ -1,4 +1,4 @@
1
- import { t as lowerAuthoredIndex } from "./index-naming-DsnarUVW.mjs";
1
+ import { n as lowerAuthoredIndex } from "./index-naming-T4UVkLjS.mjs";
2
2
  //#region src/foreign-key-materialization.ts
3
3
  /**
4
4
  * The column-list keys (`"colA,colB"`, order preserved) a table's own
@@ -50,6 +50,8 @@ function materializeForeignKeysAndIndexes(tableName, foreignKeys, declaredIndexe
50
50
  if (!satisfiedIndexColumns.has(key)) {
51
51
  synthesizedIndexes.push(lowerAuthoredIndex(tableName, {
52
52
  columns: reference.source.columns,
53
+ where: void 0,
54
+ unique: void 0,
53
55
  map: void 0,
54
56
  name: void 0,
55
57
  type: void 0,
@@ -1 +1 @@
1
- {"version":3,"file":"foreign-key-materialization.mjs","names":[],"sources":["../src/foreign-key-materialization.ts"],"sourcesContent":["import { lowerAuthoredIndex } from './index-naming';\nimport type { ForeignKeyInput, ReferentialAction } from './ir/foreign-key';\nimport type { ForeignKeyReferenceInput } from './ir/foreign-key-reference';\nimport type { PrimaryKeyInput } from './ir/primary-key';\nimport type { IndexInput } from './ir/sql-index';\nimport type { UniqueConstraintInput } from './ir/unique-constraint';\n\nexport type BackingIndexCandidates = {\n readonly indexes: readonly { readonly columns?: readonly string[] }[];\n readonly uniques: readonly { readonly columns: readonly string[] }[];\n readonly primaryKey?: { readonly columns: readonly string[] } | undefined;\n};\n\n/**\n * The column-list keys (`\"colA,colB\"`, order preserved) a table's own\n * indexes, unique constraints, and primary key already back. A foreign key\n * whose source columns join to one of these keys needs no separately\n * derived backing index.\n *\n * Shared by {@link isBackedByColumnKeys}'s callers: `materializeForeignKeysAndIndexes`\n * (deriving the discrete backing-index entities persisted at `contract emit`)\n * and the postgres PSL inferrer (deciding whether an introspected relation\n * needs an explicit `index: false`).\n */\nexport function backingIndexColumnKeys(table: BackingIndexCandidates): readonly string[] {\n return [\n ...table.indexes.flatMap((index) =>\n index.columns !== undefined ? [index.columns.join(',')] : [],\n ),\n ...table.uniques.map((unique) => unique.columns.join(',')),\n ...(table.primaryKey ? [table.primaryKey.columns.join(',')] : []),\n ];\n}\n\n/**\n * Whether `columns`, in order, matches one of `backingKeys` (see\n * {@link backingIndexColumnKeys}). Order-sensitive: `(a, b)` does not\n * satisfy a backing index declared as `(b, a)`.\n */\nexport function isBackedByColumnKeys(\n columns: readonly string[],\n backingKeys: readonly string[],\n): boolean {\n return backingKeys.includes(columns.join(','));\n}\n\n/**\n * A foreign key as authored, before FK1 materialization: the referential\n * coordinates plus the `constraint`/`index` intent booleans that drive\n * whether — and how — it survives into the persisted contract.\n */\nexport interface ForeignKeyAuthoringInput {\n readonly source: ForeignKeyReferenceInput;\n readonly target: ForeignKeyReferenceInput;\n readonly name?: string;\n readonly onDelete?: ReferentialAction;\n readonly onUpdate?: ReferentialAction;\n readonly constraint: boolean;\n readonly index: boolean;\n}\n\nexport interface MaterializedTableConstraints {\n readonly foreignKeys: readonly ForeignKeyInput[];\n readonly indexes: readonly IndexInput[];\n}\n\n/**\n * Lowers a table's authored foreign keys into the discrete entities\n * `contract.json` persists: a `constraint: false` FK contributes no\n * `foreignKeys[]` entry, and an `index: true` FK whose columns aren't already\n * backed by a declared index/unique/primary-key contributes a managed\n * `indexes[]` entry (default prefix + content-hash wire name). Declared\n * indexes always survive unchanged; a second FK sharing already-synthesized\n * backing columns does not mint a duplicate index.\n */\nexport function materializeForeignKeysAndIndexes(\n tableName: string,\n foreignKeys: readonly ForeignKeyAuthoringInput[],\n declaredIndexes: readonly IndexInput[],\n uniques: readonly UniqueConstraintInput[],\n primaryKey: PrimaryKeyInput | undefined,\n): MaterializedTableConstraints {\n const satisfiedIndexColumns = new Set(\n backingIndexColumnKeys({ indexes: declaredIndexes, uniques, primaryKey }),\n );\n const synthesizedIndexes: IndexInput[] = [];\n const materializedForeignKeys: ForeignKeyInput[] = [];\n\n for (const { constraint, index, ...reference } of foreignKeys) {\n if (constraint !== false) {\n materializedForeignKeys.push(reference);\n }\n if (index !== false) {\n const key = reference.source.columns.join(',');\n if (!satisfiedIndexColumns.has(key)) {\n synthesizedIndexes.push(\n lowerAuthoredIndex(tableName, {\n columns: reference.source.columns,\n map: undefined,\n name: undefined,\n type: undefined,\n options: undefined,\n }),\n );\n satisfiedIndexColumns.add(key);\n }\n }\n }\n\n return {\n foreignKeys: materializedForeignKeys,\n indexes: [...declaredIndexes, ...synthesizedIndexes],\n };\n}\n"],"mappings":";;;;;;;;;;;;;AAwBA,SAAgB,uBAAuB,OAAkD;CACvF,OAAO;EACL,GAAG,MAAM,QAAQ,SAAS,UACxB,MAAM,YAAY,KAAA,IAAY,CAAC,MAAM,QAAQ,KAAK,GAAG,CAAC,IAAI,CAAC,CAC7D;EACA,GAAG,MAAM,QAAQ,KAAK,WAAW,OAAO,QAAQ,KAAK,GAAG,CAAC;EACzD,GAAI,MAAM,aAAa,CAAC,MAAM,WAAW,QAAQ,KAAK,GAAG,CAAC,IAAI,CAAC;CACjE;AACF;;;;;;AAOA,SAAgB,qBACd,SACA,aACS;CACT,OAAO,YAAY,SAAS,QAAQ,KAAK,GAAG,CAAC;AAC/C;;;;;;;;;;AA+BA,SAAgB,iCACd,WACA,aACA,iBACA,SACA,YAC8B;CAC9B,MAAM,wBAAwB,IAAI,IAChC,uBAAuB;EAAE,SAAS;EAAiB;EAAS;CAAW,CAAC,CAC1E;CACA,MAAM,qBAAmC,CAAC;CAC1C,MAAM,0BAA6C,CAAC;CAEpD,KAAK,MAAM,EAAE,YAAY,OAAO,GAAG,eAAe,aAAa;EAC7D,IAAI,eAAe,OACjB,wBAAwB,KAAK,SAAS;EAExC,IAAI,UAAU,OAAO;GACnB,MAAM,MAAM,UAAU,OAAO,QAAQ,KAAK,GAAG;GAC7C,IAAI,CAAC,sBAAsB,IAAI,GAAG,GAAG;IACnC,mBAAmB,KACjB,mBAAmB,WAAW;KAC5B,SAAS,UAAU,OAAO;KAC1B,KAAK,KAAA;KACL,MAAM,KAAA;KACN,MAAM,KAAA;KACN,SAAS,KAAA;IACX,CAAC,CACH;IACA,sBAAsB,IAAI,GAAG;GAC/B;EACF;CACF;CAEA,OAAO;EACL,aAAa;EACb,SAAS,CAAC,GAAG,iBAAiB,GAAG,kBAAkB;CACrD;AACF"}
1
+ {"version":3,"file":"foreign-key-materialization.mjs","names":[],"sources":["../src/foreign-key-materialization.ts"],"sourcesContent":["import { lowerAuthoredIndex } from './index-naming';\nimport type { ForeignKeyInput, ReferentialAction } from './ir/foreign-key';\nimport type { ForeignKeyReferenceInput } from './ir/foreign-key-reference';\nimport type { PrimaryKeyInput } from './ir/primary-key';\nimport type { IndexInput } from './ir/sql-index';\nimport type { UniqueConstraintInput } from './ir/unique-constraint';\n\nexport type BackingIndexCandidates = {\n readonly indexes: readonly { readonly columns?: readonly string[] }[];\n readonly uniques: readonly { readonly columns: readonly string[] }[];\n readonly primaryKey?: { readonly columns: readonly string[] } | undefined;\n};\n\n/**\n * The column-list keys (`\"colA,colB\"`, order preserved) a table's own\n * indexes, unique constraints, and primary key already back. A foreign key\n * whose source columns join to one of these keys needs no separately\n * derived backing index.\n *\n * Shared by {@link isBackedByColumnKeys}'s callers: `materializeForeignKeysAndIndexes`\n * (deriving the discrete backing-index entities persisted at `contract emit`)\n * and the postgres PSL inferrer (deciding whether an introspected relation\n * needs an explicit `index: false`).\n */\nexport function backingIndexColumnKeys(table: BackingIndexCandidates): readonly string[] {\n return [\n ...table.indexes.flatMap((index) =>\n index.columns !== undefined ? [index.columns.join(',')] : [],\n ),\n ...table.uniques.map((unique) => unique.columns.join(',')),\n ...(table.primaryKey ? [table.primaryKey.columns.join(',')] : []),\n ];\n}\n\n/**\n * Whether `columns`, in order, matches one of `backingKeys` (see\n * {@link backingIndexColumnKeys}). Order-sensitive: `(a, b)` does not\n * satisfy a backing index declared as `(b, a)`.\n */\nexport function isBackedByColumnKeys(\n columns: readonly string[],\n backingKeys: readonly string[],\n): boolean {\n return backingKeys.includes(columns.join(','));\n}\n\n/**\n * A foreign key as authored, before FK1 materialization: the referential\n * coordinates plus the `constraint`/`index` intent booleans that drive\n * whether — and how — it survives into the persisted contract.\n */\nexport interface ForeignKeyAuthoringInput {\n readonly source: ForeignKeyReferenceInput;\n readonly target: ForeignKeyReferenceInput;\n readonly name?: string;\n readonly onDelete?: ReferentialAction;\n readonly onUpdate?: ReferentialAction;\n readonly constraint: boolean;\n readonly index: boolean;\n}\n\nexport interface MaterializedTableConstraints {\n readonly foreignKeys: readonly ForeignKeyInput[];\n readonly indexes: readonly IndexInput[];\n}\n\n/**\n * Lowers a table's authored foreign keys into the discrete entities\n * `contract.json` persists: a `constraint: false` FK contributes no\n * `foreignKeys[]` entry, and an `index: true` FK whose columns aren't already\n * backed by a declared index/unique/primary-key contributes a managed\n * `indexes[]` entry (default prefix + content-hash wire name). Declared\n * indexes always survive unchanged; a second FK sharing already-synthesized\n * backing columns does not mint a duplicate index.\n */\nexport function materializeForeignKeysAndIndexes(\n tableName: string,\n foreignKeys: readonly ForeignKeyAuthoringInput[],\n declaredIndexes: readonly IndexInput[],\n uniques: readonly UniqueConstraintInput[],\n primaryKey: PrimaryKeyInput | undefined,\n): MaterializedTableConstraints {\n const satisfiedIndexColumns = new Set(\n backingIndexColumnKeys({ indexes: declaredIndexes, uniques, primaryKey }),\n );\n const synthesizedIndexes: IndexInput[] = [];\n const materializedForeignKeys: ForeignKeyInput[] = [];\n\n for (const { constraint, index, ...reference } of foreignKeys) {\n if (constraint !== false) {\n materializedForeignKeys.push(reference);\n }\n if (index !== false) {\n const key = reference.source.columns.join(',');\n if (!satisfiedIndexColumns.has(key)) {\n synthesizedIndexes.push(\n lowerAuthoredIndex(tableName, {\n columns: reference.source.columns,\n where: undefined,\n unique: undefined,\n map: undefined,\n name: undefined,\n type: undefined,\n options: undefined,\n }),\n );\n satisfiedIndexColumns.add(key);\n }\n }\n }\n\n return {\n foreignKeys: materializedForeignKeys,\n indexes: [...declaredIndexes, ...synthesizedIndexes],\n };\n}\n"],"mappings":";;;;;;;;;;;;;AAwBA,SAAgB,uBAAuB,OAAkD;CACvF,OAAO;EACL,GAAG,MAAM,QAAQ,SAAS,UACxB,MAAM,YAAY,KAAA,IAAY,CAAC,MAAM,QAAQ,KAAK,GAAG,CAAC,IAAI,CAAC,CAC7D;EACA,GAAG,MAAM,QAAQ,KAAK,WAAW,OAAO,QAAQ,KAAK,GAAG,CAAC;EACzD,GAAI,MAAM,aAAa,CAAC,MAAM,WAAW,QAAQ,KAAK,GAAG,CAAC,IAAI,CAAC;CACjE;AACF;;;;;;AAOA,SAAgB,qBACd,SACA,aACS;CACT,OAAO,YAAY,SAAS,QAAQ,KAAK,GAAG,CAAC;AAC/C;;;;;;;;;;AA+BA,SAAgB,iCACd,WACA,aACA,iBACA,SACA,YAC8B;CAC9B,MAAM,wBAAwB,IAAI,IAChC,uBAAuB;EAAE,SAAS;EAAiB;EAAS;CAAW,CAAC,CAC1E;CACA,MAAM,qBAAmC,CAAC;CAC1C,MAAM,0BAA6C,CAAC;CAEpD,KAAK,MAAM,EAAE,YAAY,OAAO,GAAG,eAAe,aAAa;EAC7D,IAAI,eAAe,OACjB,wBAAwB,KAAK,SAAS;EAExC,IAAI,UAAU,OAAO;GACnB,MAAM,MAAM,UAAU,OAAO,QAAQ,KAAK,GAAG;GAC7C,IAAI,CAAC,sBAAsB,IAAI,GAAG,GAAG;IACnC,mBAAmB,KACjB,mBAAmB,WAAW;KAC5B,SAAS,UAAU,OAAO;KAC1B,OAAO,KAAA;KACP,QAAQ,KAAA;KACR,KAAK,KAAA;KACL,MAAM,KAAA;KACN,MAAM,KAAA;KACN,SAAS,KAAA;IACX,CAAC,CACH;IACA,sBAAsB,IAAI,GAAG;GAC/B;EACF;CACF;CAEA,OAAO;EACL,aAAa;EACb,SAAS,CAAC,GAAG,iBAAiB,GAAG,kBAAkB;CACrD;AACF"}
@@ -0,0 +1,89 @@
1
+ import { t as contractError } from "./contract-errors-CPZcBw8G.mjs";
2
+ import { assertWireNamePrefixLength, computeIndexContentHash, defaultIndexName, formatWireName } from "@prisma-next/sql-schema-ir/naming";
3
+ //#region src/index-naming.ts
4
+ const EXACT_NAME_BODY_GUIDANCE = "Drift detection compares the authored SQL text byte-for-byte against Postgres's reprinted form, which is only reliable when the text was captured by contract infer. For hand-authored definitions, use name: and let Prisma Next manage the physical name; to migrate an adopted object to managed naming, replace map: with name: (keeping the body text unchanged) and apply the resulting rename migration.";
5
+ const EXACT_NAME_BODY_WARNING_CODE = "PN_EXACT_NAME_BODY_COMPARISON";
6
+ const WARNING_BATCH_THRESHOLD = 5;
7
+ function formatExactNameBodyWarning(warning) {
8
+ return `${warning.subject} "${warning.exactName}" uses map: with a SQL body. ${EXACT_NAME_BODY_GUIDANCE}`;
9
+ }
10
+ /**
11
+ * Flushes collected exact-name-body warnings once per contract build: per-item warnings
12
+ * (each naming its object) up to the threshold, one summary with the name
13
+ * list above it — an adopted contract re-emit (which carries `map:` + body
14
+ * for every adopted object once infer emits them) must not wall-of-text.
15
+ */
16
+ function flushExactNameBodyWarnings(warnings) {
17
+ if (warnings.length === 0) return;
18
+ if (warnings.length <= WARNING_BATCH_THRESHOLD) {
19
+ for (const warning of warnings) process.emitWarning(formatExactNameBodyWarning(warning), { code: EXACT_NAME_BODY_WARNING_CODE });
20
+ return;
21
+ }
22
+ process.emitWarning(`${warnings.length} objects use map: with a SQL body. ${EXACT_NAME_BODY_GUIDANCE}\n` + warnings.map((warning) => ` - ${warning.subject} "${warning.exactName}"`).join("\n"), { code: EXACT_NAME_BODY_WARNING_CODE });
23
+ }
24
+ /**
25
+ * Lowers an authored index into the name-identified entity `contract.json`
26
+ * persists: exact mode adopts `map` verbatim (no prefix, no hash); managed
27
+ * mode appends the content-hash suffix to the authored or default prefix.
28
+ * The cross-field guards are the shared enforcement backstop for both
29
+ * authoring surfaces (PSL pre-empts them with span-anchored diagnostics).
30
+ */
31
+ function lowerAuthoredIndex(tableName, authored, warnings) {
32
+ if (authored.columns === void 0 === (authored.expression === void 0)) throw contractError("CONTRACT.ARGUMENT_INVALID", `Index on table "${tableName}": an index takes either fields (columns) or an expression — exactly one, not both.`);
33
+ if (authored.map !== void 0 && authored.name !== void 0) throw contractError("CONTRACT.ARGUMENT_INVALID", `Index "${authored.map}" on table "${tableName}": map and name are mutually exclusive — map adopts an exact physical name, name is a managed prefix.`);
34
+ if (authored.expression !== void 0 && authored.name === void 0 && authored.map === void 0) throw contractError("CONTRACT.ARGUMENT_INVALID", `Index on table "${tableName}": an expression index requires an explicit name (name:) or exact physical name (map:) — a default name cannot be derived from an expression.`);
35
+ const unique = authored.unique ?? false;
36
+ if (authored.map !== void 0) {
37
+ if (authored.expression !== void 0 || authored.where !== void 0) {
38
+ const warning = {
39
+ subject: "index",
40
+ exactName: authored.map
41
+ };
42
+ if (warnings !== void 0) warnings.push(warning);
43
+ else flushExactNameBodyWarnings([warning]);
44
+ }
45
+ const carried = {
46
+ name: authored.map,
47
+ prefix: void 0,
48
+ where: authored.where,
49
+ unique,
50
+ type: authored.type,
51
+ options: authored.options
52
+ };
53
+ return authored.expression !== void 0 ? {
54
+ ...carried,
55
+ expression: authored.expression
56
+ } : {
57
+ ...carried,
58
+ columns: authored.columns ?? []
59
+ };
60
+ }
61
+ const prefix = authored.name ?? defaultIndexName(tableName, authored.columns ?? []);
62
+ assertWireNamePrefixLength(prefix, "index prefix");
63
+ const carried = {
64
+ name: formatWireName(prefix, computeIndexContentHash({
65
+ ...authored.columns !== void 0 && { columns: authored.columns },
66
+ ...authored.expression !== void 0 && { expression: authored.expression },
67
+ ...authored.where !== void 0 && { where: authored.where },
68
+ unique,
69
+ ...authored.type !== void 0 && { type: authored.type },
70
+ ...authored.options !== void 0 && { options: authored.options }
71
+ })),
72
+ prefix,
73
+ where: authored.where,
74
+ unique,
75
+ type: authored.type,
76
+ options: authored.options
77
+ };
78
+ return authored.expression !== void 0 ? {
79
+ ...carried,
80
+ expression: authored.expression
81
+ } : {
82
+ ...carried,
83
+ columns: authored.columns ?? []
84
+ };
85
+ }
86
+ //#endregion
87
+ export { lowerAuthoredIndex as n, flushExactNameBodyWarnings as t };
88
+
89
+ //# sourceMappingURL=index-naming-T4UVkLjS.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-naming-T4UVkLjS.mjs","names":[],"sources":["../src/index-naming.ts"],"sourcesContent":["import {\n assertWireNamePrefixLength,\n computeIndexContentHash,\n defaultIndexName,\n formatWireName,\n} from '@prisma-next/sql-schema-ir/naming';\nimport { contractError } from './contract-errors';\nimport type { IndexInput } from './ir/sql-index';\n\n/**\n * The authored element structure: a fields index carries `columns`, an\n * expression index carries `expression` — exactly one, never both.\n */\nexport type AuthoredIndexElements =\n | { readonly columns: readonly string[]; readonly expression?: never }\n | { readonly columns?: never; readonly expression: string };\n\n/**\n * An index as authored, before naming: `map` is an exact physical name\n * (adopted verbatim); `name` is a managed wire-name prefix. With neither,\n * the managed prefix defaults to `defaultIndexName(table, columns)`.\n * `where`, `unique`, `type`, and `options` participate in the content hash\n * alongside the elements.\n */\nexport type AuthoredIndexInput = AuthoredIndexElements & {\n readonly where: string | undefined;\n readonly unique: boolean | undefined;\n readonly map: string | undefined;\n readonly name: string | undefined;\n readonly type: string | undefined;\n readonly options: Record<string, unknown> | undefined;\n};\n\n/**\n * One exact-name warning hit: a `map:`-named object carrying a\n * hand-authorable SQL body. `subject` is `index` here and `policy` when\n * policies adopt the same warning.\n */\nexport interface ExactNameBodyWarning {\n readonly subject: 'index' | 'policy';\n readonly exactName: string;\n}\n\nconst EXACT_NAME_BODY_GUIDANCE =\n \"Drift detection compares the authored SQL text byte-for-byte against Postgres's reprinted form, which is only reliable when the text was captured by contract infer. For hand-authored definitions, use name: and let Prisma Next manage the physical name; to migrate an adopted object to managed naming, replace map: with name: (keeping the body text unchanged) and apply the resulting rename migration.\";\n\nconst EXACT_NAME_BODY_WARNING_CODE = 'PN_EXACT_NAME_BODY_COMPARISON';\n\nconst WARNING_BATCH_THRESHOLD = 5;\n\nfunction formatExactNameBodyWarning(warning: ExactNameBodyWarning): string {\n return `${warning.subject} \"${warning.exactName}\" uses map: with a SQL body. ${EXACT_NAME_BODY_GUIDANCE}`;\n}\n\n/**\n * Flushes collected exact-name-body warnings once per contract build: per-item warnings\n * (each naming its object) up to the threshold, one summary with the name\n * list above it — an adopted contract re-emit (which carries `map:` + body\n * for every adopted object once infer emits them) must not wall-of-text.\n */\nexport function flushExactNameBodyWarnings(warnings: readonly ExactNameBodyWarning[]): void {\n if (warnings.length === 0) {\n return;\n }\n if (warnings.length <= WARNING_BATCH_THRESHOLD) {\n for (const warning of warnings) {\n process.emitWarning(formatExactNameBodyWarning(warning), {\n code: EXACT_NAME_BODY_WARNING_CODE,\n });\n }\n return;\n }\n process.emitWarning(\n `${warnings.length} objects use map: with a SQL body. ${EXACT_NAME_BODY_GUIDANCE}\\n` +\n warnings.map((warning) => ` - ${warning.subject} \"${warning.exactName}\"`).join('\\n'),\n { code: EXACT_NAME_BODY_WARNING_CODE },\n );\n}\n\n/**\n * Lowers an authored index into the name-identified entity `contract.json`\n * persists: exact mode adopts `map` verbatim (no prefix, no hash); managed\n * mode appends the content-hash suffix to the authored or default prefix.\n * The cross-field guards are the shared enforcement backstop for both\n * authoring surfaces (PSL pre-empts them with span-anchored diagnostics).\n */\nexport function lowerAuthoredIndex(\n tableName: string,\n authored: AuthoredIndexInput,\n warnings?: { push(warning: ExactNameBodyWarning): void },\n): IndexInput {\n if ((authored.columns === undefined) === (authored.expression === undefined)) {\n throw contractError(\n 'CONTRACT.ARGUMENT_INVALID',\n `Index on table \"${tableName}\": an index takes either fields (columns) or an expression — exactly one, not both.`,\n );\n }\n if (authored.map !== undefined && authored.name !== undefined) {\n throw contractError(\n 'CONTRACT.ARGUMENT_INVALID',\n `Index \"${authored.map}\" on table \"${tableName}\": map and name are mutually exclusive — map adopts an exact physical name, name is a managed prefix.`,\n );\n }\n if (\n authored.expression !== undefined &&\n authored.name === undefined &&\n authored.map === undefined\n ) {\n throw contractError(\n 'CONTRACT.ARGUMENT_INVALID',\n `Index on table \"${tableName}\": an expression index requires an explicit name (name:) or exact physical name (map:) — a default name cannot be derived from an expression.`,\n );\n }\n\n const unique = authored.unique ?? false;\n\n if (authored.map !== undefined) {\n if (authored.expression !== undefined || authored.where !== undefined) {\n const warning: ExactNameBodyWarning = { subject: 'index', exactName: authored.map };\n if (warnings !== undefined) {\n warnings.push(warning);\n } else {\n flushExactNameBodyWarnings([warning]);\n }\n }\n const carried = {\n name: authored.map,\n prefix: undefined,\n where: authored.where,\n unique,\n type: authored.type,\n options: authored.options,\n };\n return authored.expression !== undefined\n ? { ...carried, expression: authored.expression }\n : { ...carried, columns: authored.columns ?? [] };\n }\n\n const prefix = authored.name ?? defaultIndexName(tableName, authored.columns ?? []);\n assertWireNamePrefixLength(prefix, 'index prefix');\n const hash = computeIndexContentHash({\n ...(authored.columns !== undefined && { columns: authored.columns }),\n ...(authored.expression !== undefined && { expression: authored.expression }),\n ...(authored.where !== undefined && { where: authored.where }),\n unique,\n ...(authored.type !== undefined && { type: authored.type }),\n ...(authored.options !== undefined && { options: authored.options }),\n });\n const carried = {\n name: formatWireName(prefix, hash),\n prefix,\n where: authored.where,\n unique,\n type: authored.type,\n options: authored.options,\n };\n return authored.expression !== undefined\n ? { ...carried, expression: authored.expression }\n : { ...carried, columns: authored.columns ?? [] };\n}\n"],"mappings":";;;AA2CA,MAAM,2BACJ;AAEF,MAAM,+BAA+B;AAErC,MAAM,0BAA0B;AAEhC,SAAS,2BAA2B,SAAuC;CACzE,OAAO,GAAG,QAAQ,QAAQ,IAAI,QAAQ,UAAU,+BAA+B;AACjF;;;;;;;AAQA,SAAgB,2BAA2B,UAAiD;CAC1F,IAAI,SAAS,WAAW,GACtB;CAEF,IAAI,SAAS,UAAU,yBAAyB;EAC9C,KAAK,MAAM,WAAW,UACpB,QAAQ,YAAY,2BAA2B,OAAO,GAAG,EACvD,MAAM,6BACR,CAAC;EAEH;CACF;CACA,QAAQ,YACN,GAAG,SAAS,OAAO,qCAAqC,yBAAyB,MAC/E,SAAS,KAAK,YAAY,OAAO,QAAQ,QAAQ,IAAI,QAAQ,UAAU,EAAE,CAAC,CAAC,KAAK,IAAI,GACtF,EAAE,MAAM,6BAA6B,CACvC;AACF;;;;;;;;AASA,SAAgB,mBACd,WACA,UACA,UACY;CACZ,IAAK,SAAS,YAAY,KAAA,OAAgB,SAAS,eAAe,KAAA,IAChE,MAAM,cACJ,6BACA,mBAAmB,UAAU,oFAC/B;CAEF,IAAI,SAAS,QAAQ,KAAA,KAAa,SAAS,SAAS,KAAA,GAClD,MAAM,cACJ,6BACA,UAAU,SAAS,IAAI,cAAc,UAAU,sGACjD;CAEF,IACE,SAAS,eAAe,KAAA,KACxB,SAAS,SAAS,KAAA,KAClB,SAAS,QAAQ,KAAA,GAEjB,MAAM,cACJ,6BACA,mBAAmB,UAAU,8IAC/B;CAGF,MAAM,SAAS,SAAS,UAAU;CAElC,IAAI,SAAS,QAAQ,KAAA,GAAW;EAC9B,IAAI,SAAS,eAAe,KAAA,KAAa,SAAS,UAAU,KAAA,GAAW;GACrE,MAAM,UAAgC;IAAE,SAAS;IAAS,WAAW,SAAS;GAAI;GAClF,IAAI,aAAa,KAAA,GACf,SAAS,KAAK,OAAO;QAErB,2BAA2B,CAAC,OAAO,CAAC;EAExC;EACA,MAAM,UAAU;GACd,MAAM,SAAS;GACf,QAAQ,KAAA;GACR,OAAO,SAAS;GAChB;GACA,MAAM,SAAS;GACf,SAAS,SAAS;EACpB;EACA,OAAO,SAAS,eAAe,KAAA,IAC3B;GAAE,GAAG;GAAS,YAAY,SAAS;EAAW,IAC9C;GAAE,GAAG;GAAS,SAAS,SAAS,WAAW,CAAC;EAAE;CACpD;CAEA,MAAM,SAAS,SAAS,QAAQ,iBAAiB,WAAW,SAAS,WAAW,CAAC,CAAC;CAClF,2BAA2B,QAAQ,cAAc;CASjD,MAAM,UAAU;EACd,MAAM,eAAe,QATV,wBAAwB;GACnC,GAAI,SAAS,YAAY,KAAA,KAAa,EAAE,SAAS,SAAS,QAAQ;GAClE,GAAI,SAAS,eAAe,KAAA,KAAa,EAAE,YAAY,SAAS,WAAW;GAC3E,GAAI,SAAS,UAAU,KAAA,KAAa,EAAE,OAAO,SAAS,MAAM;GAC5D;GACA,GAAI,SAAS,SAAS,KAAA,KAAa,EAAE,MAAM,SAAS,KAAK;GACzD,GAAI,SAAS,YAAY,KAAA,KAAa,EAAE,SAAS,SAAS,QAAQ;EACpE,CAEkC,CAAC;EACjC;EACA,OAAO,SAAS;EAChB;EACA,MAAM,SAAS;EACf,SAAS,SAAS;CACpB;CACA,OAAO,SAAS,eAAe,KAAA,IAC3B;EAAE,GAAG;EAAS,YAAY,SAAS;CAAW,IAC9C;EAAE,GAAG;EAAS,SAAS,SAAS,WAAW,CAAC;CAAE;AACpD"}
@@ -1,25 +1,57 @@
1
1
  import { n as IndexInput } from "./sql-index-Wzbj2nbH.mjs";
2
2
  //#region src/index-naming.d.ts
3
3
  /**
4
- * An index as authored, before naming: `map` is an exact physical name
5
- * (adopted verbatim, PSL `map:`); `name` is a managed wire-name prefix
6
- * (TS `name:`). With neither, the managed prefix defaults to
7
- * `defaultIndexName(table, columns)`.
4
+ * The authored element structure: a fields index carries `columns`, an
5
+ * expression index carries `expression` exactly one, never both.
8
6
  */
9
- interface AuthoredIndexInput {
7
+ type AuthoredIndexElements = {
10
8
  readonly columns: readonly string[];
9
+ readonly expression?: never;
10
+ } | {
11
+ readonly columns?: never;
12
+ readonly expression: string;
13
+ };
14
+ /**
15
+ * An index as authored, before naming: `map` is an exact physical name
16
+ * (adopted verbatim); `name` is a managed wire-name prefix. With neither,
17
+ * the managed prefix defaults to `defaultIndexName(table, columns)`.
18
+ * `where`, `unique`, `type`, and `options` participate in the content hash
19
+ * alongside the elements.
20
+ */
21
+ type AuthoredIndexInput = AuthoredIndexElements & {
22
+ readonly where: string | undefined;
23
+ readonly unique: boolean | undefined;
11
24
  readonly map: string | undefined;
12
25
  readonly name: string | undefined;
13
26
  readonly type: string | undefined;
14
27
  readonly options: Record<string, unknown> | undefined;
28
+ };
29
+ /**
30
+ * One exact-name warning hit: a `map:`-named object carrying a
31
+ * hand-authorable SQL body. `subject` is `index` here and `policy` when
32
+ * policies adopt the same warning.
33
+ */
34
+ interface ExactNameBodyWarning {
35
+ readonly subject: 'index' | 'policy';
36
+ readonly exactName: string;
15
37
  }
38
+ /**
39
+ * Flushes collected exact-name-body warnings once per contract build: per-item warnings
40
+ * (each naming its object) up to the threshold, one summary with the name
41
+ * list above it — an adopted contract re-emit (which carries `map:` + body
42
+ * for every adopted object once infer emits them) must not wall-of-text.
43
+ */
44
+ declare function flushExactNameBodyWarnings(warnings: readonly ExactNameBodyWarning[]): void;
16
45
  /**
17
46
  * Lowers an authored index into the name-identified entity `contract.json`
18
47
  * persists: exact mode adopts `map` verbatim (no prefix, no hash); managed
19
48
  * mode appends the content-hash suffix to the authored or default prefix.
20
- * `unique` always lowers `false` no authoring surface sets it yet.
49
+ * The cross-field guards are the shared enforcement backstop for both
50
+ * authoring surfaces (PSL pre-empts them with span-anchored diagnostics).
21
51
  */
22
- declare function lowerAuthoredIndex(tableName: string, authored: AuthoredIndexInput): IndexInput;
52
+ declare function lowerAuthoredIndex(tableName: string, authored: AuthoredIndexInput, warnings?: {
53
+ push(warning: ExactNameBodyWarning): void;
54
+ }): IndexInput;
23
55
  //#endregion
24
- export { type AuthoredIndexInput, lowerAuthoredIndex };
56
+ export { type AuthoredIndexInput, type ExactNameBodyWarning, flushExactNameBodyWarnings, lowerAuthoredIndex };
25
57
  //# sourceMappingURL=index-naming.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index-naming.d.mts","names":[],"sources":["../src/index-naming.ts"],"mappings":";;;;;;;;UAeiB;WACN;WACA;WACA;WACA;WACA,SAAS;;;;;;;;iBASJ,mBAAmB,mBAAmB,UAAU,qBAAqB"}
1
+ {"version":3,"file":"index-naming.d.mts","names":[],"sources":["../src/index-naming.ts"],"mappings":";;;;;;KAaY;WACG;WAAqC;;WACrC;WAA0B;;;;;;;;;KAS7B,qBAAqB;WACtB;WACA;WACA;WACA;WACA;WACA,SAAS;;;;;;;UAQH;WACN;WACA;;;;;;;;iBAoBK,2BAA2B,mBAAmB;;;;;;;;iBA0B9C,mBACd,mBACA,UAAU,oBACV;EAAa,KAAK,SAAS;IAC1B"}
@@ -1,2 +1,2 @@
1
- import { t as lowerAuthoredIndex } from "./index-naming-DsnarUVW.mjs";
2
- export { lowerAuthoredIndex };
1
+ import { n as lowerAuthoredIndex, t as flushExactNameBodyWarnings } from "./index-naming-T4UVkLjS.mjs";
2
+ export { flushExactNameBodyWarnings, lowerAuthoredIndex };
package/package.json CHANGED
@@ -1,21 +1,21 @@
1
1
  {
2
2
  "name": "@prisma-next/sql-contract",
3
- "version": "0.16.0-dev.31",
3
+ "version": "0.16.0-dev.33",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "description": "SQL contract types, validators, and IR factories for Prisma Next",
8
8
  "dependencies": {
9
- "@prisma-next/contract": "0.16.0-dev.31",
10
- "@prisma-next/framework-components": "0.16.0-dev.31",
11
- "@prisma-next/sql-schema-ir": "0.16.0-dev.31",
12
- "@prisma-next/utils": "0.16.0-dev.31",
9
+ "@prisma-next/contract": "0.16.0-dev.33",
10
+ "@prisma-next/framework-components": "0.16.0-dev.33",
11
+ "@prisma-next/sql-schema-ir": "0.16.0-dev.33",
12
+ "@prisma-next/utils": "0.16.0-dev.33",
13
13
  "arktype": "^2.2.2"
14
14
  },
15
15
  "devDependencies": {
16
- "@prisma-next/test-utils": "0.16.0-dev.31",
17
- "@prisma-next/tsconfig": "0.16.0-dev.31",
18
- "@prisma-next/tsdown": "0.16.0-dev.31",
16
+ "@prisma-next/test-utils": "0.16.0-dev.33",
17
+ "@prisma-next/tsconfig": "0.16.0-dev.33",
18
+ "@prisma-next/tsdown": "0.16.0-dev.33",
19
19
  "tsdown": "0.22.8",
20
20
  "typescript": "5.9.3",
21
21
  "vitest": "4.1.10"
@@ -3,7 +3,11 @@ import { structuredError } from '@prisma-next/utils/structured-error';
3
3
 
4
4
  type ContractCode = `CONTRACT.${ContractSubcode}`;
5
5
 
6
- type ContractSubcode = 'PACK_CONTRIBUTION_INVALID' | 'TABLE_AMBIGUOUS' | 'VALIDATION_FAILED';
6
+ type ContractSubcode =
7
+ | 'ARGUMENT_INVALID'
8
+ | 'PACK_CONTRIBUTION_INVALID'
9
+ | 'TABLE_AMBIGUOUS'
10
+ | 'VALIDATION_FAILED';
7
11
 
8
12
  export function contractError(
9
13
  code: ContractCode,
@@ -1 +1,6 @@
1
- export { type AuthoredIndexInput, lowerAuthoredIndex } from '../index-naming';
1
+ export {
2
+ type AuthoredIndexInput,
3
+ type ExactNameBodyWarning,
4
+ flushExactNameBodyWarnings,
5
+ lowerAuthoredIndex,
6
+ } from '../index-naming';
@@ -96,6 +96,8 @@ export function materializeForeignKeysAndIndexes(
96
96
  synthesizedIndexes.push(
97
97
  lowerAuthoredIndex(tableName, {
98
98
  columns: reference.source.columns,
99
+ where: undefined,
100
+ unique: undefined,
99
101
  map: undefined,
100
102
  name: undefined,
101
103
  type: undefined,
@@ -4,62 +4,157 @@ import {
4
4
  defaultIndexName,
5
5
  formatWireName,
6
6
  } from '@prisma-next/sql-schema-ir/naming';
7
- import { InternalError } from '@prisma-next/utils/internal-error';
7
+ import { contractError } from './contract-errors';
8
8
  import type { IndexInput } from './ir/sql-index';
9
9
 
10
+ /**
11
+ * The authored element structure: a fields index carries `columns`, an
12
+ * expression index carries `expression` — exactly one, never both.
13
+ */
14
+ export type AuthoredIndexElements =
15
+ | { readonly columns: readonly string[]; readonly expression?: never }
16
+ | { readonly columns?: never; readonly expression: string };
17
+
10
18
  /**
11
19
  * An index as authored, before naming: `map` is an exact physical name
12
- * (adopted verbatim, PSL `map:`); `name` is a managed wire-name prefix
13
- * (TS `name:`). With neither, the managed prefix defaults to
14
- * `defaultIndexName(table, columns)`.
20
+ * (adopted verbatim); `name` is a managed wire-name prefix. With neither,
21
+ * the managed prefix defaults to `defaultIndexName(table, columns)`.
22
+ * `where`, `unique`, `type`, and `options` participate in the content hash
23
+ * alongside the elements.
15
24
  */
16
- export interface AuthoredIndexInput {
17
- readonly columns: readonly string[];
25
+ export type AuthoredIndexInput = AuthoredIndexElements & {
26
+ readonly where: string | undefined;
27
+ readonly unique: boolean | undefined;
18
28
  readonly map: string | undefined;
19
29
  readonly name: string | undefined;
20
30
  readonly type: string | undefined;
21
31
  readonly options: Record<string, unknown> | undefined;
32
+ };
33
+
34
+ /**
35
+ * One exact-name warning hit: a `map:`-named object carrying a
36
+ * hand-authorable SQL body. `subject` is `index` here and `policy` when
37
+ * policies adopt the same warning.
38
+ */
39
+ export interface ExactNameBodyWarning {
40
+ readonly subject: 'index' | 'policy';
41
+ readonly exactName: string;
42
+ }
43
+
44
+ const EXACT_NAME_BODY_GUIDANCE =
45
+ "Drift detection compares the authored SQL text byte-for-byte against Postgres's reprinted form, which is only reliable when the text was captured by contract infer. For hand-authored definitions, use name: and let Prisma Next manage the physical name; to migrate an adopted object to managed naming, replace map: with name: (keeping the body text unchanged) and apply the resulting rename migration.";
46
+
47
+ const EXACT_NAME_BODY_WARNING_CODE = 'PN_EXACT_NAME_BODY_COMPARISON';
48
+
49
+ const WARNING_BATCH_THRESHOLD = 5;
50
+
51
+ function formatExactNameBodyWarning(warning: ExactNameBodyWarning): string {
52
+ return `${warning.subject} "${warning.exactName}" uses map: with a SQL body. ${EXACT_NAME_BODY_GUIDANCE}`;
53
+ }
54
+
55
+ /**
56
+ * Flushes collected exact-name-body warnings once per contract build: per-item warnings
57
+ * (each naming its object) up to the threshold, one summary with the name
58
+ * list above it — an adopted contract re-emit (which carries `map:` + body
59
+ * for every adopted object once infer emits them) must not wall-of-text.
60
+ */
61
+ export function flushExactNameBodyWarnings(warnings: readonly ExactNameBodyWarning[]): void {
62
+ if (warnings.length === 0) {
63
+ return;
64
+ }
65
+ if (warnings.length <= WARNING_BATCH_THRESHOLD) {
66
+ for (const warning of warnings) {
67
+ process.emitWarning(formatExactNameBodyWarning(warning), {
68
+ code: EXACT_NAME_BODY_WARNING_CODE,
69
+ });
70
+ }
71
+ return;
72
+ }
73
+ process.emitWarning(
74
+ `${warnings.length} objects use map: with a SQL body. ${EXACT_NAME_BODY_GUIDANCE}\n` +
75
+ warnings.map((warning) => ` - ${warning.subject} "${warning.exactName}"`).join('\n'),
76
+ { code: EXACT_NAME_BODY_WARNING_CODE },
77
+ );
22
78
  }
23
79
 
24
80
  /**
25
81
  * Lowers an authored index into the name-identified entity `contract.json`
26
82
  * persists: exact mode adopts `map` verbatim (no prefix, no hash); managed
27
83
  * mode appends the content-hash suffix to the authored or default prefix.
28
- * `unique` always lowers `false` no authoring surface sets it yet.
84
+ * The cross-field guards are the shared enforcement backstop for both
85
+ * authoring surfaces (PSL pre-empts them with span-anchored diagnostics).
29
86
  */
30
- export function lowerAuthoredIndex(tableName: string, authored: AuthoredIndexInput): IndexInput {
87
+ export function lowerAuthoredIndex(
88
+ tableName: string,
89
+ authored: AuthoredIndexInput,
90
+ warnings?: { push(warning: ExactNameBodyWarning): void },
91
+ ): IndexInput {
92
+ if ((authored.columns === undefined) === (authored.expression === undefined)) {
93
+ throw contractError(
94
+ 'CONTRACT.ARGUMENT_INVALID',
95
+ `Index on table "${tableName}": an index takes either fields (columns) or an expression — exactly one, not both.`,
96
+ );
97
+ }
98
+ if (authored.map !== undefined && authored.name !== undefined) {
99
+ throw contractError(
100
+ 'CONTRACT.ARGUMENT_INVALID',
101
+ `Index "${authored.map}" on table "${tableName}": map and name are mutually exclusive — map adopts an exact physical name, name is a managed prefix.`,
102
+ );
103
+ }
104
+ if (
105
+ authored.expression !== undefined &&
106
+ authored.name === undefined &&
107
+ authored.map === undefined
108
+ ) {
109
+ throw contractError(
110
+ 'CONTRACT.ARGUMENT_INVALID',
111
+ `Index on table "${tableName}": an expression index requires an explicit name (name:) or exact physical name (map:) — a default name cannot be derived from an expression.`,
112
+ );
113
+ }
114
+
115
+ const unique = authored.unique ?? false;
116
+
31
117
  if (authored.map !== undefined) {
32
- if (authored.name !== undefined) {
33
- throw new InternalError(
34
- `Index "${authored.map}" on table "${tableName}": map and name are mutually exclusive.`,
35
- );
118
+ if (authored.expression !== undefined || authored.where !== undefined) {
119
+ const warning: ExactNameBodyWarning = { subject: 'index', exactName: authored.map };
120
+ if (warnings !== undefined) {
121
+ warnings.push(warning);
122
+ } else {
123
+ flushExactNameBodyWarnings([warning]);
124
+ }
36
125
  }
37
- return {
126
+ const carried = {
38
127
  name: authored.map,
39
128
  prefix: undefined,
40
- columns: authored.columns,
41
- where: undefined,
42
- unique: false,
129
+ where: authored.where,
130
+ unique,
43
131
  type: authored.type,
44
132
  options: authored.options,
45
133
  };
134
+ return authored.expression !== undefined
135
+ ? { ...carried, expression: authored.expression }
136
+ : { ...carried, columns: authored.columns ?? [] };
46
137
  }
47
138
 
48
- const prefix = authored.name ?? defaultIndexName(tableName, authored.columns);
139
+ const prefix = authored.name ?? defaultIndexName(tableName, authored.columns ?? []);
49
140
  assertWireNamePrefixLength(prefix, 'index prefix');
50
141
  const hash = computeIndexContentHash({
51
- columns: authored.columns,
52
- unique: false,
142
+ ...(authored.columns !== undefined && { columns: authored.columns }),
143
+ ...(authored.expression !== undefined && { expression: authored.expression }),
144
+ ...(authored.where !== undefined && { where: authored.where }),
145
+ unique,
53
146
  ...(authored.type !== undefined && { type: authored.type }),
54
147
  ...(authored.options !== undefined && { options: authored.options }),
55
148
  });
56
- return {
149
+ const carried = {
57
150
  name: formatWireName(prefix, hash),
58
151
  prefix,
59
- columns: authored.columns,
60
- where: undefined,
61
- unique: false,
152
+ where: authored.where,
153
+ unique,
62
154
  type: authored.type,
63
155
  options: authored.options,
64
156
  };
157
+ return authored.expression !== undefined
158
+ ? { ...carried, expression: authored.expression }
159
+ : { ...carried, columns: authored.columns ?? [] };
65
160
  }
@@ -1,43 +0,0 @@
1
- import { InternalError } from "@prisma-next/utils/internal-error";
2
- import { assertWireNamePrefixLength, computeIndexContentHash, defaultIndexName, formatWireName } from "@prisma-next/sql-schema-ir/naming";
3
- //#region src/index-naming.ts
4
- /**
5
- * Lowers an authored index into the name-identified entity `contract.json`
6
- * persists: exact mode adopts `map` verbatim (no prefix, no hash); managed
7
- * mode appends the content-hash suffix to the authored or default prefix.
8
- * `unique` always lowers `false` — no authoring surface sets it yet.
9
- */
10
- function lowerAuthoredIndex(tableName, authored) {
11
- if (authored.map !== void 0) {
12
- if (authored.name !== void 0) throw new InternalError(`Index "${authored.map}" on table "${tableName}": map and name are mutually exclusive.`);
13
- return {
14
- name: authored.map,
15
- prefix: void 0,
16
- columns: authored.columns,
17
- where: void 0,
18
- unique: false,
19
- type: authored.type,
20
- options: authored.options
21
- };
22
- }
23
- const prefix = authored.name ?? defaultIndexName(tableName, authored.columns);
24
- assertWireNamePrefixLength(prefix, "index prefix");
25
- return {
26
- name: formatWireName(prefix, computeIndexContentHash({
27
- columns: authored.columns,
28
- unique: false,
29
- ...authored.type !== void 0 && { type: authored.type },
30
- ...authored.options !== void 0 && { options: authored.options }
31
- })),
32
- prefix,
33
- columns: authored.columns,
34
- where: void 0,
35
- unique: false,
36
- type: authored.type,
37
- options: authored.options
38
- };
39
- }
40
- //#endregion
41
- export { lowerAuthoredIndex as t };
42
-
43
- //# sourceMappingURL=index-naming-DsnarUVW.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index-naming-DsnarUVW.mjs","names":[],"sources":["../src/index-naming.ts"],"sourcesContent":["import {\n assertWireNamePrefixLength,\n computeIndexContentHash,\n defaultIndexName,\n formatWireName,\n} from '@prisma-next/sql-schema-ir/naming';\nimport { InternalError } from '@prisma-next/utils/internal-error';\nimport type { IndexInput } from './ir/sql-index';\n\n/**\n * An index as authored, before naming: `map` is an exact physical name\n * (adopted verbatim, PSL `map:`); `name` is a managed wire-name prefix\n * (TS `name:`). With neither, the managed prefix defaults to\n * `defaultIndexName(table, columns)`.\n */\nexport interface AuthoredIndexInput {\n readonly columns: readonly string[];\n readonly map: string | undefined;\n readonly name: string | undefined;\n readonly type: string | undefined;\n readonly options: Record<string, unknown> | undefined;\n}\n\n/**\n * Lowers an authored index into the name-identified entity `contract.json`\n * persists: exact mode adopts `map` verbatim (no prefix, no hash); managed\n * mode appends the content-hash suffix to the authored or default prefix.\n * `unique` always lowers `false` — no authoring surface sets it yet.\n */\nexport function lowerAuthoredIndex(tableName: string, authored: AuthoredIndexInput): IndexInput {\n if (authored.map !== undefined) {\n if (authored.name !== undefined) {\n throw new InternalError(\n `Index \"${authored.map}\" on table \"${tableName}\": map and name are mutually exclusive.`,\n );\n }\n return {\n name: authored.map,\n prefix: undefined,\n columns: authored.columns,\n where: undefined,\n unique: false,\n type: authored.type,\n options: authored.options,\n };\n }\n\n const prefix = authored.name ?? defaultIndexName(tableName, authored.columns);\n assertWireNamePrefixLength(prefix, 'index prefix');\n const hash = computeIndexContentHash({\n columns: authored.columns,\n unique: false,\n ...(authored.type !== undefined && { type: authored.type }),\n ...(authored.options !== undefined && { options: authored.options }),\n });\n return {\n name: formatWireName(prefix, hash),\n prefix,\n columns: authored.columns,\n where: undefined,\n unique: false,\n type: authored.type,\n options: authored.options,\n };\n}\n"],"mappings":";;;;;;;;;AA6BA,SAAgB,mBAAmB,WAAmB,UAA0C;CAC9F,IAAI,SAAS,QAAQ,KAAA,GAAW;EAC9B,IAAI,SAAS,SAAS,KAAA,GACpB,MAAM,IAAI,cACR,UAAU,SAAS,IAAI,cAAc,UAAU,wCACjD;EAEF,OAAO;GACL,MAAM,SAAS;GACf,QAAQ,KAAA;GACR,SAAS,SAAS;GAClB,OAAO,KAAA;GACP,QAAQ;GACR,MAAM,SAAS;GACf,SAAS,SAAS;EACpB;CACF;CAEA,MAAM,SAAS,SAAS,QAAQ,iBAAiB,WAAW,SAAS,OAAO;CAC5E,2BAA2B,QAAQ,cAAc;CAOjD,OAAO;EACL,MAAM,eAAe,QAPV,wBAAwB;GACnC,SAAS,SAAS;GAClB,QAAQ;GACR,GAAI,SAAS,SAAS,KAAA,KAAa,EAAE,MAAM,SAAS,KAAK;GACzD,GAAI,SAAS,YAAY,KAAA,KAAa,EAAE,SAAS,SAAS,QAAQ;EACpE,CAEkC,CAAC;EACjC;EACA,SAAS,SAAS;EAClB,OAAO,KAAA;EACP,QAAQ;EACR,MAAM,SAAS;EACf,SAAS,SAAS;CACpB;AACF"}