arkormx 2.0.0-next.2 → 2.0.0-next.21

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
@@ -1,18 +1,18 @@
1
1
  #!/usr/bin/env node
2
- import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
2
+ import { AsyncLocalStorage } from "async_hooks";
3
3
  import { dirname, extname, join, resolve } from "node:path";
4
+ import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
5
+ import { createHash, randomUUID } from "node:crypto";
4
6
  import { spawnSync } from "node:child_process";
5
7
  import { str } from "@h3ravel/support";
6
- import path, { dirname as dirname$1, extname as extname$1, join as join$1, relative } from "path";
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
- import { AsyncLocalStorage } from "async_hooks";
9
8
  import { createJiti } from "@rexxars/jiti";
10
9
  import { pathToFileURL } from "node:url";
11
10
  import { createRequire } from "module";
11
+ 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";
12
12
  import { fileURLToPath } from "url";
13
+ import path, { dirname as dirname$1, extname as extname$1, join as join$1, relative } from "path";
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 {
@@ -55,6 +55,188 @@ var ArkormException = class extends Error {
55
55
  }
56
56
  };
57
57
 
58
+ //#endregion
59
+ //#region src/Exceptions/MissingDelegateException.ts
60
+ var MissingDelegateException = class extends ArkormException {
61
+ constructor(message, context = {}) {
62
+ super(message, {
63
+ code: "MISSING_DELEGATE",
64
+ ...context
65
+ });
66
+ this.name = "MissingDelegateException";
67
+ }
68
+ };
69
+
70
+ //#endregion
71
+ //#region src/Exceptions/QueryExecutionException.ts
72
+ var QueryExecutionException = class extends ArkormException {
73
+ inspection;
74
+ constructor(message = "Database query execution failed.", context = {}) {
75
+ super(message, {
76
+ code: "QUERY_EXECUTION_FAILED",
77
+ ...context,
78
+ meta: {
79
+ ...context.meta ?? {},
80
+ ...context.inspection ? { inspection: context.inspection } : {}
81
+ }
82
+ });
83
+ this.name = "QueryExecutionException";
84
+ this.inspection = context.inspection;
85
+ }
86
+ getInspection() {
87
+ return this.inspection;
88
+ }
89
+ };
90
+
91
+ //#endregion
92
+ //#region src/Exceptions/UnsupportedAdapterFeatureException.ts
93
+ var UnsupportedAdapterFeatureException = class extends ArkormException {
94
+ constructor(message, context = {}) {
95
+ super(message, {
96
+ code: "UNSUPPORTED_ADAPTER_FEATURE",
97
+ ...context
98
+ });
99
+ this.name = "UnsupportedAdapterFeatureException";
100
+ }
101
+ };
102
+
103
+ //#endregion
104
+ //#region src/helpers/migration-history.ts
105
+ const createEmptyAppliedMigrationsState = () => ({
106
+ version: 1,
107
+ migrations: [],
108
+ runs: []
109
+ });
110
+ const supportsDatabaseMigrationState = (adapter) => {
111
+ return typeof adapter?.readAppliedMigrationsState === "function" && typeof adapter?.writeAppliedMigrationsState === "function";
112
+ };
113
+ const resolveMigrationStateFilePath = (cwd, configuredPath) => {
114
+ if (configuredPath && configuredPath.trim().length > 0) return resolve(configuredPath);
115
+ return join(cwd, ".arkormx", "migrations.applied.json");
116
+ };
117
+ const buildMigrationIdentity = (filePath, className) => {
118
+ const fileName = filePath.split("/").pop()?.split("\\").pop() ?? filePath;
119
+ return `${fileName.slice(0, fileName.length - extname(fileName).length)}:${className}`;
120
+ };
121
+ const computeMigrationChecksum = (filePath) => {
122
+ const source = readFileSync(filePath, "utf-8");
123
+ return createHash("sha256").update(source).digest("hex");
124
+ };
125
+ const readAppliedMigrationsState = (stateFilePath) => {
126
+ if (!existsSync(stateFilePath)) return createEmptyAppliedMigrationsState();
127
+ try {
128
+ const parsed = JSON.parse(readFileSync(stateFilePath, "utf-8"));
129
+ if (!Array.isArray(parsed.migrations)) return createEmptyAppliedMigrationsState();
130
+ return {
131
+ version: 1,
132
+ migrations: parsed.migrations.filter((migration) => {
133
+ 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");
134
+ }),
135
+ runs: Array.isArray(parsed.runs) ? parsed.runs.filter((run) => {
136
+ return typeof run?.id === "string" && typeof run?.appliedAt === "string" && Array.isArray(run?.migrationIds) && run.migrationIds.every((item) => typeof item === "string");
137
+ }) : []
138
+ };
139
+ } catch {
140
+ return createEmptyAppliedMigrationsState();
141
+ }
142
+ };
143
+ const readAppliedMigrationsStateFromStore = async (adapter, stateFilePath) => {
144
+ if (supportsDatabaseMigrationState(adapter)) return await adapter.readAppliedMigrationsState();
145
+ return readAppliedMigrationsState(stateFilePath);
146
+ };
147
+ const writeAppliedMigrationsState = (stateFilePath, state) => {
148
+ const directory = dirname(stateFilePath);
149
+ if (!existsSync(directory)) mkdirSync(directory, { recursive: true });
150
+ writeFileSync(stateFilePath, JSON.stringify(state, null, 2));
151
+ };
152
+ const writeAppliedMigrationsStateToStore = async (adapter, stateFilePath, state) => {
153
+ if (supportsDatabaseMigrationState(adapter)) {
154
+ await adapter.writeAppliedMigrationsState(state);
155
+ return;
156
+ }
157
+ writeAppliedMigrationsState(stateFilePath, state);
158
+ };
159
+ const isMigrationApplied = (state, identity, checksum) => {
160
+ const matched = state.migrations.find((migration) => migration.id === identity);
161
+ if (!matched) return false;
162
+ if (checksum && matched.checksum) return matched.checksum === checksum;
163
+ if (checksum && !matched.checksum) return false;
164
+ return true;
165
+ };
166
+ const findAppliedMigration = (state, identity) => {
167
+ return state.migrations.find((migration) => migration.id === identity);
168
+ };
169
+ const markMigrationApplied = (state, entry) => {
170
+ const next = state.migrations.filter((migration) => migration.id !== entry.id);
171
+ next.push(entry);
172
+ return {
173
+ version: 1,
174
+ migrations: next,
175
+ runs: state.runs ?? []
176
+ };
177
+ };
178
+ const removeAppliedMigration = (state, identity) => {
179
+ return {
180
+ version: 1,
181
+ migrations: state.migrations.filter((migration) => migration.id !== identity),
182
+ runs: (state.runs ?? []).map((run) => ({
183
+ ...run,
184
+ migrationIds: run.migrationIds.filter((id) => id !== identity)
185
+ })).filter((run) => run.migrationIds.length > 0)
186
+ };
187
+ };
188
+ const buildMigrationRunId = () => {
189
+ return `run_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
190
+ };
191
+ const markMigrationRun = (state, run) => {
192
+ const nextRuns = (state.runs ?? []).filter((existing) => existing.id !== run.id);
193
+ nextRuns.push(run);
194
+ return {
195
+ version: 1,
196
+ migrations: state.migrations,
197
+ runs: nextRuns
198
+ };
199
+ };
200
+ const getLastMigrationRun = (state) => {
201
+ const runs = state.runs ?? [];
202
+ if (runs.length === 0) return void 0;
203
+ return runs.map((run, index) => ({
204
+ run,
205
+ index
206
+ })).sort((left, right) => {
207
+ const appliedAtOrder = right.run.appliedAt.localeCompare(left.run.appliedAt);
208
+ if (appliedAtOrder !== 0) return appliedAtOrder;
209
+ return right.index - left.index;
210
+ })[0]?.run;
211
+ };
212
+ const getLatestAppliedMigrations = (state, steps) => {
213
+ return state.migrations.map((migration, index) => ({
214
+ migration,
215
+ index
216
+ })).sort((left, right) => {
217
+ const appliedAtOrder = right.migration.appliedAt.localeCompare(left.migration.appliedAt);
218
+ if (appliedAtOrder !== 0) return appliedAtOrder;
219
+ return right.index - left.index;
220
+ }).slice(0, Math.max(0, steps)).map((entry) => entry.migration);
221
+ };
222
+
223
+ //#endregion
224
+ //#region src/helpers/PrimaryKeyGenerationPlanner.ts
225
+ var PrimaryKeyGenerationPlanner = class {
226
+ static plan(column) {
227
+ if (!column.primary || column.default !== void 0) return void 0;
228
+ if (column.type === "uuid" || column.type === "string") return {
229
+ strategy: "uuid",
230
+ prismaDefault: "@default(uuid())",
231
+ databaseDefault: column.type === "uuid" ? "gen_random_uuid()" : "gen_random_uuid()::text",
232
+ runtimeFactory: "uuid"
233
+ };
234
+ }
235
+ static generate(generation) {
236
+ if (generation?.runtimeFactory === "uuid") return randomUUID();
237
+ }
238
+ };
239
+
58
240
  //#endregion
59
241
  //#region src/database/ForeignKeyBuilder.ts
60
242
  /**
@@ -248,6 +430,7 @@ var TableBuilder = class {
248
430
  column.primary = true;
249
431
  if (typeof config.autoIncrement === "boolean") column.autoIncrement = config.autoIncrement;
250
432
  if (Object.prototype.hasOwnProperty.call(config, "default")) column.default = config.default;
433
+ column.primaryKeyGeneration = PrimaryKeyGenerationPlanner.plan(column);
251
434
  return this;
252
435
  }
253
436
  /**
@@ -611,8 +794,11 @@ var TableBuilder = class {
611
794
  autoIncrement: options.autoIncrement,
612
795
  after: options.after,
613
796
  default: options.default,
614
- updatedAt: options.updatedAt
797
+ updatedAt: options.updatedAt,
798
+ primaryKeyGeneration: options.primaryKeyGeneration
615
799
  });
800
+ const column = this.columns[this.columns.length - 1];
801
+ column.primaryKeyGeneration = PrimaryKeyGenerationPlanner.plan(column);
616
802
  this.latestColumnName = name;
617
803
  return this;
618
804
  }
@@ -867,7 +1053,7 @@ const buildFieldLine = (column) => {
867
1053
  const primary = column.primary ? " @id" : "";
868
1054
  const mapped = typeof column.map === "string" && column.map.trim().length > 0 ? ` @map("${column.map.replace(/"/g, "\\\"")}")` : "";
869
1055
  const updatedAt = column.updatedAt ? " @updatedAt" : "";
870
- const defaultValue = column.type === "enum" ? formatEnumDefaultValue(column.default) : formatDefaultValue(column.default) ?? (column.type === "uuid" && column.primary ? "@default(uuid())" : void 0);
1056
+ const defaultValue = column.type === "enum" ? formatEnumDefaultValue(column.default) : column.primaryKeyGeneration?.prismaDefault ?? formatDefaultValue(column.default);
871
1057
  const defaultSuffix = defaultValue ? ` ${defaultValue}` : "";
