@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.
- package/dist/contract-errors-CPZcBw8G.mjs.map +1 -1
- package/dist/foreign-key-materialization.mjs +3 -1
- package/dist/foreign-key-materialization.mjs.map +1 -1
- package/dist/index-naming-T4UVkLjS.mjs +89 -0
- package/dist/index-naming-T4UVkLjS.mjs.map +1 -0
- package/dist/index-naming.d.mts +40 -8
- package/dist/index-naming.d.mts.map +1 -1
- package/dist/index-naming.mjs +2 -2
- package/package.json +8 -8
- package/src/contract-errors.ts +5 -1
- package/src/exports/index-naming.ts +6 -1
- package/src/foreign-key-materialization.ts +2 -0
- package/src/index-naming.ts +118 -23
- package/dist/index-naming-DsnarUVW.mjs +0 -43
- package/dist/index-naming-DsnarUVW.mjs.map +0 -1
|
@@ -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
|
|
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 {
|
|
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"}
|
package/dist/index-naming.d.mts
CHANGED
|
@@ -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
|
-
*
|
|
5
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
|
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":"
|
|
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"}
|
package/dist/index-naming.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
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.
|
|
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.
|
|
10
|
-
"@prisma-next/framework-components": "0.16.0-dev.
|
|
11
|
-
"@prisma-next/sql-schema-ir": "0.16.0-dev.
|
|
12
|
-
"@prisma-next/utils": "0.16.0-dev.
|
|
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.
|
|
17
|
-
"@prisma-next/tsconfig": "0.16.0-dev.
|
|
18
|
-
"@prisma-next/tsdown": "0.16.0-dev.
|
|
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"
|
package/src/contract-errors.ts
CHANGED
|
@@ -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 =
|
|
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,
|
package/src/index-naming.ts
CHANGED
|
@@ -4,62 +4,157 @@ import {
|
|
|
4
4
|
defaultIndexName,
|
|
5
5
|
formatWireName,
|
|
6
6
|
} from '@prisma-next/sql-schema-ir/naming';
|
|
7
|
-
import {
|
|
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
|
|
13
|
-
*
|
|
14
|
-
* `
|
|
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
|
|
17
|
-
readonly
|
|
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
|
-
*
|
|
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(
|
|
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.
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
126
|
+
const carried = {
|
|
38
127
|
name: authored.map,
|
|
39
128
|
prefix: undefined,
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
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
|
-
|
|
149
|
+
const carried = {
|
|
57
150
|
name: formatWireName(prefix, hash),
|
|
58
151
|
prefix,
|
|
59
|
-
|
|
60
|
-
|
|
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"}
|