@prisma-next/sql-schema-ir 0.16.0-dev.3 → 0.16.0-dev.30
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/README.md +2 -0
- package/dist/exports/naming.d.mts +84 -2
- package/dist/exports/naming.d.mts.map +1 -1
- package/dist/exports/naming.mjs +2 -8
- package/dist/exports/types.d.mts +1 -1
- package/dist/exports/types.mjs +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +1 -1
- package/dist/naming-BD7Y-Fq1.mjs +112 -0
- package/dist/naming-BD7Y-Fq1.mjs.map +1 -0
- package/dist/{types-pxgoVAJq.mjs → types-Btj35AIt.mjs} +85 -34
- package/dist/{types-pxgoVAJq.mjs.map → types-Btj35AIt.mjs.map} +1 -1
- package/dist/{types-Bnh1XTEx.d.mts → types-DzLk5eOU.d.mts} +111 -33
- package/dist/{types-Bnh1XTEx.d.mts.map → types-DzLk5eOU.d.mts.map} +1 -1
- package/package.json +7 -7
- package/src/exports/naming.ts +12 -3
- package/src/ir/sql-index-ir.ts +172 -45
- package/src/naming.ts +135 -0
- package/src/types.ts +4 -1
- package/dist/exports/naming.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -54,6 +54,8 @@ This package defines the core types for the SQL Schema IR, a target-agnostic rep
|
|
|
54
54
|
|
|
55
55
|
3. **Shared Plane**: This package is in the **shared plane**, meaning it can be safely imported by both migration-plane (verification, migration planning) and runtime-plane code.
|
|
56
56
|
|
|
57
|
+
4. **Name-identified indexes**: `SqlIndexIR` is identified by its full physical name (its diff-tree `id` is the name, so same-column-tuple siblings and expression indexes are representable). Managed indexes carry a `prefix` plus a content-hash wire name; the shared naming helpers (`formatWireName`, `parseWireName`, `normalizeSqlBody`, `computeIndexContentHash`) live in `@prisma-next/sql-schema-ir/naming`.
|
|
58
|
+
|
|
57
59
|
## Usage
|
|
58
60
|
|
|
59
61
|
### Basic Usage
|
|
@@ -1,5 +1,87 @@
|
|
|
1
|
-
//#region src/
|
|
1
|
+
//#region src/naming.d.ts
|
|
2
2
|
declare function defaultIndexName(tableName: string, columns: readonly string[]): string;
|
|
3
|
+
interface WireName {
|
|
4
|
+
/** The user-supplied part before the `_<8hex>` suffix. */
|
|
5
|
+
readonly prefix: string;
|
|
6
|
+
/** The 8-lowercase-hex content-hash suffix. */
|
|
7
|
+
readonly hash: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Assembles a wire name from its user-supplied prefix and its 8-hex
|
|
11
|
+
* content-hash suffix. This module owns the `<prefix>_<hash>` format on both
|
|
12
|
+
* sides — construction here and parsing in {@link parseWireName} — so the two
|
|
13
|
+
* never drift.
|
|
14
|
+
*/
|
|
15
|
+
declare function formatWireName(prefix: string, hash: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* Splits a wire name (`<prefix>_<8hex>`) into its prefix and content-hash
|
|
18
|
+
* suffix. Returns `undefined` when the name does not follow the wire-name
|
|
19
|
+
* shape (e.g. an object created outside the toolchain) — callers treat such
|
|
20
|
+
* names as all-prefix. Consumed by introspection (prefix extraction) and by
|
|
21
|
+
* rename pairing (same hash, different prefix).
|
|
22
|
+
*/
|
|
23
|
+
declare function parseWireName(name: string): WireName | undefined;
|
|
24
|
+
/**
|
|
25
|
+
* Stabilizes an authored SQL body (index expression, partial-index predicate,
|
|
26
|
+
* RLS policy predicate) for hashing: trim, and collapse runs of internal
|
|
27
|
+
* whitespace to a single space.
|
|
28
|
+
*
|
|
29
|
+
* This is deliberately minimal. The content hash is the equivalence relation
|
|
30
|
+
* for a wire-named object, and the wire name (prefix + hash) is the only
|
|
31
|
+
* thing ever compared — the hash is never recomputed from an introspected
|
|
32
|
+
* body, so there is no need to match the database's reprinted form. Minimal
|
|
33
|
+
* normalization also protects the no-collision property: aggressive rewriting
|
|
34
|
+
* (lowercasing, paren-stripping, cast-alias folding) risks collapsing two
|
|
35
|
+
* distinct bodies onto one hash.
|
|
36
|
+
*
|
|
37
|
+
* The normalizer is a stability commitment: any change re-suffixes all wire names.
|
|
38
|
+
*/
|
|
39
|
+
declare function normalizeSqlBody(sql: string): string;
|
|
40
|
+
interface IndexContentHashParts {
|
|
41
|
+
readonly expression?: string;
|
|
42
|
+
readonly where?: string;
|
|
43
|
+
readonly columns?: readonly string[];
|
|
44
|
+
readonly unique: boolean;
|
|
45
|
+
readonly type?: string;
|
|
46
|
+
readonly options?: Record<string, unknown>;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Returns the first 8 lowercase hex characters of the SHA-256 digest over the
|
|
50
|
+
* canonical content tuple for an index:
|
|
51
|
+
*
|
|
52
|
+
* [normalizeSqlBody(expression), normalizeSqlBody(where), columns, unique, type, sortedOptions]
|
|
53
|
+
*
|
|
54
|
+
* Columns hash in authored order — column order is semantic in an index.
|
|
55
|
+
* Option values are `String()`-coerced (matching the loose option equality
|
|
56
|
+
* used for diffing) so a hash computed from typed contract values agrees with
|
|
57
|
+
* one recomputed from introspected reloptions strings. The prefix, schema,
|
|
58
|
+
* and table are excluded (they are orthogonal to index equivalence).
|
|
59
|
+
*
|
|
60
|
+
* The tuple order and encoding are a stability commitment with the same
|
|
61
|
+
* status as the RLS tuple: any change re-suffixes every wire name.
|
|
62
|
+
*/
|
|
63
|
+
/**
|
|
64
|
+
* Canonicalizes one index option VALUE to the `on`/`off` boolean spelling:
|
|
65
|
+
* JS booleans and the common catalog spellings (`pg_class.reloptions`
|
|
66
|
+
* stores whatever spelling the DDL used, so a live index may carry
|
|
67
|
+
* `'true'`/`'false'` or `'on'`/`'off'`) all map to one form; everything
|
|
68
|
+
* else via `String()` (fully specified for numbers, so no platform
|
|
69
|
+
* variance). Shared by the wire-name hash tuple, the node's option
|
|
70
|
+
* equality, and the DDL renderer, so an authored `{ fastupdate: true }`
|
|
71
|
+
* agrees with a live index created under any boolean spelling.
|
|
72
|
+
*/
|
|
73
|
+
declare function normalizeIndexOptionValue(value: unknown): string;
|
|
74
|
+
declare function computeIndexContentHash(parts: IndexContentHashParts): string;
|
|
75
|
+
/**
|
|
76
|
+
* Postgres identifiers cap at 63 characters and the wire name appends a
|
|
77
|
+
* 9-character `_<8hex>` suffix, so an authored prefix is bounded at 54.
|
|
78
|
+
*/
|
|
79
|
+
declare const WIRE_NAME_PREFIX_MAX_LENGTH = 54;
|
|
80
|
+
/**
|
|
81
|
+
* Rejects a wire-name prefix over {@link WIRE_NAME_PREFIX_MAX_LENGTH}.
|
|
82
|
+
* `subject` opens the error message (e.g. `defineContract: policy prefix`).
|
|
83
|
+
*/
|
|
84
|
+
declare function assertWireNamePrefixLength(prefix: string, subject: string): void;
|
|
3
85
|
//#endregion
|
|
4
|
-
export { defaultIndexName };
|
|
86
|
+
export { type IndexContentHashParts, WIRE_NAME_PREFIX_MAX_LENGTH, type WireName, assertWireNamePrefixLength, computeIndexContentHash, defaultIndexName, formatWireName, normalizeIndexOptionValue, normalizeSqlBody, parseWireName };
|
|
5
87
|
//# sourceMappingURL=naming.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"naming.d.mts","names":[],"sources":["../../src/
|
|
1
|
+
{"version":3,"file":"naming.d.mts","names":[],"sources":["../../src/naming.ts"],"mappings":";iBAGgB,iBAAiB,mBAAmB;UAInC;;WAEN;;WAEA;;;;;;;;iBAWK,eAAe,gBAAgB;;;;;;;;iBAW/B,cAAc,eAAe;;;;;;;;;;;;;;;;iBAuB7B,iBAAiB;UAIhB;WACN;WACA;WACA;WACA;WACA;WACA,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA4BL,0BAA0B;iBAM1B,wBAAwB,OAAO;;;;;cAoBlC;;;;;iBAMG,2BAA2B,gBAAgB"}
|
package/dist/exports/naming.mjs
CHANGED
|
@@ -1,8 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
return `${tableName}_${columns.join("_")}_idx`;
|
|
4
|
-
}
|
|
5
|
-
//#endregion
|
|
6
|
-
export { defaultIndexName };
|
|
7
|
-
|
|
8
|
-
//# sourceMappingURL=naming.mjs.map
|
|
1
|
+
import { a as formatWireName, c as parseWireName, i as defaultIndexName, n as assertWireNamePrefixLength, o as normalizeIndexOptionValue, r as computeIndexContentHash, s as normalizeSqlBody, t as WIRE_NAME_PREFIX_MAX_LENGTH } from "../naming-BD7Y-Fq1.mjs";
|
|
2
|
+
export { WIRE_NAME_PREFIX_MAX_LENGTH, assertWireNamePrefixLength, computeIndexContentHash, defaultIndexName, formatWireName, normalizeIndexOptionValue, normalizeSqlBody, parseWireName };
|
package/dist/exports/types.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { C as relationalNodeGranularity, D as assertNode, E as SqlSchemaIRNode, O as defineNonEnumerable, S as relationalNodeEntityKind, T as PrimaryKeyInput, _ as SqlColumnDefaultIR, a as SqlTableIR, b as SqlCheckConstraintIRInput, c as SqlUniqueIRInput, d as SqlForeignKeyIR, f as SqlForeignKeyIRInput, g as SqlColumnIRInput, h as SqlColumnIR, i as SqlSchemaIRInput, l as SqlIndexIR, m as SqlAnnotations, n as SqlTypeMetadataRegistry, o as SqlTableIRInput, p as SqlReferentialAction, r as SqlSchemaIR, s as SqlUniqueIR, t as SqlTypeMetadata, u as SqlIndexIRInput, v as SqlColumnDefaultIRInput, w as PrimaryKey, x as RelationalSchemaNodeKind, y as SqlCheckConstraintIR } from "../types-
|
|
1
|
+
import { C as relationalNodeGranularity, D as assertNode, E as SqlSchemaIRNode, O as defineNonEnumerable, S as relationalNodeEntityKind, T as PrimaryKeyInput, _ as SqlColumnDefaultIR, a as SqlTableIR, b as SqlCheckConstraintIRInput, c as SqlUniqueIRInput, d as SqlForeignKeyIR, f as SqlForeignKeyIRInput, g as SqlColumnIRInput, h as SqlColumnIR, i as SqlSchemaIRInput, l as SqlIndexIR, m as SqlAnnotations, n as SqlTypeMetadataRegistry, o as SqlTableIRInput, p as SqlReferentialAction, r as SqlSchemaIR, s as SqlUniqueIR, t as SqlTypeMetadata, u as SqlIndexIRInput, v as SqlColumnDefaultIRInput, w as PrimaryKey, x as RelationalSchemaNodeKind, y as SqlCheckConstraintIR } from "../types-DzLk5eOU.mjs";
|
|
2
2
|
export { PrimaryKey, type PrimaryKeyInput, RelationalSchemaNodeKind, type SqlAnnotations, SqlCheckConstraintIR, type SqlCheckConstraintIRInput, SqlColumnDefaultIR, type SqlColumnDefaultIRInput, SqlColumnIR, type SqlColumnIRInput, SqlForeignKeyIR, type SqlForeignKeyIRInput, SqlIndexIR, type SqlIndexIRInput, type SqlReferentialAction, SqlSchemaIR, type SqlSchemaIRInput, SqlSchemaIRNode, SqlTableIR, type SqlTableIRInput, type SqlTypeMetadata, type SqlTypeMetadataRegistry, SqlUniqueIR, type SqlUniqueIRInput, assertNode, defineNonEnumerable, relationalNodeEntityKind, relationalNodeGranularity };
|
package/dist/exports/types.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as SqlForeignKeyIR, c as SqlCheckConstraintIR, d as assertNode, f as defineNonEnumerable, h as relationalNodeGranularity, i as SqlIndexIR, l as PrimaryKey, m as relationalNodeEntityKind, n as SqlTableIR, o as SqlColumnIR, p as RelationalSchemaNodeKind, r as SqlUniqueIR, s as SqlColumnDefaultIR, t as SqlSchemaIR, u as SqlSchemaIRNode } from "../types-
|
|
1
|
+
import { a as SqlForeignKeyIR, c as SqlCheckConstraintIR, d as assertNode, f as defineNonEnumerable, h as relationalNodeGranularity, i as SqlIndexIR, l as PrimaryKey, m as relationalNodeEntityKind, n as SqlTableIR, o as SqlColumnIR, p as RelationalSchemaNodeKind, r as SqlUniqueIR, s as SqlColumnDefaultIR, t as SqlSchemaIR, u as SqlSchemaIRNode } from "../types-Btj35AIt.mjs";
|
|
2
2
|
export { PrimaryKey, RelationalSchemaNodeKind, SqlCheckConstraintIR, SqlColumnDefaultIR, SqlColumnIR, SqlForeignKeyIR, SqlIndexIR, SqlSchemaIR, SqlSchemaIRNode, SqlTableIR, SqlUniqueIR, assertNode, defineNonEnumerable, relationalNodeEntityKind, relationalNodeGranularity };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { C as relationalNodeGranularity, D as assertNode, E as SqlSchemaIRNode, O as defineNonEnumerable, S as relationalNodeEntityKind, T as PrimaryKeyInput, _ as SqlColumnDefaultIR, a as SqlTableIR, b as SqlCheckConstraintIRInput, c as SqlUniqueIRInput, d as SqlForeignKeyIR, f as SqlForeignKeyIRInput, g as SqlColumnIRInput, h as SqlColumnIR, i as SqlSchemaIRInput, l as SqlIndexIR, m as SqlAnnotations, n as SqlTypeMetadataRegistry, o as SqlTableIRInput, p as SqlReferentialAction, r as SqlSchemaIR, s as SqlUniqueIR, t as SqlTypeMetadata, u as SqlIndexIRInput, v as SqlColumnDefaultIRInput, w as PrimaryKey, x as RelationalSchemaNodeKind, y as SqlCheckConstraintIR } from "./types-
|
|
1
|
+
import { C as relationalNodeGranularity, D as assertNode, E as SqlSchemaIRNode, O as defineNonEnumerable, S as relationalNodeEntityKind, T as PrimaryKeyInput, _ as SqlColumnDefaultIR, a as SqlTableIR, b as SqlCheckConstraintIRInput, c as SqlUniqueIRInput, d as SqlForeignKeyIR, f as SqlForeignKeyIRInput, g as SqlColumnIRInput, h as SqlColumnIR, i as SqlSchemaIRInput, l as SqlIndexIR, m as SqlAnnotations, n as SqlTypeMetadataRegistry, o as SqlTableIRInput, p as SqlReferentialAction, r as SqlSchemaIR, s as SqlUniqueIR, t as SqlTypeMetadata, u as SqlIndexIRInput, v as SqlColumnDefaultIRInput, w as PrimaryKey, x as RelationalSchemaNodeKind, y as SqlCheckConstraintIR } from "./types-DzLk5eOU.mjs";
|
|
2
2
|
export { PrimaryKey, type PrimaryKeyInput, RelationalSchemaNodeKind, type SqlAnnotations, SqlCheckConstraintIR, type SqlCheckConstraintIRInput, SqlColumnDefaultIR, type SqlColumnDefaultIRInput, SqlColumnIR, type SqlColumnIRInput, SqlForeignKeyIR, type SqlForeignKeyIRInput, SqlIndexIR, type SqlIndexIRInput, type SqlReferentialAction, SqlSchemaIR, type SqlSchemaIRInput, SqlSchemaIRNode, SqlTableIR, type SqlTableIRInput, type SqlTypeMetadata, type SqlTypeMetadataRegistry, SqlUniqueIR, type SqlUniqueIRInput, assertNode, defineNonEnumerable, relationalNodeEntityKind, relationalNodeGranularity };
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as SqlForeignKeyIR, c as SqlCheckConstraintIR, d as assertNode, f as defineNonEnumerable, h as relationalNodeGranularity, i as SqlIndexIR, l as PrimaryKey, m as relationalNodeEntityKind, n as SqlTableIR, o as SqlColumnIR, p as RelationalSchemaNodeKind, r as SqlUniqueIR, s as SqlColumnDefaultIR, t as SqlSchemaIR, u as SqlSchemaIRNode } from "./types-
|
|
1
|
+
import { a as SqlForeignKeyIR, c as SqlCheckConstraintIR, d as assertNode, f as defineNonEnumerable, h as relationalNodeGranularity, i as SqlIndexIR, l as PrimaryKey, m as relationalNodeEntityKind, n as SqlTableIR, o as SqlColumnIR, p as RelationalSchemaNodeKind, r as SqlUniqueIR, s as SqlColumnDefaultIR, t as SqlSchemaIR, u as SqlSchemaIRNode } from "./types-Btj35AIt.mjs";
|
|
2
2
|
export { PrimaryKey, RelationalSchemaNodeKind, SqlCheckConstraintIR, SqlColumnDefaultIR, SqlColumnIR, SqlForeignKeyIR, SqlIndexIR, SqlSchemaIR, SqlSchemaIRNode, SqlTableIR, SqlUniqueIR, assertNode, defineNonEnumerable, relationalNodeEntityKind, relationalNodeGranularity };
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { structuredError } from "@prisma-next/utils/structured-error";
|
|
3
|
+
//#region src/naming.ts
|
|
4
|
+
function defaultIndexName(tableName, columns) {
|
|
5
|
+
return `${tableName}_${columns.join("_")}_idx`;
|
|
6
|
+
}
|
|
7
|
+
const WIRE_NAME_PATTERN = /^(.+)_([0-9a-f]{8})$/;
|
|
8
|
+
/**
|
|
9
|
+
* Assembles a wire name from its user-supplied prefix and its 8-hex
|
|
10
|
+
* content-hash suffix. This module owns the `<prefix>_<hash>` format on both
|
|
11
|
+
* sides — construction here and parsing in {@link parseWireName} — so the two
|
|
12
|
+
* never drift.
|
|
13
|
+
*/
|
|
14
|
+
function formatWireName(prefix, hash) {
|
|
15
|
+
return `${prefix}_${hash}`;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Splits a wire name (`<prefix>_<8hex>`) into its prefix and content-hash
|
|
19
|
+
* suffix. Returns `undefined` when the name does not follow the wire-name
|
|
20
|
+
* shape (e.g. an object created outside the toolchain) — callers treat such
|
|
21
|
+
* names as all-prefix. Consumed by introspection (prefix extraction) and by
|
|
22
|
+
* rename pairing (same hash, different prefix).
|
|
23
|
+
*/
|
|
24
|
+
function parseWireName(name) {
|
|
25
|
+
const match = WIRE_NAME_PATTERN.exec(name);
|
|
26
|
+
const prefix = match?.[1];
|
|
27
|
+
const hash = match?.[2];
|
|
28
|
+
if (prefix === void 0 || hash === void 0) return void 0;
|
|
29
|
+
return {
|
|
30
|
+
prefix,
|
|
31
|
+
hash
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Stabilizes an authored SQL body (index expression, partial-index predicate,
|
|
36
|
+
* RLS policy predicate) for hashing: trim, and collapse runs of internal
|
|
37
|
+
* whitespace to a single space.
|
|
38
|
+
*
|
|
39
|
+
* This is deliberately minimal. The content hash is the equivalence relation
|
|
40
|
+
* for a wire-named object, and the wire name (prefix + hash) is the only
|
|
41
|
+
* thing ever compared — the hash is never recomputed from an introspected
|
|
42
|
+
* body, so there is no need to match the database's reprinted form. Minimal
|
|
43
|
+
* normalization also protects the no-collision property: aggressive rewriting
|
|
44
|
+
* (lowercasing, paren-stripping, cast-alias folding) risks collapsing two
|
|
45
|
+
* distinct bodies onto one hash.
|
|
46
|
+
*
|
|
47
|
+
* The normalizer is a stability commitment: any change re-suffixes all wire names.
|
|
48
|
+
*/
|
|
49
|
+
function normalizeSqlBody(sql) {
|
|
50
|
+
return sql.replace(/\s+/g, " ").trim();
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Returns the first 8 lowercase hex characters of the SHA-256 digest over the
|
|
54
|
+
* canonical content tuple for an index:
|
|
55
|
+
*
|
|
56
|
+
* [normalizeSqlBody(expression), normalizeSqlBody(where), columns, unique, type, sortedOptions]
|
|
57
|
+
*
|
|
58
|
+
* Columns hash in authored order — column order is semantic in an index.
|
|
59
|
+
* Option values are `String()`-coerced (matching the loose option equality
|
|
60
|
+
* used for diffing) so a hash computed from typed contract values agrees with
|
|
61
|
+
* one recomputed from introspected reloptions strings. The prefix, schema,
|
|
62
|
+
* and table are excluded (they are orthogonal to index equivalence).
|
|
63
|
+
*
|
|
64
|
+
* The tuple order and encoding are a stability commitment with the same
|
|
65
|
+
* status as the RLS tuple: any change re-suffixes every wire name.
|
|
66
|
+
*/
|
|
67
|
+
/**
|
|
68
|
+
* Canonicalizes one index option VALUE to the `on`/`off` boolean spelling:
|
|
69
|
+
* JS booleans and the common catalog spellings (`pg_class.reloptions`
|
|
70
|
+
* stores whatever spelling the DDL used, so a live index may carry
|
|
71
|
+
* `'true'`/`'false'` or `'on'`/`'off'`) all map to one form; everything
|
|
72
|
+
* else via `String()` (fully specified for numbers, so no platform
|
|
73
|
+
* variance). Shared by the wire-name hash tuple, the node's option
|
|
74
|
+
* equality, and the DDL renderer, so an authored `{ fastupdate: true }`
|
|
75
|
+
* agrees with a live index created under any boolean spelling.
|
|
76
|
+
*/
|
|
77
|
+
function normalizeIndexOptionValue(value) {
|
|
78
|
+
if (value === true || value === "true" || value === "on") return "on";
|
|
79
|
+
if (value === false || value === "false" || value === "off") return "off";
|
|
80
|
+
return String(value);
|
|
81
|
+
}
|
|
82
|
+
function computeIndexContentHash(parts) {
|
|
83
|
+
const sortedOptions = Object.entries(parts.options ?? {}).map(([key, value]) => [key, normalizeIndexOptionValue(value)]).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
84
|
+
const tuple = JSON.stringify([
|
|
85
|
+
normalizeSqlBody(parts.expression ?? ""),
|
|
86
|
+
normalizeSqlBody(parts.where ?? ""),
|
|
87
|
+
parts.columns ?? [],
|
|
88
|
+
parts.unique,
|
|
89
|
+
parts.type ?? "",
|
|
90
|
+
sortedOptions
|
|
91
|
+
]);
|
|
92
|
+
return createHash("sha256").update(tuple).digest("hex").slice(0, 8);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Postgres identifiers cap at 63 characters and the wire name appends a
|
|
96
|
+
* 9-character `_<8hex>` suffix, so an authored prefix is bounded at 54.
|
|
97
|
+
*/
|
|
98
|
+
const WIRE_NAME_PREFIX_MAX_LENGTH = 54;
|
|
99
|
+
/**
|
|
100
|
+
* Rejects a wire-name prefix over {@link WIRE_NAME_PREFIX_MAX_LENGTH}.
|
|
101
|
+
* `subject` opens the error message (e.g. `defineContract: policy prefix`).
|
|
102
|
+
*/
|
|
103
|
+
function assertWireNamePrefixLength(prefix, subject) {
|
|
104
|
+
if (prefix.length > 54) throw structuredError("CONTRACT.WIRE_NAME_PREFIX_TOO_LONG", `${subject} "${prefix}" exceeds the 54-character maximum (Postgres identifiers cap at 63 characters and the wire name appends a 9-character hash suffix).`, { meta: {
|
|
105
|
+
prefix,
|
|
106
|
+
maxLength: 54
|
|
107
|
+
} });
|
|
108
|
+
}
|
|
109
|
+
//#endregion
|
|
110
|
+
export { formatWireName as a, parseWireName as c, defaultIndexName as i, assertWireNamePrefixLength as n, normalizeIndexOptionValue as o, computeIndexContentHash as r, normalizeSqlBody as s, WIRE_NAME_PREFIX_MAX_LENGTH as t };
|
|
111
|
+
|
|
112
|
+
//# sourceMappingURL=naming-BD7Y-Fq1.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"naming-BD7Y-Fq1.mjs","names":[],"sources":["../src/naming.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport { structuredError } from '@prisma-next/utils/structured-error';\n\nexport function defaultIndexName(tableName: string, columns: readonly string[]): string {\n return `${tableName}_${columns.join('_')}_idx`;\n}\n\nexport interface WireName {\n /** The user-supplied part before the `_<8hex>` suffix. */\n readonly prefix: string;\n /** The 8-lowercase-hex content-hash suffix. */\n readonly hash: string;\n}\n\nconst WIRE_NAME_PATTERN = /^(.+)_([0-9a-f]{8})$/;\n\n/**\n * Assembles a wire name from its user-supplied prefix and its 8-hex\n * content-hash suffix. This module owns the `<prefix>_<hash>` format on both\n * sides — construction here and parsing in {@link parseWireName} — so the two\n * never drift.\n */\nexport function formatWireName(prefix: string, hash: string): string {\n return `${prefix}_${hash}`;\n}\n\n/**\n * Splits a wire name (`<prefix>_<8hex>`) into its prefix and content-hash\n * suffix. Returns `undefined` when the name does not follow the wire-name\n * shape (e.g. an object created outside the toolchain) — callers treat such\n * names as all-prefix. Consumed by introspection (prefix extraction) and by\n * rename pairing (same hash, different prefix).\n */\nexport function parseWireName(name: string): WireName | undefined {\n const match = WIRE_NAME_PATTERN.exec(name);\n const prefix = match?.[1];\n const hash = match?.[2];\n if (prefix === undefined || hash === undefined) return undefined;\n return { prefix, hash };\n}\n\n/**\n * Stabilizes an authored SQL body (index expression, partial-index predicate,\n * RLS policy predicate) for hashing: trim, and collapse runs of internal\n * whitespace to a single space.\n *\n * This is deliberately minimal. The content hash is the equivalence relation\n * for a wire-named object, and the wire name (prefix + hash) is the only\n * thing ever compared — the hash is never recomputed from an introspected\n * body, so there is no need to match the database's reprinted form. Minimal\n * normalization also protects the no-collision property: aggressive rewriting\n * (lowercasing, paren-stripping, cast-alias folding) risks collapsing two\n * distinct bodies onto one hash.\n *\n * The normalizer is a stability commitment: any change re-suffixes all wire names.\n */\nexport function normalizeSqlBody(sql: string): string {\n return sql.replace(/\\s+/g, ' ').trim();\n}\n\nexport interface IndexContentHashParts {\n readonly expression?: string;\n readonly where?: string;\n readonly columns?: readonly string[];\n readonly unique: boolean;\n readonly type?: string;\n readonly options?: Record<string, unknown>;\n}\n\n/**\n * Returns the first 8 lowercase hex characters of the SHA-256 digest over the\n * canonical content tuple for an index:\n *\n * [normalizeSqlBody(expression), normalizeSqlBody(where), columns, unique, type, sortedOptions]\n *\n * Columns hash in authored order — column order is semantic in an index.\n * Option values are `String()`-coerced (matching the loose option equality\n * used for diffing) so a hash computed from typed contract values agrees with\n * one recomputed from introspected reloptions strings. The prefix, schema,\n * and table are excluded (they are orthogonal to index equivalence).\n *\n * The tuple order and encoding are a stability commitment with the same\n * status as the RLS tuple: any change re-suffixes every wire name.\n */\n/**\n * Canonicalizes one index option VALUE to the `on`/`off` boolean spelling:\n * JS booleans and the common catalog spellings (`pg_class.reloptions`\n * stores whatever spelling the DDL used, so a live index may carry\n * `'true'`/`'false'` or `'on'`/`'off'`) all map to one form; everything\n * else via `String()` (fully specified for numbers, so no platform\n * variance). Shared by the wire-name hash tuple, the node's option\n * equality, and the DDL renderer, so an authored `{ fastupdate: true }`\n * agrees with a live index created under any boolean spelling.\n */\nexport function normalizeIndexOptionValue(value: unknown): string {\n if (value === true || value === 'true' || value === 'on') return 'on';\n if (value === false || value === 'false' || value === 'off') return 'off';\n return String(value);\n}\n\nexport function computeIndexContentHash(parts: IndexContentHashParts): string {\n const sortedOptions = Object.entries(parts.options ?? {})\n .map(([key, value]): readonly [string, string] => [key, normalizeIndexOptionValue(value)])\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));\n\n const tuple = JSON.stringify([\n normalizeSqlBody(parts.expression ?? ''),\n normalizeSqlBody(parts.where ?? ''),\n parts.columns ?? [],\n parts.unique,\n parts.type ?? '',\n sortedOptions,\n ]);\n return createHash('sha256').update(tuple).digest('hex').slice(0, 8);\n}\n\n/**\n * Postgres identifiers cap at 63 characters and the wire name appends a\n * 9-character `_<8hex>` suffix, so an authored prefix is bounded at 54.\n */\nexport const WIRE_NAME_PREFIX_MAX_LENGTH = 54;\n\n/**\n * Rejects a wire-name prefix over {@link WIRE_NAME_PREFIX_MAX_LENGTH}.\n * `subject` opens the error message (e.g. `defineContract: policy prefix`).\n */\nexport function assertWireNamePrefixLength(prefix: string, subject: string): void {\n if (prefix.length > WIRE_NAME_PREFIX_MAX_LENGTH) {\n throw structuredError(\n 'CONTRACT.WIRE_NAME_PREFIX_TOO_LONG',\n `${subject} \"${prefix}\" exceeds the ${WIRE_NAME_PREFIX_MAX_LENGTH}-character maximum (Postgres identifiers cap at 63 characters and the wire name appends a 9-character hash suffix).`,\n { meta: { prefix, maxLength: WIRE_NAME_PREFIX_MAX_LENGTH } },\n );\n }\n}\n"],"mappings":";;;AAGA,SAAgB,iBAAiB,WAAmB,SAAoC;CACtF,OAAO,GAAG,UAAU,GAAG,QAAQ,KAAK,GAAG,EAAE;AAC3C;AASA,MAAM,oBAAoB;;;;;;;AAQ1B,SAAgB,eAAe,QAAgB,MAAsB;CACnE,OAAO,GAAG,OAAO,GAAG;AACtB;;;;;;;;AASA,SAAgB,cAAc,MAAoC;CAChE,MAAM,QAAQ,kBAAkB,KAAK,IAAI;CACzC,MAAM,SAAS,QAAQ;CACvB,MAAM,OAAO,QAAQ;CACrB,IAAI,WAAW,KAAA,KAAa,SAAS,KAAA,GAAW,OAAO,KAAA;CACvD,OAAO;EAAE;EAAQ;CAAK;AACxB;;;;;;;;;;;;;;;;AAiBA,SAAgB,iBAAiB,KAAqB;CACpD,OAAO,IAAI,QAAQ,QAAQ,GAAG,CAAC,CAAC,KAAK;AACvC;;;;;;;;;;;;;;;;;;;;;;;;;;AAoCA,SAAgB,0BAA0B,OAAwB;CAChE,IAAI,UAAU,QAAQ,UAAU,UAAU,UAAU,MAAM,OAAO;CACjE,IAAI,UAAU,SAAS,UAAU,WAAW,UAAU,OAAO,OAAO;CACpE,OAAO,OAAO,KAAK;AACrB;AAEA,SAAgB,wBAAwB,OAAsC;CAC5E,MAAM,gBAAgB,OAAO,QAAQ,MAAM,WAAW,CAAC,CAAC,CAAC,CACtD,KAAK,CAAC,KAAK,WAAsC,CAAC,KAAK,0BAA0B,KAAK,CAAC,CAAC,CAAC,CACzF,MAAM,CAAC,IAAI,CAAC,OAAQ,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,CAAE;CAElD,MAAM,QAAQ,KAAK,UAAU;EAC3B,iBAAiB,MAAM,cAAc,EAAE;EACvC,iBAAiB,MAAM,SAAS,EAAE;EAClC,MAAM,WAAW,CAAC;EAClB,MAAM;EACN,MAAM,QAAQ;EACd;CACF,CAAC;CACD,OAAO,WAAW,QAAQ,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC;AACpE;;;;;AAMA,MAAa,8BAA8B;;;;;AAM3C,SAAgB,2BAA2B,QAAgB,SAAuB;CAChF,IAAI,OAAO,SAAA,IACT,MAAM,gBACJ,sCACA,GAAG,QAAQ,IAAI,OAAO,sIACtB,EAAE,MAAM;EAAE;EAAQ,WAAA;CAAuC,EAAE,CAC7D;AAEJ"}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { o as normalizeIndexOptionValue } from "./naming-BD7Y-Fq1.mjs";
|
|
1
2
|
import { IRNodeBase, freezeNode } from "@prisma-next/framework-components/ir";
|
|
2
3
|
import { blindCast } from "@prisma-next/utils/casts";
|
|
3
4
|
import { canonicalStringify } from "@prisma-next/utils/canonical-stringify";
|
|
4
5
|
import { ifDefined } from "@prisma-next/utils/defined";
|
|
6
|
+
import { isArrayEqual } from "@prisma-next/utils/array-equal";
|
|
7
|
+
import { InternalError } from "@prisma-next/utils/internal-error";
|
|
5
8
|
//#region src/ir/schema-node-kinds.ts
|
|
6
9
|
/**
|
|
7
10
|
* The `nodeKind` discriminant for each relational schema-diff leaf node.
|
|
@@ -472,41 +475,49 @@ function referentialActionMatches(expected, actual) {
|
|
|
472
475
|
* verifier needs to distinguish them when comparing to the Contract.
|
|
473
476
|
*
|
|
474
477
|
* Implements `DiffableNode` so an index is directly a table's diff-tree
|
|
475
|
-
* child. Indexes are
|
|
476
|
-
*
|
|
477
|
-
*
|
|
478
|
-
*
|
|
479
|
-
*
|
|
480
|
-
* and are not equal — there is no "stronger satisfies weaker".
|
|
478
|
+
* child. Indexes are name-identified: every index — contract-derived or
|
|
479
|
+
* introspected — carries its full physical name, and `id` is that name.
|
|
480
|
+
* Names are catalog-unique per schema, so two indexes legitimately sharing
|
|
481
|
+
* one column tuple (a unique index beside a redundant plain index) are two
|
|
482
|
+
* distinct siblings, and expression indexes need no column tuple at all.
|
|
481
483
|
*
|
|
482
|
-
*
|
|
483
|
-
* (
|
|
484
|
-
*
|
|
485
|
-
*
|
|
486
|
-
* `
|
|
487
|
-
*
|
|
488
|
-
* (
|
|
489
|
-
*
|
|
490
|
-
*
|
|
491
|
-
*
|
|
484
|
+
* `isEqualTo` is selected by the receiver (the differ always calls
|
|
485
|
+
* `expected.isEqualTo(actual)`) and delegates to {@link contentEquals} —
|
|
486
|
+
* the single node-owned content relation: both modes compare `unique`
|
|
487
|
+
* strict, `type` and option values through the named normalization seams,
|
|
488
|
+
* and `columns` ordered-strict when both sides carry them; an exact-named
|
|
489
|
+
* receiver (`prefix === undefined`) additionally byte-compares
|
|
490
|
+
* `expression`/`where` (both sides are reprints in the supported flow —
|
|
491
|
+
* normalizing would only mask real drift); a managed receiver never
|
|
492
|
+
* compares bodies (the wire-name hash already commits to them).
|
|
493
|
+
*
|
|
494
|
+
* `expression`, `where`, and `unique` are genuine SQL-family attributes —
|
|
495
|
+
* functional and partial indexes are standard SQL that any SQL target may
|
|
496
|
+
* introspect, so the family node must represent them; a target declining
|
|
497
|
+
* to author them is a capability decision, not target-specificity.
|
|
492
498
|
*/
|
|
493
499
|
var SqlIndexIR = class SqlIndexIR extends SqlSchemaIRNode {
|
|
494
500
|
nodeKind = RelationalSchemaNodeKind.index;
|
|
495
|
-
|
|
501
|
+
name;
|
|
496
502
|
unique;
|
|
497
503
|
constructor(input) {
|
|
498
504
|
super();
|
|
499
|
-
|
|
505
|
+
if (input.columns === void 0 === (input.expression === void 0)) throw new InternalError(`SqlIndexIR "${input.name}": exactly one of columns or expression must be set.`);
|
|
506
|
+
this.name = input.name;
|
|
500
507
|
this.unique = input.unique;
|
|
501
|
-
if (input.
|
|
508
|
+
if (input.prefix !== void 0) this.prefix = input.prefix;
|
|
509
|
+
if (input.columns !== void 0) this.columns = input.columns;
|
|
510
|
+
if (input.expression !== void 0) this.expression = input.expression;
|
|
511
|
+
if (input.where !== void 0) this.where = input.where;
|
|
502
512
|
if (input.type !== void 0) this.type = input.type;
|
|
503
513
|
if (input.options !== void 0) this.options = input.options;
|
|
504
514
|
if (input.annotations !== void 0) this.annotations = input.annotations;
|
|
505
515
|
defineNonEnumerable(this, "dependsOn", input.dependsOn);
|
|
516
|
+
defineNonEnumerable(this, "partial", input.partial);
|
|
506
517
|
freezeNode(this);
|
|
507
518
|
}
|
|
508
519
|
get id() {
|
|
509
|
-
return `index:${this.
|
|
520
|
+
return `index:${this.name}`;
|
|
510
521
|
}
|
|
511
522
|
children() {
|
|
512
523
|
return [];
|
|
@@ -515,32 +526,72 @@ var SqlIndexIR = class SqlIndexIR extends SqlSchemaIRNode {
|
|
|
515
526
|
return node.nodeKind === RelationalSchemaNodeKind.index;
|
|
516
527
|
}
|
|
517
528
|
/**
|
|
518
|
-
*
|
|
519
|
-
*
|
|
520
|
-
*
|
|
521
|
-
* `
|
|
522
|
-
* compares
|
|
523
|
-
* normalization done at construction.
|
|
529
|
+
* Mode-selected structural equality — see the class doc. Delegates to the
|
|
530
|
+
* single node-owned relation: `columns` compare ordered-strict when both
|
|
531
|
+
* sides carry them; an exact receiver (`prefix === undefined`)
|
|
532
|
+
* byte-compares `expression ?? ''` and `where ?? ''`; a managed receiver
|
|
533
|
+
* never compares bodies (the wire-name hash already commits to them).
|
|
524
534
|
*/
|
|
525
535
|
isEqualTo(other) {
|
|
526
536
|
const node = blindCast(other);
|
|
527
537
|
assertNode(node, "SqlIndexIR", SqlIndexIR.is);
|
|
528
|
-
return this.
|
|
538
|
+
return this.contentEquals(node, {
|
|
539
|
+
columnPresence: "when-both-defined",
|
|
540
|
+
bodies: this.prefix !== void 0 ? "ignored" : "verbatim"
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* The single index content-equality relation — every comparer (the differ
|
|
545
|
+
* via {@link isEqualTo}, the planner's rename content-pairing) calls this
|
|
546
|
+
* with its mode-appropriate strictness rather than growing a parallel
|
|
547
|
+
* relation:
|
|
548
|
+
*
|
|
549
|
+
* - `columnPresence: 'when-both-defined'` (the differ's rule) compares
|
|
550
|
+
* the tuples ordered-strict only when both sides carry them — a paired
|
|
551
|
+
* node's identity already agreed, so a column node meeting an
|
|
552
|
+
* expression node skips the tuple.
|
|
553
|
+
* - `columnPresence: 'matching'` (the rename-pairing rule) additionally
|
|
554
|
+
* requires presence to agree: a column index never pairs an expression
|
|
555
|
+
* index.
|
|
556
|
+
* - `bodies: 'verbatim'` byte-compares `expression ?? ''` / `where ?? ''`
|
|
557
|
+
* (absent ≡ empty, no normalization — both sides are reprints in the
|
|
558
|
+
* supported flow); `bodies: 'ignored'` skips them (managed identity —
|
|
559
|
+
* the wire-name hash commits to the content).
|
|
560
|
+
*
|
|
561
|
+
* `unique` compares strictly; `type` and option VALUES compare through
|
|
562
|
+
* the named normalization seams below.
|
|
563
|
+
*/
|
|
564
|
+
contentEquals(other, strictness) {
|
|
565
|
+
const columnsEqual = strictness.columnPresence === "matching" ? this.columns === void 0 === (other.columns === void 0) && (this.columns === void 0 || isArrayEqual(this.columns, other.columns ?? [])) : this.columns === void 0 || other.columns === void 0 || isArrayEqual(this.columns, other.columns);
|
|
566
|
+
if (!(this.unique === other.unique && normalizeIndexType(this.type) === normalizeIndexType(other.type) && indexOptionsEqual(this.options, other.options) && columnsEqual)) return false;
|
|
567
|
+
if (strictness.bodies === "ignored") return true;
|
|
568
|
+
return (this.expression ?? "") === (other.expression ?? "") && (this.where ?? "") === (other.where ?? "");
|
|
529
569
|
}
|
|
530
570
|
};
|
|
531
571
|
/**
|
|
532
|
-
*
|
|
533
|
-
*
|
|
534
|
-
*
|
|
535
|
-
*
|
|
572
|
+
* Comparison-side normalization seam: the default access method (`btree` in
|
|
573
|
+
* every supported SQL target) compares as absent, so an authored
|
|
574
|
+
* `type: "btree"` and a default-method introspected index (whose type the
|
|
575
|
+
* adapter or constructor normalized away) are equal. Applied by
|
|
576
|
+
* {@link SqlIndexIR.contentEquals} only — the wire-name hash keeps the
|
|
577
|
+
* authored spelling.
|
|
578
|
+
*/
|
|
579
|
+
function normalizeIndexType(type) {
|
|
580
|
+
return type === "btree" ? void 0 : type;
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Option-bag equality: same key set, values compared through
|
|
584
|
+
* {@link normalizeIndexOptionValue} — Postgres introspection returns
|
|
585
|
+
* reloptions values as catalog-reprint strings (`'70'`, `'on'`) while
|
|
586
|
+
* contract option leaves are typed (number, boolean, string).
|
|
536
587
|
*/
|
|
537
|
-
function
|
|
588
|
+
function indexOptionsEqual(a, b) {
|
|
538
589
|
const aKeys = a ? Object.keys(a).sort() : [];
|
|
539
590
|
const bKeys = b ? Object.keys(b).sort() : [];
|
|
540
591
|
if (aKeys.length !== bKeys.length) return false;
|
|
541
592
|
for (let i = 0; i < aKeys.length; i += 1) if (aKeys[i] !== bKeys[i]) return false;
|
|
542
593
|
if (aKeys.length === 0) return true;
|
|
543
|
-
for (const key of aKeys) if (
|
|
594
|
+
for (const key of aKeys) if (normalizeIndexOptionValue(a?.[key]) !== normalizeIndexOptionValue(b?.[key])) return false;
|
|
544
595
|
return true;
|
|
545
596
|
}
|
|
546
597
|
//#endregion
|
|
@@ -680,4 +731,4 @@ var SqlSchemaIR = class extends SqlSchemaIRNode {
|
|
|
680
731
|
//#endregion
|
|
681
732
|
export { SqlForeignKeyIR as a, SqlCheckConstraintIR as c, assertNode as d, defineNonEnumerable as f, relationalNodeGranularity as h, SqlIndexIR as i, PrimaryKey as l, relationalNodeEntityKind as m, SqlTableIR as n, SqlColumnIR as o, RelationalSchemaNodeKind as p, SqlUniqueIR as r, SqlColumnDefaultIR as s, SqlSchemaIR as t, SqlSchemaIRNode as u };
|
|
682
733
|
|
|
683
|
-
//# sourceMappingURL=types-
|
|
734
|
+
//# sourceMappingURL=types-Btj35AIt.mjs.map
|