@prisma/orm-family-sql 8.0.0-rc.20-dev.8 → 8.0.0-rc.20-dev.10

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.
@@ -19,14 +19,14 @@ import { ifDefined } from "@prisma/orm-framework/utils/defined";
19
19
  import { collectScalarTypeConstructors, tsTaggedTemplateSource } from "@prisma/orm-framework/components/authoring";
20
20
  import { ContractValidationError } from "@prisma/orm-framework/contract/contract-validation-error";
21
21
  import { UNBOUND_NAMESPACE_ID, isPlainRecord } from "@prisma/orm-framework/components/ir";
22
- import { assembleDataTypes, codecForRef, dataTypeParams, objectSchemaKeys } from "@prisma/orm-framework/components/codec";
22
+ import { assembleDataTypes, codecForRef, dataTypeParams, objectSchemaKeys, readContractValue } from "@prisma/orm-framework/components/codec";
23
23
  import { canonicalizeJson } from "@prisma/orm-framework/components/utils";
24
24
  import { blindCast } from "@prisma/orm-framework/utils/casts";
25
25
  import { InternalError, isInternalError } from "@prisma/orm-framework/utils/internal-error";
26
26
  import { type } from "arktype";
27
27
  import { asNamespaceId, governingControlPolicy } from "@prisma/orm-framework/contract/types";
28
28
  import { notOk, ok } from "@prisma/orm-framework/utils/result";
29
- import { APP_SPACE_ID, SchemaTreeNode, VERIFY_CODE_HASH_MISMATCH, VERIFY_CODE_MARKER_MISSING, VERIFY_CODE_TARGET_MISMATCH, assembleAuthoringContributions as assembleAuthoringContributions$1, describeMigrationStatement, extractCodecLookup, fieldStatementText, issueOutcome, migrationRunnerFailure, modelDisplayName, sameMarkerHashes } from "@prisma/orm-framework/components/control";
29
+ import { APP_SPACE_ID, SchemaTreeNode, VERIFY_CODE_HASH_MISMATCH, VERIFY_CODE_MARKER_MISSING, VERIFY_CODE_TARGET_MISMATCH, assembleAuthoringContributions as assembleAuthoringContributions$1, describeMigrationStatement, enumValueStatementText, extractCodecLookup, fieldStatementText, issueOutcome, migrationRunnerFailure, modelDisplayName, sameMarkerHashes } from "@prisma/orm-framework/components/control";
30
30
  import { assertDescriptorSelfConsistency } from "@prisma/orm-toolchain/migration-tools/spaces";
31
31
  import { jsonToTsSource } from "@prisma/orm-framework/ts-render";
32
32
  //#region ../../../2-sql-family/9-family/dist/control.mjs