872
1058
  return ` ${column.name} ${scalar}${nullable}${primary}${unique}${defaultSuffix}${updatedAt}${mapped}`;
873
1059
  };
@@ -1371,6 +1557,28 @@ const getMigrationPlan = async (migration, direction = "up") => {
1371
1557
  else await instance.down(schema);
1372
1558
  return schema.getOperations();
1373
1559
  };
1560
+ const supportsDatabaseMigrationExecution = (adapter) => {
1561
+ return typeof adapter?.executeSchemaOperations === "function";
1562
+ };
1563
+ const supportsDatabaseReset = (adapter) => {
1564
+ return typeof adapter?.resetDatabase === "function";
1565
+ };
1566
+ const stripPrismaSchemaModelsAndEnums = (schema) => {
1567
+ const stripped = schema.replace(PRISMA_MODEL_REGEX, "").replace(PRISMA_ENUM_REGEX, "").replace(/\n{3,}/g, "\n\n").trimEnd();
1568
+ return stripped.length > 0 ? `${stripped}\n` : "";
1569
+ };
1570
+ const applyMigrationToDatabase = async (adapter, migration) => {
1571
+ if (!supportsDatabaseMigrationExecution(adapter)) throw new ArkormException("The configured adapter does not support database-backed migration execution.");
1572
+ const operations = await getMigrationPlan(migration, "up");
1573
+ await adapter.executeSchemaOperations(operations);
1574
+ return { operations };
1575
+ };
1576
+ const applyMigrationRollbackToDatabase = async (adapter, migration) => {
1577
+ if (!supportsDatabaseMigrationExecution(adapter)) throw new ArkormException("The configured adapter does not support database-backed migration execution.");
1578
+ const operations = await getMigrationPlan(migration, "down");
1579
+ await adapter.executeSchemaOperations(operations);
1580
+ return { operations };
1581
+ };
1374
1582
  /**
1375
1583
  * Apply the schema operations defined in a migration to a Prisma schema
1376
1584
  * file, updating the file on disk if specified, and return the updated
@@ -1414,6 +1622,321 @@ const applyMigrationRollbackToPrismaSchema = async (migration, options = {}) =>
1414
1622
  };
1415
1623
  };
1416
1624
 
1625
+ //#endregion
1626
+ //#region src/helpers/column-mappings.ts
1627
+ let cachedColumnMappingsPath;
1628
+ let cachedColumnMappingsState;
1629
+ const resolvePersistedMetadataFeatures = (features) => {
1630
+ return {
1631
+ persistedColumnMappings: features?.persistedColumnMappings !== false,
1632
+ persistedEnums: features?.persistedEnums !== false
1633
+ };
1634
+ };
1635
+ const createEmptyPersistedColumnMappingsState = () => ({
1636
+ version: 1,
1637
+ tables: {}
1638
+ });
1639
+ const resolveColumnMappingsFilePath = (cwd, configuredPath) => {
1640
+ if (configuredPath && configuredPath.trim().length > 0) return resolve(configuredPath);
1641
+ return join(cwd, ".arkormx", "column-mappings.json");
1642
+ };
1643
+ const normalizePersistedEnumValues = (values) => {
1644
+ if (!Array.isArray(values)) return [];
1645
+ return values.filter((value) => typeof value === "string" && value.trim().length > 0);
1646
+ };
1647
+ const normalizeLegacyTableColumns = (columns) => {
1648
+ return Object.entries(columns).reduce((mapped, [attribute, column]) => {
1649
+ if (attribute.trim().length === 0) return mapped;
1650
+ if (typeof column !== "string" || column.trim().length === 0) return mapped;
1651
+ mapped[attribute] = column;
1652
+ return mapped;
1653
+ }, {});
1654
+ };
1655
+ const normalizePersistedTableMetadata = (table) => {
1656
+ if (!table || typeof table !== "object" || Array.isArray(table)) return {
1657
+ columns: {},
1658
+ enums: {}
1659
+ };
1660
+ const candidate = table;
1661
+ if (!(Object.prototype.hasOwnProperty.call(candidate, "columns") || Object.prototype.hasOwnProperty.call(candidate, "enums") || Object.prototype.hasOwnProperty.call(candidate, "primaryKeyGeneration") || Object.prototype.hasOwnProperty.call(candidate, "timestampColumns"))) return {
1662
+ columns: normalizeLegacyTableColumns(candidate),
1663
+ enums: {}
1664
+ };
1665
+ return {
1666
+ columns: normalizeLegacyTableColumns(candidate.columns ?? {}),
1667
+ enums: Object.entries(candidate.enums ?? {}).reduce((all, [columnName, values]) => {
1668
+ if (columnName.trim().length === 0) return all;
1669
+ const normalizedValues = normalizePersistedEnumValues(values);
1670
+ if (normalizedValues.length > 0) all[columnName] = normalizedValues;
1671
+ return all;
1672
+ }, {}),
1673
+ primaryKeyGeneration: normalizePersistedPrimaryKeyGeneration(candidate.primaryKeyGeneration),
1674
+ timestampColumns: normalizePersistedTimestampColumns(candidate.timestampColumns)
1675
+ };
1676
+ };
1677
+ const normalizePersistedPrimaryKeyGeneration = (value) => {
1678
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
1679
+ const candidate = value;
1680
+ if (candidate.strategy !== "uuid" || typeof candidate.column !== "string" || candidate.column.trim().length === 0) return void 0;
1681
+ return {
1682
+ column: candidate.column,
1683
+ strategy: "uuid",
1684
+ prismaDefault: typeof candidate.prismaDefault === "string" && candidate.prismaDefault.trim().length > 0 ? candidate.prismaDefault : void 0,
1685
+ databaseDefault: typeof candidate.databaseDefault === "string" && candidate.databaseDefault.trim().length > 0 ? candidate.databaseDefault : void 0,
1686
+ runtimeFactory: candidate.runtimeFactory === "uuid" ? "uuid" : void 0
1687
+ };
1688
+ };
1689
+ const normalizePersistedTimestampColumns = (value) => {
1690
+ if (!Array.isArray(value)) return void 0;
1691
+ const columns = value.reduce((all, entry) => {
1692
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) return all;
1693
+ const candidate = entry;
1694
+ if (typeof candidate.column !== "string" || candidate.column.trim().length === 0) return all;
1695
+ const normalized = { column: candidate.column };
1696
+ if (candidate.default === "now()") normalized.default = "now()";
1697
+ if (candidate.updatedAt === true) normalized.updatedAt = true;
1698
+ if (!normalized.default && !normalized.updatedAt) return all;
1699
+ all.push(normalized);
1700
+ return all;
1701
+ }, []);
1702
+ return columns.length > 0 ? columns : void 0;
1703
+ };
1704
+ const normalizePersistedColumnMappingsState = (state) => {
1705
+ return {
1706
+ version: 1,
1707
+ tables: Object.entries(state?.tables ?? {}).reduce((all, [tableName, tableMetadata]) => {
1708
+ if (tableName.trim().length === 0) return all;
1709
+ const normalized = normalizePersistedTableMetadata(tableMetadata);
1710
+ if (Object.keys(normalized.columns).length > 0 || Object.keys(normalized.enums).length > 0 || normalized.primaryKeyGeneration || normalized.timestampColumns?.length) all[tableName] = normalized;
1711
+ return all;
1712
+ }, {})
1713
+ };
1714
+ };
1715
+ const buildPersistedFeatureDisabledError = (feature, table) => {
1716
+ 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.*.`, {
1717
+ operation: "metadata.persisted",
1718
+ meta: {
1719
+ feature,
1720
+ table
1721
+ }
1722
+ });
1723
+ };
1724
+ const assertPersistedTableMetadataEnabled = (table, metadata, features, strict) => {
1725
+ if (!strict) return;
1726
+ if (!features.persistedColumnMappings && Object.keys(metadata.columns).length > 0) throw buildPersistedFeatureDisabledError("persistedColumnMappings", table);
1727
+ if (!features.persistedEnums && Object.keys(metadata.enums).length > 0) throw buildPersistedFeatureDisabledError("persistedEnums", table);
1728
+ };
1729
+ const buildEnumUnionType = (values) => {
1730
+ return values.map((value) => {
1731
+ return `'${value.replace(/'/g, String.raw`\'`)}'`;
1732
+ }).join(" | ");
1733
+ };
1734
+ const resetPersistedColumnMappingsCache = () => {
1735
+ cachedColumnMappingsPath = void 0;
1736
+ cachedColumnMappingsState = void 0;
1737
+ };
1738
+ const readPersistedColumnMappingsState = (filePath) => {
1739
+ if (cachedColumnMappingsPath === filePath && cachedColumnMappingsState) return cachedColumnMappingsState;
1740
+ if (!existsSync(filePath)) {
1741
+ const empty = createEmptyPersistedColumnMappingsState();
1742
+ cachedColumnMappingsPath = filePath;
1743
+ cachedColumnMappingsState = empty;
1744
+ return empty;
1745
+ }
1746
+ try {
1747
+ const normalized = normalizePersistedColumnMappingsState(JSON.parse(readFileSync(filePath, "utf-8")));
1748
+ cachedColumnMappingsPath = filePath;
1749
+ cachedColumnMappingsState = normalized;
1750
+ return normalized;
1751
+ } catch {
1752
+ const empty = createEmptyPersistedColumnMappingsState();
1753
+ cachedColumnMappingsPath = filePath;
1754
+ cachedColumnMappingsState = empty;
1755
+ return empty;
1756
+ }
1757
+ };
1758
+ const writePersistedColumnMappingsState = (filePath, state) => {
1759
+ const normalized = normalizePersistedColumnMappingsState(state);
1760
+ const directory = dirname(filePath);
1761
+ if (!existsSync(directory)) mkdirSync(directory, { recursive: true });
1762
+ writeFileSync(filePath, JSON.stringify(normalized, null, 2));
1763
+ cachedColumnMappingsPath = filePath;
1764
+ cachedColumnMappingsState = normalized;
1765
+ };
1766
+ const deletePersistedColumnMappingsState = (filePath) => {
1767
+ if (existsSync(filePath)) rmSync(filePath, { force: true });
1768
+ resetPersistedColumnMappingsCache();
1769
+ };
1770
+ const getPersistedTableMetadata = (table, options = {}) => {
1771
+ const metadata = readPersistedColumnMappingsState(resolveColumnMappingsFilePath(options.cwd ?? process.cwd(), options.configuredPath)).tables[table] ?? {
1772
+ columns: {},
1773
+ enums: {}
1774
+ };
1775
+ assertPersistedTableMetadataEnabled(table, metadata, options.features ?? resolvePersistedMetadataFeatures(), options.strict ?? false);
1776
+ return {
1777
+ columns: { ...metadata.columns },
1778
+ enums: Object.entries(metadata.enums).reduce((all, [columnName, values]) => {
1779
+ all[columnName] = [...values];
1780
+ return all;
1781
+ }, {}),
1782
+ primaryKeyGeneration: metadata.primaryKeyGeneration ? { ...metadata.primaryKeyGeneration } : void 0,
1783
+ timestampColumns: metadata.timestampColumns?.map((column) => ({ ...column }))
1784
+ };
1785
+ };
1786
+ const applyMappedColumn = (tableColumns, column, features, table) => {
1787
+ if (typeof column.map === "string" && column.map.trim().length > 0 && column.map !== column.name) {
1788
+ if (!features.persistedColumnMappings) throw buildPersistedFeatureDisabledError("persistedColumnMappings", table);
1789
+ tableColumns[column.name] = column.map;
1790
+ return;
1791
+ }
1792
+ delete tableColumns[column.name];
1793
+ };
1794
+ const applyEnumColumn = (tableEnums, column, features, table) => {
1795
+ const values = column.enumValues ?? [];
1796
+ if (column.type === "enum" && values.length > 0) {
1797
+ if (!features.persistedEnums) throw buildPersistedFeatureDisabledError("persistedEnums", table);
1798
+ tableEnums[column.name] = [...values];
1799
+ return;
1800
+ }
1801
+ delete tableEnums[column.name];
1802
+ };
1803
+ const removePersistedColumnMetadata = (tableMetadata, columnName) => {
1804
+ delete tableMetadata.columns[columnName];
1805
+ delete tableMetadata.enums[columnName];
1806
+ Object.entries(tableMetadata.columns).forEach(([attribute, mappedColumn]) => {
1807
+ if (mappedColumn === columnName) delete tableMetadata.columns[attribute];
1808
+ });
1809
+ if (tableMetadata.primaryKeyGeneration?.column === columnName) delete tableMetadata.primaryKeyGeneration;
1810
+ if (tableMetadata.timestampColumns) {
1811
+ tableMetadata.timestampColumns = tableMetadata.timestampColumns.filter((column) => column.column !== columnName);
1812
+ if (tableMetadata.timestampColumns.length === 0) delete tableMetadata.timestampColumns;
1813
+ }
1814
+ };
1815
+ const applyPrimaryKeyGeneration = (tableMetadata, column) => {
1816
+ if (!column.primary || !column.primaryKeyGeneration) {
1817
+ if (tableMetadata.primaryKeyGeneration?.column === column.name) delete tableMetadata.primaryKeyGeneration;
1818
+ return;
1819
+ }
1820
+ tableMetadata.primaryKeyGeneration = {
1821
+ column: column.name,
1822
+ ...column.primaryKeyGeneration
1823
+ };
1824
+ };
1825
+ const applyTimestampColumn = (tableMetadata, column) => {
1826
+ if (column.type !== "timestamp" || column.default !== "now()" && column.updatedAt !== true) {
1827
+ if (tableMetadata.timestampColumns) {
1828
+ tableMetadata.timestampColumns = tableMetadata.timestampColumns.filter((entry) => entry.column !== column.name);
1829
+ if (tableMetadata.timestampColumns.length === 0) delete tableMetadata.timestampColumns;
1830
+ }
1831
+ return;
1832
+ }
1833
+ const nextColumn = {
1834
+ column: column.name,
1835
+ ...column.default === "now()" ? { default: "now()" } : {},
1836
+ ...column.updatedAt ? { updatedAt: true } : {}
1837
+ };
1838
+ tableMetadata.timestampColumns = [...(tableMetadata.timestampColumns ?? []).filter((entry) => entry.column !== column.name), nextColumn];
1839
+ };
1840
+ const applyOperationsToPersistedColumnMappingsState = (state, operations, features = resolvePersistedMetadataFeatures()) => {
1841
+ const nextTables = Object.entries(state.tables).reduce((all, [table, metadata]) => {
1842
+ all[table] = {
1843
+ columns: { ...metadata.columns },
1844
+ enums: Object.entries(metadata.enums).reduce((nextEnums, [columnName, values]) => {
1845
+ nextEnums[columnName] = [...values];
1846
+ return nextEnums;
1847
+ }, {}),
1848
+ primaryKeyGeneration: metadata.primaryKeyGeneration ? { ...metadata.primaryKeyGeneration } : void 0,
1849
+ timestampColumns: metadata.timestampColumns?.map((column) => ({ ...column }))
1850
+ };
1851
+ return all;
1852
+ }, {});
1853
+ operations.forEach((operation) => {
1854
+ if (operation.type === "createTable") {
1855
+ const tableMetadata = nextTables[operation.table] ?? {
1856
+ columns: {},
1857
+ enums: {}
1858
+ };
1859
+ operation.columns.forEach((column) => {
1860
+ applyMappedColumn(tableMetadata.columns, column, features, operation.table);
1861
+ applyEnumColumn(tableMetadata.enums, column, features, operation.table);
1862
+ applyPrimaryKeyGeneration(tableMetadata, column);
1863
+ applyTimestampColumn(tableMetadata, column);
1864
+ });
1865
+ if (Object.keys(tableMetadata.columns).length > 0 || Object.keys(tableMetadata.enums).length > 0 || tableMetadata.primaryKeyGeneration || tableMetadata.timestampColumns?.length) nextTables[operation.table] = tableMetadata;
1866
+ else delete nextTables[operation.table];
1867
+ return;
1868
+ }
1869
+ if (operation.type === "alterTable") {
1870
+ const tableMetadata = nextTables[operation.table] ?? {
1871
+ columns: {},
1872
+ enums: {}
1873
+ };
1874
+ operation.addColumns.forEach((column) => {
1875
+ applyMappedColumn(tableMetadata.columns, column, features, operation.table);
1876
+ applyEnumColumn(tableMetadata.enums, column, features, operation.table);
1877
+ applyPrimaryKeyGeneration(tableMetadata, column);
1878
+ applyTimestampColumn(tableMetadata, column);
1879
+ });
1880
+ operation.dropColumns.forEach((columnName) => {
1881
+ removePersistedColumnMetadata(tableMetadata, columnName);
1882
+ });
1883
+ if (Object.keys(tableMetadata.columns).length > 0 || Object.keys(tableMetadata.enums).length > 0 || tableMetadata.primaryKeyGeneration || tableMetadata.timestampColumns?.length) nextTables[operation.table] = tableMetadata;
1884
+ else delete nextTables[operation.table];
1885
+ return;
1886
+ }
1887
+ delete nextTables[operation.table];
1888
+ });
1889
+ return {
1890
+ version: 1,
1891
+ tables: nextTables
1892
+ };
1893
+ };
1894
+ const rebuildPersistedColumnMappingsState = async (state, availableMigrations, features = resolvePersistedMetadataFeatures()) => {
1895
+ const availableByIdentity = new Map(availableMigrations.map(([migrationClass, file]) => [buildMigrationIdentity(file, migrationClass.name), migrationClass]));
1896
+ let nextState = createEmptyPersistedColumnMappingsState();
1897
+ const orderedMigrations = state.migrations.map((migration, index) => ({
1898
+ migration,
1899
+ index
1900
+ })).sort((left, right) => {
1901
+ const appliedAtOrder = left.migration.appliedAt.localeCompare(right.migration.appliedAt);
1902
+ if (appliedAtOrder !== 0) return appliedAtOrder;
1903
+ return left.index - right.index;
1904
+ });
1905
+ for (const { migration } of orderedMigrations) {
1906
+ const migrationClass = availableByIdentity.get(migration.id);
1907
+ if (!migrationClass) throw new ArkormException(`Unable to rebuild persisted column mappings because migration [${migration.id}] could not be resolved from the current migration files.`, {
1908
+ operation: "migration.columnMappings",
1909
+ meta: {
1910
+ migrationId: migration.id,
1911
+ file: migration.file,
1912
+ className: migration.className
1913
+ }
1914
+ });
1915
+ const operations = await getMigrationPlan(migrationClass, "up");
1916
+ nextState = applyOperationsToPersistedColumnMappingsState(nextState, operations, features);
1917
+ }
1918
+ return nextState;
1919
+ };
1920
+ const syncPersistedColumnMappingsFromState = async (cwd, state, availableMigrations, features = resolvePersistedMetadataFeatures()) => {
1921
+ const filePath = resolveColumnMappingsFilePath(cwd);
1922
+ const nextState = await rebuildPersistedColumnMappingsState(state, availableMigrations, features);
1923
+ if (Object.keys(nextState.tables).length === 0) {
1924
+ deletePersistedColumnMappingsState(filePath);
1925
+ return;
1926
+ }
1927
+ writePersistedColumnMappingsState(filePath, nextState);
1928
+ };
1929
+ const validatePersistedMetadataFeaturesForMigrations = async (migrations, features = resolvePersistedMetadataFeatures()) => {
1930
+ let nextState = createEmptyPersistedColumnMappingsState();
1931
+ for (const [migrationClass] of migrations) {
1932
+ const operations = await getMigrationPlan(migrationClass, "up");
1933
+ nextState = applyOperationsToPersistedColumnMappingsState(nextState, operations, features);
1934
+ }
1935
+ };
1936
+ const getPersistedEnumTsType = (values) => {
1937
+ return buildEnumUnionType(values);
1938
+ };
1939
+
1417
1940
  //#endregion
1418
1941
  //#region src/helpers/runtime-module-loader.ts
1419
1942
  var RuntimeModuleLoader = class {
@@ -1441,6 +1964,10 @@ const resolveDefaultStubsPath = () => {
1441
1964
  return path.join(process.cwd(), "stubs");
1442
1965
  };
1443
1966
  const baseConfig = {
1967
+ features: {
1968
+ persistedColumnMappings: true,
1969
+ persistedEnums: true
1970
+ },
1444
1971
  paths: {
1445
1972
  stubs: resolveDefaultStubsPath(),
1446
1973
  seeders: path.join(process.cwd(), "database", "seeders"),
@@ -1453,6 +1980,7 @@ const baseConfig = {
1453
1980
  };
1454
1981
  const userConfig = {
1455
1982
  ...baseConfig,
1983
+ features: { ...baseConfig.features ?? {} },
1456
1984
  paths: { ...baseConfig.paths ?? {} }
1457
1985
  };
1458
1986
  let runtimeConfigLoaded = false;
@@ -1461,7 +1989,27 @@ let runtimeClientResolver;
1461
1989
  let runtimeAdapter;
1462
1990
  let runtimePaginationURLDriverFactory;
1463
1991
  let runtimePaginationCurrentPageResolver;
1992
+ let runtimeDebugHandler;
1464
1993
  const transactionClientStorage = new AsyncLocalStorage();
1994
+ const defaultDebugHandler = (event) => {
1995
+ const prefix = `[arkorm:${event.adapter}] ${event.operation}${event.target ? ` [${event.target}]` : ""}`;
1996
+ const payload = {
1997
+ phase: event.phase,
1998
+ durationMs: event.durationMs,
1999
+ inspection: event.inspection ?? void 0,
2000
+ meta: event.meta,
2001
+ error: event.error
2002
+ };
2003
+ if (event.phase === "error") {
2004
+ console.error(prefix, payload);
2005
+ return;
2006
+ }
2007
+ console.debug(prefix, payload);
2008
+ };
2009
+ const resolveDebugHandler = (debug) => {
2010
+ if (debug === true) return defaultDebugHandler;
2011
+ return typeof debug === "function" ? debug : void 0;
2012
+ };
1465
2013
  const mergePathConfig = (paths) => {
1466
2014
  const defaults = baseConfig.paths ?? {};
1467
2015
  const current = userConfig.paths ?? {};
@@ -1475,6 +2023,15 @@ const mergePathConfig = (paths) => {
1475
2023
  ...incoming
1476
2024
  };
1477
2025
  };
2026
+ const mergeFeatureConfig = (features) => {
2027
+ const defaults = baseConfig.features ?? {};
2028
+ const current = userConfig.features ?? {};
2029
+ return {
2030
+ ...defaults,
2031
+ ...current,
2032
+ ...features ?? {}
2033
+ };
2034
+ };
1478
2035
  const bindAdapterToModels = (adapter, models) => {
1479
2036
  models.forEach((model) => {
1480
2037
  model.setAdapter(adapter);
@@ -1500,18 +2057,21 @@ const getUserConfig = (key) => {
1500
2057
  const configureArkormRuntime = (prisma, options = {}) => {
1501
2058
  const nextConfig = {
1502
2059
  ...userConfig,
2060
+ features: mergeFeatureConfig(options.features),
1503
2061
  paths: mergePathConfig(options.paths)
1504
2062
  };
1505
2063
  nextConfig.prisma = prisma;
1506
2064
  if (options.pagination !== void 0) nextConfig.pagination = options.pagination;
1507
2065
  if (options.adapter !== void 0) nextConfig.adapter = options.adapter;
1508
2066
  if (options.boot !== void 0) nextConfig.boot = options.boot;
2067
+ if (options.debug !== void 0) nextConfig.debug = options.debug;
1509
2068
  if (options.outputExt !== void 0) nextConfig.outputExt = options.outputExt;
1510
2069
  Object.assign(userConfig, { ...nextConfig });
1511
2070
  runtimeClientResolver = prisma;
1512
2071
  runtimeAdapter = options.adapter;
1513
2072
  runtimePaginationURLDriverFactory = nextConfig.pagination?.urlDriver;
1514
2073
  runtimePaginationCurrentPageResolver = nextConfig.pagination?.resolveCurrentPage;
2074
+ runtimeDebugHandler = resolveDebugHandler(nextConfig.debug);
1515
2075
  options.boot?.({
1516
2076
  prisma: resolveClient(prisma),
1517
2077
  bindAdapter: bindAdapterToModels
@@ -1544,6 +2104,8 @@ const resolveAndApplyConfig = (imported) => {
1544
2104
  configureArkormRuntime(config.prisma, {
1545
2105
  adapter: config.adapter,
1546
2106
  boot: config.boot,
2107
+ debug: config.debug,
2108
+ features: config.features,
1547
2109
  pagination: config.pagination,
1548
2110
  paths: config.paths,
1549
2111
  outputExt: config.outputExt
@@ -1601,8 +2163,497 @@ const loadArkormConfig = async () => {
1601
2163
  const getDefaultStubsPath = () => {
1602
2164
  return resolveDefaultStubsPath();
1603
2165
  };
2166
+ const getRuntimeDebugHandler = () => {
2167
+ if (!runtimeConfigLoaded) loadRuntimeConfigSync();
2168
+ return runtimeDebugHandler;
2169
+ };
2170
+ const emitRuntimeDebugEvent = (event) => {
2171
+ getRuntimeDebugHandler()?.(event);
2172
+ };
2173
+ /**
2174
+ * Check if a given value is a Prisma delegate-like object
2175
+ * by verifying the presence of common delegate methods.
2176
+ *
2177
+ * @param value The value to check.
2178
+ * @returns True if the value is a Prisma delegate-like object, false otherwise.
2179
+ */
2180
+ const isDelegateLike = (value) => {
2181
+ if (!value || typeof value !== "object") return false;
2182
+ const candidate = value;
2183
+ return [
2184
+ "findMany",
2185
+ "findFirst",
2186
+ "create",
2187
+ "update",
2188
+ "delete",
2189
+ "count"
2190
+ ].every((method) => typeof candidate[method] === "function");
2191
+ };
1604
2192
  loadArkormConfig();
