@happyvertical/smrt-core 0.47.2 → 0.49.0
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/agents/generators.md +12 -0
- package/agents/object-runtime.md +7 -0
- package/agents/schema-paths.md +62 -0
- package/agents/system-diagnostics.md +6 -1
- package/dist/cascade.d.ts.map +1 -1
- package/dist/cascade.js +9 -6
- package/dist/cascade.js.map +1 -1
- package/dist/collection.d.ts +0 -1
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +35 -45
- package/dist/collection.js.map +1 -1
- package/dist/decorators/compatibility.d.ts +1 -1
- package/dist/decorators/compatibility.d.ts.map +1 -1
- package/dist/decorators/compatibility.js +2 -2
- package/dist/decorators/compatibility.js.map +1 -1
- package/dist/decorators/index.d.ts.map +1 -1
- package/dist/decorators/index.js +33 -24
- package/dist/decorators/index.js.map +1 -1
- package/dist/interceptors.d.ts +3 -1
- package/dist/interceptors.d.ts.map +1 -1
- package/dist/interceptors.js +3 -2
- package/dist/interceptors.js.map +1 -1
- package/dist/manifest/static-manifest.js +1 -1
- package/dist/manifest/static-manifest.js.map +1 -1
- package/dist/manifest/store.js +1 -1
- package/dist/manifest.json +1 -1
- package/dist/migrations/differ.d.ts +10 -4
- package/dist/migrations/differ.d.ts.map +1 -1
- package/dist/migrations/differ.js +173 -5
- package/dist/migrations/differ.js.map +1 -1
- package/dist/object.d.ts +1 -5
- package/dist/object.d.ts.map +1 -1
- package/dist/object.js +28 -37
- package/dist/object.js.map +1 -1
- package/dist/registry/class-registration.d.ts +2 -1
- package/dist/registry/class-registration.d.ts.map +1 -1
- package/dist/registry/class-registration.js +47 -16
- package/dist/registry/class-registration.js.map +1 -1
- package/dist/registry/name-resolver.d.ts.map +1 -1
- package/dist/registry/name-resolver.js +3 -3
- package/dist/registry/name-resolver.js.map +1 -1
- package/dist/registry/relationship-graph.d.ts +2 -0
- package/dist/registry/relationship-graph.d.ts.map +1 -1
- package/dist/registry/relationship-graph.js +60 -5
- package/dist/registry/relationship-graph.js.map +1 -1
- package/dist/registry/schema-builder.d.ts.map +1 -1
- package/dist/registry/schema-builder.js +4 -1
- package/dist/registry/schema-builder.js.map +1 -1
- package/dist/registry/shared-state.d.ts +18 -0
- package/dist/registry/shared-state.d.ts.map +1 -1
- package/dist/registry/shared-state.js +25 -1
- package/dist/registry/shared-state.js.map +1 -1
- package/dist/registry/types.d.ts +8 -0
- package/dist/registry/types.d.ts.map +1 -1
- package/dist/registry.d.ts +32 -5
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +120 -12
- package/dist/registry.js.map +1 -1
- package/dist/relationship-loader.d.ts +6 -0
- package/dist/relationship-loader.d.ts.map +1 -0
- package/dist/relationship-loader.js +35 -0
- package/dist/relationship-loader.js.map +1 -0
- package/dist/scanner/manifest-generator.d.ts.map +1 -1
- package/dist/scanner/manifest-generator.js +6 -3
- package/dist/scanner/manifest-generator.js.map +1 -1
- package/dist/schema/foreign-key-ddl.d.ts +8 -0
- package/dist/schema/foreign-key-ddl.d.ts.map +1 -1
- package/dist/schema/foreign-key-ddl.js +12 -1
- package/dist/schema/foreign-key-ddl.js.map +1 -1
- package/dist/schema/foreign-key-orphan-report.d.ts.map +1 -1
- package/dist/schema/foreign-key-orphan-report.js +18 -1
- package/dist/schema/foreign-key-orphan-report.js.map +1 -1
- package/dist/schema/generator.d.ts +2 -0
- package/dist/schema/generator.d.ts.map +1 -1
- package/dist/schema/generator.js +16 -9
- package/dist/schema/generator.js.map +1 -1
- package/dist/schema/index.d.ts +1 -1
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +2 -2
- package/dist/schema/live-parity.d.ts.map +1 -1
- package/dist/schema/live-parity.js +74 -0
- package/dist/schema/live-parity.js.map +1 -1
- package/dist/schema/text-cast-probe.d.ts +66 -0
- package/dist/schema/text-cast-probe.d.ts.map +1 -0
- package/dist/schema/text-cast-probe.js +142 -0
- package/dist/schema/text-cast-probe.js.map +1 -0
- package/dist/schema/types.d.ts +28 -0
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/schema/utils.d.ts.map +1 -1
- package/dist/schema/utils.js +2 -1
- package/dist/schema/utils.js.map +1 -1
- package/dist/schema.js +2 -2
- package/dist/smrt-knowledge.json +11 -11
- package/dist/system/diagnostics.d.ts +22 -0
- package/dist/system/diagnostics.d.ts.map +1 -1
- package/dist/system/diagnostics.js +88 -31
- package/dist/system/diagnostics.js.map +1 -1
- package/dist/test-utils.d.ts.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +2 -1
- package/dist/utils.js.map +1 -1
- package/dist/vite-plugin/dev-plane-route.d.ts +5 -0
- package/dist/vite-plugin/dev-plane-route.d.ts.map +1 -0
- package/dist/vite-plugin/dev-plane-route.js +91 -0
- package/dist/vite-plugin/dev-plane-route.js.map +1 -0
- package/dist/vite-plugin/sveltekit-generator.d.ts +8 -0
- package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -1
- package/dist/vite-plugin/sveltekit-generator.js +2 -0
- package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
- package/package.json +4 -4
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { normalizeForeignKeyAction, requireForeignKeyAction } from "../schema/foreign-key-policy.js";
|
|
2
2
|
import { isJsonPathIndex, isStiSubtypeUniqueIndex, renderIndexTarget } from "../schema/index-utils.js";
|
|
3
|
-
import { CANONICAL_UUID_PATTERN, CANONICAL_UUID_SQLITE_GLOB_PATTERN, foreignKeyConstraintName, foreignKeyRelationshipKey,
|
|
3
|
+
import { CANONICAL_UUID_PATTERN, CANONICAL_UUID_SQLITE_GLOB_PATTERN, foreignKeyConstraintName, foreignKeyRelationshipKey, renderForeignKeyAddStatements, renderForeignKeyOrphanDetector, renderForeignKeyOrphanRepair, schemaForeignKeys, schemaForeignKeysForEngine } from "../schema/foreign-key-ddl.js";
|
|
4
4
|
import { detectEngine, getDDLStrategy } from "../schema/ddl/index.js";
|
|
5
|
+
import { maskSampleValue, probeCastSafety, renderJsonbColumnConversion, renderTimestamptzColumnConversion } from "../schema/text-cast-probe.js";
|
|
5
6
|
import { describeForeignKeyTypeConflict, planUuidConvergence, renderUuidConvergenceRepairHint, renderUuidShapeProbe } from "../schema/uuid-convergence.js";
|
|
6
7
|
import "../schema/utils.js";
|
|
7
8
|
import { planSqliteTableRebuilds, sqliteRebuildPlaceholderSql } from "./sqlite-rebuild.js";
|
|
@@ -64,6 +65,18 @@ function uniqueColumnIndexName(tableName, columnName) {
|
|
|
64
65
|
* True when a change is report-only: it carries an advisory and no
|
|
65
66
|
* executable statement. Such changes never reach the migration stream.
|
|
66
67
|
*/
|
|
68
|
+
/**
|
|
69
|
+
* Single- vs double-precision float classification for #2770's float-width
|
|
70
|
+
* drift detection. `null` for anything that isn't unambiguously one or the
|
|
71
|
+
* other (DECIMAL/NUMERIC have no fixed binary width and are out of scope).
|
|
72
|
+
*/
|
|
73
|
+
function floatPrecisionOf(type, engine) {
|
|
74
|
+
const upper = type.toUpperCase().trim().replace(/\(\s*\d+(\s*,\s*\d+)?\s*\)/g, "");
|
|
75
|
+
if (/^(REAL|FLOAT4)$/.test(upper)) return "single";
|
|
76
|
+
if (engine === "duckdb" && upper === "FLOAT") return "single";
|
|
77
|
+
if (/^(FLOAT8|DOUBLE|DOUBLE PRECISION|FLOAT)$/.test(upper)) return "double";
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
67
80
|
function isAdvisoryOnlyChange(change) {
|
|
68
81
|
if (!change.advisory) return false;
|
|
69
82
|
return (change.sqlStatements ?? (change.sql ? [change.sql] : [])).length === 0;
|
|
@@ -475,6 +488,18 @@ var SchemaComparer = class {
|
|
|
475
488
|
}
|
|
476
489
|
}
|
|
477
490
|
/**
|
|
491
|
+
* Probe a candidate `text` -> `timestamptz`/`jsonb` column (#2771, #2772)
|
|
492
|
+
* with a real, exception-safe cast attempt (see `text-cast-probe.ts`).
|
|
493
|
+
* Unlike {@link probeUuidShape}, a failed/unrealistic probe
|
|
494
|
+
* (`unavailable`) is the caller's cue to fall back to the pre-existing
|
|
495
|
+
* behavior for that pair rather than surface a new finding — this keeps
|
|
496
|
+
* the new convergence strictly additive for every case that isn't
|
|
497
|
+
* affirmatively confirmed safe or affirmatively confirmed unsafe.
|
|
498
|
+
*/
|
|
499
|
+
async probeTextCastShape(tableName, columnName, kind) {
|
|
500
|
+
return probeCastSafety(this.db, tableName, columnName, kind);
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
478
503
|
* Turn the convergence plan into diff entries: executable `type_upgrade`
|
|
479
504
|
* conversions marked `phase: 'pre_foreign_key'`, plus report-only warnings
|
|
480
505
|
* for components the planner refused.
|
|
@@ -608,6 +633,7 @@ var SchemaComparer = class {
|
|
|
608
633
|
table: tableName,
|
|
609
634
|
name: foreignKeyConstraintName(tableName, foreignKey),
|
|
610
635
|
foreignKey,
|
|
636
|
+
engineUnsupported: true,
|
|
611
637
|
advisory: {
|
|
612
638
|
severity: "warning",
|
|
613
639
|
message: `${engineReason} Run the orphan detector, repair rows, and rebuild the table with the generated constraint.`,
|
|
@@ -645,6 +671,8 @@ var SchemaComparer = class {
|
|
|
645
671
|
table: tableName,
|
|
646
672
|
name: foreignKeyConstraintName(tableName, foreignKey),
|
|
647
673
|
foreignKey,
|
|
674
|
+
orphanBlocked: true,
|
|
675
|
+
orphanNullable: orphanOptions.nullable,
|
|
648
676
|
advisory: {
|
|
649
677
|
severity: "warning",
|
|
650
678
|
message: `Cannot add foreign key ${tableName}.${foreignKey.column}: existing rows do not match ${foreignKey.referencesTable}.${foreignKey.referencesColumn}. Repair them, then rerun.`,
|
|
@@ -659,7 +687,7 @@ var SchemaComparer = class {
|
|
|
659
687
|
table: tableName,
|
|
660
688
|
name: constraintName,
|
|
661
689
|
foreignKey,
|
|
662
|
-
sqlStatements:
|
|
690
|
+
sqlStatements: renderForeignKeyAddStatements(tableName, foreignKey)
|
|
663
691
|
});
|
|
664
692
|
}
|
|
665
693
|
return changes;
|
|
@@ -680,7 +708,9 @@ var SchemaComparer = class {
|
|
|
680
708
|
async getForeignKeyOrphanOptions(tableName, manifest, dbSchema, foreignKey, manifestSchemas) {
|
|
681
709
|
const sourceColumn = manifest.columns[foreignKey.column];
|
|
682
710
|
const targetColumn = manifestSchemas[foreignKey.referencesTable]?.columns[foreignKey.referencesColumn];
|
|
683
|
-
const
|
|
711
|
+
const manifestNullable = sourceColumn?.notNull !== true;
|
|
712
|
+
const liveNotNull = dbSchema.columns[foreignKey.column]?.notNull === true;
|
|
713
|
+
const nullable = manifestNullable && !liveNotNull;
|
|
684
714
|
const uuidComparison = sourceColumn?.type === "UUID" && (targetColumn === void 0 || targetColumn.type === "UUID");
|
|
685
715
|
if (!uuidComparison) return {
|
|
686
716
|
nullable,
|
|
@@ -743,10 +773,148 @@ var SchemaComparer = class {
|
|
|
743
773
|
const expectedEngineType = this.ddlStrategy.mapType(validatedType);
|
|
744
774
|
const normalizedExpected = this.normalizeType(expectedEngineType);
|
|
745
775
|
const normalizedActual = this.normalizeType(dbCol.type);
|
|
776
|
+
if ((this.engine === "postgres" || this.engine === "duckdb") && normalizedExpected === "REAL" && normalizedActual === "REAL") {
|
|
777
|
+
const expectedPrecision = floatPrecisionOf(expectedEngineType, this.engine);
|
|
778
|
+
const actualPrecision = floatPrecisionOf(dbCol.type, this.engine);
|
|
779
|
+
if (expectedPrecision && actualPrecision && expectedPrecision !== actualPrecision) {
|
|
780
|
+
typeDrifted = true;
|
|
781
|
+
const table = this.quoteIdentifier(tableName);
|
|
782
|
+
const column = this.quoteIdentifier(colName);
|
|
783
|
+
if (expectedPrecision === "double" && actualPrecision === "single") {
|
|
784
|
+
const targetType = this.engine === "postgres" ? "DOUBLE PRECISION" : "DOUBLE";
|
|
785
|
+
changes.push({
|
|
786
|
+
type: "type_upgrade",
|
|
787
|
+
table: tableName,
|
|
788
|
+
name: colName,
|
|
789
|
+
column: colDef,
|
|
790
|
+
mismatch: {
|
|
791
|
+
expected: expectedEngineType,
|
|
792
|
+
actual: dbCol.type
|
|
793
|
+
},
|
|
794
|
+
sql: `ALTER TABLE ${table} ALTER COLUMN ${column} TYPE ${targetType} USING ${column}::${targetType}`
|
|
795
|
+
});
|
|
796
|
+
} else changes.push({
|
|
797
|
+
type: "type_upgrade",
|
|
798
|
+
table: tableName,
|
|
799
|
+
name: colName,
|
|
800
|
+
column: colDef,
|
|
801
|
+
mismatch: {
|
|
802
|
+
expected: expectedEngineType,
|
|
803
|
+
actual: dbCol.type
|
|
804
|
+
},
|
|
805
|
+
advisory: {
|
|
806
|
+
severity: "warning",
|
|
807
|
+
message: `blocked: ${tableName}.${colName} is declared single-precision (${expectedEngineType}) but the live column is double-precision (${dbCol.type}). Narrowing loses precision, so this stays manual: confirm the narrower declaration is intentional, or widen the manifest field instead of the column.`,
|
|
808
|
+
suggestedSql: [`ALTER TABLE ${table} ALTER COLUMN ${column} TYPE ${expectedEngineType} USING ${column}::${expectedEngineType}`]
|
|
809
|
+
}
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
}
|
|
746
813
|
const isUuidTextEquivalent = this.isUuidTextEquivalentColumn(colName, colDef, normalizedExpected, normalizedActual);
|
|
747
|
-
|
|
814
|
+
const jsonUpgradeCandidate = this.engine === "postgres" && normalizedExpected === "JSON" && normalizedActual === "TEXT";
|
|
815
|
+
let jsonProbe;
|
|
816
|
+
if (jsonUpgradeCandidate) jsonProbe = await this.probeTextCastShape(tableName, colName, "jsonb");
|
|
817
|
+
const isJsonTextEquivalent = normalizedExpected === "TEXT" && normalizedActual === "JSON" || normalizedExpected === "JSON" && normalizedActual === "TEXT" && (!jsonUpgradeCandidate || jsonProbe?.status === "unavailable");
|
|
818
|
+
const timestamptzUpgradeCandidate = this.engine === "postgres" && this.normalizeType(colDef.type) === "TIMESTAMP" && normalizedActual === "TEXT" && normalizedExpected === "TIMESTAMPTZ" && !this.isCompatibleTypeUpgrade(colDef.type, dbCol.type);
|
|
819
|
+
let timestamptzProbe;
|
|
820
|
+
if (timestamptzUpgradeCandidate) timestamptzProbe = await this.probeTextCastShape(tableName, colName, "timestamptz");
|
|
821
|
+
if (normalizedExpected !== normalizedActual && !isUuidTextEquivalent && !isJsonTextEquivalent) {
|
|
748
822
|
typeDrifted = true;
|
|
749
|
-
|
|
823
|
+
const hasLiveDefault = dbCol.defaultValue !== null && dbCol.defaultValue !== void 0;
|
|
824
|
+
const conversionOptions = {
|
|
825
|
+
hasLiveDefault,
|
|
826
|
+
manifestDefaultValue: colDef.defaultValue
|
|
827
|
+
};
|
|
828
|
+
const liveOnlyDefaultNeedsRelaxOptIn = hasLiveDefault && colDef.defaultValue === void 0 && !this.options.relaxColumns;
|
|
829
|
+
if (jsonUpgradeCandidate && jsonProbe?.status === "clean" && liveOnlyDefaultNeedsRelaxOptIn) changes.push({
|
|
830
|
+
type: "type_upgrade",
|
|
831
|
+
table: tableName,
|
|
832
|
+
name: colName,
|
|
833
|
+
column: colDef,
|
|
834
|
+
mismatch: {
|
|
835
|
+
expected: colDef.type,
|
|
836
|
+
actual: dbCol.type
|
|
837
|
+
},
|
|
838
|
+
advisory: {
|
|
839
|
+
severity: "warning",
|
|
840
|
+
message: `blocked: ${tableName}.${colName} has a live default (${String(dbCol.defaultValue)}) the manifest no longer declares; converging to jsonb requires dropping it first. ${this.relaxHint("drop it as part of this conversion")}`,
|
|
841
|
+
suggestedSql: renderJsonbColumnConversion(tableName, colName, conversionOptions)
|
|
842
|
+
}
|
|
843
|
+
});
|
|
844
|
+
else if (jsonUpgradeCandidate && jsonProbe?.status === "clean") {
|
|
845
|
+
const statements = renderJsonbColumnConversion(tableName, colName, conversionOptions);
|
|
846
|
+
changes.push({
|
|
847
|
+
type: "type_upgrade",
|
|
848
|
+
table: tableName,
|
|
849
|
+
name: colName,
|
|
850
|
+
column: colDef,
|
|
851
|
+
mismatch: {
|
|
852
|
+
expected: colDef.type,
|
|
853
|
+
actual: dbCol.type
|
|
854
|
+
},
|
|
855
|
+
sql: statements[statements.length - 1],
|
|
856
|
+
sqlStatements: statements
|
|
857
|
+
});
|
|
858
|
+
} else if (jsonUpgradeCandidate && jsonProbe?.status === "dirty") changes.push({
|
|
859
|
+
type: "type_upgrade",
|
|
860
|
+
table: tableName,
|
|
861
|
+
name: colName,
|
|
862
|
+
column: colDef,
|
|
863
|
+
mismatch: {
|
|
864
|
+
expected: colDef.type,
|
|
865
|
+
actual: dbCol.type
|
|
866
|
+
},
|
|
867
|
+
advisory: {
|
|
868
|
+
severity: "warning",
|
|
869
|
+
message: `blocked: ${tableName}.${colName} is declared JSON but ${jsonProbe.count} live value(s) are not valid JSON (sample: ${jsonProbe.sample ? maskSampleValue(jsonProbe.sample) : "unavailable"}). Repair or clear the offending value(s), then rerun \`smrt db:migrate\`.`,
|
|
870
|
+
suggestedSql: renderJsonbColumnConversion(tableName, colName, conversionOptions)
|
|
871
|
+
}
|
|
872
|
+
});
|
|
873
|
+
else if (timestamptzUpgradeCandidate && timestamptzProbe?.status === "clean" && liveOnlyDefaultNeedsRelaxOptIn) changes.push({
|
|
874
|
+
type: "type_upgrade",
|
|
875
|
+
table: tableName,
|
|
876
|
+
name: colName,
|
|
877
|
+
column: colDef,
|
|
878
|
+
mismatch: {
|
|
879
|
+
expected: colDef.type,
|
|
880
|
+
actual: dbCol.type
|
|
881
|
+
},
|
|
882
|
+
advisory: {
|
|
883
|
+
severity: "warning",
|
|
884
|
+
message: `blocked: ${tableName}.${colName} has a live default (${String(dbCol.defaultValue)}) the manifest no longer declares; converging to timestamptz requires dropping it first. ${this.relaxHint("drop it as part of this conversion")}`,
|
|
885
|
+
suggestedSql: renderTimestamptzColumnConversion(tableName, colName, conversionOptions)
|
|
886
|
+
}
|
|
887
|
+
});
|
|
888
|
+
else if (timestamptzUpgradeCandidate && timestamptzProbe?.status === "clean") {
|
|
889
|
+
const statements = renderTimestamptzColumnConversion(tableName, colName, conversionOptions);
|
|
890
|
+
changes.push({
|
|
891
|
+
type: "type_upgrade",
|
|
892
|
+
table: tableName,
|
|
893
|
+
name: colName,
|
|
894
|
+
column: colDef,
|
|
895
|
+
mismatch: {
|
|
896
|
+
expected: colDef.type,
|
|
897
|
+
actual: dbCol.type
|
|
898
|
+
},
|
|
899
|
+
sql: statements[statements.length - 1],
|
|
900
|
+
sqlStatements: statements
|
|
901
|
+
});
|
|
902
|
+
} else if (timestamptzUpgradeCandidate && timestamptzProbe?.status === "dirty") changes.push({
|
|
903
|
+
type: "type_upgrade",
|
|
904
|
+
table: tableName,
|
|
905
|
+
name: colName,
|
|
906
|
+
column: colDef,
|
|
907
|
+
mismatch: {
|
|
908
|
+
expected: colDef.type,
|
|
909
|
+
actual: dbCol.type
|
|
910
|
+
},
|
|
911
|
+
advisory: {
|
|
912
|
+
severity: "warning",
|
|
913
|
+
message: `blocked: ${tableName}.${colName} is declared TIMESTAMP but ${timestamptzProbe.count} live value(s) do not parse as an unambiguous timestamp (sample: ${timestamptzProbe.sample ? maskSampleValue(timestamptzProbe.sample) : "unavailable"}). Repair the offending value(s), or confirm legacy naive wall-clock provenance with \`smrt db:migrate --postgres-timestamp-legacy-timezone=UTC\`, then rerun.`,
|
|
914
|
+
suggestedSql: renderTimestamptzColumnConversion(tableName, colName, conversionOptions)
|
|
915
|
+
}
|
|
916
|
+
});
|
|
917
|
+
else if (this.isCompatibleTypeUpgrade(colDef.type, dbCol.type)) {
|
|
750
918
|
const generatedSQL = this.generateTypeUpgradeSQL(tableName, colName, colDef, dbCol.type);
|
|
751
919
|
changes.push({
|
|
752
920
|
type: "type_upgrade",
|