@@ -1319,152 +1319,6 @@ function partitionIssuesByControlPolicy(options) {
1319
1319
  function subjectKey(subject) {
1320
1320
  return `${subject.namespaceId}\u0000${subject.entityKind ?? ""}\u0000${subject.entityName ?? ""}\u0000${subject.rlsPolicy ?? ""}`;
1321
1321
  }
1322
- function planFieldEventOperations(options) {
1323
- return planFieldEventCalls(options).map(({ call }) => call);
1324
- }
1325
- /** The calls the codec hooks return, each with the field event's column. */
1326
- function planFieldEventCalls(options) {
1327
- const priorContract = options.priorContract;
1328
- const newContract = options.newContract;
1329
- const renamedTo = new Map(options.tableRenames.map((rename) => [renameKey(rename.namespaceId, rename.from), rename.to]));
1330
- const newNameOf = (namespaceId, tableName) => renamedTo.get(renameKey(namespaceId, tableName));
1331
- const columnRenamedTo = new Map(options.columnRenames.map((rename) => [renameKey(rename.namespaceId, rename.table, rename.from), rename.to]));
1332
- const added = [];
1333
- const dropped = [];
1334
- const altered = [];
1335
- const namespaceIds = unionSorted(priorContract ? Object.keys(priorContract.storage.namespaces) : [], Object.keys(newContract.storage.namespaces));
1336
- for (const namespaceId of namespaceIds) {
1337
- const priorNs = priorContract?.storage.namespaces[namespaceId];
1338
- const newNs = newContract.storage.namespaces[namespaceId];
1339
- const priorTables = underNewNames(priorNs?.entries.table, (tableName) => newNameOf(namespaceId, tableName));
1340
- const newTables = newNs?.entries.table;
1341
- const tableNames = unionSorted(priorTables ? Object.keys(priorTables) : [], newTables ? Object.keys(newTables) : []);
1342
- for (const tableName of tableNames) {
1343
- const priorTableRaw = priorTables?.[tableName];
1344
- const newTableRaw = newTables?.[tableName];
1345
- const priorTable = StorageTable.is(priorTableRaw) ? priorTableRaw : void 0;
1346
- const newTable = StorageTable.is(newTableRaw) ? newTableRaw : void 0;
1347
- const priorColumns = underNewNames(priorTable?.columns, (columnName) => columnRenamedTo.get(renameKey(namespaceId, tableName, columnName)));
1348
- const fieldNames = unionSorted(priorColumns ? Object.keys(priorColumns) : [], newTable ? Object.keys(newTable.columns) : []);
1349
- for (const fieldName of fieldNames) {
1350
- const priorField = priorColumns?.[fieldName];
1351
- const newField = newTable?.columns[fieldName];
1352
- const entry = {
1353
- namespaceId,
1354
- tableName,
1355
- fieldName,
1356
- priorTable,
1357
- newTable,
1358
- priorField,
1359
- newField
1360
- };
1361
- if (priorField === void 0 && newField !== void 0) added.push(entry);
1362
- else if (priorField !== void 0 && newField === void 0) dropped.push(entry);
1363
- else if (priorField !== void 0 && newField !== void 0) {
1364
- if (isAlteration(priorField, newField, options.dataTypes)) altered.push(entry);
1365
- }
1366
- }
1367
- }
1368
- }
1369
- const calls = [];
1370
- appendCalls("added", added, options.codecHooks, calls, (e) => e.newField?.codecId);
1371
- appendCalls("dropped", dropped, options.codecHooks, calls, (e) => e.priorField?.codecId);
1372
- appendCalls("altered", altered, options.codecHooks, calls, (e) => e.newField?.codecId);
1373
- return calls;
1374
- }
1375
- function renameKey(...parts) {
1376
- return JSON.stringify(parts);
1377
- }
1378
- function underNewNames(tables, newNameOf) {
1379
- if (tables === void 0) return void 0;
1380
- return Object.fromEntries(Object.entries(tables).map(([tableName, table]) => [newNameOf(tableName) ?? tableName, table]));
1381
- }
1382
- function appendCalls(event, entries, codecHooks, calls, pickCodecId) {
1383
- for (const entry of entries) {
1384
- const codecId = pickCodecId(entry);
1385
- if (codecId === void 0) continue;
1386
- const hook = codecHooks.get(codecId);
1387
- if (!hook?.onFieldEvent) continue;
1388
- const ctx = buildContext(event, entry);
1389
- const emitted = hook.onFieldEvent(event, ctx);
1390
- for (const call of emitted) calls.push({
1391
- call,
1392
- namespaceId: entry.namespaceId,
1393
- tableName: entry.tableName,
1394
- columnName: entry.fieldName
1395
- });
1396
- }
1397
- }
1398
- /**
1399
- * The context's prior/new sides are scoped to the event:
1400
- *
1401
- * - `'added'` — only `newTable` / `newField` populated.
1402
- * - `'dropped'` — only `priorTable` / `priorField` populated.
1403
- * - `'altered'` — both sides populated.
1404
- */
1405
- function buildContext(event, entry) {
1406
- const base = {
1407
- namespaceId: entry.namespaceId,
1408
- tableName: entry.tableName,
1409
- fieldName: entry.fieldName
1410
- };
1411
- if (event === "added") return {
1412
- ...base,
1413
- ...entry.newTable !== void 0 ? { newTable: entry.newTable } : {},
1414
- ...entry.newField !== void 0 ? { newField: entry.newField } : {}
1415
- };
1416
- if (event === "dropped") return {
1417
- ...base,
1418
- ...entry.priorTable !== void 0 ? { priorTable: entry.priorTable } : {},
1419
- ...entry.priorField !== void 0 ? { priorField: entry.priorField } : {}
1420
- };
1421
- return {
1422
- ...base,
1423
- ...entry.priorTable !== void 0 ? { priorTable: entry.priorTable } : {},
1424
- ...entry.newTable !== void 0 ? { newTable: entry.newTable } : {},
1425
- ...entry.priorField !== void 0 ? { priorField: entry.priorField } : {},
1426
- ...entry.newField !== void 0 ? { newField: entry.newField } : {}
1427
- };
1428
- }
1429
- /**
1430
- * `'altered'` predicate. Returns `false` whenever `codecId` differs —
1431
- * any codec change suppresses the `altered` event entirely, including
1432
- * cases where another property also differs in the same diff. Codec
1433
- * rotation is a v1 non-goal; avoiding the mixed event keeps the
1434
- * migration semantics for codec changes explicit rather than smuggling
1435
- * them through as `altered`.
1436
- *
1437
- * For non-`codecId` diffs, returns `true` iff any other column property
1438
- * differs.
1439
- */
1440
- function isAlteration(prior, current, dataTypes) {
1441
- if (prior.codecId !== current.codecId) return false;
1442
- return !sameStorageColumn(prior, current, dataTypes);
1443
- }
1444
- function sameStorageColumn(a, b, dataTypes) {
1445
- if (a === b) return true;
1446
- if (!sameSqlColumnType(a, b, dataTypes)) return false;
1447
- if (a.nullable !== b.nullable) return false;
1448
- if (canonicalizeJson(codecOwnParams(a, dataTypes)) !== canonicalizeJson(codecOwnParams(b, dataTypes))) return false;
1449
- if (!sameJson(a.default, b.default)) return false;
1450
- return true;
1451
- }
1452
- /** The parameters a column's codec keeps for itself: every key its data type does not declare. */
1453
- function codecOwnParams(column, dataTypes) {
1454
- const declared = new Set(objectSchemaKeys(dataTypes.get(column.dataType)?.params) ?? []);
1455
- return Object.fromEntries(Object.entries(column.typeParams ?? {}).filter(([key]) => !declared.has(key)));
1456
- }
1457
- function sameJson(a, b) {
1458
- if (a === b) return true;
1459
- if (a === void 0 || b === void 0) return false;
1460
- return JSON.stringify(a) === JSON.stringify(b);
1461
- }
1462
- function unionSorted(a, b) {
1463
- const set = /* @__PURE__ */ new Set();
1464
- for (const name of a) set.add(name);
1465
- for (const name of b) set.add(name);
1466
- return [...set].sort((x, y) => x < y ? -1 : x > y ? 1 : 0);
1467
- }
1468
1322
  const COLUMN_RENAME_UNMATCHED_CODE = "MIGRATION.COLUMN_RENAME_UNMATCHED";
1469
1323
  function tableLabel$1(namespaceId, table) {
1470
1324
  return namespaceId === void 0 || namespaceId === UNBOUND_NAMESPACE_ID ? table : `${namespaceId}.${table}`;
@@ -1797,6 +1651,7 @@ var StatementPlanner = class {
1797
1651
  case "namespace": return this.#planNamespace(statement);
1798
1652
  case "model": return this.#planModel(statement);
1799
1653
  case "field": return this.#planField(statement);
1654
+ case "enumValue": return ok([]);
1800
1655
  }
1801
1656
  }
1802
1657
  /** Where a table of the origin is in the working schema, after the renames planned so far. */
@@ -2108,6 +1963,459 @@ function appliedStatementsInOrder(statements, renames) {
2108
1963
  operationIndexes: []
2109
1964
  });