1605
2193
 
2194
+ //#endregion
2195
+ //#region src/helpers/prisma.ts
2196
+ /**
2197
+ * Infer the Prisma delegate name for a given model name using a simple convention.
2198
+ *
2199
+ * @param modelName The name of the model to infer the delegate name for.
2200
+ * @returns The inferred Prisma delegate name.
2201
+ */
2202
+ function inferDelegateName(modelName) {
2203
+ return `${modelName.charAt(0).toLowerCase()}${modelName.slice(1)}s`;
2204
+ }
2205
+
2206
+ //#endregion
2207
+ //#region src/adapters/PrismaDatabaseAdapter.ts
2208
+ /**
2209
+ * Database adapter implementation for Prisma, allowing Arkorm to execute queries using Prisma
2210
+ * as the underlying query builder and executor.
2211
+ *
2212
+ * @author Legacy (3m1n3nc3)
2213
+ * @since 2.0.0-next.0
2214
+ */
2215
+ var PrismaDatabaseAdapter = class PrismaDatabaseAdapter {
2216
+ capabilities;
2217
+ delegates;
2218
+ constructor(prisma, mapping = {}) {
2219
+ this.prisma = prisma;
2220
+ this.mapping = mapping;
2221
+ this.delegates = Object.entries(prisma).reduce((accumulator, [key, value]) => {
2222
+ if (!isDelegateLike(value)) return accumulator;
2223
+ accumulator[key] = value;
2224
+ return accumulator;
2225
+ }, {});
2226
+ this.capabilities = {
2227
+ transactions: this.hasTransactionSupport(prisma),
2228
+ insertMany: Object.values(this.delegates).some((delegate) => typeof delegate.createMany === "function"),
2229
+ upsert: false,
2230
+ updateMany: Object.values(this.delegates).some((delegate) => typeof delegate.updateMany === "function"),
2231
+ deleteMany: false,
2232
+ exists: true,
2233
+ relationLoads: false,
2234
+ relationAggregates: false,
2235
+ relationFilters: false,
2236
+ rawWhere: false,
2237
+ returning: false
2238
+ };
2239
+ }
2240
+ hasTransactionSupport(client) {
2241
+ return Boolean(client) && typeof client.$transaction === "function";
2242
+ }
2243
+ normalizeCandidate(value) {
2244
+ return value.trim();
2245
+ }
2246
+ unique(values) {
2247
+ return [...new Set(values.filter(Boolean))];
2248
+ }
2249
+ runtimeModelTypeToTs(typeName, kind, enumValues) {
2250
+ if (kind === "enum" && enumValues && enumValues.length > 0) return enumValues.map((value) => `'${value.replace(/'/g, "\\'")}'`).join(" | ");
2251
+ switch (typeName) {
2252
+ case "Int":
2253
+ case "Float":
2254
+ case "Decimal":
2255
+ case "BigInt": return "number";
2256
+ case "Boolean": return "boolean";
2257
+ case "DateTime": return "Date";
2258
+ case "Json": return "Record<string, unknown> | unknown[]";
2259
+ case "Bytes": return "Uint8Array";
2260
+ case "String":
2261
+ case "UUID": return "string";
2262
+ default: return "string";
2263
+ }
2264
+ }
2265
+ getRuntimeDataModel() {
2266
+ const runtimeDataModel = this.prisma._runtimeDataModel;
2267
+ if (runtimeDataModel && typeof runtimeDataModel === "object") return runtimeDataModel;
2268
+ return null;
2269
+ }
2270
+ toQuerySelect(columns) {
2271
+ if (!columns || columns.length === 0) return void 0;
2272
+ return columns.reduce((select, column) => {
2273
+ select[column.column] = true;
2274
+ return select;
2275
+ }, {});
2276
+ }
2277
+ toQueryOrderBy(orderBy) {
2278
+ if (!orderBy || orderBy.length === 0) return void 0;
2279
+ return orderBy.map((entry) => ({ [entry.column]: entry.direction }));
2280
+ }
2281
+ toComparisonWhere(condition) {
2282
+ if (condition.operator === "is-null") return { [condition.column]: null };
2283
+ if (condition.operator === "is-not-null") return { [condition.column]: { not: null } };
2284
+ if (condition.operator === "=") return { [condition.column]: condition.value };
2285
+ if (condition.operator === "!=") return { [condition.column]: { not: condition.value } };
2286
+ if (condition.operator === ">") return { [condition.column]: { gt: condition.value } };
2287
+ if (condition.operator === ">=") return { [condition.column]: { gte: condition.value } };
2288
+ if (condition.operator === "<") return { [condition.column]: { lt: condition.value } };
2289
+ if (condition.operator === "<=") return { [condition.column]: { lte: condition.value } };
2290
+ if (condition.operator === "in") return { [condition.column]: { in: Array.isArray(condition.value) ? condition.value : [condition.value] } };
2291
+ if (condition.operator === "not-in") return { [condition.column]: { notIn: Array.isArray(condition.value) ? condition.value : [condition.value] } };
2292
+ if (condition.operator === "contains") return { [condition.column]: { contains: condition.value } };
2293
+ if (condition.operator === "starts-with") return { [condition.column]: { startsWith: condition.value } };
2294
+ return { [condition.column]: { endsWith: condition.value } };
2295
+ }
2296
+ toQueryWhere(condition) {
2297
+ if (!condition) return void 0;
2298
+ if (condition.type === "comparison") return this.toComparisonWhere(condition);
2299
+ if (condition.type === "group") {
2300
+ const group = condition;
2301
+ const grouped = group.conditions.map((entry) => this.toQueryWhere(entry)).filter((entry) => Boolean(entry));
2302
+ if (grouped.length === 0) return void 0;
2303
+ return group.operator === "and" ? { AND: grouped } : { OR: grouped };
2304
+ }
2305
+ if (condition.type === "not") {
2306
+ const notCondition = condition;
2307
+ const nested = this.toQueryWhere(notCondition.condition);
2308
+ if (!nested) return void 0;
2309
+ return { NOT: nested };
2310
+ }
2311
+ throw new UnsupportedAdapterFeatureException("Raw where clauses are not supported by the Prisma compatibility adapter.", {
2312
+ operation: "adapter.where",
2313
+ meta: {
2314
+ feature: "rawWhere",
2315
+ sql: condition.sql
2316
+ }
2317
+ });
2318
+ }
2319
+ buildFindArgs(spec) {
2320
+ return {
2321
+ include: this.toQueryInclude(spec.relationLoads),
2322
+ where: this.toQueryWhere(spec.where),
2323
+ orderBy: this.toQueryOrderBy(spec.orderBy),
2324
+ select: this.toQuerySelect(spec.columns),
2325
+ skip: spec.offset,
2326
+ take: spec.limit
2327
+ };
2328
+ }
2329
+ emitDebugQuery(phase, operation, target, meta, durationMs, error, inspection = null) {
2330
+ emitRuntimeDebugEvent({
2331
+ type: "query",
2332
+ phase,
2333
+ adapter: "prisma",
2334
+ operation,
2335
+ target: target.table,
2336
+ inspection,
2337
+ meta,
2338
+ durationMs,
2339
+ error
2340
+ });
2341
+ }
2342
+ wrapExecutionError(error, operation, target, meta) {
2343
+ if (error instanceof ArkormException) return error;
2344
+ return new QueryExecutionException(`Failed to execute ${operation} query.`, {
2345
+ operation: `adapter.${operation}`,
2346
+ model: target.modelName,
2347
+ delegate: target.table,
2348
+ meta,
2349
+ cause: error
2350
+ });
2351
+ }
2352
+ async runWithDebug(operation, target, executor, meta) {
2353
+ const startedAt = Date.now();
2354
+ this.emitDebugQuery("before", operation, target, meta);
2355
+ try {
2356
+ const result = await executor();
2357
+ this.emitDebugQuery("after", operation, target, meta, Date.now() - startedAt);
2358
+ return result;
2359
+ } catch (error) {
2360
+ const wrapped = this.wrapExecutionError(error, operation, target, meta);
2361
+ this.emitDebugQuery("error", operation, target, meta, Date.now() - startedAt, wrapped);
2362
+ throw wrapped;
2363
+ }
2364
+ }
2365
+ inspectQuery(_request) {
2366
+ return null;
2367
+ }
2368
+ toQueryInclude(relationLoads) {
2369
+ if (!relationLoads || relationLoads.length === 0) return void 0;
2370
+ return relationLoads.reduce((include, plan) => {
2371
+ const nestedInclude = this.toQueryInclude(plan.relationLoads);
2372
+ const nestedSelect = this.toQuerySelect(plan.columns);
2373
+ const nestedWhere = this.toQueryWhere(plan.constraint);
2374
+ const nestedOrderBy = this.toQueryOrderBy(plan.orderBy);
2375
+ if (!nestedInclude && !nestedSelect && !nestedWhere && !nestedOrderBy && plan.offset === void 0 && plan.limit === void 0) {
2376
+ include[plan.relation] = true;
2377
+ return include;
2378
+ }
2379
+ include[plan.relation] = {
2380
+ where: nestedWhere,
2381
+ orderBy: nestedOrderBy,
2382
+ select: nestedSelect,
2383
+ include: nestedInclude,
2384
+ skip: plan.offset,
2385
+ take: plan.limit
2386
+ };
2387
+ return include;
2388
+ }, {});
2389
+ }
2390
+ async introspectModels(options = {}) {
2391
+ const runtimeDataModel = this.getRuntimeDataModel();
2392
+ if (!runtimeDataModel?.models) return [];
2393
+ const requestedTables = new Set(options.tables?.filter(Boolean) ?? []);
2394
+ const enums = runtimeDataModel.enums ?? {};
2395
+ return Object.entries(runtimeDataModel.models).flatMap(([name, model]) => {
2396
+ const table = model.dbName ?? `${str(name).camel().plural()}`;
2397
+ if (requestedTables.size > 0 && !requestedTables.has(table)) return [];
2398
+ return [{
2399
+ name,
2400
+ table,
2401
+ fields: (model.fields ?? []).filter((field) => field.kind !== "object").map((field) => {
2402
+ const enumValues = field.kind === "enum" ? (enums[field.type]?.values ?? []).map((value) => typeof value === "string" ? value : value.name ?? "").filter(Boolean) : null;
2403
+ const baseType = this.runtimeModelTypeToTs(field.type, field.kind, enumValues);
2404
+ return {
2405
+ name: field.name,
2406
+ type: field.isList ? `Array<${baseType}>` : baseType,
2407
+ nullable: field.isRequired === false
2408
+ };
2409
+ })
2410
+ }];
2411
+ });
2412
+ }
2413
+ resolveDelegate(target) {
2414
+ const tableName = target.table ? this.normalizeCandidate(target.table) : "";
2415
+ const singularTableName = tableName ? `${str(tableName).singular()}` : "";
2416
+ const camelTableName = tableName ? `${str(tableName).camel()}` : "";
2417
+ const camelSingularTableName = tableName ? `${str(tableName).camel().singular()}` : "";
2418
+ const candidates = this.unique([
2419
+ target.table ? this.mapping[target.table] : "",
2420
+ tableName,
2421
+ singularTableName ? this.mapping[singularTableName] : "",
2422
+ singularTableName,
2423
+ camelTableName ? this.mapping[camelTableName] : "",
2424
+ camelTableName,
2425
+ camelSingularTableName ? this.mapping[camelSingularTableName] : "",
2426
+ camelSingularTableName,
2427
+ target.modelName ? this.mapping[target.modelName] : "",
2428
+ target.modelName ? this.normalizeCandidate(target.modelName) : "",
2429
+ target.modelName ? inferDelegateName(target.modelName) : "",
2430
+ target.modelName ? this.mapping[inferDelegateName(target.modelName)] : ""
2431
+ ]);
2432
+ const resolved = candidates.map((candidate) => this.delegates[candidate]).find(Boolean);
2433
+ if (resolved) return resolved;
2434
+ throw new MissingDelegateException("Prisma delegate could not be resolved for adapter target.", {
2435
+ operation: "getDelegate",
2436
+ model: target.modelName,
2437
+ delegate: target.table,
2438
+ meta: {
2439
+ target,
2440
+ candidates
2441
+ }
2442
+ });
2443
+ }
2444
+ /**
2445
+ * @todo Implement relationLoads by performing separate queries and merging results
2446
+ * in-memory, since Prisma does not support nested reads with constraints, ordering, or
2447
+ * pagination on related models as of now.
2448
+ *
2449
+ * @param spec
2450
+ * @returns
2451
+ */
2452
+ async select(spec) {
2453
+ const delegate = this.resolveDelegate(spec.target);
2454
+ const args = this.buildFindArgs(spec);
2455
+ return await this.runWithDebug("select", spec.target, async () => {
2456
+ return await delegate.findMany(args);
2457
+ }, { args });
2458
+ }
2459
+ /**
2460
+ * Selects a single record matching the specified criteria.
2461
+ *
2462
+ * @param spec
2463
+ * @returns
2464
+ */
2465
+ async selectOne(spec) {
2466
+ const delegate = this.resolveDelegate(spec.target);
2467
+ const args = this.buildFindArgs(spec);
2468
+ return await this.runWithDebug("selectOne", spec.target, async () => {
2469
+ return await delegate.findFirst(args);
2470
+ }, { args });
2471
+ }
2472
+ /**
2473
+ * Inserts a single record into the database and returns the created record.
2474
+ *
2475
+ * @param spec
2476
+ * @returns
2477
+ */
2478
+ async insert(spec) {
2479
+ const delegate = this.resolveDelegate(spec.target);
2480
+ return await this.runWithDebug("insert", spec.target, async () => {
2481
+ return await delegate.create({ data: spec.values });
2482
+ }, { values: spec.values });
2483
+ }
2484
+ /**
2485
+ * Inserts multiple records into the database.
2486
+ *
2487
+ * @param spec
2488
+ * @returns
2489
+ */
2490
+ async insertMany(spec) {
2491
+ const delegate = this.resolveDelegate(spec.target);
2492
+ const meta = {
2493
+ values: spec.values,
2494
+ ignoreDuplicates: spec.ignoreDuplicates
2495
+ };
2496
+ if (typeof delegate.createMany === "function") {
2497
+ const result = await this.runWithDebug("insertMany", spec.target, async () => {
2498
+ return await delegate.createMany?.({
2499
+ data: spec.values,
2500
+ skipDuplicates: spec.ignoreDuplicates
2501
+ });
2502
+ }, meta);
2503
+ if (typeof result === "number") return result;
2504
+ return typeof result?.count === "number" ? result.count : spec.values.length;
2505
+ }
2506
+ let inserted = 0;
2507
+ for (const values of spec.values) try {
2508
+ await delegate.create({ data: values });
2509
+ inserted += 1;
2510
+ } catch (error) {
2511
+ if (!spec.ignoreDuplicates) throw error;
2512
+ }
2513
+ return spec.ignoreDuplicates ? inserted : spec.values.length;
2514
+ }
2515
+ /**
2516
+ * Updates a single record matching the specified criteria and returns the updated record.
2517
+ *
2518
+ * @param spec
2519
+ * @returns
2520
+ */
2521
+ async update(spec) {
2522
+ const delegate = this.resolveDelegate(spec.target);
2523
+ const where = this.toQueryWhere(spec.where);
2524
+ if (!where) return null;
2525
+ return await this.runWithDebug("update", spec.target, async () => {
2526
+ return await delegate.update({
2527
+ where,
2528
+ data: spec.values
2529
+ });
2530
+ }, {
2531
+ where,
2532
+ values: spec.values
2533
+ });
2534
+ }
2535
+ /**
2536
+ * Updates multiple records matching the specified criteria.
2537
+ *
2538
+ * @param spec
2539
+ * @returns
2540
+ */
2541
+ async updateMany(spec) {
2542
+ const delegate = this.resolveDelegate(spec.target);
2543
+ const where = this.toQueryWhere(spec.where);
2544
+ const meta = {
2545
+ where,
2546
+ values: spec.values
2547
+ };
2548
+ if (typeof delegate.updateMany === "function") {
2549
+ const result = await this.runWithDebug("updateMany", spec.target, async () => {
2550
+ return await delegate.updateMany?.({
2551
+ where,
2552
+ data: spec.values
2553
+ });
2554
+ }, meta);
2555
+ if (typeof result === "number") return result;
2556
+ return typeof result?.count === "number" ? result.count : 0;
2557
+ }
2558
+ const rows = await delegate.findMany({ where });
2559
+ await Promise.all(rows.map(async (row) => {
2560
+ await delegate.update({
2561
+ where: row,
2562
+ data: spec.values
2563
+ });
2564
+ }));
2565
+ return rows.length;
2566
+ }
2567
+ /**
2568
+ * Deletes a single record matching the specified criteria and returns the deleted record.
2569
+ *
2570
+ * @param spec
2571
+ * @returns
2572
+ */
2573
+ async delete(spec) {
2574
+ const delegate = this.resolveDelegate(spec.target);
2575
+ const where = this.toQueryWhere(spec.where);
2576
+ if (!where) return null;
2577
+ return await this.runWithDebug("delete", spec.target, async () => {
2578
+ return await delegate.delete({ where });
2579
+ }, { where });
2580
+ }
2581
+ /**
2582
+ * Deletes multiple records matching the specified criteria.
2583
+ *
2584
+ * @param spec
2585
+ * @returns
2586
+ */
2587
+ async deleteMany(spec) {
2588
+ const delegate = this.resolveDelegate(spec.target);
2589
+ const where = this.toQueryWhere(spec.where);
2590
+ const rows = await this.runWithDebug("deleteMany", spec.target, async () => {
2591
+ return await delegate.findMany({ where });
2592
+ }, { where });
2593
+ await Promise.all(rows.map(async (row) => {
2594
+ await delegate.delete({ where: row });
2595
+ }));
2596
+ return rows.length;
2597
+ }
2598
+ /**
2599
+ * Counts the number of records matching the specified criteria.
2600
+ *
2601
+ * @param spec
2602
+ * @returns
2603
+ */
2604
+ async count(spec) {
2605
+ const delegate = this.resolveDelegate(spec.target);
2606
+ const where = this.toQueryWhere(spec.where);
2607
+ return await this.runWithDebug("count", spec.target, async () => {
2608
+ return await delegate.count({ where });
2609
+ }, { where });
2610
+ }
2611
+ /**
2612
+ * Checks for the existence of records matching the specified criteria.
2613
+ *
2614
+ * @param spec
2615
+ * @returns
2616
+ */
2617
+ async exists(spec) {
2618
+ return await this.selectOne({
2619
+ ...spec,
2620
+ limit: 1
2621
+ }) != null;
2622
+ }
2623
+ /**
2624
+ * Loads related models for a batch of parent records based on the specified relation load plans.
2625
+ *
2626
+ * @param _spec
2627
+ */
2628
+ async loadRelations(_spec) {
2629
+ throw new UnsupportedAdapterFeatureException("Relation batch loading is not supported by the Prisma compatibility adapter yet.", {
2630
+ operation: "adapter.loadRelations",
2631
+ meta: { feature: "relationLoads" }
2632
+ });
2633
+ }
2634
+ /**
2635
+ * Executes a series of database operations within a transaction.
2636
+ * If the underlying Prisma client does not support transactions, an exception is thrown.
2637
+ *
2638
+ * @param callback
2639
+ * @param context
2640
+ * @returns
2641
+ */
2642
+ async transaction(callback, context = {}) {
2643
+ if (!this.hasTransactionSupport(this.prisma)) throw new UnsupportedAdapterFeatureException("Transactions are not supported by the Prisma compatibility adapter.", {
2644
+ operation: "adapter.transaction",
2645
+ meta: { feature: "transactions" }
2646
+ });
2647
+ return await this.prisma.$transaction(async (transactionClient) => {
2648
+ return await callback(new PrismaDatabaseAdapter(transactionClient, this.mapping));
2649
+ }, {
2650
+ isolationLevel: context.isolationLevel,
2651
+ maxWait: context.maxWait,
2652
+ timeout: context.timeout
2653
+ });
2654
+ }
2655
+ };
2656
+
1606
2657
  //#endregion
