@prisma-next/family-sql 0.12.0 → 0.13.0-dev.1
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-CgIL9Vtx.d.mts +182 -0
- package/dist/control-adapter-CgIL9Vtx.d.mts.map +1 -0
- package/dist/control-adapter.d.mts +2 -109
- package/dist/control.d.mts +132 -4
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +277 -215
- 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-r7BP5n3l.mjs → timestamp-now-generator-CloimujU.mjs} +2 -1
- package/dist/{timestamp-now-generator-r7BP5n3l.mjs.map → timestamp-now-generator-CloimujU.mjs.map} +1 -1
- package/dist/{types-CeeCStqw.d.mts → types-CbwQCzXY.d.mts} +70 -16
- package/dist/types-CbwQCzXY.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-DlAgBiT_.mjs} +756 -319
- package/dist/verify-sql-schema-DlAgBiT_.mjs.map +1 -0
- package/dist/verify.mjs +1 -1
- package/package.json +23 -23
- package/src/core/control-adapter.ts +116 -7
- package/src/core/control-instance.ts +269 -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 +75 -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 +17 -7
- package/src/core/psl-contract-infer/sql-schema-ir-to-psl-ast.ts +8 -6
- package/src/core/schema-verify/control-verify-emit.ts +46 -0
- package/src/core/schema-verify/verifier-disposition.ts +58 -0
- package/src/core/schema-verify/verify-helpers.ts +310 -111
- package/src/core/schema-verify/verify-sql-schema.ts +309 -178
- package/src/core/timestamp-now-generator.ts +1 -0
- package/src/exports/control-adapter.ts +5 -1
- package/src/exports/control.ts +7 -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,17 @@
|
|
|
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 contractToSchemaIR, c as extractCodecControlHooks, o as detectDestructiveChanges, s as resolveValueSetValues, t as verifySqlSchema } from "./verify-sql-schema-DlAgBiT_.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
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
12
|
+
import { StorageTable } from "@prisma-next/sql-contract/types";
|
|
13
|
+
import { UNSPECIFIED_PSL_NAMESPACE_ID, makePslNamespace, makePslNamespaceEntries } from "@prisma-next/framework-components/psl-ast";
|
|
14
|
+
import { effectiveControlPolicy } from "@prisma-next/contract/types";
|
|
16
15
|
import { notOk, ok } from "@prisma-next/utils/result";
|
|
17
16
|
//#region src/core/operation-preview.ts
|
|
18
17
|
function isDdlStatement(sqlStatement) {
|
|
@@ -660,14 +659,12 @@ function buildPslDocumentAst(schemaIR, options) {
|
|
|
660
659
|
return {
|
|
661
660
|
kind: "document",
|
|
662
661
|
sourceId: "<sql-schema-ir>",
|
|
663
|
-
namespaces: [{
|
|
662
|
+
namespaces: [makePslNamespace({
|
|
664
663
|
kind: "namespace",
|
|
665
664
|
name: UNSPECIFIED_PSL_NAMESPACE_ID,
|
|
666
|
-
|
|
667
|
-
enums,
|
|
668
|
-
compositeTypes: [],
|
|
665
|
+
entries: makePslNamespaceEntries(sortedModels, enums, [], []),
|
|
669
666
|
span: SYNTHETIC_SPAN
|
|
670
|
-
}],
|
|
667
|
+
})],
|
|
671
668
|
...types ? { types } : {},
|
|
672
669
|
span: SYNTHETIC_SPAN
|
|
673
670
|
};
|
|
@@ -1023,7 +1020,7 @@ function extractCodecTypeIdsFromContract(contract) {
|
|
|
1023
1020
|
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
1021
|
const namespaces = contract.storage.namespaces;
|
|
1025
1022
|
for (const ns of Object.values(namespaces)) {
|
|
1026
|
-
const tbls = ns.
|
|
1023
|
+
const tbls = ns.entries.table;
|
|
1027
1024
|
if (typeof tbls !== "object" || tbls === null) continue;
|
|
1028
1025
|
for (const table of Object.values(tbls)) if (typeof table === "object" && table !== null && "columns" in table && typeof table.columns === "object" && table.columns !== null) {
|
|
1029
1026
|
const columns = table.columns;
|
|
@@ -1057,9 +1054,6 @@ function createVerifyResult(options) {
|
|
|
1057
1054
|
if (options.codecCoverageSkipped) result.codecCoverageSkipped = options.codecCoverageSkipped;
|
|
1058
1055
|
return result;
|
|
1059
1056
|
}
|
|
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
1057
|
function buildSqlTypeMetadataRegistry(options) {
|
|
1064
1058
|
const { target, adapter, extensionPacks: extensions } = options;
|
|
1065
1059
|
const registry = /* @__PURE__ */ new Map();
|
|
@@ -1081,6 +1075,81 @@ function buildSqlTypeMetadataRegistry(options) {
|
|
|
1081
1075
|
}
|
|
1082
1076
|
return registry;
|
|
1083
1077
|
}
|
|
1078
|
+
/**
|
|
1079
|
+
* Builds a map from each extension id to the set of extension ids it
|
|
1080
|
+
* transitively depends on. Uses the same declared-dependency data that
|
|
1081
|
+
* `buildExtensionLoadOrder` in control-stack uses.
|
|
1082
|
+
*/
|
|
1083
|
+
function buildTransitiveDependsOnMap(extensions) {
|
|
1084
|
+
const directDeps = /* @__PURE__ */ new Map();
|
|
1085
|
+
for (const ext of extensions) {
|
|
1086
|
+
const packs = ext.contractSpace?.contractJson?.extensionPacks;
|
|
1087
|
+
const deps = packs !== null && typeof packs === "object" ? Object.keys(packs) : [];
|
|
1088
|
+
directDeps.set(ext.id, deps);
|
|
1089
|
+
}
|
|
1090
|
+
const result = /* @__PURE__ */ new Map();
|
|
1091
|
+
const resolve = (id, visiting) => {
|
|
1092
|
+
const cached = result.get(id);
|
|
1093
|
+
if (cached !== void 0) return cached;
|
|
1094
|
+
const set = /* @__PURE__ */ new Set();
|
|
1095
|
+
result.set(id, set);
|
|
1096
|
+
for (const depId of directDeps.get(id) ?? []) {
|
|
1097
|
+
set.add(depId);
|
|
1098
|
+
if (!visiting.has(depId)) {
|
|
1099
|
+
visiting.add(depId);
|
|
1100
|
+
for (const transitive of resolve(depId, visiting)) set.add(transitive);
|
|
1101
|
+
visiting.delete(depId);
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
return set;
|
|
1105
|
+
};
|
|
1106
|
+
for (const ext of extensions) resolve(ext.id, new Set([ext.id]));
|
|
1107
|
+
return result;
|
|
1108
|
+
}
|
|
1109
|
+
/**
|
|
1110
|
+
* Asserts that no cross-space FK in any extension points against the
|
|
1111
|
+
* dependency direction.
|
|
1112
|
+
*
|
|
1113
|
+
* A cross-space FK (target.spaceId present) from extension A pointing at
|
|
1114
|
+
* space B is a violation when B depends on A (directly or transitively),
|
|
1115
|
+
* because that means A is pointing "upward" against the dependency arrows
|
|
1116
|
+
* established by the extension load order.
|
|
1117
|
+
*
|
|
1118
|
+
* Throws with a diagnostic naming the violating extension (source), the
|
|
1119
|
+
* target space, and the direction violation.
|
|
1120
|
+
*/
|
|
1121
|
+
function isObjectRecord(v) {
|
|
1122
|
+
return typeof v === "object" && v !== null;
|
|
1123
|
+
}
|
|
1124
|
+
function assertNoCrossSpaceFkReverseReferences(extensions) {
|
|
1125
|
+
const dependsOnMap = buildTransitiveDependsOnMap(extensions);
|
|
1126
|
+
for (const ext of extensions) {
|
|
1127
|
+
const namespaces = ext.contractSpace?.contractJson?.storage?.namespaces;
|
|
1128
|
+
if (!isObjectRecord(namespaces)) continue;
|
|
1129
|
+
for (const ns of Object.values(namespaces)) {
|
|
1130
|
+
if (!isObjectRecord(ns)) continue;
|
|
1131
|
+
const entries = ns["entries"];
|
|
1132
|
+
if (!isObjectRecord(entries)) continue;
|
|
1133
|
+
for (const slot of Object.values(entries)) {
|
|
1134
|
+
if (!isObjectRecord(slot)) continue;
|
|
1135
|
+
for (const table of Object.values(slot)) {
|
|
1136
|
+
if (!isObjectRecord(table)) continue;
|
|
1137
|
+
const foreignKeys = table["foreignKeys"];
|
|
1138
|
+
if (!Array.isArray(foreignKeys)) continue;
|
|
1139
|
+
for (const fk of foreignKeys) {
|
|
1140
|
+
if (!isObjectRecord(fk)) continue;
|
|
1141
|
+
const target = fk["target"];
|
|
1142
|
+
if (!isObjectRecord(target)) continue;
|
|
1143
|
+
if (target["spaceId"] === void 0) continue;
|
|
1144
|
+
const targetSpaceId = target["spaceId"];
|
|
1145
|
+
if (typeof targetSpaceId !== "string") continue;
|
|
1146
|
+
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).`);
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1084
1153
|
function createSqlFamilyInstance(stack) {
|
|
1085
1154
|
if (!stack.adapter) throw new Error("SQL family requires an adapter descriptor in ControlStack");
|
|
1086
1155
|
const target = stack.target;
|
|
@@ -1097,17 +1166,15 @@ function createSqlFamilyInstance(stack) {
|
|
|
1097
1166
|
...sqlContractCanonicalizationHooks
|
|
1098
1167
|
});
|
|
1099
1168
|
}
|
|
1169
|
+
assertNoCrossSpaceFkReverseReferences(extensions);
|
|
1100
1170
|
const { codecTypeImports, extensionIds } = stack;
|
|
1101
1171
|
const typeMetadataRegistry = buildSqlTypeMetadataRegistry({
|
|
1102
1172
|
target,
|
|
1103
1173
|
adapter,
|
|
1104
1174
|
extensionPacks: extensions
|
|
1105
1175
|
});
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
if (!isSqlControlAdapter(controlAdapter)) throw new Error("Adapter does not implement SqlControlAdapter (missing introspect, readMarker, or readAllMarkers)");
|
|
1109
|
-
return controlAdapter;
|
|
1110
|
-
};
|
|
1176
|
+
let controlAdapter;
|
|
1177
|
+
const getControlAdapter = () => controlAdapter ??= adapter.create(stack);
|
|
1111
1178
|
const targetSerializer = target.contractSerializer;
|
|
1112
1179
|
const deserializeWithTargetSerializer = (contractJson) => {
|
|
1113
1180
|
return (targetSerializer ?? new SqlContractSerializer()).deserializeContract(contractJson);
|
|
@@ -1233,21 +1300,21 @@ function createSqlFamilyInstance(stack) {
|
|
|
1233
1300
|
const contractStorageHash = contract.storage.storageHash;
|
|
1234
1301
|
const contractProfileHash = "profileHash" in contract && typeof contract.profileHash === "string" ? contract.profileHash : contractStorageHash;
|
|
1235
1302
|
const contractTarget = contract.target;
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
const
|
|
1303
|
+
const controlAdapter = getControlAdapter();
|
|
1304
|
+
const lowererContext = { contract };
|
|
1305
|
+
for (const query of controlAdapter.bootstrapSignMarkerQueries()) {
|
|
1306
|
+
const lowered = controlAdapter.lower(query, lowererContext);
|
|
1307
|
+
await driver.query(lowered.sql, lowered.params);
|
|
1308
|
+
}
|
|
1309
|
+
const existingMarker = await controlAdapter.readMarker(driver, APP_SPACE_ID);
|
|
1239
1310
|
let markerCreated = false;
|
|
1240
1311
|
let markerUpdated = false;
|
|
1241
1312
|
let previousHashes;
|
|
1242
1313
|
if (!existingMarker) {
|
|
1243
|
-
|
|
1244
|
-
space: APP_SPACE_ID,
|
|
1314
|
+
await controlAdapter.insertMarker(driver, APP_SPACE_ID, {
|
|
1245
1315
|
storageHash: contractStorageHash,
|
|
1246
|
-
profileHash: contractProfileHash
|
|
1247
|
-
contractJson: contractInput,
|
|
1248
|
-
canonicalVersion: 1
|
|
1316
|
+
profileHash: contractProfileHash
|
|
1249
1317
|
});
|
|
1250
|
-
await driver.query(write.insert.sql, write.insert.params);
|
|
1251
1318
|
markerCreated = true;
|
|
1252
1319
|
} else {
|
|
1253
1320
|
const existingStorageHash = existingMarker.storageHash;
|
|
@@ -1257,14 +1324,10 @@ function createSqlFamilyInstance(stack) {
|
|
|
1257
1324
|
storageHash: existingStorageHash,
|
|
1258
1325
|
profileHash: existingProfileHash
|
|
1259
1326
|
};
|
|
1260
|
-
|
|
1261
|
-
space: APP_SPACE_ID,
|
|
1327
|
+
if (!await controlAdapter.updateMarker(driver, APP_SPACE_ID, existingStorageHash, {
|
|
1262
1328
|
storageHash: contractStorageHash,
|
|
1263
|
-
profileHash: contractProfileHash
|
|
1264
|
-
|
|
1265
|
-
canonicalVersion: existingMarker.canonicalVersion ?? 1
|
|
1266
|
-
});
|
|
1267
|
-
await driver.query(write.update.sql, write.update.params);
|
|
1329
|
+
profileHash: contractProfileHash
|
|
1330
|
+
})) throw new Error("CAS conflict: marker was modified by another process during sign");
|
|
1268
1331
|
markerUpdated = true;
|
|
1269
1332
|
}
|
|
1270
1333
|
}
|
|
@@ -1302,6 +1365,18 @@ function createSqlFamilyInstance(stack) {
|
|
|
1302
1365
|
async readAllMarkers(options) {
|
|
1303
1366
|
return getControlAdapter().readAllMarkers(options.driver);
|
|
1304
1367
|
},
|
|
1368
|
+
async readLedger(options) {
|
|
1369
|
+
return getControlAdapter().readLedger(options.driver, options.space);
|
|
1370
|
+
},
|
|
1371
|
+
async initMarker(options) {
|
|
1372
|
+
return getControlAdapter().initMarker(options.driver, options.space, options.destination);
|
|
1373
|
+
},
|
|
1374
|
+
async updateMarker(options) {
|
|
1375
|
+
return getControlAdapter().updateMarker(options.driver, options.space, options.expectedFrom, options.destination);
|
|
1376
|
+
},
|
|
1377
|
+
async writeLedgerEntry(options) {
|
|
1378
|
+
return getControlAdapter().writeLedgerEntry(options.driver, options.space, options.entry);
|
|
1379
|
+
},
|
|
1305
1380
|
async introspect(options) {
|
|
1306
1381
|
return getControlAdapter().introspect(options.driver, options.contract);
|
|
1307
1382
|
},
|
|
@@ -1311,6 +1386,12 @@ function createSqlFamilyInstance(stack) {
|
|
|
1311
1386
|
lowerAst(ast, context) {
|
|
1312
1387
|
return getControlAdapter().lower(ast, context);
|
|
1313
1388
|
},
|
|
1389
|
+
bootstrapControlTableQueries() {
|
|
1390
|
+
return getControlAdapter().bootstrapControlTableQueries();
|
|
1391
|
+
},
|
|
1392
|
+
bootstrapSignMarkerQueries() {
|
|
1393
|
+
return getControlAdapter().bootstrapSignMarkerQueries();
|
|
1394
|
+
},
|
|
1314
1395
|
toOperationPreview(operations) {
|
|
1315
1396
|
return sqlOperationsToPreview(operations);
|
|
1316
1397
|
},
|
|
@@ -1420,200 +1501,174 @@ var SqlFamilyDescriptor = class {
|
|
|
1420
1501
|
}
|
|
1421
1502
|
};
|
|
1422
1503
|
//#endregion
|
|
1423
|
-
//#region src/core/migrations/
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
...ifDefined("default", column.default != null && renderDefault ? renderDefault(column.default, column) : void 0)
|
|
1435
|
-
};
|
|
1504
|
+
//#region src/core/migrations/control-policy.ts
|
|
1505
|
+
/**
|
|
1506
|
+
* The control policy that governs a single call. The `external` default is an
|
|
1507
|
+
* un-overridable namespace floor: when the contract default is `external`, no
|
|
1508
|
+
* per-object `managed` override can escalate DDL above the floor, so the
|
|
1509
|
+
* policy is forced to `external` regardless of the node's own declaration.
|
|
1510
|
+
* Every other default defers to the node's effective control policy.
|
|
1511
|
+
*/
|
|
1512
|
+
function controlPolicyForCall(subject, defaultControlPolicy) {
|
|
1513
|
+
if (defaultControlPolicy === "external") return "external";
|
|
1514
|
+
return effectiveControlPolicy(subject?.explicitNodeControlPolicy, defaultControlPolicy);
|
|
1436
1515
|
}
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1516
|
+
/**
|
|
1517
|
+
* Whether a call is allowed to emit under a given control policy.
|
|
1518
|
+
*
|
|
1519
|
+
* - `managed` — full lifecycle, every op allowed.
|
|
1520
|
+
* - `tolerated` — create-if-absent only: allowed iff the call creates a whole
|
|
1521
|
+
* new top-level object (and its subject was positively resolved). Anything
|
|
1522
|
+
* that modifies an existing object, and anything whose subject could not be
|
|
1523
|
+
* resolved, is suppressed.
|
|
1524
|
+
* - `external` / `observed` — no DDL at all.
|
|
1525
|
+
*/
|
|
1526
|
+
function callAllowedUnderControlPolicy(policy, subject) {
|
|
1527
|
+
switch (policy) {
|
|
1528
|
+
case "managed": return true;
|
|
1529
|
+
case "tolerated": return subject?.createsNewObject === true;
|
|
1530
|
+
case "external":
|
|
1531
|
+
case "observed": return false;
|
|
1532
|
+
}
|
|
1452
1533
|
}
|
|
1453
|
-
function
|
|
1454
|
-
return {
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
};
|
|
1534
|
+
function defaultSubjectLabel(factoryName, subject) {
|
|
1535
|
+
if (subject?.table) return `${factoryName}(${subject.table})`;
|
|
1536
|
+
if (subject?.typeName) return `${factoryName}(${subject.typeName})`;
|
|
1537
|
+
return factoryName;
|
|
1458
1538
|
}
|
|
1459
|
-
function
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
};
|
|
1539
|
+
function suppressionSummary(subjectLabel, subject, effectivePolicy) {
|
|
1540
|
+
const namespace = subject?.namespaceId ?? "unknown";
|
|
1541
|
+
const declared = subject?.explicitNodeControlPolicy;
|
|
1542
|
+
if (effectivePolicy === "external" && declared === "managed") return `control policy suppressed: ${subjectLabel} — namespace '${namespace}' has effective control 'external' but table declared 'managed'`;
|
|
1543
|
+
return `control policy suppressed: ${subjectLabel} — namespace '${namespace}' has effective control '${effectivePolicy}'${declared ? ` but table declared '${declared}'` : ""}`;
|
|
1465
1544
|
}
|
|
1466
|
-
function
|
|
1545
|
+
function buildSubjectSuppressionWarning(subject, effectivePolicy, factoryName, formatSubjectLabel) {
|
|
1467
1546
|
return {
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1547
|
+
kind: "controlPolicySuppressedCall",
|
|
1548
|
+
summary: suppressionSummary(formatSubjectLabel(factoryName, subject), subject, effectivePolicy),
|
|
1549
|
+
location: {
|
|
1550
|
+
...ifDefined("namespace", subject?.namespaceId),
|
|
1551
|
+
...ifDefined("table", subject?.table),
|
|
1552
|
+
...ifDefined("column", subject?.column),
|
|
1553
|
+
...ifDefined("type", subject?.typeName)
|
|
1554
|
+
},
|
|
1555
|
+
meta: {
|
|
1556
|
+
controlPolicy: effectivePolicy,
|
|
1557
|
+
factoryName,
|
|
1558
|
+
...ifDefined("declaredControlPolicy", subject?.explicitNodeControlPolicy)
|
|
1559
|
+
}
|
|
1475
1560
|
};
|
|
1476
1561
|
}
|
|
1477
|
-
function
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
...table.indexes.map((idx) => idx.columns.join(",")),
|
|
1482
|
-
...table.uniques.map((unique) => unique.columns.join(",")),
|
|
1483
|
-
...table.primaryKey ? [table.primaryKey.columns.join(",")] : []
|
|
1484
|
-
]);
|
|
1485
|
-
const fkBackingIndexes = [];
|
|
1486
|
-
for (const fk of table.foreignKeys) {
|
|
1487
|
-
if (fk.index === false) continue;
|
|
1488
|
-
const key = fk.source.columns.join(",");
|
|
1489
|
-
if (satisfiedIndexColumns.has(key)) continue;
|
|
1490
|
-
fkBackingIndexes.push({
|
|
1491
|
-
columns: fk.source.columns,
|
|
1492
|
-
unique: false,
|
|
1493
|
-
name: defaultIndexName(name, fk.source.columns)
|
|
1494
|
-
});
|
|
1495
|
-
satisfiedIndexColumns.add(key);
|
|
1496
|
-
}
|
|
1497
|
-
return {
|
|
1498
|
-
name,
|
|
1499
|
-
columns,
|
|
1500
|
-
...ifDefined("primaryKey", table.primaryKey),
|
|
1501
|
-
foreignKeys: table.foreignKeys.map(convertForeignKey),
|
|
1502
|
-
uniques: table.uniques.map(convertUnique),
|
|
1503
|
-
indexes: [...table.indexes.map(convertIndex), ...fkBackingIndexes]
|
|
1504
|
-
};
|
|
1562
|
+
function defaultModificationFactoryNameForSubject(subject) {
|
|
1563
|
+
if (subject.table) return "alterTable";
|
|
1564
|
+
if (subject.typeName) return "alterType";
|
|
1565
|
+
return "alterSchema";
|
|
1505
1566
|
}
|
|
1506
1567
|
/**
|
|
1507
|
-
*
|
|
1568
|
+
* Partition the calls produced for a single set of subjects into those the
|
|
1569
|
+
* effective control policy permits (`kept`) and a list of
|
|
1570
|
+
* {@link SqlPlannerConflict} warnings describing the suppressed calls.
|
|
1508
1571
|
*
|
|
1509
|
-
*
|
|
1510
|
-
*
|
|
1511
|
-
*
|
|
1512
|
-
*
|
|
1513
|
-
*
|
|
1572
|
+
* **Prefer {@link partitionIssuesByControlPolicy}** for the schema-issue
|
|
1573
|
+
* pipeline: it filters subjects out of the planner's *input* so the planner
|
|
1574
|
+
* never has to reason about un-modeled state on `external`/`observed`
|
|
1575
|
+
* subjects. This call-level helper remains for paths that bypass the issue
|
|
1576
|
+
* pipeline — currently the codec-emitted field-event ops, which originate
|
|
1577
|
+
* from declared contract fields rather than from introspected schema state
|
|
1578
|
+
* and therefore cannot trip the diff engine.
|
|
1514
1579
|
*/
|
|
1515
|
-
function
|
|
1516
|
-
|
|
1517
|
-
const
|
|
1518
|
-
const
|
|
1519
|
-
const
|
|
1520
|
-
for (const
|
|
1521
|
-
const
|
|
1522
|
-
const
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
if (!(toTableRaw instanceof StorageTable)) {
|
|
1528
|
-
conflicts.push({
|
|
1529
|
-
kind: "tableRemoved",
|
|
1530
|
-
summary: `Table "${tableName}" was removed`
|
|
1531
|
-
});
|
|
1532
|
-
continue;
|
|
1533
|
-
}
|
|
1534
|
-
const toTable = toTableRaw;
|
|
1535
|
-
const fromTableRaw = fromTables[tableName];
|
|
1536
|
-
if (!(fromTableRaw instanceof StorageTable)) continue;
|
|
1537
|
-
const fromTable = fromTableRaw;
|
|
1538
|
-
for (const columnName of Object.keys(fromTable.columns)) if (!hasOwn(toTable.columns, columnName)) conflicts.push({
|
|
1539
|
-
kind: "columnRemoved",
|
|
1540
|
-
summary: `Column "${tableName}"."${columnName}" was removed`
|
|
1541
|
-
});
|
|
1580
|
+
function partitionCallsByControlPolicy(options) {
|
|
1581
|
+
const defaultControlPolicy = options.contract.defaultControlPolicy;
|
|
1582
|
+
const formatSubjectLabel = options.formatSubjectLabel ?? defaultSubjectLabel;
|
|
1583
|
+
const kept = [];
|
|
1584
|
+
const warnings = [];
|
|
1585
|
+
for (const call of options.calls) {
|
|
1586
|
+
const subject = options.resolveControlPolicySubject(call);
|
|
1587
|
+
const policy = controlPolicyForCall(subject, defaultControlPolicy);
|
|
1588
|
+
if (callAllowedUnderControlPolicy(policy, subject)) kept.push(call);
|
|
1589
|
+
else {
|
|
1590
|
+
const factoryName = options.resolveFactoryName(call);
|
|
1591
|
+
warnings.push(buildSubjectSuppressionWarning(subject, policy, factoryName, formatSubjectLabel));
|
|
1542
1592
|
}
|
|
1543
1593
|
}
|
|
1544
|
-
return
|
|
1594
|
+
return Object.freeze({
|
|
1595
|
+
kept: Object.freeze(kept),
|
|
1596
|
+
warnings: Object.freeze(warnings)
|
|
1597
|
+
});
|
|
1545
1598
|
}
|
|
1546
1599
|
/**
|
|
1547
|
-
*
|
|
1600
|
+
* Partition a list of schema-issue-shaped inputs by the effective control
|
|
1601
|
+
* policy of each issue's subject *before* the planner is invoked.
|
|
1548
1602
|
*
|
|
1549
|
-
*
|
|
1550
|
-
*
|
|
1551
|
-
* `
|
|
1603
|
+
* `plannable` is the list of issues whose subject's effective policy permits
|
|
1604
|
+
* the planner to act on them (`managed`, or `tolerated` for whole-object
|
|
1605
|
+
* creation issues only). Issues for `external`/`observed` subjects, and
|
|
1606
|
+
* non-creation issues for `tolerated` subjects, are dropped from the planner's
|
|
1607
|
+
* input entirely — they never enter introspection-driven planning, never feed
|
|
1608
|
+
* the diff engine, and never produce DDL calls that would have to be
|
|
1609
|
+
* post-filtered. This sidesteps a class of failure where the diff engine
|
|
1610
|
+
* cannot reason about the live shape of a subject the user marked as
|
|
1611
|
+
* out-of-scope (`external`).
|
|
1552
1612
|
*
|
|
1553
|
-
*
|
|
1554
|
-
*
|
|
1555
|
-
*
|
|
1556
|
-
*
|
|
1613
|
+
* `warnings` is one {@link SqlPlannerConflict} per suppressed subject (not per
|
|
1614
|
+
* suppressed issue). `factoryName` is inferred from the subject's issue mix:
|
|
1615
|
+
* if any of the subject's issues is whole-object creation, the warning takes
|
|
1616
|
+
* the corresponding creation factoryName (e.g. `createTable`,
|
|
1617
|
+
* `createEnumType`, `createSchema`); otherwise it falls back to
|
|
1618
|
+
* `defaultModificationFactoryName(subject)` — a synthetic label that names
|
|
1619
|
+
* the *kind* of mutation that would have run, since no concrete DDL call was
|
|
1620
|
+
* generated.
|
|
1557
1621
|
*
|
|
1558
|
-
*
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
if (options.annotationNamespace.length === 0) throw new Error("annotationNamespace must be a non-empty string");
|
|
1562
|
-
if (!contract) return { tables: {} };
|
|
1563
|
-
const storage = contract.storage;
|
|
1564
|
-
const allTypes = { ...storage.types ?? {} };
|
|
1565
|
-
for (const ns of Object.values(storage.namespaces)) {
|
|
1566
|
-
const nsEnums = ns.enum;
|
|
1567
|
-
if (nsEnums) for (const [k, v] of Object.entries(nsEnums)) allTypes[k] = v;
|
|
1568
|
-
}
|
|
1569
|
-
const storageTypes = allTypes;
|
|
1570
|
-
const tables = {};
|
|
1571
|
-
for (const ns of Object.values(storage.namespaces)) for (const [tableName, tableDefRaw] of Object.entries(ns.tables)) {
|
|
1572
|
-
if (!(tableDefRaw instanceof StorageTable)) throw new Error(`contractToSchemaIR: expected StorageTable at namespaces.${ns.id}.tables.${tableName}`);
|
|
1573
|
-
const tableDef = tableDefRaw;
|
|
1574
|
-
if (tables[tableName] !== void 0) throw new Error(`contractToSchemaIR: duplicate SQL table name "${tableName}" across namespaces (ambiguous for flat SqlSchemaIR.tables).`);
|
|
1575
|
-
tables[tableName] = convertTable(tableName, tableDef, storageTypes, options.expandNativeType, options.renderDefault);
|
|
1576
|
-
}
|
|
1577
|
-
return {
|
|
1578
|
-
tables,
|
|
1579
|
-
...ifDefined("annotations", deriveAnnotations(storage, options.annotationNamespace, options.resolveEnumStorageKey))
|
|
1580
|
-
};
|
|
1581
|
-
}
|
|
1582
|
-
/**
|
|
1583
|
-
* Normalises a native enum storage entry to the codec-typed annotation shape
|
|
1584
|
-
* `{codecId, nativeType, typeParams}` the introspector writes and
|
|
1585
|
-
* `readExistingEnumValues` reads (`existing.codecId` + `existing.typeParams.values`).
|
|
1586
|
-
* Without this the projector would emit the raw `PostgresEnumStorageEntry`
|
|
1587
|
-
* shape (top-level `values`, no `typeParams`) and the enum would read as new.
|
|
1622
|
+
* Unresolved-subject issues (`resolveControlPolicySubject` returns
|
|
1623
|
+
* `undefined`) emit one warning each; they cannot be deduplicated because
|
|
1624
|
+
* they carry no subject coordinate.
|
|
1588
1625
|
*/
|
|
1589
|
-
function
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1626
|
+
function partitionIssuesByControlPolicy(options) {
|
|
1627
|
+
const defaultControlPolicy = options.contract.defaultControlPolicy;
|
|
1628
|
+
const formatSubjectLabel = options.formatSubjectLabel ?? defaultSubjectLabel;
|
|
1629
|
+
const inferModificationFactoryName = options.defaultModificationFactoryName ?? defaultModificationFactoryNameForSubject;
|
|
1630
|
+
const plannable = [];
|
|
1631
|
+
const suppressedSubjects = /* @__PURE__ */ new Map();
|
|
1632
|
+
const unresolvedSuppressions = [];
|
|
1633
|
+
for (const issue of options.issues) {
|
|
1634
|
+
const subject = options.resolveControlPolicySubject(issue);
|
|
1635
|
+
const policy = controlPolicyForCall(subject, defaultControlPolicy);
|
|
1636
|
+
const creationFactoryName = options.resolveCreationFactoryName(issue);
|
|
1637
|
+
if (policy === "managed") {
|
|
1638
|
+
plannable.push(issue);
|
|
1602
1639
|
continue;
|
|
1603
1640
|
}
|
|
1604
|
-
if (
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
if (
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
storageTypes[key] = normalizeEnumAnnotation(entry);
|
|
1641
|
+
if (policy === "tolerated" && subject !== void 0 && creationFactoryName !== void 0 && subject.createsNewObject) {
|
|
1642
|
+
plannable.push(issue);
|
|
1643
|
+
continue;
|
|
1644
|
+
}
|
|
1645
|
+
if (subject === void 0) {
|
|
1646
|
+
const factoryName = creationFactoryName ?? "unknown";
|
|
1647
|
+
unresolvedSuppressions.push(buildSubjectSuppressionWarning(void 0, policy, factoryName, formatSubjectLabel));
|
|
1648
|
+
continue;
|
|
1613
1649
|
}
|
|
1650
|
+
const key = subjectKey(subject);
|
|
1651
|
+
const existing = suppressedSubjects.get(key);
|
|
1652
|
+
if (existing) {
|
|
1653
|
+
if (existing.creationFactoryName === void 0 && creationFactoryName !== void 0) existing.creationFactoryName = creationFactoryName;
|
|
1654
|
+
} else suppressedSubjects.set(key, {
|
|
1655
|
+
subject,
|
|
1656
|
+
policy,
|
|
1657
|
+
...ifDefined("creationFactoryName", creationFactoryName)
|
|
1658
|
+
});
|
|
1659
|
+
}
|
|
1660
|
+
const warnings = [...unresolvedSuppressions];
|
|
1661
|
+
for (const entry of suppressedSubjects.values()) {
|
|
1662
|
+
const factoryName = entry.creationFactoryName ?? inferModificationFactoryName(entry.subject);
|
|
1663
|
+
warnings.push(buildSubjectSuppressionWarning(entry.subject, entry.policy, factoryName, formatSubjectLabel));
|
|
1614
1664
|
}
|
|
1615
|
-
|
|
1616
|
-
|
|
1665
|
+
return Object.freeze({
|
|
1666
|
+
plannable: Object.freeze(plannable),
|
|
1667
|
+
warnings: Object.freeze(warnings)
|
|
1668
|
+
});
|
|
1669
|
+
}
|
|
1670
|
+
function subjectKey(subject) {
|
|
1671
|
+
return `${subject.namespaceId}\u0000${subject.table ?? ""}\u0000${subject.typeName ?? ""}`;
|
|
1617
1672
|
}
|
|
1618
1673
|
//#endregion
|
|
1619
1674
|
//#region src/core/migrations/field-event-planner.ts
|
|
@@ -1627,8 +1682,8 @@ function planFieldEventOperations(options) {
|
|
|
1627
1682
|
for (const namespaceId of namespaceIds) {
|
|
1628
1683
|
const priorNs = priorContract?.storage.namespaces[namespaceId];
|
|
1629
1684
|
const newNs = newContract.storage.namespaces[namespaceId];
|
|
1630
|
-
const priorTables = priorNs?.
|
|
1631
|
-
const newTables = newNs?.
|
|
1685
|
+
const priorTables = priorNs?.entries.table;
|
|
1686
|
+
const newTables = newNs?.entries.table;
|
|
1632
1687
|
const tableNames = unionSorted(priorTables ? Object.keys(priorTables) : [], newTables ? Object.keys(newTables) : []);
|
|
1633
1688
|
for (const tableName of tableNames) {
|
|
1634
1689
|
const priorTableRaw = priorTables?.[tableName];
|
|
@@ -1796,10 +1851,17 @@ function createMigrationPlan(options) {
|
|
|
1796
1851
|
...options.meta ? { meta: cloneRecord(options.meta) } : {}
|
|
1797
1852
|
});
|
|
1798
1853
|
}
|
|
1799
|
-
function plannerSuccess(plan) {
|
|
1854
|
+
function plannerSuccess(plan, warnings) {
|
|
1800
1855
|
return Object.freeze({
|
|
1801
1856
|
kind: "success",
|
|
1802
|
-
plan
|
|
1857
|
+
plan,
|
|
1858
|
+
...warnings && warnings.length > 0 ? { warnings: Object.freeze(warnings.map((conflict) => Object.freeze({
|
|
1859
|
+
kind: conflict.kind,
|
|
1860
|
+
summary: conflict.summary,
|
|
1861
|
+
...conflict.why ? { why: conflict.why } : {},
|
|
1862
|
+
...conflict.location ? { location: Object.freeze({ ...conflict.location }) } : {},
|
|
1863
|
+
...conflict.meta ? { meta: cloneRecord(conflict.meta) } : {}
|
|
1864
|
+
}))) } : {}
|
|
1803
1865
|
});
|
|
1804
1866
|
}
|
|
1805
1867
|
function plannerFailure(conflicts) {
|
|
@@ -1844,6 +1906,6 @@ const INIT_ADDITIVE_POLICY = Object.freeze({ allowedOperationClasses: Object.fre
|
|
|
1844
1906
|
//#region src/exports/control.ts
|
|
1845
1907
|
var control_default = new SqlFamilyDescriptor();
|
|
1846
1908
|
//#endregion
|
|
1847
|
-
export { INIT_ADDITIVE_POLICY, assembleAuthoringContributions, contractToSchemaIR, createMigrationPlan, control_default as default, detectDestructiveChanges, extractCodecControlHooks, planFieldEventOperations, plannerFailure, plannerSuccess, runnerFailure, runnerSuccess, temporalAuthoringPresets, timestampNowControlDescriptor };
|
|
1909
|
+
export { INIT_ADDITIVE_POLICY, assembleAuthoringContributions, contractToSchemaIR, controlPolicyForCall, createMigrationPlan, control_default as default, detectDestructiveChanges, extractCodecControlHooks, partitionCallsByControlPolicy, partitionIssuesByControlPolicy, planFieldEventOperations, plannerFailure, plannerSuccess, resolveValueSetValues, runnerFailure, runnerSuccess, temporalAuthoringPresets, timestampNowControlDescriptor };
|
|
1848
1910
|
|
|
1849
1911
|
//# sourceMappingURL=control.mjs.map
|