2110
1965
  }
1966
+ function sameValue(left, right) {
1967
+ return JSON.stringify(left) === JSON.stringify(right);
1968
+ }
1969
+ function enumOf(contract, namespaceId, name) {
1970
+ return contract.domain.namespaces[namespaceId]?.enum?.[name];
1971
+ }
1972
+ /** The columns of the destination whose fields the enum types, in managed tables. */
1973
+ function columnsTypedBy(contract, namespaceId, enumName) {
1974
+ const found = /* @__PURE__ */ new Map();
1975
+ for (const namespace of Object.values(contract.domain.namespaces)) for (const model of Object.values(namespace.models)) {
1976
+ const { storage } = model;
1977
+ if (!isSqlModelStorage(storage)) continue;
1978
+ for (const [fieldName, field] of Object.entries(model.fields)) {
1979
+ const ref = field.valueSet;
1980
+ if (ref === void 0 || ref.plane !== "domain" || ref.entityKind !== "enum" || ref.spaceId !== void 0 || ref.namespaceId !== namespaceId || ref.entityName !== enumName) continue;
1981
+ const column = storage.fields[fieldName]?.column;
1982
+ if (column === void 0) continue;
1983
+ const stored = contract.storage.namespaces[storage.namespaceId]?.entries.table?.[storage.table]?.columns[column];
1984
+ if (stored === void 0) continue;
1985
+ const location = {
1986
+ namespaceId: storage.namespaceId,
1987
+ table: storage.table
1988
+ };
1989
+ if (tableControlPolicy(contract, location) !== "managed") continue;
1990
+ found.set(JSON.stringify([
1991
+ storage.namespaceId,
1992
+ storage.table,
1993
+ column
1994
+ ]), {
1995
+ ...location,
1996
+ column,
1997
+ codecRef: buildColumnCodecRef(stored, void 0),
1998
+ many: stored.many !== false && stored.many !== void 0
1999
+ });
2000
+ }
2001
+ }
2002
+ return [...found.values()];
2003
+ }
2004
+ function isStoredEnumValue(value) {
2005
+ return typeof value === "string" || typeof value === "number";
2006
+ }
2007
+ /** A stored value as messages write it: a number without quotes, as the enum's codec reads it. */
2008
+ function valueText(value, codec) {
2009
+ const built = codecForRef(codec.codecs, codec.codecRef);
2010
+ if (built === void 0) return JSON.stringify(value);
2011
+ try {
2012
+ const read = readContractValue(built, value, codec.codecRef.typeParams);
2013
+ return typeof read === "number" || typeof read === "bigint" ? String(read) : JSON.stringify(value);
2014
+ } catch {
2015
+ return JSON.stringify(value);
2016
+ }
2017
+ }
2018
+ function unsupportedValue(contract, value, stored) {
2019
+ const text = enumValueStatementText(contract, value);
2020
+ return {
2021
+ kind: "unsupportedOperation",
2022
+ summary: `Cannot rewrite enum value "${text}": its stored value ${JSON.stringify(stored)} is not a string or a number`,
2023
+ why: `The plan rewrites the rows that hold a removed or renamed enum value, and it compares only string and number values. Rewrite the rows that hold enum value "${text}" yourself, then plan again.`
2024
+ };
2025
+ }
2026
+ function stillStoredRefusal(statement, enums, stored, holder) {
2027
+ const holderText = enumValueStatementText(enums.contract, {
2028
+ ...statement.to,
2029
+ member: holder
2030
+ });
2031
+ const value = enums.text(stored);
2032
+ return {
2033
+ kind: "statementRefused",
2034
+ summary: `Cannot rename enum value "${enumValueStatementText(enums.fromContract, statement.from)}" to "${enumValueStatementText(enums.contract, statement.to)}": the destination enum still stores ${value}, as enum value "${holderText}"`,
2035
+ why: `A rename changes the stored value in every row that holds it, so the rows that hold ${value} for "${holderText}" would change too. Leave out this statement.`,
2036
+ refusedStatement: statement
2037
+ };
2038
+ }
2039
+ function mergingRefusal(statement, enums, from, to, holder) {
2040
+ const holderText = enumValueStatementText(enums.fromContract, {
2041
+ ...statement.from,
2042
+ member: holder
2043
+ });
2044
+ return {
2045
+ kind: "statementRefused",
2046
+ summary: `Cannot rename enum value "${enumValueStatementText(enums.fromContract, statement.from)}" to "${enumValueStatementText(enums.contract, statement.to)}": the origin enum already stores ${enums.text(to)}, as enum value "${holderText}"`,
2047
+ why: `A rename changes ${enums.text(from)} to ${enums.text(to)} in every row that holds it, which merges its data with the data of "${holderText}". Leave out this statement. To merge them on purpose, plan the change with migration plan and write this.replaceStoredValue(...) in the migration.ts it writes.`,
2048
+ refusedStatement: statement
2049
+ };
2050
+ }
2051
+ function cycleRefusal(statement, cycle, enums) {
2052
+ const moves = cycle.map(({ from, to }) => `${enums.text(from)} to ${enums.text(to ?? null)}`);
2053
+ const listed = `${moves.slice(0, -1).join(", ")} and ${moves.at(-1)}`;
2054
+ return {
2055
+ kind: "statementRefused",
2056
+ summary: `Cannot rename enum value "${enumValueStatementText(enums.fromContract, statement.from)}" to "${enumValueStatementText(enums.contract, statement.to)}": the renames move stored values in a cycle, ${listed}, and a rewrite of one would overwrite the other before it is read`,
2057
+ why: "Leave out the renames in the cycle, and write the rewrites by hand in the migration.ts a plan without them writes: rewrite the first value to a value no member stores, then each other value, then that value to its new value, with this.replaceStoredValue({ table, column, from, to }) for each.",
2058
+ refusedStatement: statement,
2059
+ leaveOut: cycle.flatMap((change) => change.statement === void 0 ? [] : [change.statement])
2060
+ };
2061
+ }
2062
+ /**
2063
+ * What changes for each member of the origin enum. A rename statement gives a member the stored
2064
+ * value of the member it names. A member the destination has under the same name takes that
2065
+ * member's stored value, even when another member had it before. A member the destination lacks is
2066
+ * kept when the destination still stores its value, under another name; otherwise its value is
2067
+ * removed.
2068
+ */
2069
+ function valueChanges(enums, enumValueOf, statementOf) {
2070
+ const changes = [];
2071
+ for (const member of enums.originEnum.members) {
2072
+ const enumValue = enumValueOf(member.name);
2073
+ const statement = statementOf(member.name);
2074
+ const named = statement?.to.member ?? member.name;
2075
+ const target = enums.destinationEnum.members.find(({ name }) => name === named);
2076
+ if (target !== void 0) {
2077
+ if (!sameValue(target.value, member.value)) changes.push({
2078
+ enumValue,
2079
+ from: member.value,
2080
+ to: target.value,
2081
+ statement
2082
+ });
2083
+ continue;
2084
+ }
2085
+ if (statement !== void 0) continue;
2086
+ if (enums.destinationEnum.members.some(({ value }) => sameValue(value, member.value))) continue;
2087
+ changes.push({
2088
+ enumValue,
2089
+ from: member.value,
2090
+ to: void 0,
2091
+ statement: void 0
2092
+ });
2093
+ }
2094
+ return changes;
2095
+ }
2096
+ /**
2097
+ * Orders the changes so each runs before the change that writes its old value: in a chain, the
2098
+ * last value moves first. A cycle has no such order; it comes out as the changes in it, from the
2099
+ * first change of the origin enum's order around.
2100
+ */
2101
+ function orderChanges(changes) {
2102
+ const bySource = new Map(changes.map((change) => [JSON.stringify(change.from), change]));
2103
+ const placed = /* @__PURE__ */ new Set();
2104
+ const groups = [];
2105
+ for (const start of changes) {
2106
+ if (placed.has(start)) continue;
2107
+ const path = [];
2108
+ let cycle = false;
2109
+ for (let change = start; change !== void 0 && !placed.has(change); change = change.to === void 0 ? void 0 : bySource.get(JSON.stringify(change.to))) {
2110
+ if (path.includes(change)) {
2111
+ cycle = true;
2112
+ break;
2113
+ }
2114
+ path.push(change);
2115
+ }
2116
+ for (const change of path) placed.add(change);
2117
+ groups.push(cycle ? {
2118
+ changes: path,
2119
+ cycle
2120
+ } : {
2121
+ changes: [...path].reverse(),
2122
+ cycle
2123
+ });
2124
+ }
2125
+ return groups;
2126
+ }
2127
+ /** A value no member of either enum stores, of the kind of `value`: the next whole number, or a marked string. */
2128
+ function temporaryValue(value, taken) {
2129
+ const numbers = taken.flatMap((stored) => {
2130
+ const number = typeof stored === "string" ? Number(stored) : stored;
2131
+ return typeof number === "number" && Number.isFinite(number) ? [number] : [];
2132
+ });
2133
+ const nextWholeNumber = numbers.length === 0 ? 0 : Math.floor(Math.max(...numbers)) + 1;
2134
+ if (typeof value === "number") return nextWholeNumber;
2135
+ if (/^-?\d+$/.test(value)) return String(nextWholeNumber);
2136
+ let candidate = "prisma_swap";
2137
+ while (taken.some((stored) => sameValue(stored, candidate))) candidate = `${candidate}_`;
2138
+ return candidate;
2139
+ }
2140
+ /**
2141
+ * The rewrites of stored enum values a plan makes. Enums are compared by the stored value of each
2142
+ * member (see `valueChanges`). A changed value is rewritten in every column, in the working schema,
2143
+ * whose field the destination enum types: a rename statement replaces it, and any other change
2144
+ * removes it, which the plan asks about, with the replacements that would keep its data. Changes
2145
+ * that shift values along are ordered so none overwrites a value another still has to read; rename
2146
+ * statements that move values in a cycle are refused, and other changes in a cycle keep their data
2147
+ * through a temporary value. With no origin contract, nothing is known to have changed.
2148
+ */
2149
+ function planEnumValueRewrites(input) {
2150
+ const { fromContract, contract } = input;
2151
+ if (fromContract === null) return ok([]);
2152
+ const renamedNamespaces = /* @__PURE__ */ new Map();
2153
+ const valueRenames = [];
2154
+ for (const statement of input.statements) {
2155
+ if (statement.kind !== "rename") continue;
2156
+ if (statement.entity === "namespace") renamedNamespaces.set(statement.from, statement.to);
2157
+ if (statement.entity === "enumValue") valueRenames.push(statement);
2158
+ }
2159
+ const rewrites = [];
2160
+ for (const [originNamespace, namespace] of Object.entries(fromContract.domain.namespaces)) for (const [enumName, originEnum] of Object.entries(namespace.enum ?? {})) {
2161
+ const namespaceId = renamedNamespaces.get(originNamespace) ?? originNamespace;
2162
+ const destinationEnum = enumOf(contract, namespaceId, enumName);
2163
+ if (destinationEnum === void 0) continue;
2164
+ const codec = {
2165
+ codecRef: { codecId: destinationEnum.codecId },
2166
+ codecs: input.codecs
2167
+ };
2168
+ const enums = {
2169
+ fromContract,
2170
+ contract,
2171
+ originEnum,
2172
+ destinationEnum,
2173
+ text: (value) => valueText(value, codec)
2174
+ };
2175
+ const changes = valueChanges(enums, (member) => ({
2176
+ namespaceId: asNamespaceId(originNamespace),
2177
+ enum: enumName,
2178
+ member
2179
+ }), (member) => valueRenames.find((rename) => rename.from.namespaceId === originNamespace && rename.from.enum === enumName && rename.from.member === member));
2180
+ const checked = [];
2181
+ for (const change of changes) {
2182
+ const { from, to } = change;
2183
+ if (!isStoredEnumValue(from)) return notOk(unsupportedValue(fromContract, change.enumValue, from));
2184
+ if (to !== void 0 && !isStoredEnumValue(to)) return notOk(unsupportedValue(fromContract, change.enumValue, to));
2185
+ checked.push({
2186
+ ...change,
2187
+ from,
2188
+ to
2189
+ });
2190
+ }
2191
+ const changedFrom = (value) => checked.some(({ from }) => sameValue(from, value));
2192
+ const changedTo = (value) => checked.some(({ to }) => to !== void 0 && sameValue(to, value));
2193
+ const merging = (to) => originEnum.members.find(({ value }) => sameValue(value, to) && !changedFrom(value));
2194
+ for (const { statement, from, to } of checked) {
2195
+ if (statement === void 0 || to === void 0) continue;
2196
+ const holder = destinationEnum.members.find(({ value }) => sameValue(value, from));
2197
+ if (holder !== void 0 && !changedTo(from)) return notOk(stillStoredRefusal(statement, enums, from, holder.name));
2198
+ const merged = merging(to);
2199
+ if (merged !== void 0) return notOk(mergingRefusal(statement, enums, from, to, merged.name));
2200
+ }
2201
+ const columns = columnsTypedBy(contract, namespaceId, enumName).filter((column) => input.tables.hasColumn(column.namespaceId, column.table, column.column));
2202
+ if (columns.length === 0) continue;
2203
+ const taken = [...originEnum.members.map(({ value }) => value), ...destinationEnum.members.map(({ value }) => value)];
2204
+ for (const group of orderChanges(checked)) {
2205
+ const refused = group.cycle ? group.changes.find(({ statement }) => statement !== void 0)?.statement : void 0;
2206
+ if (refused !== void 0) return notOk(cycleRefusal(refused, group.changes, enums));
2207
+ for (const [change, keepData] of keptData(group, taken, merging)) for (const column of columns) rewrites.push({
2208
+ enumValue: change.enumValue,
2209
+ ...column,
2210
+ from: change.from,
2211
+ to: change.statement === void 0 ? void 0 : change.to,
2212
+ statement: change.statement,
2213
+ keepData,
2214
+ swapsValues: group.cycle
2215
+ });
2216
+ }
2217
+ }
2218
+ return ok(rewrites);
2219
+ }
2220
+ /**
2221
+ * Each change of a group, in the order it runs, with the replacements that keep the data of a
2222
+ * change the plan removes: its value replaced by its new value, unless another member of the
2223
+ * origin enum keeps that value, which would merge their data. In a cycle, the first value moves to
2224
+ * a temporary value, the rest move around, and the temporary value moves last.
2225
+ */
2226
+ function keptData(group, taken, merging) {
2227
+ const moved = ({ from, to }) => to === void 0 ? [] : [{
2228
+ from,
2229
+ to
2230
+ }];
2231
+ const [first, ...rest] = group.changes;
2232
+ if (!group.cycle || first === void 0) return group.changes.map((change) => [change, change.statement === void 0 && change.to !== void 0 && merging(change.to) === void 0 ? moved(change) : void 0]);
2233
+ const swap = temporaryValue(first.from, taken);
2234
+ const reversed = [...rest].reverse();
2235
+ return [[first, [{
2236
+ from: first.from,
2237
+ to: swap
2238
+ }]], ...reversed.map((change, index) => [change, index === reversed.length - 1 ? [...moved(change), ...moved({
2239
+ ...first,
2240
+ from: swap
2241
+ })] : moved(change)])];
2242
+ }
2243
+ /**
2244
+ * The applied statements with each enum value rename given the operations its rewrites lowered
2245
+ * to, at their positions in the plan: the rewrites are ordered with the diff, not with the renames.
2246
+ */
2247
+ function withEnumValueOperations(applied, operationIndexesOf) {
2248
+ return applied.map(({ statement, operationIndexes }) => statement.kind === "rename" && statement.entity === "enumValue" ? {
2249
+ statement,
2250
+ operationIndexes: operationIndexesOf(statement)
2251
+ } : {
2252
+ statement,
2253
+ operationIndexes
2254
+ });
2255
+ }
2256
+ /**
2257
+ * The value the database stores for `value`, a stored value as the contract writes it: the column's
2258
+ * codec reads it as a value of its data type and writes it with `toWire`, as a literal default is
2259
+ * written. With no codec, or one the stack does not have, the value is used as written.
2260
+ */
2261
+ async function encodeStoredValue(value, codec) {
2262
+ if (codec === void 0) return value;
2263
+ const built = codecForRef(codec.codecs, codec.codecRef);
2264
+ if (built === void 0) return value;
2265
+ const wire = await built.toWire(readContractValue(built, value, codec.codecRef.typeParams), {});
2266
+ if (typeof wire === "string" || typeof wire === "number") return wire;
2267
+ if (typeof wire === "bigint") return wire.toString();
2268
+ throw structuredError("CONTRACT.ARGUMENT_INVALID", `Codec ${codec.codecRef.codecId} writes the stored value ${JSON.stringify(value)} as neither a string nor a number, which a migration cannot compare`, { meta: {
2269
+ what: "stored value",
2270
+ codecId: codec.codecRef.codecId
2271
+ } });
2272
+ }
2273
+ function planFieldEventOperations(options) {
2274
+ return planFieldEventCalls(options).map(({ call }) => call);
2275
+ }
2276
+ /** The calls the codec hooks return, each with the field event's column. */
2277
+ function planFieldEventCalls(options) {
2278
+ const priorContract = options.priorContract;
2279
+ const newContract = options.newContract;
2280
+ const renamedTo = new Map(options.tableRenames.map((rename) => [renameKey(rename.namespaceId, rename.from), rename.to]));
2281
+ const newNameOf = (namespaceId, tableName) => renamedTo.get(renameKey(namespaceId, tableName));
2282
+ const columnRenamedTo = new Map(options.columnRenames.map((rename) => [renameKey(rename.namespaceId, rename.table, rename.from), rename.to]));
2283
+ const added = [];
2284
+ const dropped = [];
2285
+ const altered = [];
2286
+ const namespaceIds = unionSorted(priorContract ? Object.keys(priorContract.storage.namespaces) : [], Object.keys(newContract.storage.namespaces));
2287
+ for (const namespaceId of namespaceIds) {
2288
+ const priorNs = priorContract?.storage.namespaces[namespaceId];
2289
+ const newNs = newContract.storage.namespaces[namespaceId];
2290
+ const priorTables = underNewNames(priorNs?.entries.table, (tableName) => newNameOf(namespaceId, tableName));
2291
+ const newTables = newNs?.entries.table;
2292
+ const tableNames = unionSorted(priorTables ? Object.keys(priorTables) : [], newTables ? Object.keys(newTables) : []);
2293
+ for (const tableName of tableNames) {
2294
+ const priorTableRaw = priorTables?.[tableName];
2295
+ const newTableRaw = newTables?.[tableName];
2296
+ const priorTable = StorageTable.is(priorTableRaw) ? priorTableRaw : void 0;
2297
+ const newTable = StorageTable.is(newTableRaw) ? newTableRaw : void 0;
2298
+ const priorColumns = underNewNames(priorTable?.columns, (columnName) => columnRenamedTo.get(renameKey(namespaceId, tableName, columnName)));
2299
+ const fieldNames = unionSorted(priorColumns ? Object.keys(priorColumns) : [], newTable ? Object.keys(newTable.columns) : []);
2300
+ for (const fieldName of fieldNames) {
2301
+ const priorField = priorColumns?.[fieldName];
2302
+ const newField = newTable?.columns[fieldName];
2303
+ const entry = {
2304
+ namespaceId,
2305
+ tableName,
2306
+ fieldName,
2307
+ priorTable,
2308
+ newTable,
2309
+ priorField,
2310
+ newField
2311
+ };
2312
+ if (priorField === void 0 && newField !== void 0) added.push(entry);
2313
+ else if (priorField !== void 0 && newField === void 0) dropped.push(entry);
2314
+ else if (priorField !== void 0 && newField !== void 0) {
2315
+ if (isAlteration(priorField, newField, options.dataTypes)) altered.push(entry);
2316
+ }
2317
+ }
2318
+ }
2319
+ }
2320
+ const calls = [];
2321
+ appendCalls("added", added, options.codecHooks, calls, (e) => e.newField?.codecId);
2322
+ appendCalls("dropped", dropped, options.codecHooks, calls, (e) => e.priorField?.codecId);
2323
+ appendCalls("altered", altered, options.codecHooks, calls, (e) => e.newField?.codecId);
2324
+ return calls;
2325
+ }
2326
+ function renameKey(...parts) {
2327
+ return JSON.stringify(parts);
2328
+ }
2329
+ function underNewNames(tables, newNameOf) {
2330
+ if (tables === void 0) return void 0;
2331
+ return Object.fromEntries(Object.entries(tables).map(([tableName, table]) => [newNameOf(tableName) ?? tableName, table]));
2332
+ }
2333
+ function appendCalls(event, entries, codecHooks, calls, pickCodecId) {
2334
+ for (const entry of entries) {
2335
+ const codecId = pickCodecId(entry);
2336
+ if (codecId === void 0) continue;
2337
+ const hook = codecHooks.get(codecId);
2338
+ if (!hook?.onFieldEvent) continue;
2339
+ const ctx = buildContext(event, entry);
2340
+ const emitted = hook.onFieldEvent(event, ctx);
2341
+ for (const call of emitted) calls.push({
2342
+ call,
2343
+ namespaceId: entry.namespaceId,
2344
+ tableName: entry.tableName,
2345
+ columnName: entry.fieldName
2346
+ });
2347
+ }
2348
+ }
2349
+ /**
2350
+ * The context's prior/new sides are scoped to the event:
2351
+ *
2352
+ * - `'added'` — only `newTable` / `newField` populated.
2353
+ * - `'dropped'` — only `priorTable` / `priorField` populated.
2354
+ * - `'altered'` — both sides populated.
2355
+ */
2356
+ function buildContext(event, entry) {
2357
+ const base = {
2358
+ namespaceId: entry.namespaceId,
2359
+ tableName: entry.tableName,
2360
+ fieldName: entry.fieldName
2361
+ };
2362
+ if (event === "added") return {
2363
+ ...base,
2364
+ ...entry.newTable !== void 0 ? { newTable: entry.newTable } : {},
2365
+ ...entry.newField !== void 0 ? { newField: entry.newField } : {}
2366
+ };
2367
+ if (event === "dropped") return {
2368
+ ...base,
2369
+ ...entry.priorTable !== void 0 ? { priorTable: entry.priorTable } : {},
2370
+ ...entry.priorField !== void 0 ? { priorField: entry.priorField } : {}
2371
+ };
2372
+ return {
2373
+ ...base,
2374
+ ...entry.priorTable !== void 0 ? { priorTable: entry.priorTable } : {},
2375
+ ...entry.newTable !== void 0 ? { newTable: entry.newTable } : {},
2376
+ ...entry.priorField !== void 0 ? { priorField: entry.priorField } : {},
2377
+ ...entry.newField !== void 0 ? { newField: entry.newField } : {}
2378
+ };
2379
+ }
2380
+ /**
2381
+ * `'altered'` predicate. Returns `false` whenever `codecId` differs —
2382
+ * any codec change suppresses the `altered` event entirely, including
2383
+ * cases where another property also differs in the same diff. Codec
2384
+ * rotation is a v1 non-goal; avoiding the mixed event keeps the
2385
+ * migration semantics for codec changes explicit rather than smuggling
2386
+ * them through as `altered`.
2387
+ *
2388
+ * For non-`codecId` diffs, returns `true` iff any other column property
2389
+ * differs.
2390
+ */
2391
+ function isAlteration(prior, current, dataTypes) {
2392
+ if (prior.codecId !== current.codecId) return false;
2393
+ return !sameStorageColumn(prior, current, dataTypes);
2394
+ }
2395
+ function sameStorageColumn(a, b, dataTypes) {
2396
+ if (a === b) return true;
2397
+ if (!sameSqlColumnType(a, b, dataTypes)) return false;
2398
+ if (a.nullable !== b.nullable) return false;
2399
+ if (canonicalizeJson(codecOwnParams(a, dataTypes)) !== canonicalizeJson(codecOwnParams(b, dataTypes))) return false;
2400
+ if (!sameJson(a.default, b.default)) return false;
2401
+ return true;
2402
+ }
2403
+ /** The parameters a column's codec keeps for itself: every key its data type does not declare. */
2404
+ function codecOwnParams(column, dataTypes) {
2405
+ const declared = new Set(objectSchemaKeys(dataTypes.get(column.dataType)?.params) ?? []);
2406
+ return Object.fromEntries(Object.entries(column.typeParams ?? {}).filter(([key]) => !declared.has(key)));
2407
+ }
2408
+ function sameJson(a, b) {
2409
+ if (a === b) return true;
2410
+ if (a === void 0 || b === void 0) return false;
2411
+ return JSON.stringify(a) === JSON.stringify(b);
2412
+ }
2413
+ function unionSorted(a, b) {
2414
+ const set = /* @__PURE__ */ new Set();
2415
+ for (const name of a) set.add(name);
2416
+ for (const name of b) set.add(name);
2417
+ return [...set].sort((x, y) => x < y ? -1 : x > y ? 1 : 0);
2418
+ }
2111
2419
  /**
2112
2420
  * The field of `contract` that stores `column` of `table`, or undefined when no field stores it or
2113
2421
  * tables of two storage namespaces share the table's name.
@@ -2178,6 +2486,10 @@ function modelsStoring(contract, namespaceId, table) {
2178
2486
  }));
2179
2487
  }
2180
2488
  function subjectOf(target, context) {
2489
+ if (target.enumValue !== void 0) return {
2490
+ kind: "enumValue",
2491
+ ...target.enumValue
2492
+ };
2181
2493
  const storage = {
2182
2494
  kind: "storage",
2183
2495
  name: target.storageName
@@ -2252,7 +2564,8 @@ function subjectsOfCalls(calls, context) {
2252
2564
  subject,
2253
2565
  loss: target.loss,
2254
2566
  label: call.label,
2255
- ...ifDefined("keepDataAdvice", keepTableAdvice(target, subject, context))
2567
+ ...ifDefined("keepDataAdvice", keepTableAdvice(target, subject, context)),
2568
+ ...ifDefined("keepDataEdit", target.keepDataEdit)
2256
2569
  });
2257
2570
  }
2258
2571
  for (const target of call.accessChanges) accessChanges.push({
@@ -2352,6 +2665,7 @@ function plannerFailure(conflicts) {
2352
2665
  ...conflict.why ? { why: conflict.why } : {},
2353
2666
  ...conflict.location ? { location: Object.freeze({ ...conflict.location }) } : {},
2354
2667
  ...conflict.refusedStatement ? { refusedStatement: conflict.refusedStatement } : {},
2668
+ ...conflict.leaveOut ? { leaveOut: Object.freeze([...conflict.leaveOut]) } : {},
2355
2669
  ...conflict.refusedOperationClass ? { refusedOperationClass: conflict.refusedOperationClass } : {},
2356
2670
  ...conflict.meta ? { meta: cloneRecord(conflict.meta) } : {}
2357
2671
  })))
@@ -2474,6 +2788,50 @@ async function rowsWithoutValueFailure(input) {
2474
2788
  };
2475
2789
  }
2476
2790
  }
2791
+ const RowsHoldingValueMeta = type({ rowsHoldingValue: {
2792
+ enumValue: {
2793
+ "namespaceId?": "string",
2794
+ enum: "string",
2795
+ member: "string"
2796
+ },
2797
+ value: "string | number",
2798
+ "codecRef?": {
2799
+ codecId: "string",
2800
+ "typeParams?": "unknown"
2801
+ }
2802
+ } });
2803
+ /**
2804
+ * The meta of the check before a stored enum value is removed from a column: it fails while rows
2805
+ * hold the value in a column that takes no NULL, and the runner then counts those rows.
2806
+ */
2807
+ function rowsHoldingValueMeta(mark) {
2808
+ return { rowsHoldingValue: mark };
2809
+ }
2810
+ /** The enum value a check guards, as {@link rowsHoldingValueMeta} marks it. */
2811
+ function rowsHoldingValueOf(step) {
2812
+ const meta = RowsHoldingValueMeta(step.meta ?? {});
2813
+ if (meta instanceof type.errors) return void 0;
2814
+ const { enumValue, value, codecRef } = meta.rowsHoldingValue;
2815
+ return {
2816
+ enumValue: {
2817
+ ...enumValue.namespaceId === void 0 ? {} : { namespaceId: asNamespaceId(enumValue.namespaceId) },
2818
+ enum: enumValue.enum,
2819
+ member: enumValue.member
2820
+ },
2821
+ value,
2822
+ ...codecRef === void 0 ? {} : { codecRef: codecRefOf(codecRef) }
2823
+ };
2824
+ }
2825
+ function codecRefOf(codecRef) {
2826
+ return codecRef.typeParams === void 0 ? { codecId: codecRef.codecId } : {
2827
+ codecId: codecRef.codecId,
2828
+ typeParams: blindCast(codecRef.typeParams)
2829
+ };
2830
+ }
2831
+ /** What a runner failure says of the rows that hold a stored value a column cannot lose: the value as the database stores it. */
2832
+ function rowsHoldingValueSummary(table, column, storedValue, rows) {
2833
+ return `${`${rows} ${rows === 1 ? "row" : "rows"} of "${table}"`} ${rows === 1 ? "holds" : "hold"} ${JSON.stringify(storedValue)} in column "${column}", which takes no NULL, so the value cannot be removed.`;
2834
+ }
2477
2835
  /** Keywords that end an expression and continue the statement around it. */