1607
2658
  //#region src/cli/CliApp.ts
1608
2659
  /**
@@ -1624,6 +2675,9 @@ var CliApp = class {
1624
2675
  * @returns The entire configuration object or the value of the specified key
1625
2676
  */
1626
2677
  getConfig = getUserConfig;
2678
+ isUsingPrismaAdapter() {
2679
+ return this.getConfig("adapter") instanceof PrismaDatabaseAdapter;
2680
+ }
1627
2681
  /**
1628
2682
  * Utility to ensure directory exists
1629
2683
  *
@@ -1836,14 +2890,16 @@ var CliApp = class {
1836
2890
  const factoryName = `${baseName}Factory`;
1837
2891
  const factoryPath = join$1(this.resolveConfigPath("factories", join$1(process.cwd(), "database", "factories")), `${factoryName}.${outputExt}`);
1838
2892
  const factoryImportPath = `./${relative(dirname$1(outputPath), factoryPath).replace(/\\/g, "/").replace(/\.(ts|tsx|mts|cts|js|mjs|cjs)$/i, "")}${outputExt === "js" ? ".js" : ""}`;
1839
- const stubPath = this.resolveStubPath(outputExt === "js" ? "model.js.stub" : "model.stub");
2893
+ let stubPath;
2894
+ if (options.pivot) stubPath = this.resolveStubPath("pivot-model.stub");
2895
+ else stubPath = this.resolveStubPath(outputExt === "js" ? "model.js.stub" : "model.stub");
1840
2896
  const modelPath = this.generateFile(stubPath, outputPath, {
1841
2897
  ModelName: modelName,
1842
2898
  DelegateName: delegateName,
1843
2899
  FactoryImport: shouldBuildFactory ? `import { ${factoryName} } from '${factoryImportPath}'\n` : "",
1844
2900
  FactoryLink: shouldBuildFactory ? outputExt === "js" ? `\n static factoryClass = ${factoryName}` : `\n protected static override factoryClass = ${factoryName}` : ""
1845
2901
  }, options);
1846
- const prisma = this.ensurePrismaModelEntry(modelName, delegateName);
2902
+ const prisma = this.isUsingPrismaAdapter() ? this.ensurePrismaModelEntry(modelName, delegateName) : void 0;
1847
2903
  const created = {
1848
2904
  model: {
1849
2905
  name: modelName,
@@ -1873,10 +2929,7 @@ var CliApp = class {
1873
2929
  */
