arkormx 2.0.0-next.3 → 2.0.0-next.5

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/cli.mjs CHANGED
@@ -6,13 +6,13 @@ import { str } from "@h3ravel/support";
6
6
  import path, { dirname as dirname$1, extname as extname$1, join as join$1, relative } from "path";
7
7
  import { copyFileSync, existsSync as existsSync$1, mkdirSync as mkdirSync$1, readFileSync as readFileSync$1, readdirSync as readdirSync$1, rmSync as rmSync$1, writeFileSync as writeFileSync$1 } from "fs";
8
8
  import { AsyncLocalStorage } from "async_hooks";
9
+ import { createHash } from "node:crypto";
9
10
  import { createJiti } from "@rexxars/jiti";
10
11
  import { pathToFileURL } from "node:url";
11
12
  import { createRequire } from "module";
12
13
  import { fileURLToPath } from "url";
13
14
  import { Logger } from "@h3ravel/shared";
14
15
  import { Command, Kernel } from "@h3ravel/musket";
15
- import { createHash } from "node:crypto";
16
16
 
17
17
  //#region src/Exceptions/ArkormException.ts
18
18
  var ArkormException = class extends Error {
@@ -1436,6 +1436,374 @@ const applyMigrationRollbackToPrismaSchema = async (migration, options = {}) =>
1436
1436
  };
1437
1437
  };
1438
1438
 