2478
2836
  const STATEMENT_KEYWORDS = /* @__PURE__ */ new Set([
2479
2837
  "FROM",
@@ -2786,6 +3144,6 @@ function columnCodecRef(codecId, column) {
2786
3144
  }
2787
3145
  var control_default = new SqlFamilyDescriptor();
2788
3146
  //#endregion
2789
- export { unmatchedColumnRename as $, planFieldEventOperations as A, resolveTableRenameAgainst as B, fieldStatementColumns as C, partitionCallsByControlPolicy as D, otherSpaceDeclaring as E, readReportedDefault as F, slotSource as G, rowsWithoutValueSummary as H, requiredColumnMeta as I, sqlTypeLookupsOf as J, sqlDefaultFunctions as K, requiredColumnOf as L, plannerFailure as M, plannerSuccess as N, partitionIssuesByControlPolicy as O, readColumnDefault as P, unknownCallNames as Q, requiredColumnsOfMigration as R, fieldRenameStorageEffect as S, modelRenameStorageEffect as T, runnerFailure as U, rowsWithoutValueFailure as V, runnerSuccess as W, subjectsOfCalls as X, storageNameOfOperation as Y, unconvertedFieldOf as Z, createSqlTextSources as _, appliedStatementsInOrder as a, fieldEventStorage as b, checkSqlExpressionText as c, contractNamespaceToSchemaIR as d, unmatchedTableRename as et, contractToSchemaIR as f, createMigrationPlan as g, convertedColumnOf as h, TABLE_NAME_CASE_CHANGED_CODE as i, planStatements as j, planFieldEventCalls as k, columnTakesTemporaryValue as l, convertedColumnMeta as m, INIT_ADDITIVE_POLICY as n, assembleAuthoringContributions$1 as o, control_default as p, sqlOfSlot as q, NO_RENAMED_ORIGINS as r, backfillOfDefaultedField as s, COLUMN_RENAME_UNMATCHED_CODE as t, contractColumns as u, detectDestructiveChanges as v, isUnfilledSlot as w, fieldOfColumn as x, detectTableNameCaseChanges as y, resolveColumnRenameAgainst as z };
3147
+ export { sqlTypeLookupsOf as $, planEnumValueRewrites as A, requiredColumnsOfMigration as B, fieldRenameStorageEffect as C, otherSpaceDeclaring as D, modelRenameStorageEffect as E, plannerSuccess as F, rowsHoldingValueSummary as G, resolveTableRenameAgainst as H, readColumnDefault as I, runnerFailure as J, rowsWithoutValueFailure as K, readReportedDefault as L, planFieldEventOperations as M, planStatements as N, partitionCallsByControlPolicy as O, plannerFailure as P, sqlOfSlot as Q, requiredColumnMeta as R, fieldOfColumn as S, isUnfilledSlot as T, rowsHoldingValueMeta as U, resolveColumnRenameAgainst as V, rowsHoldingValueOf as W, slotSource as X, runnerSuccess as Y, sqlDefaultFunctions as Z, createSqlTextSources as _, appliedStatementsInOrder as a, unmatchedTableRename as at, encodeStoredValue as b, checkSqlExpressionText as c, contractNamespaceToSchemaIR as d, storageNameOfOperation as et, contractToSchemaIR as f, createMigrationPlan as g, convertedColumnOf as h, TABLE_NAME_CASE_CHANGED_CODE as i, unmatchedColumnRename as it, planFieldEventCalls as j, partitionIssuesByControlPolicy as k, columnTakesTemporaryValue as l, convertedColumnMeta as m, INIT_ADDITIVE_POLICY as n, unconvertedFieldOf as nt, assembleAuthoringContributions$1 as o, withEnumValueOperations as ot, control_default as p, rowsWithoutValueSummary as q, NO_RENAMED_ORIGINS as r, unknownCallNames as rt, backfillOfDefaultedField as s, COLUMN_RENAME_UNMATCHED_CODE as t, subjectsOfCalls as tt, contractColumns as u, detectDestructiveChanges as v, fieldStatementColumns as w, fieldEventStorage as x, detectTableNameCaseChanges as y, requiredColumnOf as z };
2790
3148
 
2791
- //# sourceMappingURL=control-DQzNuCAM.mjs.map
3149
+ //# sourceMappingURL=control-D7N_L18S.mjs.map