1874
2930
  ensurePrismaModelEntry(modelName, delegateName) {
1875
2931
  const schemaPath = join$1(process.cwd(), "prisma", "schema.prisma");
1876
- if (!existsSync$1(schemaPath)) return {
1877
- path: schemaPath,
1878
- updated: false
1879
- };
2932
+ if (!existsSync$1(schemaPath)) return void 0;
1880
2933
  const source = readFileSync$1(schemaPath, "utf-8");
1881
2934
  const existingByTable = findModelBlock(source, delegateName);
1882
2935
  const existingByName = new RegExp(`model\\s+${modelName}\\s*\\{`, "m").test(source);
@@ -2135,6 +3188,35 @@ var CliApp = class {
2135
3188
  skipped
2136
3189
  };
2137
3190
  }
3191
+ applyPersistedFieldMetadata(structure) {
3192
+ const persistedMetadata = getPersistedTableMetadata(structure.table, {
3193
+ features: resolvePersistedMetadataFeatures(this.getConfig("features")),
3194
+ strict: true
3195
+ });
3196
+ if (Object.keys(persistedMetadata.columns).length === 0 && Object.keys(persistedMetadata.enums).length === 0) return structure;
3197
+ const attributesByColumn = Object.entries(persistedMetadata.columns).reduce((all, [attribute, column]) => {
3198
+ all[column] = attribute;
3199
+ return all;
3200
+ }, {});
3201
+ return {
3202
+ ...structure,
3203
+ fields: structure.fields.map((field) => {
3204
+ const logicalName = attributesByColumn[field.name] ?? field.name;
3205
+ const enumValues = persistedMetadata.enums[logicalName] ?? persistedMetadata.enums[field.name];
3206
+ if (!enumValues || enumValues.length === 0) return {
3207
+ ...field,
3208
+ name: logicalName
3209
+ };
3210
+ const enumType = getPersistedEnumTsType(enumValues);
3211
+ const isArray = /^Array<.+>$/.test(field.type);
3212
+ return {
3213
+ ...field,
3214
+ name: logicalName,
3215
+ type: isArray ? `Array<${enumType}>` : enumType
3216
+ };
3217
+ })
3218
+ };
3219
+ }
2138
3220
  /**
2139
3221
  * Parse Prisma enum definitions from a schema and return their member names.
2140
3222
  *
@@ -2293,7 +3375,10 @@ var CliApp = class {
2293
3375
  return all;
2294
3376
  }, /* @__PURE__ */ new Map());