1439
+ //#endregion
1440
+ //#region src/helpers/migration-history.ts
1441
+ const createEmptyAppliedMigrationsState = () => ({
1442
+ version: 1,
1443
+ migrations: [],
1444
+ runs: []
1445
+ });
1446
+ const supportsDatabaseMigrationState = (adapter) => {
1447
+ return typeof adapter?.readAppliedMigrationsState === "function" && typeof adapter?.writeAppliedMigrationsState === "function";
1448
+ };
1449
+ const resolveMigrationStateFilePath = (cwd, configuredPath) => {
1450
+ if (configuredPath && configuredPath.trim().length > 0) return resolve(configuredPath);
1451
+ return join(cwd, ".arkormx", "migrations.applied.json");
1452
+ };
1453
+ const buildMigrationIdentity = (filePath, className) => {
1454
+ const fileName = filePath.split("/").pop()?.split("\\").pop() ?? filePath;
1455
+ return `${fileName.slice(0, fileName.length - extname(fileName).length)}:${className}`;
1456
+ };
1457
+ const computeMigrationChecksum = (filePath) => {
1458
+ const source = readFileSync(filePath, "utf-8");
1459
+ return createHash("sha256").update(source).digest("hex");
1460
+ };
1461
+ const readAppliedMigrationsState = (stateFilePath) => {
1462
+ if (!existsSync(stateFilePath)) return createEmptyAppliedMigrationsState();
1463
+ try {
1464
+ const parsed = JSON.parse(readFileSync(stateFilePath, "utf-8"));
1465
+ if (!Array.isArray(parsed.migrations)) return createEmptyAppliedMigrationsState();
1466
+ return {
1467
+ version: 1,
1468
+ migrations: parsed.migrations.filter((migration) => {
1469
+ return typeof migration?.id === "string" && typeof migration?.file === "string" && typeof migration?.className === "string" && typeof migration?.appliedAt === "string" && (migration?.checksum === void 0 || typeof migration?.checksum === "string");
1470
+ }),
1471
+ runs: Array.isArray(parsed.runs) ? parsed.runs.filter((run) => {
1472
+ return typeof run?.id === "string" && typeof run?.appliedAt === "string" && Array.isArray(run?.migrationIds) && run.migrationIds.every((item) => typeof item === "string");
1473
+ }) : []
1474
+ };
1475
+ } catch {
1476
+ return createEmptyAppliedMigrationsState();
1477
+ }
1478
+ };
1479
+ const readAppliedMigrationsStateFromStore = async (adapter, stateFilePath) => {
1480
+ if (supportsDatabaseMigrationState(adapter)) return await adapter.readAppliedMigrationsState();
1481
+ return readAppliedMigrationsState(stateFilePath);
1482
+ };
1483
+ const writeAppliedMigrationsState = (stateFilePath, state) => {
1484
+ const directory = dirname(stateFilePath);
1485
+ if (!existsSync(directory)) mkdirSync(directory, { recursive: true });
1486
+ writeFileSync(stateFilePath, JSON.stringify(state, null, 2));
1487
+ };
1488
+ const writeAppliedMigrationsStateToStore = async (adapter, stateFilePath, state) => {
1489
+ if (supportsDatabaseMigrationState(adapter)) {
1490
+ await adapter.writeAppliedMigrationsState(state);
1491
+ return;
1492
+ }
1493
+ writeAppliedMigrationsState(stateFilePath, state);
1494
+ };
1495
+ const isMigrationApplied = (state, identity, checksum) => {
1496
+ const matched = state.migrations.find((migration) => migration.id === identity);
1497
+ if (!matched) return false;
1498
+ if (checksum && matched.checksum) return matched.checksum === checksum;
1499
+ if (checksum && !matched.checksum) return false;
1500
+ return true;
1501
+ };
1502
+ const findAppliedMigration = (state, identity) => {
1503
+ return state.migrations.find((migration) => migration.id === identity);
1504
+ };
1505
+ const markMigrationApplied = (state, entry) => {
1506
+ const next = state.migrations.filter((migration) => migration.id !== entry.id);
1507
+ next.push(entry);
1508
+ return {
1509
+ version: 1,
1510
+ migrations: next,
1511
+ runs: state.runs ?? []
1512
+ };
1513
+ };
1514
+ const removeAppliedMigration = (state, identity) => {
1515
+ return {
1516
+ version: 1,
1517
+ migrations: state.migrations.filter((migration) => migration.id !== identity),
1518
+ runs: (state.runs ?? []).map((run) => ({
1519
+ ...run,
1520
+ migrationIds: run.migrationIds.filter((id) => id !== identity)
1521
+ })).filter((run) => run.migrationIds.length > 0)
1522
+ };
1523
+ };
1524
+ const buildMigrationRunId = () => {
1525
+ return `run_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
1526
+ };
1527
+ const markMigrationRun = (state, run) => {
1528
+ const nextRuns = (state.runs ?? []).filter((existing) => existing.id !== run.id);
1529
+ nextRuns.push(run);
1530
+ return {
1531
+ version: 1,
1532
+ migrations: state.migrations,
1533
+ runs: nextRuns
1534
+ };
1535
+ };
1536
+ const getLastMigrationRun = (state) => {
1537
+ const runs = state.runs ?? [];
1538
+ if (runs.length === 0) return void 0;
1539
+ return runs.map((run, index) => ({
1540
+ run,
1541
+ index
1542
+ })).sort((left, right) => {
1543
+ const appliedAtOrder = right.run.appliedAt.localeCompare(left.run.appliedAt);
1544
+ if (appliedAtOrder !== 0) return appliedAtOrder;
1545
+ return right.index - left.index;
1546
+ })[0]?.run;
1547
+ };
1548
+ const getLatestAppliedMigrations = (state, steps) => {
1549
+ return state.migrations.map((migration, index) => ({
1550
+ migration,
1551
+ index
1552
+ })).sort((left, right) => {
1553
+ const appliedAtOrder = right.migration.appliedAt.localeCompare(left.migration.appliedAt);
1554
+ if (appliedAtOrder !== 0) return appliedAtOrder;
1555
+ return right.index - left.index;
1556
+ }).slice(0, Math.max(0, steps)).map((entry) => entry.migration);
1557
+ };
1558
+
1559
+ //#endregion
1560
+ //#region src/helpers/column-mappings.ts
1561
+ let cachedColumnMappingsPath;
1562
+ let cachedColumnMappingsState;
1563
+ const resolvePersistedMetadataFeatures = (features) => {
1564
+ return {
1565
+ persistedColumnMappings: features?.persistedColumnMappings !== false,
1566
+ persistedEnums: features?.persistedEnums !== false
1567
+ };
1568
+ };
1569
+ const createEmptyPersistedColumnMappingsState = () => ({
1570
+ version: 1,
1571
+ tables: {}
1572
+ });
1573
+ const resolveColumnMappingsFilePath = (cwd, configuredPath) => {
1574
+ if (configuredPath && configuredPath.trim().length > 0) return resolve(configuredPath);
1575
+ return join(cwd, ".arkormx", "column-mappings.json");
1576
+ };
1577
+ const normalizePersistedEnumValues = (values) => {
1578
+ if (!Array.isArray(values)) return [];
1579
+ return values.filter((value) => typeof value === "string" && value.trim().length > 0);
1580
+ };
1581
+ const normalizeLegacyTableColumns = (columns) => {
1582
+ return Object.entries(columns).reduce((mapped, [attribute, column]) => {
1583
+ if (attribute.trim().length === 0) return mapped;
1584
+ if (typeof column !== "string" || column.trim().length === 0) return mapped;
1585
+ mapped[attribute] = column;
1586
+ return mapped;
1587
+ }, {});
1588
+ };
1589
+ const normalizePersistedTableMetadata = (table) => {
1590
+ if (!table || typeof table !== "object" || Array.isArray(table)) return {
1591
+ columns: {},
1592
+ enums: {}
1593
+ };
1594
+ const candidate = table;
1595
+ if (!(Object.prototype.hasOwnProperty.call(candidate, "columns") || Object.prototype.hasOwnProperty.call(candidate, "enums"))) return {
1596
+ columns: normalizeLegacyTableColumns(candidate),
1597
+ enums: {}
1598
+ };
1599
+ return {
1600
+ columns: normalizeLegacyTableColumns(candidate.columns ?? {}),
1601
+ enums: Object.entries(candidate.enums ?? {}).reduce((all, [columnName, values]) => {
1602
+ if (columnName.trim().length === 0) return all;
1603
+ const normalizedValues = normalizePersistedEnumValues(values);
1604
+ if (normalizedValues.length > 0) all[columnName] = normalizedValues;
1605
+ return all;
1606
+ }, {})
1607
+ };
1608
+ };
1609
+ const normalizePersistedColumnMappingsState = (state) => {
1610
+ return {
1611
+ version: 1,
1612
+ tables: Object.entries(state?.tables ?? {}).reduce((all, [tableName, tableMetadata]) => {
1613
+ if (tableName.trim().length === 0) return all;
1614
+ const normalized = normalizePersistedTableMetadata(tableMetadata);
1615
+ if (Object.keys(normalized.columns).length > 0 || Object.keys(normalized.enums).length > 0) all[tableName] = normalized;
1616
+ return all;
1617
+ }, {})
1618
+ };
1619
+ };
1620
+ const buildPersistedFeatureDisabledError = (feature, table) => {
1621
+ return new ArkormException(`Table [${table}] requires ${feature === "persistedColumnMappings" ? "persisted column mappings" : "persisted enum metadata"}, but ${feature === "persistedColumnMappings" ? "features.persistedColumnMappings" : "features.persistedEnums"} is disabled in arkormx.config.*.`, {
1622
+ operation: "metadata.persisted",
1623
+ meta: {
1624
+ feature,
1625
+ table
1626
+ }
1627
+ });
1628
+ };
1629
+ const assertPersistedTableMetadataEnabled = (table, metadata, features, strict) => {
1630
+ if (!strict) return;
1631
+ if (!features.persistedColumnMappings && Object.keys(metadata.columns).length > 0) throw buildPersistedFeatureDisabledError("persistedColumnMappings", table);
1632
+ if (!features.persistedEnums && Object.keys(metadata.enums).length > 0) throw buildPersistedFeatureDisabledError("persistedEnums", table);
1633
+ };
1634
+ const buildEnumUnionType = (values) => {
1635
+ return values.map((value) => {
1636
+ return `'${value.replace(/'/g, String.raw`\'`)}'`;
1637
+ }).join(" | ");
1638
+ };
1639
+ const resetPersistedColumnMappingsCache = () => {
1640
+ cachedColumnMappingsPath = void 0;
1641
+ cachedColumnMappingsState = void 0;
1642
+ };
1643
+ const readPersistedColumnMappingsState = (filePath) => {
1644
+ if (cachedColumnMappingsPath === filePath && cachedColumnMappingsState) return cachedColumnMappingsState;
1645
+ if (!existsSync(filePath)) {
1646
+ const empty = createEmptyPersistedColumnMappingsState();
1647
+ cachedColumnMappingsPath = filePath;
1648
+ cachedColumnMappingsState = empty;
1649
+ return empty;
1650
+ }
1651
+ try {
1652
+ const normalized = normalizePersistedColumnMappingsState(JSON.parse(readFileSync(filePath, "utf-8")));
1653
+ cachedColumnMappingsPath = filePath;
1654
+ cachedColumnMappingsState = normalized;
1655
+ return normalized;
1656
+ } catch {
1657
+ const empty = createEmptyPersistedColumnMappingsState();
1658
+ cachedColumnMappingsPath = filePath;
1659
+ cachedColumnMappingsState = empty;
1660
+ return empty;
1661
+ }
1662
+ };
1663
+ const writePersistedColumnMappingsState = (filePath, state) => {
1664
+ const normalized = normalizePersistedColumnMappingsState(state);
1665
+ const directory = dirname(filePath);
1666
+ if (!existsSync(directory)) mkdirSync(directory, { recursive: true });
1667
+ writeFileSync(filePath, JSON.stringify(normalized, null, 2));
1668
+ cachedColumnMappingsPath = filePath;
1669
+ cachedColumnMappingsState = normalized;
1670
+ };
1671
+ const deletePersistedColumnMappingsState = (filePath) => {
1672
+ if (existsSync(filePath)) rmSync(filePath, { force: true });
1673
+ resetPersistedColumnMappingsCache();
1674
+ };
1675
+ const getPersistedTableMetadata = (table, options = {}) => {
1676
+ const metadata = readPersistedColumnMappingsState(resolveColumnMappingsFilePath(options.cwd ?? process.cwd(), options.configuredPath)).tables[table] ?? {
1677
+ columns: {},
1678
+ enums: {}
1679
+ };
1680
+ assertPersistedTableMetadataEnabled(table, metadata, options.features ?? resolvePersistedMetadataFeatures(), options.strict ?? false);
1681
+ return {
1682
+ columns: { ...metadata.columns },
1683
+ enums: Object.entries(metadata.enums).reduce((all, [columnName, values]) => {
1684
+ all[columnName] = [...values];
1685
+ return all;
1686
+ }, {})
1687
+ };
1688
+ };
1689
+ const applyMappedColumn = (tableColumns, column, features, table) => {
1690
+ if (typeof column.map === "string" && column.map.trim().length > 0 && column.map !== column.name) {
1691
+ if (!features.persistedColumnMappings) throw buildPersistedFeatureDisabledError("persistedColumnMappings", table);
1692
+ tableColumns[column.name] = column.map;
1693
+ return;
1694
+ }
1695
+ delete tableColumns[column.name];
1696
+ };
1697
+ const applyEnumColumn = (tableEnums, column, features, table) => {
1698
+ const values = column.enumValues ?? [];
1699
+ if (column.type === "enum" && values.length > 0) {
1700
+ if (!features.persistedEnums) throw buildPersistedFeatureDisabledError("persistedEnums", table);
1701
+ tableEnums[column.name] = [...values];
1702
+ return;
1703
+ }
1704
+ delete tableEnums[column.name];
1705
+ };
1706
+ const removePersistedColumnMetadata = (tableMetadata, columnName) => {
1707
+ delete tableMetadata.columns[columnName];
1708
+ delete tableMetadata.enums[columnName];
1709
+ Object.entries(tableMetadata.columns).forEach(([attribute, mappedColumn]) => {
1710
+ if (mappedColumn === columnName) delete tableMetadata.columns[attribute];
1711
+ });
1712
+ };
1713
+ const applyOperationsToPersistedColumnMappingsState = (state, operations, features = resolvePersistedMetadataFeatures()) => {
1714
+ const nextTables = Object.entries(state.tables).reduce((all, [table, metadata]) => {
1715
+ all[table] = {
1716
+ columns: { ...metadata.columns },
1717
+ enums: Object.entries(metadata.enums).reduce((nextEnums, [columnName, values]) => {
1718
+ nextEnums[columnName] = [...values];
1719
+ return nextEnums;
1720
+ }, {})
1721
+ };
1722
+ return all;
1723
+ }, {});
1724
+ operations.forEach((operation) => {
1725
+ if (operation.type === "createTable") {
1726
+ const tableMetadata = nextTables[operation.table] ?? {
1727
+ columns: {},
1728
+ enums: {}
1729
+ };
1730
+ operation.columns.forEach((column) => {
1731
+ applyMappedColumn(tableMetadata.columns, column, features, operation.table);
1732
+ applyEnumColumn(tableMetadata.enums, column, features, operation.table);
1733
+ });
1734
+ if (Object.keys(tableMetadata.columns).length > 0 || Object.keys(tableMetadata.enums).length > 0) nextTables[operation.table] = tableMetadata;
1735
+ else delete nextTables[operation.table];
1736
+ return;
1737
+ }
1738
+ if (operation.type === "alterTable") {
1739
+ const tableMetadata = nextTables[operation.table] ?? {
1740
+ columns: {},
1741
+ enums: {}
1742
+ };
1743
+ operation.addColumns.forEach((column) => {
1744
+ applyMappedColumn(tableMetadata.columns, column, features, operation.table);
1745
+ applyEnumColumn(tableMetadata.enums, column, features, operation.table);
1746
+ });
1747
+ operation.dropColumns.forEach((columnName) => {
1748
+ removePersistedColumnMetadata(tableMetadata, columnName);
1749
+ });
1750
+ if (Object.keys(tableMetadata.columns).length > 0 || Object.keys(tableMetadata.enums).length > 0) nextTables[operation.table] = tableMetadata;
1751
+ else delete nextTables[operation.table];
1752
+ return;
1753
+ }
1754
+ delete nextTables[operation.table];
1755
+ });
1756
+ return {
1757
+ version: 1,
1758
+ tables: nextTables
1759
+ };
1760
+ };
1761
+ const rebuildPersistedColumnMappingsState = async (state, availableMigrations, features = resolvePersistedMetadataFeatures()) => {
1762
+ const availableByIdentity = new Map(availableMigrations.map(([migrationClass, file]) => [buildMigrationIdentity(file, migrationClass.name), migrationClass]));
1763
+ let nextState = createEmptyPersistedColumnMappingsState();
1764
+ const orderedMigrations = state.migrations.map((migration, index) => ({
1765
+ migration,
1766
+ index
1767
+ })).sort((left, right) => {
1768
+ const appliedAtOrder = left.migration.appliedAt.localeCompare(right.migration.appliedAt);
1769
+ if (appliedAtOrder !== 0) return appliedAtOrder;
1770
+ return left.index - right.index;
1771
+ });
1772
+ for (const { migration } of orderedMigrations) {
1773
+ const migrationClass = availableByIdentity.get(migration.id);
1774
+ if (!migrationClass) throw new ArkormException(`Unable to rebuild persisted column mappings because migration [${migration.id}] could not be resolved from the current migration files.`, {
1775
+ operation: "migration.columnMappings",
1776
+ meta: {
1777
+ migrationId: migration.id,
1778
+ file: migration.file,
1779
+ className: migration.className
1780
+ }
1781
+ });
1782
+ const operations = await getMigrationPlan(migrationClass, "up");
1783
+ nextState = applyOperationsToPersistedColumnMappingsState(nextState, operations, features);
1784
+ }
1785
+ return nextState;
1786
+ };
1787
+ const syncPersistedColumnMappingsFromState = async (cwd, state, availableMigrations, features = resolvePersistedMetadataFeatures()) => {
1788
+ const filePath = resolveColumnMappingsFilePath(cwd);
1789
+ const nextState = await rebuildPersistedColumnMappingsState(state, availableMigrations, features);
1790
+ if (Object.keys(nextState.tables).length === 0) {
1791
+ deletePersistedColumnMappingsState(filePath);
1792
+ return;
1793
+ }
1794
+ writePersistedColumnMappingsState(filePath, nextState);
1795
+ };
1796
+ const validatePersistedMetadataFeaturesForMigrations = async (migrations, features = resolvePersistedMetadataFeatures()) => {
1797
+ let nextState = createEmptyPersistedColumnMappingsState();
1798
+ for (const [migrationClass] of migrations) {
1799
+ const operations = await getMigrationPlan(migrationClass, "up");
1800
+ nextState = applyOperationsToPersistedColumnMappingsState(nextState, operations, features);
1801
+ }
1802
+ };
1803
+ const getPersistedEnumTsType = (values) => {
1804
+ return buildEnumUnionType(values);
1805
+ };
1806
+
1439
1807
  //#endregion
