@prisma-next/family-sql 0.12.0-dev.6 → 0.12.0-dev.61
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/{authoring-type-constructors-F4JpCJl7.mjs → authoring-type-constructors-D4lQ-qpj.mjs} +1 -1
- package/dist/{authoring-type-constructors-F4JpCJl7.mjs.map → authoring-type-constructors-D4lQ-qpj.mjs.map} +1 -1
- package/dist/control-adapter-76pzcIFV.d.mts +172 -0
- package/dist/control-adapter-76pzcIFV.d.mts.map +1 -0
- package/dist/control-adapter.d.mts +2 -109
- package/dist/control.d.mts +118 -4
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +305 -44
- package/dist/control.mjs.map +1 -1
- package/dist/ir.d.mts +4 -5
- package/dist/ir.d.mts.map +1 -1
- package/dist/ir.mjs +1 -1
- package/dist/migration.d.mts +1 -1
- package/dist/migration.d.mts.map +1 -1
- package/dist/pack.mjs +1 -1
- package/dist/runtime.d.mts +4 -2
- package/dist/runtime.d.mts.map +1 -1
- package/dist/runtime.mjs +4 -2
- package/dist/runtime.mjs.map +1 -1
- package/dist/schema-verify.d.mts +2 -1
- package/dist/schema-verify.d.mts.map +1 -1
- package/dist/schema-verify.mjs +1 -1
- package/dist/{sql-contract-serializer-8axtK4lg.mjs → sql-contract-serializer-CY7qnms7.mjs} +18 -36
- package/dist/sql-contract-serializer-CY7qnms7.mjs.map +1 -0
- package/dist/{timestamp-now-generator-BkjCQIde.mjs → timestamp-now-generator-CloimujU.mjs} +1 -1
- package/dist/{timestamp-now-generator-BkjCQIde.mjs.map → timestamp-now-generator-CloimujU.mjs.map} +1 -1
- package/dist/{types-CeeCStqw.d.mts → types-CRpx_fk5.d.mts} +69 -15
- package/dist/types-CRpx_fk5.d.mts.map +1 -0
- package/dist/{verify-Crewz6hG.mjs → verify-C-G0obRm.mjs} +1 -1
- package/dist/{verify-Crewz6hG.mjs.map → verify-C-G0obRm.mjs.map} +1 -1
- package/dist/{verify-sql-schema-CN7pPoTC.d.mts → verify-sql-schema-DcMaT5Zj.d.mts} +1 -1
- package/dist/{verify-sql-schema-CN7pPoTC.d.mts.map → verify-sql-schema-DcMaT5Zj.d.mts.map} +1 -1
- package/dist/{verify-sql-schema-CYLsGCFO.mjs → verify-sql-schema-d3P9NA_8.mjs} +400 -317
- package/dist/verify-sql-schema-d3P9NA_8.mjs.map +1 -0
- package/dist/verify.mjs +1 -1
- package/package.json +23 -23
- package/src/core/control-adapter.ts +105 -7
- package/src/core/control-instance.ts +260 -66
- package/src/core/default-namespace.ts +9 -0
- package/src/core/ir/sql-contract-serializer-base.ts +72 -56
- package/src/core/migrations/contract-to-schema-ir.ts +13 -9
- package/src/core/migrations/control-policy.ts +322 -0
- package/src/core/migrations/field-event-planner.ts +2 -2
- package/src/core/migrations/plan-helpers.ts +16 -0
- package/src/core/migrations/types.ts +16 -6
- package/src/core/schema-verify/control-verify-emit.ts +46 -0
- package/src/core/schema-verify/verifier-disposition.ts +53 -0
- package/src/core/schema-verify/verify-helpers.ts +151 -110
- package/src/core/schema-verify/verify-sql-schema.ts +281 -178
- package/src/exports/control.ts +6 -0
- package/src/exports/runtime.ts +7 -0
- package/dist/control-adapter.d.mts.map +0 -1
- package/dist/sql-contract-serializer-8axtK4lg.mjs.map +0 -1
- package/dist/types-CeeCStqw.d.mts.map +0 -1
- package/dist/verify-sql-schema-CYLsGCFO.mjs.map +0 -1
package/dist/control.mjs
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
|
-
import { n as sqlFamilyAuthoringFieldPresets, t as sqlFamilyAuthoringTypes } from "./authoring-type-constructors-
|
|
2
|
-
import { t as SqlContractSerializer } from "./sql-contract-serializer-
|
|
3
|
-
import { a as extractCodecControlHooks, t as verifySqlSchema } from "./verify-sql-schema-
|
|
4
|
-
import { t as collectSupportedCodecTypeIds } from "./verify-
|
|
5
|
-
import { n as temporalAuthoringPresets, r as timestampNowControlDescriptor } from "./timestamp-now-generator-
|
|
1
|
+
import { n as sqlFamilyAuthoringFieldPresets, t as sqlFamilyAuthoringTypes } from "./authoring-type-constructors-D4lQ-qpj.mjs";
|
|
2
|
+
import { t as SqlContractSerializer } from "./sql-contract-serializer-CY7qnms7.mjs";
|
|
3
|
+
import { a as extractCodecControlHooks, t as verifySqlSchema } from "./verify-sql-schema-d3P9NA_8.mjs";
|
|
4
|
+
import { t as collectSupportedCodecTypeIds } from "./verify-C-G0obRm.mjs";
|
|
5
|
+
import { n as temporalAuthoringPresets, r as timestampNowControlDescriptor } from "./timestamp-now-generator-CloimujU.mjs";
|
|
6
6
|
import { sqlEmission } from "@prisma-next/sql-contract-emitter";
|
|
7
7
|
import { APP_SPACE_ID, SchemaTreeNode, VERIFY_CODE_HASH_MISMATCH, VERIFY_CODE_MARKER_MISSING, VERIFY_CODE_TARGET_MISMATCH, assembleAuthoringContributions } from "@prisma-next/framework-components/control";
|
|
8
8
|
import { assertDescriptorSelfConsistency } from "@prisma-next/migration-tools/spaces";
|
|
9
9
|
import { sqlContractCanonicalizationHooks } from "@prisma-next/sql-contract/canonicalization-hooks";
|
|
10
|
-
import { ensureSchemaStatement, ensureTableStatement, writeContractMarker } from "@prisma-next/sql-runtime";
|
|
11
10
|
import { defaultIndexName } from "@prisma-next/sql-schema-ir/naming";
|
|
12
11
|
import { ifDefined } from "@prisma-next/utils/defined";
|
|
13
12
|
import { UNBOUND_NAMESPACE_ID } from "@prisma-next/framework-components/ir";
|
|
14
13
|
import { StorageTable, isPostgresEnumStorageEntry, isStorageTypeInstance, toStorageTypeInstance } from "@prisma-next/sql-contract/types";
|
|
14
|
+
import { blindCast } from "@prisma-next/utils/casts";
|
|
15
15
|
import { UNSPECIFIED_PSL_NAMESPACE_ID } from "@prisma-next/framework-components/psl-ast";
|
|
16
|
+
import { effectiveControlPolicy } from "@prisma-next/contract/types";
|
|
16
17
|
import { notOk, ok } from "@prisma-next/utils/result";
|
|
17
18
|
//#region src/core/operation-preview.ts
|
|
18
19
|
function isDdlStatement(sqlStatement) {
|
|
@@ -1023,7 +1024,7 @@ function extractCodecTypeIdsFromContract(contract) {
|
|
|
1023
1024
|
if (typeof contract === "object" && contract !== null && "storage" in contract && typeof contract.storage === "object" && contract.storage !== null && "namespaces" in contract.storage && typeof contract.storage.namespaces === "object" && contract.storage.namespaces !== null) {
|
|
1024
1025
|
const namespaces = contract.storage.namespaces;
|
|
1025
1026
|
for (const ns of Object.values(namespaces)) {
|
|
1026
|
-
const tbls = ns.
|
|
1027
|
+
const tbls = ns.entries.table;
|
|
1027
1028
|
if (typeof tbls !== "object" || tbls === null) continue;
|
|
1028
1029
|
for (const table of Object.values(tbls)) if (typeof table === "object" && table !== null && "columns" in table && typeof table.columns === "object" && table.columns !== null) {
|
|
1029
1030
|
const columns = table.columns;
|
|
@@ -1057,9 +1058,6 @@ function createVerifyResult(options) {
|
|
|
1057
1058
|
if (options.codecCoverageSkipped) result.codecCoverageSkipped = options.codecCoverageSkipped;
|
|
1058
1059
|
return result;
|
|
1059
1060
|
}
|
|
1060
|
-
function isSqlControlAdapter(value) {
|
|
1061
|
-
return typeof value === "object" && value !== null && "introspect" in value && typeof value.introspect === "function" && "readMarker" in value && typeof value.readMarker === "function" && "readAllMarkers" in value && typeof value.readAllMarkers === "function" && "lower" in value && typeof value.lower === "function";
|
|
1062
|
-
}
|
|
1063
1061
|
function buildSqlTypeMetadataRegistry(options) {
|
|
1064
1062
|
const { target, adapter, extensionPacks: extensions } = options;
|
|
1065
1063
|
const registry = /* @__PURE__ */ new Map();
|
|
@@ -1081,6 +1079,81 @@ function buildSqlTypeMetadataRegistry(options) {
|
|
|
1081
1079
|
}
|
|
1082
1080
|
return registry;
|
|
1083
1081
|
}
|
|
1082
|
+
/**
|
|
1083
|
+
* Builds a map from each extension id to the set of extension ids it
|
|
1084
|
+
* transitively depends on. Uses the same declared-dependency data that
|
|
1085
|
+
* `buildExtensionLoadOrder` in control-stack uses.
|
|
1086
|
+
*/
|
|
1087
|
+
function buildTransitiveDependsOnMap(extensions) {
|
|
1088
|
+
const directDeps = /* @__PURE__ */ new Map();
|
|
1089
|
+
for (const ext of extensions) {
|
|
1090
|
+
const packs = ext.contractSpace?.contractJson?.extensionPacks;
|
|
1091
|
+
const deps = packs !== null && typeof packs === "object" ? Object.keys(packs) : [];
|
|
1092
|
+
directDeps.set(ext.id, deps);
|
|
1093
|
+
}
|
|
1094
|
+
const result = /* @__PURE__ */ new Map();
|
|
1095
|
+
const resolve = (id, visiting) => {
|
|
1096
|
+
const cached = result.get(id);
|
|
1097
|
+
if (cached !== void 0) return cached;
|
|
1098
|
+
const set = /* @__PURE__ */ new Set();
|
|
1099
|
+
result.set(id, set);
|
|
1100
|
+
for (const depId of directDeps.get(id) ?? []) {
|
|
1101
|
+
set.add(depId);
|
|
1102
|
+
if (!visiting.has(depId)) {
|
|
1103
|
+
visiting.add(depId);
|
|
1104
|
+
for (const transitive of resolve(depId, visiting)) set.add(transitive);
|
|
1105
|
+
visiting.delete(depId);
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
return set;
|
|
1109
|
+
};
|
|
1110
|
+
for (const ext of extensions) resolve(ext.id, new Set([ext.id]));
|
|
1111
|
+
return result;
|
|
1112
|
+
}
|
|
1113
|
+
/**
|
|
1114
|
+
* Asserts that no cross-space FK in any extension points against the
|
|
1115
|
+
* dependency direction.
|
|
1116
|
+
*
|
|
1117
|
+
* A cross-space FK (target.spaceId present) from extension A pointing at
|
|
1118
|
+
* space B is a violation when B depends on A (directly or transitively),
|
|
1119
|
+
* because that means A is pointing "upward" against the dependency arrows
|
|
1120
|
+
* established by the extension load order.
|
|
1121
|
+
*
|
|
1122
|
+
* Throws with a diagnostic naming the violating extension (source), the
|
|
1123
|
+
* target space, and the direction violation.
|
|
1124
|
+
*/
|
|
1125
|
+
function isObjectRecord(v) {
|
|
1126
|
+
return typeof v === "object" && v !== null;
|
|
1127
|
+
}
|
|
1128
|
+
function assertNoCrossSpaceFkReverseReferences(extensions) {
|
|
1129
|
+
const dependsOnMap = buildTransitiveDependsOnMap(extensions);
|
|
1130
|
+
for (const ext of extensions) {
|
|
1131
|
+
const namespaces = ext.contractSpace?.contractJson?.storage?.namespaces;
|
|
1132
|
+
if (!isObjectRecord(namespaces)) continue;
|
|
1133
|
+
for (const ns of Object.values(namespaces)) {
|
|
1134
|
+
if (!isObjectRecord(ns)) continue;
|
|
1135
|
+
const entries = ns["entries"];
|
|
1136
|
+
if (!isObjectRecord(entries)) continue;
|
|
1137
|
+
for (const slot of Object.values(entries)) {
|
|
1138
|
+
if (!isObjectRecord(slot)) continue;
|
|
1139
|
+
for (const table of Object.values(slot)) {
|
|
1140
|
+
if (!isObjectRecord(table)) continue;
|
|
1141
|
+
const foreignKeys = table["foreignKeys"];
|
|
1142
|
+
if (!Array.isArray(foreignKeys)) continue;
|
|
1143
|
+
for (const fk of foreignKeys) {
|
|
1144
|
+
if (!isObjectRecord(fk)) continue;
|
|
1145
|
+
const target = fk["target"];
|
|
1146
|
+
if (!isObjectRecord(target)) continue;
|
|
1147
|
+
if (target["spaceId"] === void 0) continue;
|
|
1148
|
+
const targetSpaceId = target["spaceId"];
|
|
1149
|
+
if (typeof targetSpaceId !== "string") continue;
|
|
1150
|
+
if (dependsOnMap.get(targetSpaceId)?.has(ext.id)) throw new Error(`Cross-space FK reverse-reference detected: extension "${ext.id}" has a cross-space FK targeting space "${targetSpaceId}", but "${targetSpaceId}" depends on "${ext.id}". Cross-space FKs must follow the dependency direction (a space can only reference spaces it depends on, not spaces that depend on it).`);
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1084
1157
|
function createSqlFamilyInstance(stack) {
|
|
1085
1158
|
if (!stack.adapter) throw new Error("SQL family requires an adapter descriptor in ControlStack");
|
|
1086
1159
|
const target = stack.target;
|
|
@@ -1097,17 +1170,14 @@ function createSqlFamilyInstance(stack) {
|
|
|
1097
1170
|
...sqlContractCanonicalizationHooks
|
|
1098
1171
|
});
|
|
1099
1172
|
}
|
|
1173
|
+
assertNoCrossSpaceFkReverseReferences(extensions);
|
|
1100
1174
|
const { codecTypeImports, extensionIds } = stack;
|
|
1101
1175
|
const typeMetadataRegistry = buildSqlTypeMetadataRegistry({
|
|
1102
1176
|
target,
|
|
1103
1177
|
adapter,
|
|
1104
1178
|
extensionPacks: extensions
|
|
1105
1179
|
});
|
|
1106
|
-
const getControlAdapter = () =>
|
|
1107
|
-
const controlAdapter = adapter.create(stack);
|
|
1108
|
-
if (!isSqlControlAdapter(controlAdapter)) throw new Error("Adapter does not implement SqlControlAdapter (missing introspect, readMarker, or readAllMarkers)");
|
|
1109
|
-
return controlAdapter;
|
|
1110
|
-
};
|
|
1180
|
+
const getControlAdapter = () => adapter.create(stack);
|
|
1111
1181
|
const targetSerializer = target.contractSerializer;
|
|
1112
1182
|
const deserializeWithTargetSerializer = (contractJson) => {
|
|
1113
1183
|
return (targetSerializer ?? new SqlContractSerializer()).deserializeContract(contractJson);
|
|
@@ -1233,21 +1303,21 @@ function createSqlFamilyInstance(stack) {
|
|
|
1233
1303
|
const contractStorageHash = contract.storage.storageHash;
|
|
1234
1304
|
const contractProfileHash = "profileHash" in contract && typeof contract.profileHash === "string" ? contract.profileHash : contractStorageHash;
|
|
1235
1305
|
const contractTarget = contract.target;
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
const
|
|
1306
|
+
const controlAdapter = getControlAdapter();
|
|
1307
|
+
const lowererContext = { contract };
|
|
1308
|
+
for (const query of controlAdapter.bootstrapSignMarkerQueries()) {
|
|
1309
|
+
const lowered = controlAdapter.lower(query, lowererContext);
|
|
1310
|
+
await driver.query(lowered.sql, lowered.params);
|
|
1311
|
+
}
|
|
1312
|
+
const existingMarker = await controlAdapter.readMarker(driver, APP_SPACE_ID);
|
|
1239
1313
|
let markerCreated = false;
|
|
1240
1314
|
let markerUpdated = false;
|
|
1241
1315
|
let previousHashes;
|
|
1242
1316
|
if (!existingMarker) {
|
|
1243
|
-
|
|
1244
|
-
space: APP_SPACE_ID,
|
|
1317
|
+
await controlAdapter.insertMarker(driver, APP_SPACE_ID, {
|
|
1245
1318
|
storageHash: contractStorageHash,
|
|
1246
|
-
profileHash: contractProfileHash
|
|
1247
|
-
contractJson: contractInput,
|
|
1248
|
-
canonicalVersion: 1
|
|
1319
|
+
profileHash: contractProfileHash
|
|
1249
1320
|
});
|
|
1250
|
-
await driver.query(write.insert.sql, write.insert.params);
|
|
1251
1321
|
markerCreated = true;
|
|
1252
1322
|
} else {
|
|
1253
1323
|
const existingStorageHash = existingMarker.storageHash;
|
|
@@ -1257,14 +1327,10 @@ function createSqlFamilyInstance(stack) {
|
|
|
1257
1327
|
storageHash: existingStorageHash,
|
|
1258
1328
|
profileHash: existingProfileHash
|
|
1259
1329
|
};
|
|
1260
|
-
|
|
1261
|
-
space: APP_SPACE_ID,
|
|
1330
|
+
if (!await controlAdapter.updateMarker(driver, APP_SPACE_ID, existingStorageHash, {
|
|
1262
1331
|
storageHash: contractStorageHash,
|
|
1263
|
-
profileHash: contractProfileHash
|
|
1264
|
-
|
|
1265
|
-
canonicalVersion: existingMarker.canonicalVersion ?? 1
|
|
1266
|
-
});
|
|
1267
|
-
await driver.query(write.update.sql, write.update.params);
|
|
1332
|
+
profileHash: contractProfileHash
|
|
1333
|
+
})) throw new Error("CAS conflict: marker was modified by another process during sign");
|
|
1268
1334
|
markerUpdated = true;
|
|
1269
1335
|
}
|
|
1270
1336
|
}
|
|
@@ -1302,6 +1368,18 @@ function createSqlFamilyInstance(stack) {
|
|
|
1302
1368
|
async readAllMarkers(options) {
|
|
1303
1369
|
return getControlAdapter().readAllMarkers(options.driver);
|
|
1304
1370
|
},
|
|
1371
|
+
async readLedger(options) {
|
|
1372
|
+
return getControlAdapter().readLedger(options.driver, options.space);
|
|
1373
|
+
},
|
|
1374
|
+
async initMarker(options) {
|
|
1375
|
+
return getControlAdapter().initMarker(options.driver, options.space, options.destination);
|
|
1376
|
+
},
|
|
1377
|
+
async updateMarker(options) {
|
|
1378
|
+
return getControlAdapter().updateMarker(options.driver, options.space, options.expectedFrom, options.destination);
|
|
1379
|
+
},
|
|
1380
|
+
async writeLedgerEntry(options) {
|
|
1381
|
+
return getControlAdapter().writeLedgerEntry(options.driver, options.space, options.entry);
|
|
1382
|
+
},
|
|
1305
1383
|
async introspect(options) {
|
|
1306
1384
|
return getControlAdapter().introspect(options.driver, options.contract);
|
|
1307
1385
|
},
|
|
@@ -1311,6 +1389,12 @@ function createSqlFamilyInstance(stack) {
|
|
|
1311
1389
|
lowerAst(ast, context) {
|
|
1312
1390
|
return getControlAdapter().lower(ast, context);
|
|
1313
1391
|
},
|
|
1392
|
+
bootstrapControlTableQueries() {
|
|
1393
|
+
return getControlAdapter().bootstrapControlTableQueries();
|
|
1394
|
+
},
|
|
1395
|
+
bootstrapSignMarkerQueries() {
|
|
1396
|
+
return getControlAdapter().bootstrapSignMarkerQueries();
|
|
1397
|
+
},
|
|
1314
1398
|
toOperationPreview(operations) {
|
|
1315
1399
|
return sqlOperationsToPreview(operations);
|
|
1316
1400
|
},
|
|
@@ -1498,7 +1582,7 @@ function convertTable(name, table, storageTypes, expandNativeType, renderDefault
|
|
|
1498
1582
|
name,
|
|
1499
1583
|
columns,
|
|
1500
1584
|
...ifDefined("primaryKey", table.primaryKey),
|
|
1501
|
-
foreignKeys: table.foreignKeys.map(convertForeignKey),
|
|
1585
|
+
foreignKeys: table.foreignKeys.filter((fk) => fk.constraint !== false).map(convertForeignKey),
|
|
1502
1586
|
uniques: table.uniques.map(convertUnique),
|
|
1503
1587
|
indexes: [...table.indexes.map(convertIndex), ...fkBackingIndexes]
|
|
1504
1588
|
};
|
|
@@ -1520,10 +1604,10 @@ function detectDestructiveChanges(from, to) {
|
|
|
1520
1604
|
for (const namespaceId of namespaceIds) {
|
|
1521
1605
|
const fromNs = from.namespaces[namespaceId];
|
|
1522
1606
|
const toNs = to.namespaces[namespaceId];
|
|
1523
|
-
const fromTables = fromNs?.
|
|
1607
|
+
const fromTables = fromNs?.entries.table;
|
|
1524
1608
|
if (!fromTables) continue;
|
|
1525
1609
|
for (const tableName of Object.keys(fromTables)) {
|
|
1526
|
-
const toTableRaw = toNs?.
|
|
1610
|
+
const toTableRaw = toNs?.entries.table[tableName];
|
|
1527
1611
|
if (!(toTableRaw instanceof StorageTable)) {
|
|
1528
1612
|
conflicts.push({
|
|
1529
1613
|
kind: "tableRemoved",
|
|
@@ -1563,13 +1647,13 @@ function contractToSchemaIR(contract, options) {
|
|
|
1563
1647
|
const storage = contract.storage;
|
|
1564
1648
|
const allTypes = { ...storage.types ?? {} };
|
|
1565
1649
|
for (const ns of Object.values(storage.namespaces)) {
|
|
1566
|
-
const nsEnums = ns.
|
|
1567
|
-
if (nsEnums) for (const [k, v] of Object.entries(nsEnums)) allTypes[k] = v;
|
|
1650
|
+
const nsEnums = ns.entries["type"];
|
|
1651
|
+
if (nsEnums) for (const [k, v] of Object.entries(nsEnums)) allTypes[k] = blindCast(v);
|
|
1568
1652
|
}
|
|
1569
1653
|
const storageTypes = allTypes;
|
|
1570
1654
|
const tables = {};
|
|
1571
|
-
for (const ns of Object.values(storage.namespaces)) for (const [tableName, tableDefRaw] of Object.entries(ns.
|
|
1572
|
-
if (!(tableDefRaw instanceof StorageTable)) throw new Error(`contractToSchemaIR: expected StorageTable at namespaces.${ns.id}.
|
|
1655
|
+
for (const ns of Object.values(storage.namespaces)) for (const [tableName, tableDefRaw] of Object.entries(ns.entries.table)) {
|
|
1656
|
+
if (!(tableDefRaw instanceof StorageTable)) throw new Error(`contractToSchemaIR: expected StorageTable at namespaces.${ns.id}.entries.table.${tableName}`);
|
|
1573
1657
|
const tableDef = tableDefRaw;
|
|
1574
1658
|
if (tables[tableName] !== void 0) throw new Error(`contractToSchemaIR: duplicate SQL table name "${tableName}" across namespaces (ambiguous for flat SqlSchemaIR.tables).`);
|
|
1575
1659
|
tables[tableName] = convertTable(tableName, tableDef, storageTypes, options.expandNativeType, options.renderDefault);
|
|
@@ -1604,7 +1688,7 @@ function deriveAnnotations(storage, annotationNamespace, resolveEnumStorageKey)
|
|
|
1604
1688
|
if (isStorageTypeInstance(typeInstance)) storageTypes[typeInstance.nativeType] = typeInstance;
|
|
1605
1689
|
}
|
|
1606
1690
|
for (const [namespaceId, ns] of Object.entries(storage.namespaces)) {
|
|
1607
|
-
const nsEnums = ns.
|
|
1691
|
+
const nsEnums = ns.entries["type"];
|
|
1608
1692
|
if (!nsEnums) continue;
|
|
1609
1693
|
for (const entry of Object.values(nsEnums)) {
|
|
1610
1694
|
if (!isPostgresEnumStorageEntry(entry)) continue;
|
|
@@ -1616,6 +1700,176 @@ function deriveAnnotations(storage, annotationNamespace, resolveEnumStorageKey)
|
|
|
1616
1700
|
return { [annotationNamespace]: { storageTypes } };
|
|
1617
1701
|
}
|
|
1618
1702
|
//#endregion
|
|
1703
|
+
//#region src/core/migrations/control-policy.ts
|
|
1704
|
+
/**
|
|
1705
|
+
* The control policy that governs a single call. The `external` default is an
|
|
1706
|
+
* un-overridable namespace floor: when the contract default is `external`, no
|
|
1707
|
+
* per-object `managed` override can escalate DDL above the floor, so the
|
|
1708
|
+
* policy is forced to `external` regardless of the node's own declaration.
|
|
1709
|
+
* Every other default defers to the node's effective control policy.
|
|
1710
|
+
*/
|
|
1711
|
+
function controlPolicyForCall(subject, defaultControlPolicy) {
|
|
1712
|
+
if (defaultControlPolicy === "external") return "external";
|
|
1713
|
+
return effectiveControlPolicy(subject?.explicitNodeControlPolicy, defaultControlPolicy);
|
|
1714
|
+
}
|
|
1715
|
+
/**
|
|
1716
|
+
* Whether a call is allowed to emit under a given control policy.
|
|
1717
|
+
*
|
|
1718
|
+
* - `managed` — full lifecycle, every op allowed.
|
|
1719
|
+
* - `tolerated` — create-if-absent only: allowed iff the call creates a whole
|
|
1720
|
+
* new top-level object (and its subject was positively resolved). Anything
|
|
1721
|
+
* that modifies an existing object, and anything whose subject could not be
|
|
1722
|
+
* resolved, is suppressed.
|
|
1723
|
+
* - `external` / `observed` — no DDL at all.
|
|
1724
|
+
*/
|
|
1725
|
+
function callAllowedUnderControlPolicy(policy, subject) {
|
|
1726
|
+
switch (policy) {
|
|
1727
|
+
case "managed": return true;
|
|
1728
|
+
case "tolerated": return subject?.createsNewObject === true;
|
|
1729
|
+
case "external":
|
|
1730
|
+
case "observed": return false;
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
function defaultSubjectLabel(factoryName, subject) {
|
|
1734
|
+
if (subject?.table) return `${factoryName}(${subject.table})`;
|
|
1735
|
+
if (subject?.typeName) return `${factoryName}(${subject.typeName})`;
|
|
1736
|
+
return factoryName;
|
|
1737
|
+
}
|
|
1738
|
+
function suppressionSummary(subjectLabel, subject, effectivePolicy) {
|
|
1739
|
+
const namespace = subject?.namespaceId ?? "unknown";
|
|
1740
|
+
const declared = subject?.explicitNodeControlPolicy;
|
|
1741
|
+
if (effectivePolicy === "external" && declared === "managed") return `control policy suppressed: ${subjectLabel} — namespace '${namespace}' has effective control 'external' but table declared 'managed'`;
|
|
1742
|
+
return `control policy suppressed: ${subjectLabel} — namespace '${namespace}' has effective control '${effectivePolicy}'${declared ? ` but table declared '${declared}'` : ""}`;
|
|
1743
|
+
}
|
|
1744
|
+
function buildSubjectSuppressionWarning(subject, effectivePolicy, factoryName, formatSubjectLabel) {
|
|
1745
|
+
return {
|
|
1746
|
+
kind: "controlPolicySuppressedCall",
|
|
1747
|
+
summary: suppressionSummary(formatSubjectLabel(factoryName, subject), subject, effectivePolicy),
|
|
1748
|
+
location: {
|
|
1749
|
+
...ifDefined("namespace", subject?.namespaceId),
|
|
1750
|
+
...ifDefined("table", subject?.table),
|
|
1751
|
+
...ifDefined("column", subject?.column),
|
|
1752
|
+
...ifDefined("type", subject?.typeName)
|
|
1753
|
+
},
|
|
1754
|
+
meta: {
|
|
1755
|
+
controlPolicy: effectivePolicy,
|
|
1756
|
+
factoryName,
|
|
1757
|
+
...ifDefined("declaredControlPolicy", subject?.explicitNodeControlPolicy)
|
|
1758
|
+
}
|
|
1759
|
+
};
|
|
1760
|
+
}
|
|
1761
|
+
function defaultModificationFactoryNameForSubject(subject) {
|
|
1762
|
+
if (subject.table) return "alterTable";
|
|
1763
|
+
if (subject.typeName) return "alterType";
|
|
1764
|
+
return "alterSchema";
|
|
1765
|
+
}
|
|
1766
|
+
/**
|
|
1767
|
+
* Partition the calls produced for a single set of subjects into those the
|
|
1768
|
+
* effective control policy permits (`kept`) and a list of
|
|
1769
|
+
* {@link SqlPlannerConflict} warnings describing the suppressed calls.
|
|
1770
|
+
*
|
|
1771
|
+
* **Prefer {@link partitionIssuesByControlPolicy}** for the schema-issue
|
|
1772
|
+
* pipeline: it filters subjects out of the planner's *input* so the planner
|
|
1773
|
+
* never has to reason about un-modeled state on `external`/`observed`
|
|
1774
|
+
* subjects. This call-level helper remains for paths that bypass the issue
|
|
1775
|
+
* pipeline — currently the codec-emitted field-event ops, which originate
|
|
1776
|
+
* from declared contract fields rather than from introspected schema state
|
|
1777
|
+
* and therefore cannot trip the diff engine.
|
|
1778
|
+
*/
|
|
1779
|
+
function partitionCallsByControlPolicy(options) {
|
|
1780
|
+
const defaultControlPolicy = options.contract.defaultControlPolicy;
|
|
1781
|
+
const formatSubjectLabel = options.formatSubjectLabel ?? defaultSubjectLabel;
|
|
1782
|
+
const kept = [];
|
|
1783
|
+
const warnings = [];
|
|
1784
|
+
for (const call of options.calls) {
|
|
1785
|
+
const subject = options.resolveControlPolicySubject(call);
|
|
1786
|
+
const policy = controlPolicyForCall(subject, defaultControlPolicy);
|
|
1787
|
+
if (callAllowedUnderControlPolicy(policy, subject)) kept.push(call);
|
|
1788
|
+
else {
|
|
1789
|
+
const factoryName = options.resolveFactoryName(call);
|
|
1790
|
+
warnings.push(buildSubjectSuppressionWarning(subject, policy, factoryName, formatSubjectLabel));
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
return Object.freeze({
|
|
1794
|
+
kept: Object.freeze(kept),
|
|
1795
|
+
warnings: Object.freeze(warnings)
|
|
1796
|
+
});
|
|
1797
|
+
}
|
|
1798
|
+
/**
|
|
1799
|
+
* Partition a list of schema-issue-shaped inputs by the effective control
|
|
1800
|
+
* policy of each issue's subject *before* the planner is invoked.
|
|
1801
|
+
*
|
|
1802
|
+
* `plannable` is the list of issues whose subject's effective policy permits
|
|
1803
|
+
* the planner to act on them (`managed`, or `tolerated` for whole-object
|
|
1804
|
+
* creation issues only). Issues for `external`/`observed` subjects, and
|
|
1805
|
+
* non-creation issues for `tolerated` subjects, are dropped from the planner's
|
|
1806
|
+
* input entirely — they never enter introspection-driven planning, never feed
|
|
1807
|
+
* the diff engine, and never produce DDL calls that would have to be
|
|
1808
|
+
* post-filtered. This sidesteps a class of failure where the diff engine
|
|
1809
|
+
* cannot reason about the live shape of a subject the user marked as
|
|
1810
|
+
* out-of-scope (`external`).
|
|
1811
|
+
*
|
|
1812
|
+
* `warnings` is one {@link SqlPlannerConflict} per suppressed subject (not per
|
|
1813
|
+
* suppressed issue). `factoryName` is inferred from the subject's issue mix:
|
|
1814
|
+
* if any of the subject's issues is whole-object creation, the warning takes
|
|
1815
|
+
* the corresponding creation factoryName (e.g. `createTable`,
|
|
1816
|
+
* `createEnumType`, `createSchema`); otherwise it falls back to
|
|
1817
|
+
* `defaultModificationFactoryName(subject)` — a synthetic label that names
|
|
1818
|
+
* the *kind* of mutation that would have run, since no concrete DDL call was
|
|
1819
|
+
* generated.
|
|
1820
|
+
*
|
|
1821
|
+
* Unresolved-subject issues (`resolveControlPolicySubject` returns
|
|
1822
|
+
* `undefined`) emit one warning each; they cannot be deduplicated because
|
|
1823
|
+
* they carry no subject coordinate.
|
|
1824
|
+
*/
|
|
1825
|
+
function partitionIssuesByControlPolicy(options) {
|
|
1826
|
+
const defaultControlPolicy = options.contract.defaultControlPolicy;
|
|
1827
|
+
const formatSubjectLabel = options.formatSubjectLabel ?? defaultSubjectLabel;
|
|
1828
|
+
const inferModificationFactoryName = options.defaultModificationFactoryName ?? defaultModificationFactoryNameForSubject;
|
|
1829
|
+
const plannable = [];
|
|
1830
|
+
const suppressedSubjects = /* @__PURE__ */ new Map();
|
|
1831
|
+
const unresolvedSuppressions = [];
|
|
1832
|
+
for (const issue of options.issues) {
|
|
1833
|
+
const subject = options.resolveControlPolicySubject(issue);
|
|
1834
|
+
const policy = controlPolicyForCall(subject, defaultControlPolicy);
|
|
1835
|
+
const creationFactoryName = options.resolveCreationFactoryName(issue);
|
|
1836
|
+
if (policy === "managed") {
|
|
1837
|
+
plannable.push(issue);
|
|
1838
|
+
continue;
|
|
1839
|
+
}
|
|
1840
|
+
if (policy === "tolerated" && subject !== void 0 && creationFactoryName !== void 0 && subject.createsNewObject) {
|
|
1841
|
+
plannable.push(issue);
|
|
1842
|
+
continue;
|
|
1843
|
+
}
|
|
1844
|
+
if (subject === void 0) {
|
|
1845
|
+
const factoryName = creationFactoryName ?? "unknown";
|
|
1846
|
+
unresolvedSuppressions.push(buildSubjectSuppressionWarning(void 0, policy, factoryName, formatSubjectLabel));
|
|
1847
|
+
continue;
|
|
1848
|
+
}
|
|
1849
|
+
const key = subjectKey(subject);
|
|
1850
|
+
const existing = suppressedSubjects.get(key);
|
|
1851
|
+
if (existing) {
|
|
1852
|
+
if (existing.creationFactoryName === void 0 && creationFactoryName !== void 0) existing.creationFactoryName = creationFactoryName;
|
|
1853
|
+
} else suppressedSubjects.set(key, {
|
|
1854
|
+
subject,
|
|
1855
|
+
policy,
|
|
1856
|
+
...ifDefined("creationFactoryName", creationFactoryName)
|
|
1857
|
+
});
|
|
1858
|
+
}
|
|
1859
|
+
const warnings = [...unresolvedSuppressions];
|
|
1860
|
+
for (const entry of suppressedSubjects.values()) {
|
|
1861
|
+
const factoryName = entry.creationFactoryName ?? inferModificationFactoryName(entry.subject);
|
|
1862
|
+
warnings.push(buildSubjectSuppressionWarning(entry.subject, entry.policy, factoryName, formatSubjectLabel));
|
|
1863
|
+
}
|
|
1864
|
+
return Object.freeze({
|
|
1865
|
+
plannable: Object.freeze(plannable),
|
|
1866
|
+
warnings: Object.freeze(warnings)
|
|
1867
|
+
});
|
|
1868
|
+
}
|
|
1869
|
+
function subjectKey(subject) {
|
|
1870
|
+
return `${subject.namespaceId}\u0000${subject.table ?? ""}\u0000${subject.typeName ?? ""}`;
|
|
1871
|
+
}
|
|
1872
|
+
//#endregion
|
|
1619
1873
|
//#region src/core/migrations/field-event-planner.ts
|
|
1620
1874
|
function planFieldEventOperations(options) {
|
|
1621
1875
|
const priorContract = options.priorContract;
|
|
@@ -1627,8 +1881,8 @@ function planFieldEventOperations(options) {
|
|
|
1627
1881
|
for (const namespaceId of namespaceIds) {
|
|
1628
1882
|
const priorNs = priorContract?.storage.namespaces[namespaceId];
|
|
1629
1883
|
const newNs = newContract.storage.namespaces[namespaceId];
|
|
1630
|
-
const priorTables = priorNs?.
|
|
1631
|
-
const newTables = newNs?.
|
|
1884
|
+
const priorTables = priorNs?.entries.table;
|
|
1885
|
+
const newTables = newNs?.entries.table;
|
|
1632
1886
|
const tableNames = unionSorted(priorTables ? Object.keys(priorTables) : [], newTables ? Object.keys(newTables) : []);
|
|
1633
1887
|
for (const tableName of tableNames) {
|
|
1634
1888
|
const priorTableRaw = priorTables?.[tableName];
|
|
@@ -1796,10 +2050,17 @@ function createMigrationPlan(options) {
|
|
|
1796
2050
|
...options.meta ? { meta: cloneRecord(options.meta) } : {}
|
|
1797
2051
|
});
|
|
1798
2052
|
}
|
|
1799
|
-
function plannerSuccess(plan) {
|
|
2053
|
+
function plannerSuccess(plan, warnings) {
|
|
1800
2054
|
return Object.freeze({
|
|
1801
2055
|
kind: "success",
|
|
1802
|
-
plan
|
|
2056
|
+
plan,
|
|
2057
|
+
...warnings && warnings.length > 0 ? { warnings: Object.freeze(warnings.map((conflict) => Object.freeze({
|
|
2058
|
+
kind: conflict.kind,
|
|
2059
|
+
summary: conflict.summary,
|
|
2060
|
+
...conflict.why ? { why: conflict.why } : {},
|
|
2061
|
+
...conflict.location ? { location: Object.freeze({ ...conflict.location }) } : {},
|
|
2062
|
+
...conflict.meta ? { meta: cloneRecord(conflict.meta) } : {}
|
|
2063
|
+
}))) } : {}
|
|
1803
2064
|
});
|
|
1804
2065
|
}
|
|
1805
2066
|
function plannerFailure(conflicts) {
|
|
@@ -1844,6 +2105,6 @@ const INIT_ADDITIVE_POLICY = Object.freeze({ allowedOperationClasses: Object.fre
|
|
|
1844
2105
|
//#region src/exports/control.ts
|
|
1845
2106
|
var control_default = new SqlFamilyDescriptor();
|
|
1846
2107
|
//#endregion
|
|
1847
|
-
export { INIT_ADDITIVE_POLICY, assembleAuthoringContributions, contractToSchemaIR, createMigrationPlan, control_default as default, detectDestructiveChanges, extractCodecControlHooks, planFieldEventOperations, plannerFailure, plannerSuccess, runnerFailure, runnerSuccess, temporalAuthoringPresets, timestampNowControlDescriptor };
|
|
2108
|
+
export { INIT_ADDITIVE_POLICY, assembleAuthoringContributions, contractToSchemaIR, controlPolicyForCall, createMigrationPlan, control_default as default, detectDestructiveChanges, extractCodecControlHooks, partitionCallsByControlPolicy, partitionIssuesByControlPolicy, planFieldEventOperations, plannerFailure, plannerSuccess, runnerFailure, runnerSuccess, temporalAuthoringPresets, timestampNowControlDescriptor };
|
|
1848
2109
|
|
|
1849
2110
|
//# sourceMappingURL=control.mjs.map
|