2295
3377
  const discovered = await adapter.introspectModels({ tables: [...new Set([...sources.values()].map((source) => source.table))] });
2296
- const structuresByTable = new Map(discovered.map((model) => [model.table, model]));
3378
+ const structuresByTable = new Map(discovered.map((model) => {
3379
+ const enriched = this.applyPersistedFieldMetadata(model);
3380
+ return [enriched.table, enriched];
3381
+ }));
2297
3382
  const result = this.syncModelFiles(modelFiles, (filePath) => {
2298
3383
  const parsed = sources.get(filePath);
2299
3384
  return parsed ? structuresByTable.get(parsed.table) : void 0;
@@ -2457,6 +3542,7 @@ var MakeModelCommand = class extends Command {
2457
3542
  {--factory : Create and link a factory}
2458
3543
  {--seeder : Create a seeder}
2459
3544
  {--migration : Create a migration}
3545
+ {--p|pivot : Indicate the required model is an intermediate pivot model}
2460
3546
  {--all : Create and link factory, seeder, and migration}
2461
3547
  `;
2462
3548
  description = "Create a new model and optional linked resources";
@@ -2470,14 +3556,13 @@ var MakeModelCommand = class extends Command {
2470
3556
  const name = this.argument("name");
2471
3557
  if (!name) return void this.error("Error: Name argument is required.");
2472
3558
  const created = this.app.makeModel(name, this.options());
3559
+ const createdFiles = [["Model", created.model.path]];
3560
+ if (created.prisma) createdFiles.push([`Prisma schema ${created.prisma.updated ? "(updated)" : "(already up to date)"}`, created.prisma.path]);
3561
+ if (created.factory) createdFiles.push(["Factory", created.factory.path]);
3562
+ if (created.seeder) createdFiles.push(["Seeder", created.seeder.path]);
3563
+ if (created.migration) createdFiles.push(["Migration", created.migration.path]);
2473
3564
  this.success("Created files:");
2474
- [
2475
- ["Model", created.model.path],
2476
- [`Prisma schema ${created.prisma.updated ? "(updated)" : "(already up to date)"}`, created.prisma.path],
2477
- created.factory ? ["Factory", created.factory.path] : "",
2478
- created.seeder ? ["Seeder", created.seeder.path] : "",
2479
- created.migration ? ["Migration", created.migration.path] : ""
2480
- ].filter(Boolean).map(([name, path]) => this.success(this.app.splitLogger(name, path)));
3565
+ createdFiles.map(([fileType, path]) => this.success(this.app.splitLogger(fileType, path)));
2481
3566
  }
2482
3567
  };
2483
3568
 
@@ -2507,112 +3592,6 @@ var MakeSeederCommand = class extends Command {
2507
3592
  }
2508
3593
  };
2509
3594
 
2510
- //#endregion
2511
- //#region src/helpers/migration-history.ts
2512
- const DEFAULT_STATE = {
2513
- version: 1,
2514
- migrations: [],
2515
- runs: []
2516
- };
2517
- const resolveMigrationStateFilePath = (cwd, configuredPath) => {
2518
- if (configuredPath && configuredPath.trim().length > 0) return resolve(configuredPath);
2519
- return join(cwd, ".arkormx", "migrations.applied.json");
2520
- };
2521
- const buildMigrationIdentity = (filePath, className) => {
2522
- const fileName = filePath.split("/").pop()?.split("\\").pop() ?? filePath;
2523
- return `${fileName.slice(0, fileName.length - extname(fileName).length)}:${className}`;
2524
- };
2525
- const computeMigrationChecksum = (filePath) => {
2526
- const source = readFileSync(filePath, "utf-8");
2527
- return createHash("sha256").update(source).digest("hex");
2528
- };
2529
- const readAppliedMigrationsState = (stateFilePath) => {
2530
- if (!existsSync(stateFilePath)) return { ...DEFAULT_STATE };
2531
- try {
2532
- const parsed = JSON.parse(readFileSync(stateFilePath, "utf-8"));
2533
- if (!Array.isArray(parsed.migrations)) return { ...DEFAULT_STATE };
2534
- return {
2535
- version: 1,
2536
- migrations: parsed.migrations.filter((migration) => {
2537
- 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");
2538
- }),
2539
- runs: Array.isArray(parsed.runs) ? parsed.runs.filter((run) => {
2540
- return typeof run?.id === "string" && typeof run?.appliedAt === "string" && Array.isArray(run?.migrationIds) && run.migrationIds.every((item) => typeof item === "string");
2541
- }) : []
2542
- };
2543
- } catch {
2544
- return { ...DEFAULT_STATE };
2545
- }
2546
- };
2547
- const writeAppliedMigrationsState = (stateFilePath, state) => {
2548
- const directory = dirname(stateFilePath);
2549
- if (!existsSync(directory)) mkdirSync(directory, { recursive: true });
2550
- writeFileSync(stateFilePath, JSON.stringify(state, null, 2));
2551
- };
2552
- const isMigrationApplied = (state, identity, checksum) => {
2553
- const matched = state.migrations.find((migration) => migration.id === identity);
2554
- if (!matched) return false;
2555
- if (checksum && matched.checksum) return matched.checksum === checksum;
2556
- if (checksum && !matched.checksum) return false;
2557
- return true;
2558
- };
2559
- const findAppliedMigration = (state, identity) => {
2560
- return state.migrations.find((migration) => migration.id === identity);
2561
- };
2562
- const markMigrationApplied = (state, entry) => {
2563
- const next = state.migrations.filter((migration) => migration.id !== entry.id);
2564
- next.push(entry);
2565
- return {
2566
- version: 1,
2567
- migrations: next,
2568
- runs: state.runs ?? []
2569
- };
2570
- };
2571
- const removeAppliedMigration = (state, identity) => {
2572
- return {
2573
- version: 1,
2574
- migrations: state.migrations.filter((migration) => migration.id !== identity),
2575
- runs: (state.runs ?? []).map((run) => ({
2576
- ...run,
2577
- migrationIds: run.migrationIds.filter((id) => id !== identity)
2578
- })).filter((run) => run.migrationIds.length > 0)
2579
- };
2580
- };
2581
- const buildMigrationRunId = () => {
2582
- return `run_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
2583
- };
2584
- const markMigrationRun = (state, run) => {
2585
- const nextRuns = (state.runs ?? []).filter((existing) => existing.id !== run.id);
2586
- nextRuns.push(run);
2587
- return {
2588
- version: 1,
2589
- migrations: state.migrations,
2590
- runs: nextRuns
2591
- };
2592
- };
2593
- const getLastMigrationRun = (state) => {
2594
- const runs = state.runs ?? [];
2595
- if (runs.length === 0) return void 0;
2596
- return runs.map((run, index) => ({
2597
- run,
2598
- index
2599
- })).sort((left, right) => {
2600
- const appliedAtOrder = right.run.appliedAt.localeCompare(left.run.appliedAt);
2601
- if (appliedAtOrder !== 0) return appliedAtOrder;
2602
- return right.index - left.index;
2603
- })[0]?.run;
2604
- };
2605
- const getLatestAppliedMigrations = (state, steps) => {
2606
- return state.migrations.map((migration, index) => ({
2607
- migration,
2608
- index
2609
- })).sort((left, right) => {
2610
- const appliedAtOrder = right.migration.appliedAt.localeCompare(left.migration.appliedAt);
2611
- if (appliedAtOrder !== 0) return appliedAtOrder;
2612
- return right.index - left.index;
2613
- }).slice(0, Math.max(0, steps)).map((entry) => entry.migration);
2614
- };
2615
-
2616
3595
  //#endregion
2617
3596
  //#region src/database/Migration.ts
2618
3597
  const MIGRATION_BRAND = Symbol.for("arkormx.migration");
@@ -2667,7 +3646,10 @@ var MigrateCommand = class extends Command {
2667
3646
  const classes = this.option("all") || !this.argument("name") ? await this.loadAllMigrations(migrationsDir) : (await this.loadNamedMigration(migrationsDir, this.argument("name"))).filter(([cls]) => cls !== void 0);
2668
3647
  if (classes.length === 0) return void this.error("Error: No migration classes found to run.");
2669
3648
  const stateFilePath = resolveMigrationStateFilePath(process.cwd(), this.option("state-file") ? String(this.option("state-file")) : void 0);
2670
- let appliedState = readAppliedMigrationsState(stateFilePath);
3649
+ let appliedState = await readAppliedMigrationsStateFromStore(this.app.getConfig("adapter"), stateFilePath);
3650
+ const adapter = this.app.getConfig("adapter");
3651
+ const useDatabaseMigrations = supportsDatabaseMigrationExecution(adapter);
3652
+ const persistedFeatures = resolvePersistedMetadataFeatures(this.app.getConfig("features"));
2671
3653
  const skipped = [];
2672
3654
  const changed = [];
2673
3655
  const pending = classes.filter(([migrationClass, file]) => {
@@ -2686,13 +3668,31 @@ var MigrateCommand = class extends Command {
2686
3668
  this.success(this.app.splitLogger("Changed", `${file} (${migrationClass.name})`));
2687
3669
  });
2688
3670
  if (pending.length === 0) {
3671
+ if (appliedState) try {
3672
+ await syncPersistedColumnMappingsFromState(process.cwd(), appliedState, await this.loadAllMigrations(migrationsDir), persistedFeatures);
3673
+ } catch (error) {
3674
+ this.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
3675
+ return;
3676
+ }
2689
3677
  this.success("No pending migration classes to apply.");
2690
3678
  return;
2691
3679
  }
2692
- for (const [MigrationClassItem] of pending) await applyMigrationToPrismaSchema(MigrationClassItem, {
2693
- schemaPath,
2694
- write: true
2695
- });
3680
+ if (useDatabaseMigrations) try {
3681
+ await validatePersistedMetadataFeaturesForMigrations(pending, persistedFeatures);
3682
+ } catch (error) {
3683
+ this.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
3684
+ return;
3685
+ }
3686
+ for (const [MigrationClassItem] of pending) {
3687
+ if (useDatabaseMigrations) {
3688
+ await applyMigrationToDatabase(adapter, MigrationClassItem);
3689
+ continue;
3690
+ }
3691
+ await applyMigrationToPrismaSchema(MigrationClassItem, {
3692
+ schemaPath,
3693
+ write: true
3694
+ });
3695
+ }
2696
3696
  if (appliedState) {
2697
3697
  const runAppliedIds = [];
2698
3698
  for (const [migrationClass, file] of pending) {
@@ -2711,10 +3711,16 @@ var MigrateCommand = class extends Command {
2711
3711
  appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
2712
3712
  migrationIds: runAppliedIds
2713
3713
  });
2714
- writeAppliedMigrationsState(stateFilePath, appliedState);
3714
+ await writeAppliedMigrationsStateToStore(adapter, stateFilePath, appliedState);
3715
+ try {
3716
+ await syncPersistedColumnMappingsFromState(process.cwd(), appliedState, await this.loadAllMigrations(migrationsDir), persistedFeatures);
3717
+ } catch (error) {
3718
+ this.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
3719
+ return;
3720
+ }
2715
3721
  }
2716
- if (!this.option("skip-generate")) runPrismaCommand(["generate"], process.cwd());
2717
- if (!this.option("skip-migrate")) if (this.option("deploy")) runPrismaCommand(["migrate", "deploy"], process.cwd());
3722
+ if (!useDatabaseMigrations && !this.option("skip-generate")) runPrismaCommand(["generate"], process.cwd());
3723
+ if (!useDatabaseMigrations && !this.option("skip-migrate")) if (this.option("deploy")) runPrismaCommand(["migrate", "deploy"], process.cwd());
2718
3724
  else runPrismaCommand([
2719
3725
  "migrate",
2720
3726
  "dev",
@@ -2774,6 +3780,103 @@ var MigrateCommand = class extends Command {
2774
3780
  }
2775
3781
  };
2776
3782
 
3783
+ //#endregion
3784
+ //#region src/cli/commands/MigrateFreshCommand.ts
3785
+ var MigrateFreshCommand = class extends Command {
3786
+ signature = `migrate:fresh
3787
+ {--skip-generate : Skip prisma generate}
3788
+ {--skip-migrate : Skip prisma database sync}
3789
+ {--state-file= : Path to applied migration state file}
3790
+ {--schema= : Explicit prisma schema path}
3791
+ `;
3792
+ description = "Reset the database and rerun all migration classes";
3793
+ async handle() {
3794
+ this.app.command = this;
3795
+ const configuredMigrationsDir = this.app.getConfig("paths")?.migrations ?? join(process.cwd(), "database", "migrations");
3796
+ const migrationsDir = this.app.resolveRuntimeDirectoryPath(configuredMigrationsDir);
3797
+ if (!existsSync(migrationsDir)) return void this.error(`Error: Migrations directory not found: ${this.app.formatPathForLog(configuredMigrationsDir)}`);
3798
+ const adapter = this.app.getConfig("adapter");
3799
+ const useDatabaseMigrations = supportsDatabaseMigrationExecution(adapter);
3800
+ const persistedFeatures = resolvePersistedMetadataFeatures(this.app.getConfig("features"));
3801
+ const schemaPath = this.option("schema") ? resolve(String(this.option("schema"))) : join(process.cwd(), "prisma", "schema.prisma");
3802
+ const stateFilePath = resolveMigrationStateFilePath(process.cwd(), this.option("state-file") ? String(this.option("state-file")) : void 0);
3803
+ const migrations = await this.loadAllMigrations(migrationsDir);
3804
+ if (migrations.length === 0) return void this.error("Error: No migration classes found to run.");
3805
+ if (useDatabaseMigrations) try {
3806
+ await validatePersistedMetadataFeaturesForMigrations(migrations, persistedFeatures);
3807
+ } catch (error) {
3808
+ this.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
3809
+ return;
3810
+ }
3811
+ if (useDatabaseMigrations) {
3812
+ if (!supportsDatabaseReset(adapter)) {
3813
+ this.error("Error: Your current database adapter does not support database reset.");
3814
+ return;
3815
+ }
3816
+ await adapter.resetDatabase();
3817
+ } else {
3818
+ if (!existsSync(schemaPath)) return void this.error(`Error: Prisma schema file not found: ${this.app.formatPathForLog(schemaPath)}`);
3819
+ writeFileSync(schemaPath, stripPrismaSchemaModelsAndEnums(readFileSync(schemaPath, "utf-8")));
3820
+ }
3821
+ let appliedState = createEmptyAppliedMigrationsState();
3822
+ await writeAppliedMigrationsStateToStore(adapter, stateFilePath, appliedState);
3823
+ for (const [MigrationClassItem] of migrations) {
3824
+ if (useDatabaseMigrations) {
3825
+ await applyMigrationToDatabase(adapter, MigrationClassItem);
3826
+ continue;
3827
+ }
3828
+ await applyMigrationToPrismaSchema(MigrationClassItem, {
3829
+ schemaPath,
3830
+ write: true
3831
+ });
3832
+ }
3833
+ for (const [migrationClass, file] of migrations) appliedState = markMigrationApplied(appliedState, {
3834
+ id: buildMigrationIdentity(file, migrationClass.name),
3835
+ file,
3836
+ className: migrationClass.name,
3837
+ appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
3838
+ checksum: computeMigrationChecksum(file)
3839
+ });
3840
+ appliedState = markMigrationRun(appliedState, {
3841
+ id: buildMigrationRunId(),
3842
+ appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
3843
+ migrationIds: appliedState.migrations.map((migration) => migration.id)
3844
+ });
3845
+ await writeAppliedMigrationsStateToStore(adapter, stateFilePath, appliedState);
3846
+ try {
3847
+ await syncPersistedColumnMappingsFromState(process.cwd(), appliedState, migrations, persistedFeatures);
3848
+ } catch (error) {
3849
+ this.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
3850
+ return;
3851
+ }
3852
+ if (!useDatabaseMigrations) {
3853
+ const schemaArgs = this.option("schema") ? ["--schema", schemaPath] : [];
3854
+ if (!this.option("skip-generate")) runPrismaCommand(["generate", ...schemaArgs], process.cwd());
3855
+ if (!this.option("skip-migrate")) runPrismaCommand([
3856
+ "db",
3857
+ "push",
3858
+ "--force-reset",
3859
+ ...schemaArgs
3860
+ ], process.cwd());
3861
+ }
3862
+ this.success(`Refreshed database with ${migrations.length} migration(s).`);
3863
+ migrations.forEach(([_, file]) => this.success(this.app.splitLogger("Migrated", file)));
3864
+ }
3865
+ async loadAllMigrations(migrationsDir) {
3866
+ const files = readdirSync(migrationsDir).filter((file) => /\.(ts|js|mjs|cjs)$/i.test(file)).sort((left, right) => left.localeCompare(right)).map((file) => this.app.resolveRuntimeScriptPath(join(migrationsDir, file)));
3867
+ return (await Promise.all(files.map(async (file) => (await this.loadMigrationClassesFromFile(file)).map((cls) => [cls, file])))).flat();
3868
+ }
3869
+ async loadMigrationClassesFromFile(filePath) {
3870
+ const imported = await RuntimeModuleLoader.load(filePath);
3871
+ return Object.values(imported).filter((value) => {
3872
+ if (typeof value !== "function") return false;
3873
+ const candidate = value;
3874
+ const prototype = candidate.prototype;
3875
+ return candidate[MIGRATION_BRAND] === true || typeof prototype?.up === "function" && typeof prototype?.down === "function";
3876
+ });
3877
+ }
3878
+ };
3879
+
2777
3880
  //#endregion
2778
3881
  //#region src/cli/commands/MigrateRollbackCommand.ts
2779
3882
  /**
@@ -2802,7 +3905,10 @@ var MigrateRollbackCommand = class extends Command {
2802
3905
  if (!existsSync(migrationsDir)) return void this.error(`Error: Migrations directory not found: ${this.app.formatPathForLog(configuredMigrationsDir)}`);
2803
3906
  const schemaPath = this.option("schema") ? resolve(String(this.option("schema"))) : join(process.cwd(), "prisma", "schema.prisma");
2804
3907
  const stateFilePath = resolveMigrationStateFilePath(process.cwd(), this.option("state-file") ? String(this.option("state-file")) : void 0);
2805
- let appliedState = readAppliedMigrationsState(stateFilePath);
3908
+ const adapter = this.app.getConfig("adapter");
3909
+ const useDatabaseMigrations = supportsDatabaseMigrationExecution(adapter);
3910
+ const persistedFeatures = resolvePersistedMetadataFeatures(this.app.getConfig("features"));
3911
+ let appliedState = await readAppliedMigrationsStateFromStore(adapter, stateFilePath);
2806
3912
  const stepOption = this.option("step");
2807
3913
  const stepCount = stepOption == null ? void 0 : Number(stepOption);
2808
3914
  if (stepCount != null && (!Number.isFinite(stepCount) || stepCount <= 0 || !Number.isInteger(stepCount))) return void this.error("Error: --step must be a positive integer.");
@@ -2824,17 +3930,29 @@ var MigrateRollbackCommand = class extends Command {
2824
3930
  rollbackClasses.forEach(([_, file]) => this.success(this.app.splitLogger("WouldRollback", file)));
2825
3931
  return;
2826
3932
  }
2827
- for (const [MigrationClassItem] of rollbackClasses) await applyMigrationRollbackToPrismaSchema(MigrationClassItem, {
2828
- schemaPath,
2829
- write: true
2830
- });
3933
+ for (const [MigrationClassItem] of rollbackClasses) {
3934
+ if (useDatabaseMigrations) {
3935
+ await applyMigrationRollbackToDatabase(adapter, MigrationClassItem);
3936
+ continue;
3937
+ }
3938
+ await applyMigrationRollbackToPrismaSchema(MigrationClassItem, {
3939
+ schemaPath,
3940
+ write: true
3941
+ });
3942
+ }
2831
3943
  for (const [migrationClass, file] of rollbackClasses) {
2832
3944
  const identity = buildMigrationIdentity(file, migrationClass.name);
2833
3945
  appliedState = removeAppliedMigration(appliedState, identity);
2834
3946
  }
2835
- writeAppliedMigrationsState(stateFilePath, appliedState);
2836
- if (!this.option("skip-generate")) runPrismaCommand(["generate"], process.cwd());
2837
- if (!this.option("skip-migrate")) if (this.option("deploy")) runPrismaCommand(["migrate", "deploy"], process.cwd());
3947
+ await writeAppliedMigrationsStateToStore(adapter, stateFilePath, appliedState);
3948
+ try {
3949
+ await syncPersistedColumnMappingsFromState(process.cwd(), appliedState, available, persistedFeatures);
3950
+ } catch (error) {
3951
+ this.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
3952
+ return;
3953
+ }
3954
+ if (!useDatabaseMigrations && !this.option("skip-generate")) runPrismaCommand(["generate"], process.cwd());
3955
+ if (!useDatabaseMigrations && !this.option("skip-migrate")) if (this.option("deploy")) runPrismaCommand(["migrate", "deploy"], process.cwd());
2838
3956
  else runPrismaCommand([
2839
3957
  "migrate",
2840
3958
  "dev",
@@ -2878,32 +3996,39 @@ var MigrationHistoryCommand = class extends Command {
2878
3996
  async handle() {
2879
3997
  this.app.command = this;
2880
3998
  const stateFilePath = resolveMigrationStateFilePath(process.cwd(), this.option("state-file") ? String(this.option("state-file")) : void 0);
3999
+ const adapter = this.app.getConfig("adapter");
4000
+ const usesDatabaseState = supportsDatabaseMigrationState(adapter);
2881
4001
  if (this.option("delete")) {
4002
+ if (usesDatabaseState) {
4003
+ await adapter.writeAppliedMigrationsState(createEmptyAppliedMigrationsState());
4004
+ deletePersistedColumnMappingsState(resolveColumnMappingsFilePath(process.cwd()));
4005
+ this.success("Deleted tracked migration state from database.");
4006
+ return;
4007
+ }
2882
4008
  if (!existsSync(stateFilePath)) {
2883
4009
  this.success(`No migration state file found at ${this.app.formatPathForLog(stateFilePath)}`);
2884
4010
  return;
2885
4011
  }
2886
4012
  rmSync(stateFilePath);
4013
+ deletePersistedColumnMappingsState(resolveColumnMappingsFilePath(process.cwd()));
2887
4014
  this.success(`Deleted migration state file: ${this.app.formatPathForLog(stateFilePath)}`);
2888
4015
  return;
2889
4016
  }
2890
4017
  if (this.option("reset")) {
2891
- writeAppliedMigrationsState(stateFilePath, {
2892
- version: 1,
2893
- migrations: []
2894
- });
2895
- this.success(`Reset migration state: ${this.app.formatPathForLog(stateFilePath)}`);
4018
+ await writeAppliedMigrationsStateToStore(adapter, stateFilePath, createEmptyAppliedMigrationsState());
4019
+ deletePersistedColumnMappingsState(resolveColumnMappingsFilePath(process.cwd()));
4020
+ this.success(usesDatabaseState ? "Reset migration state in database." : `Reset migration state: ${this.app.formatPathForLog(stateFilePath)}`);
2896
4021
  return;
2897
4022
  }
2898
- const state = readAppliedMigrationsState(stateFilePath);
4023
+ const state = await readAppliedMigrationsStateFromStore(adapter, stateFilePath);
2899
4024
  if (this.option("json")) {
2900
4025
  this.success(JSON.stringify({
2901
- path: stateFilePath,
4026
+ path: usesDatabaseState ? "database" : stateFilePath,
2902
4027
  ...state
2903
4028
  }, null, 2));
2904
4029
  return;
2905
4030
  }
2906
- this.success(this.app.splitLogger("State", stateFilePath));
4031
+ this.success(this.app.splitLogger("State", usesDatabaseState ? "database" : stateFilePath));
2907
4032
  this.success(this.app.splitLogger("Tracked", String(state.migrations.length)));
2908
4033
  if (state.migrations.length === 0) {
2909
4034
  this.success("No tracked migrations found.");
@@ -2925,10 +4050,16 @@ var ModelsSyncCommand = class extends Command {
2925
4050
  description = "Sync model declare attributes from the active adapter when supported, otherwise fall back to the Prisma schema";
2926
4051
  async handle() {
2927
4052
  this.app.command = this;
2928
- const result = await this.app.syncModels({
2929
- schemaPath: this.option("schema") ? resolve(String(this.option("schema"))) : void 0,
2930
- modelsDir: this.option("models") ? resolve(String(this.option("models"))) : void 0
2931
- });
4053
+ let result;
4054
+ try {
4055
+ result = await this.app.syncModels({
4056
+ schemaPath: this.option("schema") ? resolve(String(this.option("schema"))) : void 0,
4057
+ modelsDir: this.option("models") ? resolve(String(this.option("models"))) : void 0
4058
+ });
4059
+ } catch (error) {
4060
+ this.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
4061
+ return;
4062
+ }
2932
4063
  const updatedLines = result.updated.length === 0 ? [this.app.splitLogger("Updated", "none")] : result.updated.map((path) => this.app.splitLogger("Updated", path));
2933
4064
  this.success("SUCCESS: Model sync completed with the following results:");
2934
4065
  [
@@ -3096,6 +4227,7 @@ await Kernel.init(app, {
3096
4227
  ModelsSyncCommand,
3097
4228
  SeedCommand,
3098
4229
  MigrateCommand,
4230
+ MigrateFreshCommand,
3099
4231
  MigrateRollbackCommand,
3100
4232
  MigrationHistoryCommand
3101
4233
  ],