1440
1808
  //#region src/helpers/runtime-module-loader.ts
1441
1809
  var RuntimeModuleLoader = class {
@@ -1463,6 +1831,10 @@ const resolveDefaultStubsPath = () => {
1463
1831
  return path.join(process.cwd(), "stubs");
1464
1832
  };
1465
1833
  const baseConfig = {
1834
+ features: {
1835
+ persistedColumnMappings: true,
1836
+ persistedEnums: true
1837
+ },
1466
1838
  paths: {
1467
1839
  stubs: resolveDefaultStubsPath(),
1468
1840
  seeders: path.join(process.cwd(), "database", "seeders"),
@@ -1475,6 +1847,7 @@ const baseConfig = {
1475
1847
  };
1476
1848
  const userConfig = {
1477
1849
  ...baseConfig,
1850
+ features: { ...baseConfig.features ?? {} },
1478
1851
  paths: { ...baseConfig.paths ?? {} }
1479
1852
  };
1480
1853
  let runtimeConfigLoaded = false;
@@ -1497,6 +1870,15 @@ const mergePathConfig = (paths) => {
1497
1870
  ...incoming
1498
1871
  };
1499
1872
  };
1873
+ const mergeFeatureConfig = (features) => {
1874
+ const defaults = baseConfig.features ?? {};
1875
+ const current = userConfig.features ?? {};
1876
+ return {
1877
+ ...defaults,
1878
+ ...current,
1879
+ ...features ?? {}
1880
+ };
1881
+ };
1500
1882
  const bindAdapterToModels = (adapter, models) => {
1501
1883
  models.forEach((model) => {
1502
1884
  model.setAdapter(adapter);
@@ -1522,6 +1904,7 @@ const getUserConfig = (key) => {
1522
1904
  const configureArkormRuntime = (prisma, options = {}) => {
1523
1905
  const nextConfig = {
1524
1906
  ...userConfig,
1907
+ features: mergeFeatureConfig(options.features),
1525
1908
  paths: mergePathConfig(options.paths)
1526
1909
  };
1527
1910
  nextConfig.prisma = prisma;
@@ -1566,6 +1949,7 @@ const resolveAndApplyConfig = (imported) => {
1566
1949
  configureArkormRuntime(config.prisma, {
1567
1950
  adapter: config.adapter,
1568
1951
  boot: config.boot,
1952
+ features: config.features,
1569
1953
  pagination: config.pagination,
1570
1954
  paths: config.paths,
1571
1955
  outputExt: config.outputExt
@@ -2157,6 +2541,35 @@ var CliApp = class {
2157
2541
  skipped
2158
2542
  };
2159
2543
  }
2544
+ applyPersistedFieldMetadata(structure) {
2545
+ const persistedMetadata = getPersistedTableMetadata(structure.table, {
2546
+ features: resolvePersistedMetadataFeatures(this.getConfig("features")),
2547
+ strict: true
2548
+ });
2549
+ if (Object.keys(persistedMetadata.columns).length === 0 && Object.keys(persistedMetadata.enums).length === 0) return structure;
2550
+ const attributesByColumn = Object.entries(persistedMetadata.columns).reduce((all, [attribute, column]) => {
2551
+ all[column] = attribute;
2552
+ return all;
2553
+ }, {});
2554
+ return {
2555
+ ...structure,
2556
+ fields: structure.fields.map((field) => {
2557
+ const logicalName = attributesByColumn[field.name] ?? field.name;
2558
+ const enumValues = persistedMetadata.enums[logicalName] ?? persistedMetadata.enums[field.name];
2559
+ if (!enumValues || enumValues.length === 0) return {
2560
+ ...field,
2561
+ name: logicalName
2562
+ };
2563
+ const enumType = getPersistedEnumTsType(enumValues);
2564
+ const isArray = /^Array<.+>$/.test(field.type);
2565
+ return {
2566
+ ...field,
2567
+ name: logicalName,
2568
+ type: isArray ? `Array<${enumType}>` : enumType
2569
+ };
2570
+ })
2571
+ };
2572
+ }
2160
2573
  /**
2161
2574
  * Parse Prisma enum definitions from a schema and return their member names.
2162
2575
  *
@@ -2315,7 +2728,10 @@ var CliApp = class {
2315
2728
  return all;
2316
2729
  }, /* @__PURE__ */ new Map());
2317
2730
  const discovered = await adapter.introspectModels({ tables: [...new Set([...sources.values()].map((source) => source.table))] });
2318
- const structuresByTable = new Map(discovered.map((model) => [model.table, model]));
2731
+ const structuresByTable = new Map(discovered.map((model) => {
2732
+ const enriched = this.applyPersistedFieldMetadata(model);
2733
+ return [enriched.table, enriched];
2734
+ }));
2319
2735
  const result = this.syncModelFiles(modelFiles, (filePath) => {
2320
2736
  const parsed = sources.get(filePath);
2321
2737
  return parsed ? structuresByTable.get(parsed.table) : void 0;
@@ -2529,126 +2945,6 @@ var MakeSeederCommand = class extends Command {
2529
2945
  }
2530
2946
  };
2531
2947
 
2532
- //#endregion
2533
- //#region src/helpers/migration-history.ts
2534
- const createEmptyAppliedMigrationsState = () => ({
2535
- version: 1,
2536
- migrations: [],
2537
- runs: []
2538
- });
2539
- const supportsDatabaseMigrationState = (adapter) => {
2540
- return typeof adapter?.readAppliedMigrationsState === "function" && typeof adapter?.writeAppliedMigrationsState === "function";
2541
- };
2542
- const resolveMigrationStateFilePath = (cwd, configuredPath) => {
2543
- if (configuredPath && configuredPath.trim().length > 0) return resolve(configuredPath);
2544
- return join(cwd, ".arkormx", "migrations.applied.json");
2545
- };
2546
- const buildMigrationIdentity = (filePath, className) => {
2547
- const fileName = filePath.split("/").pop()?.split("\\").pop() ?? filePath;
2548
- return `${fileName.slice(0, fileName.length - extname(fileName).length)}:${className}`;
2549
- };
2550
- const computeMigrationChecksum = (filePath) => {
2551
- const source = readFileSync(filePath, "utf-8");
2552
- return createHash("sha256").update(source).digest("hex");
2553
- };
2554
- const readAppliedMigrationsState = (stateFilePath) => {
2555
- if (!existsSync(stateFilePath)) return createEmptyAppliedMigrationsState();
2556
- try {
2557
- const parsed = JSON.parse(readFileSync(stateFilePath, "utf-8"));
2558
- if (!Array.isArray(parsed.migrations)) return createEmptyAppliedMigrationsState();
2559
- return {
2560
- version: 1,
2561
- migrations: parsed.migrations.filter((migration) => {
2562
- return typeof migration?.id === "string" && typeof migration?.file === "string" && typeof migration?.className === "string" && typeof migration?.appliedAt === "string" && (migration?.checksum === void 0 || typeof migration?.checksum === "string");
2563
- }),
2564
- runs: Array.isArray(parsed.runs) ? parsed.runs.filter((run) => {
2565
- return typeof run?.id === "string" && typeof run?.appliedAt === "string" && Array.isArray(run?.migrationIds) && run.migrationIds.every((item) => typeof item === "string");
2566
- }) : []
2567
- };
2568
- } catch {
2569
- return createEmptyAppliedMigrationsState();
2570
- }
2571
- };
2572
- const readAppliedMigrationsStateFromStore = async (adapter, stateFilePath) => {
2573
- if (supportsDatabaseMigrationState(adapter)) return await adapter.readAppliedMigrationsState();
2574
- return readAppliedMigrationsState(stateFilePath);
2575
- };
2576
- const writeAppliedMigrationsState = (stateFilePath, state) => {
2577
- const directory = dirname(stateFilePath);
2578
- if (!existsSync(directory)) mkdirSync(directory, { recursive: true });
2579
- writeFileSync(stateFilePath, JSON.stringify(state, null, 2));
2580
- };
2581
- const writeAppliedMigrationsStateToStore = async (adapter, stateFilePath, state) => {
2582
- if (supportsDatabaseMigrationState(adapter)) {
2583
- await adapter.writeAppliedMigrationsState(state);
2584
- return;
2585
- }
2586
- writeAppliedMigrationsState(stateFilePath, state);
2587
- };
2588
- const isMigrationApplied = (state, identity, checksum) => {
2589
- const matched = state.migrations.find((migration) => migration.id === identity);
2590
- if (!matched) return false;
2591
- if (checksum && matched.checksum) return matched.checksum === checksum;
2592
- if (checksum && !matched.checksum) return false;
2593
- return true;
2594
- };
2595
- const findAppliedMigration = (state, identity) => {
2596
- return state.migrations.find((migration) => migration.id === identity);
2597
- };
2598
- const markMigrationApplied = (state, entry) => {
2599
- const next = state.migrations.filter((migration) => migration.id !== entry.id);
2600
- next.push(entry);
2601
- return {
2602
- version: 1,
2603
- migrations: next,
2604
- runs: state.runs ?? []
2605
- };
2606
- };
2607
- const removeAppliedMigration = (state, identity) => {
2608
- return {
2609
- version: 1,
2610
- migrations: state.migrations.filter((migration) => migration.id !== identity),
2611
- runs: (state.runs ?? []).map((run) => ({
2612
- ...run,
2613
- migrationIds: run.migrationIds.filter((id) => id !== identity)
2614
- })).filter((run) => run.migrationIds.length > 0)
2615
- };
2616
- };
2617
- const buildMigrationRunId = () => {
2618
- return `run_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
2619
- };
2620
- const markMigrationRun = (state, run) => {
2621
- const nextRuns = (state.runs ?? []).filter((existing) => existing.id !== run.id);
2622
- nextRuns.push(run);
2623
- return {
2624
- version: 1,
2625
- migrations: state.migrations,
2626
- runs: nextRuns
2627
- };
2628
- };
2629
- const getLastMigrationRun = (state) => {
2630
- const runs = state.runs ?? [];
2631
- if (runs.length === 0) return void 0;
2632
- return runs.map((run, index) => ({
2633
- run,
2634
- index
2635
- })).sort((left, right) => {
2636
- const appliedAtOrder = right.run.appliedAt.localeCompare(left.run.appliedAt);
2637
- if (appliedAtOrder !== 0) return appliedAtOrder;
2638
- return right.index - left.index;
2639
- })[0]?.run;
2640
- };
2641
- const getLatestAppliedMigrations = (state, steps) => {
2642
- return state.migrations.map((migration, index) => ({
2643
- migration,
2644
- index
2645
- })).sort((left, right) => {
2646
- const appliedAtOrder = right.migration.appliedAt.localeCompare(left.migration.appliedAt);
2647
- if (appliedAtOrder !== 0) return appliedAtOrder;
2648
- return right.index - left.index;
2649
- }).slice(0, Math.max(0, steps)).map((entry) => entry.migration);
2650
- };
2651
-
2652
2948
  //#endregion
2653
2949
  //#region src/database/Migration.ts
2654
2950
  const MIGRATION_BRAND = Symbol.for("arkormx.migration");
@@ -2706,6 +3002,7 @@ var MigrateCommand = class extends Command {
2706
3002
  let appliedState = await readAppliedMigrationsStateFromStore(this.app.getConfig("adapter"), stateFilePath);
2707
3003
  const adapter = this.app.getConfig("adapter");
2708
3004
  const useDatabaseMigrations = supportsDatabaseMigrationExecution(adapter);
3005
+ const persistedFeatures = resolvePersistedMetadataFeatures(this.app.getConfig("features"));
2709
3006
  const skipped = [];
2710
3007
  const changed = [];
2711
3008
  const pending = classes.filter(([migrationClass, file]) => {
@@ -2724,9 +3021,21 @@ var MigrateCommand = class extends Command {
2724
3021
  this.success(this.app.splitLogger("Changed", `${file} (${migrationClass.name})`));
2725
3022
  });
2726
3023
  if (pending.length === 0) {
3024
+ if (appliedState) try {
3025
+ await syncPersistedColumnMappingsFromState(process.cwd(), appliedState, await this.loadAllMigrations(migrationsDir), persistedFeatures);
3026
+ } catch (error) {
3027
+ this.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
3028
+ return;
3029
+ }
2727
3030
  this.success("No pending migration classes to apply.");
2728
3031
  return;
2729
3032
  }
3033
+ if (useDatabaseMigrations) try {
3034
+ await validatePersistedMetadataFeaturesForMigrations(pending, persistedFeatures);
3035
+ } catch (error) {
3036
+ this.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
3037
+ return;
3038
+ }
2730
3039
  for (const [MigrationClassItem] of pending) {
2731
3040
  if (useDatabaseMigrations) {
2732
3041
  await applyMigrationToDatabase(adapter, MigrationClassItem);
@@ -2756,6 +3065,12 @@ var MigrateCommand = class extends Command {
2756
3065
  migrationIds: runAppliedIds
2757
3066
  });
2758
3067
  await writeAppliedMigrationsStateToStore(adapter, stateFilePath, appliedState);
3068
+ try {
3069
+ await syncPersistedColumnMappingsFromState(process.cwd(), appliedState, await this.loadAllMigrations(migrationsDir), persistedFeatures);
3070
+ } catch (error) {
3071
+ this.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
3072
+ return;
3073
+ }
2759
3074
  }
2760
3075
  if (!useDatabaseMigrations && !this.option("skip-generate")) runPrismaCommand(["generate"], process.cwd());
2761
3076
  if (!useDatabaseMigrations && !this.option("skip-migrate")) if (this.option("deploy")) runPrismaCommand(["migrate", "deploy"], process.cwd());
@@ -2835,12 +3150,24 @@ var MigrateFreshCommand = class extends Command {
2835
3150
  if (!existsSync(migrationsDir)) return void this.error(`Error: Migrations directory not found: ${this.app.formatPathForLog(configuredMigrationsDir)}`);
2836
3151
  const adapter = this.app.getConfig("adapter");
2837
3152
  const useDatabaseMigrations = supportsDatabaseMigrationExecution(adapter);
3153
+ const persistedFeatures = resolvePersistedMetadataFeatures(this.app.getConfig("features"));
2838
3154
  const schemaPath = this.option("schema") ? resolve(String(this.option("schema"))) : join(process.cwd(), "prisma", "schema.prisma");
2839
3155
  const stateFilePath = resolveMigrationStateFilePath(process.cwd(), this.option("state-file") ? String(this.option("state-file")) : void 0);
2840
3156
  const migrations = await this.loadAllMigrations(migrationsDir);
2841
3157
  if (migrations.length === 0) return void this.error("Error: No migration classes found to run.");
2842
- if (supportsDatabaseReset(adapter)) await adapter.resetDatabase();
2843
- else {
3158
+ if (useDatabaseMigrations) try {
3159
+ await validatePersistedMetadataFeaturesForMigrations(migrations, persistedFeatures);
3160
+ } catch (error) {
3161
+ this.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
3162
+ return;
3163
+ }
3164
+ if (useDatabaseMigrations) {
3165
+ if (!supportsDatabaseReset(adapter)) {
3166
+ this.error("Error: Your current database adapter does not support database reset.");
3167
+ return;
3168
+ }
3169
+ await adapter.resetDatabase();
3170
+ } else {
2844
3171
  if (!existsSync(schemaPath)) return void this.error(`Error: Prisma schema file not found: ${this.app.formatPathForLog(schemaPath)}`);
2845
3172
  writeFileSync(schemaPath, stripPrismaSchemaModelsAndEnums(readFileSync(schemaPath, "utf-8")));
2846
3173
  }
@@ -2869,6 +3196,12 @@ var MigrateFreshCommand = class extends Command {
2869
3196
  migrationIds: appliedState.migrations.map((migration) => migration.id)
2870
3197
  });
2871
3198
  await writeAppliedMigrationsStateToStore(adapter, stateFilePath, appliedState);
3199
+ try {
3200
+ await syncPersistedColumnMappingsFromState(process.cwd(), appliedState, migrations, persistedFeatures);
3201
+ } catch (error) {
3202
+ this.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
3203
+ return;
3204
+ }
2872
3205
  if (!useDatabaseMigrations) {
2873
3206
  const schemaArgs = this.option("schema") ? ["--schema", schemaPath] : [];
2874
3207
  if (!this.option("skip-generate")) runPrismaCommand(["generate", ...schemaArgs], process.cwd());
@@ -2927,6 +3260,7 @@ var MigrateRollbackCommand = class extends Command {
2927
3260
  const stateFilePath = resolveMigrationStateFilePath(process.cwd(), this.option("state-file") ? String(this.option("state-file")) : void 0);
2928
3261
  const adapter = this.app.getConfig("adapter");
2929
3262
  const useDatabaseMigrations = supportsDatabaseMigrationExecution(adapter);
3263
+ const persistedFeatures = resolvePersistedMetadataFeatures(this.app.getConfig("features"));
2930
3264
  let appliedState = await readAppliedMigrationsStateFromStore(adapter, stateFilePath);
2931
3265
  const stepOption = this.option("step");
2932
3266
  const stepCount = stepOption == null ? void 0 : Number(stepOption);
@@ -2964,6 +3298,12 @@ var MigrateRollbackCommand = class extends Command {
2964
3298
  appliedState = removeAppliedMigration(appliedState, identity);
2965
3299
  }
2966
3300
  await writeAppliedMigrationsStateToStore(adapter, stateFilePath, appliedState);
3301
+ try {
3302
+ await syncPersistedColumnMappingsFromState(process.cwd(), appliedState, available, persistedFeatures);
3303
+ } catch (error) {
3304
+ this.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
3305
+ return;
3306
+ }
2967
3307
  if (!useDatabaseMigrations && !this.option("skip-generate")) runPrismaCommand(["generate"], process.cwd());
2968
3308
  if (!useDatabaseMigrations && !this.option("skip-migrate")) if (this.option("deploy")) runPrismaCommand(["migrate", "deploy"], process.cwd());
2969
3309
  else runPrismaCommand([
@@ -3014,6 +3354,7 @@ var MigrationHistoryCommand = class extends Command {
3014
3354
  if (this.option("delete")) {
3015
3355
  if (usesDatabaseState) {
3016
3356
  await adapter.writeAppliedMigrationsState(createEmptyAppliedMigrationsState());
3357
+ deletePersistedColumnMappingsState(resolveColumnMappingsFilePath(process.cwd()));
3017
3358
  this.success("Deleted tracked migration state from database.");
3018
3359
  return;
3019
3360
  }
@@ -3022,11 +3363,13 @@ var MigrationHistoryCommand = class extends Command {
3022
3363
  return;
3023
3364
  }
3024
3365
  rmSync(stateFilePath);
3366
+ deletePersistedColumnMappingsState(resolveColumnMappingsFilePath(process.cwd()));
3025
3367
  this.success(`Deleted migration state file: ${this.app.formatPathForLog(stateFilePath)}`);
3026
3368
  return;
3027
3369
  }
3028
3370
  if (this.option("reset")) {
3029
3371
  await writeAppliedMigrationsStateToStore(adapter, stateFilePath, createEmptyAppliedMigrationsState());
3372
+ deletePersistedColumnMappingsState(resolveColumnMappingsFilePath(process.cwd()));
3030
3373
  this.success(usesDatabaseState ? "Reset migration state in database." : `Reset migration state: ${this.app.formatPathForLog(stateFilePath)}`);
3031
3374
  return;
3032
3375
  }
@@ -3060,10 +3403,16 @@ var ModelsSyncCommand = class extends Command {
3060
3403
  description = "Sync model declare attributes from the active adapter when supported, otherwise fall back to the Prisma schema";
3061
3404
  async handle() {
3062
3405
  this.app.command = this;
3063
- const result = await this.app.syncModels({
3064
- schemaPath: this.option("schema") ? resolve(String(this.option("schema"))) : void 0,
3065
- modelsDir: this.option("models") ? resolve(String(this.option("models"))) : void 0
3066
- });
3406
+ let result;
3407
+ try {
3408
+ result = await this.app.syncModels({
3409
+ schemaPath: this.option("schema") ? resolve(String(this.option("schema"))) : void 0,
3410
+ modelsDir: this.option("models") ? resolve(String(this.option("models"))) : void 0
3411
+ });
3412
+ } catch (error) {
3413
+ this.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
3414
+ return;
3415
+ }
3067
3416
  const updatedLines = result.updated.length === 0 ? [this.app.splitLogger("Updated", "none")] : result.updated.map((path) => this.app.splitLogger("Updated", path));
3068
3417
  this.success("SUCCESS: Model sync completed with the following results:");
3069
3418
  [