ts-prorm-orm 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (310) hide show
  1. package/CHANGELOG.md +1111 -0
  2. package/LICENSE +21 -0
  3. package/README.md +573 -0
  4. package/dist/audit/history-query.js +180 -0
  5. package/dist/audit/index.js +23 -0
  6. package/dist/audit/logger.js +236 -0
  7. package/dist/cache/cache-manager.js +84 -0
  8. package/dist/cache/index.js +16 -0
  9. package/dist/cache/redis-cluster-cache.js +557 -0
  10. package/dist/cli.js +2200 -0
  11. package/dist/compliance/audit-trail.js +68 -0
  12. package/dist/compliance/backup-verification.js +685 -0
  13. package/dist/compliance/breach-detector.js +505 -0
  14. package/dist/compliance/consent-record.js +227 -0
  15. package/dist/compliance/consent-versioning.js +331 -0
  16. package/dist/compliance/cross-border-log.js +530 -0
  17. package/dist/compliance/data-classifier.js +258 -0
  18. package/dist/compliance/data-lineage.js +565 -0
  19. package/dist/compliance/data-masker.js +303 -0
  20. package/dist/compliance/data-portability.js +265 -0
  21. package/dist/compliance/data-retention.js +195 -0
  22. package/dist/compliance/dsar-workflow.js +342 -0
  23. package/dist/compliance/field-encryption.js +223 -0
  24. package/dist/compliance/immutable-record.js +148 -0
  25. package/dist/compliance/index.js +260 -0
  26. package/dist/compliance/privacy-impact-assessment.js +364 -0
  27. package/dist/compliance/pseudonymization.js +269 -0
  28. package/dist/compliance/query-firewall.js +1182 -0
  29. package/dist/compliance/rate-limiter.js +580 -0
  30. package/dist/compliance/right-to-erasure.js +117 -0
  31. package/dist/compliance/row-level-security.js +246 -0
  32. package/dist/compliance/security-decorator.js +574 -0
  33. package/dist/compliance/security-monitor.js +525 -0
  34. package/dist/compliance/sensitive-data-discovery.js +476 -0
  35. package/dist/compliance/session-isolation.js +472 -0
  36. package/dist/compliance/tls-enforcer.js +108 -0
  37. package/dist/compliance/worm-storage.js +712 -0
  38. package/dist/connection-manager.js +198 -0
  39. package/dist/connection-pool.js +519 -0
  40. package/dist/decorators/audit.js +136 -0
  41. package/dist/decorators/belongs-to-many.js +115 -0
  42. package/dist/decorators/belongs-to.js +115 -0
  43. package/dist/decorators/check.js +435 -0
  44. package/dist/decorators/collate.js +329 -0
  45. package/dist/decorators/comment.js +205 -0
  46. package/dist/decorators/database-settings.js +236 -0
  47. package/dist/decorators/default.js +244 -0
  48. package/dist/decorators/encryption.js +235 -0
  49. package/dist/decorators/engine.js +97 -0
  50. package/dist/decorators/fk-constraints.js +594 -0
  51. package/dist/decorators/foreign-table.js +136 -0
  52. package/dist/decorators/generated.js +274 -0
  53. package/dist/decorators/has-many.js +127 -0
  54. package/dist/decorators/has-one.js +116 -0
  55. package/dist/decorators/hstore.js +129 -0
  56. package/dist/decorators/index.js +355 -0
  57. package/dist/decorators/json-column.js +83 -0
  58. package/dist/decorators/jsonb.js +101 -0
  59. package/dist/decorators/orm-decorators.js +425 -0
  60. package/dist/decorators/permissions.js +294 -0
  61. package/dist/decorators/procedure.js +210 -0
  62. package/dist/decorators/query-options.js +556 -0
  63. package/dist/decorators/range.js +167 -0
  64. package/dist/decorators/set-column.js +83 -0
  65. package/dist/decorators/spatial.js +114 -0
  66. package/dist/decorators/storage.js +580 -0
  67. package/dist/decorators/timezone.js +512 -0
  68. package/dist/decorators/trigger.js +90 -0
  69. package/dist/decorators/uuid.js +135 -0
  70. package/dist/decorators/view.js +258 -0
  71. package/dist/diagrams/chen-diagram.js +354 -0
  72. package/dist/diagrams/class-diagram.js +384 -0
  73. package/dist/diagrams/dependency-diagram.js +432 -0
  74. package/dist/diagrams/er-diagram.js +605 -0
  75. package/dist/diagrams/flow-diagram.js +394 -0
  76. package/dist/diagrams/gantt-diagram.js +411 -0
  77. package/dist/diagrams/index-diagram.js +353 -0
  78. package/dist/diagrams/index.js +184 -0
  79. package/dist/diagrams/migration-diagram.js +316 -0
  80. package/dist/diagrams/model-diagram.js +616 -0
  81. package/dist/diagrams/package-diagram.js +376 -0
  82. package/dist/diagrams/palette.js +85 -0
  83. package/dist/diagrams/relational-diagram.js +455 -0
  84. package/dist/diagrams/schemadoc-diagram.js +309 -0
  85. package/dist/diagrams/sequence-diagram.js +307 -0
  86. package/dist/diagrams/state-diagram.js +344 -0
  87. package/dist/diagrams/svg-dom.js +111 -0
  88. package/dist/diagrams/tree-diagram.js +250 -0
  89. package/dist/dialects/clickhouse/index.js +1742 -0
  90. package/dist/dialects/cockroachdb/index.js +4677 -0
  91. package/dist/dialects/cratedb/index.js +441 -0
  92. package/dist/dialects/databricks/index.js +517 -0
  93. package/dist/dialects/db2/index.js +2225 -0
  94. package/dist/dialects/dialect.js +720 -0
  95. package/dist/dialects/duckdb/index.js +2014 -0
  96. package/dist/dialects/exasol/index.js +357 -0
  97. package/dist/dialects/firebird/index.js +495 -0
  98. package/dist/dialects/greenplum/index.js +457 -0
  99. package/dist/dialects/hana/index.js +1741 -0
  100. package/dist/dialects/mariadb/index.js +3590 -0
  101. package/dist/dialects/mssql/index.js +2296 -0
  102. package/dist/dialects/mysql/index.js +4058 -0
  103. package/dist/dialects/oracle/index.js +2999 -0
  104. package/dist/dialects/postgres/index.js +5319 -0
  105. package/dist/dialects/query-stream-helper.js +152 -0
  106. package/dist/dialects/questdb/index.js +433 -0
  107. package/dist/dialects/redshift/index.js +2277 -0
  108. package/dist/dialects/singlestore/index.js +354 -0
  109. package/dist/dialects/snowflake/index.js +2082 -0
  110. package/dist/dialects/spanner/index.js +1768 -0
  111. package/dist/dialects/sqlite/index.js +3382 -0
  112. package/dist/dialects/tidb/index.js +377 -0
  113. package/dist/dialects/timescaledb/index.js +164 -0
  114. package/dist/dialects/trino/index.js +420 -0
  115. package/dist/dialects/turso/index.js +227 -0
  116. package/dist/dialects/vertica/index.js +317 -0
  117. package/dist/dialects/yugabytedb/index.js +424 -0
  118. package/dist/errors/index.js +472 -0
  119. package/dist/errors/utils.js +340 -0
  120. package/dist/errors.js +21 -0
  121. package/dist/extensions/catalog/cloud-warehouse-features.js +207 -0
  122. package/dist/extensions/catalog/mssql-features.js +147 -0
  123. package/dist/extensions/catalog/mysql-mariadb-plugins.js +229 -0
  124. package/dist/extensions/catalog/oracle-db2-features.js +196 -0
  125. package/dist/extensions/catalog/postgres-extensions.js +516 -0
  126. package/dist/extensions/index.js +71 -0
  127. package/dist/extensions/types.js +13 -0
  128. package/dist/foreign-data.js +173 -0
  129. package/dist/hooks/hooks-manager.js +350 -0
  130. package/dist/hooks/index.js +37 -0
  131. package/dist/index.js +463 -0
  132. package/dist/logging.js +335 -0
  133. package/dist/migrations/index.js +40 -0
  134. package/dist/migrations/migration.js +196 -0
  135. package/dist/migrations/migrator.js +411 -0
  136. package/dist/migrations/prormmigration.js +275 -0
  137. package/dist/migrations/query-interface.js +435 -0
  138. package/dist/migrations/seeder.js +353 -0
  139. package/dist/models/associations.js +852 -0
  140. package/dist/models/constraints.js +288 -0
  141. package/dist/models/data-types.js +2518 -0
  142. package/dist/models/decorators.js +445 -0
  143. package/dist/models/index.js +33 -0
  144. package/dist/models/indexes.js +531 -0
  145. package/dist/models/methods.js +382 -0
  146. package/dist/models/model-manager.js +103 -0
  147. package/dist/models/model.js +5349 -0
  148. package/dist/models/operators.js +67 -0
  149. package/dist/models/scopes.js +189 -0
  150. package/dist/models/typescript-types.js +26 -0
  151. package/dist/nosql/aerospike/index.js +205 -0
  152. package/dist/nosql/allegrograph/index.js +169 -0
  153. package/dist/nosql/arangodb/index.js +364 -0
  154. package/dist/nosql/azure-blob/index.js +206 -0
  155. package/dist/nosql/beanstalkd/index.js +231 -0
  156. package/dist/nosql/beequeue/index.js +210 -0
  157. package/dist/nosql/bigchaindb/index.js +195 -0
  158. package/dist/nosql/bigtable/index.js +224 -0
  159. package/dist/nosql/blazegraph/index.js +173 -0
  160. package/dist/nosql/bullmq/index.js +191 -0
  161. package/dist/nosql/cassandra/index.js +174 -0
  162. package/dist/nosql/chroma/index.js +190 -0
  163. package/dist/nosql/cloudflare-kv/index.js +220 -0
  164. package/dist/nosql/coherence/index.js +200 -0
  165. package/dist/nosql/cosmosdb/index.js +157 -0
  166. package/dist/nosql/couchbase/index.js +213 -0
  167. package/dist/nosql/dax/index.js +212 -0
  168. package/dist/nosql/deno-kv/index.js +206 -0
  169. package/dist/nosql/dgraph/index.js +171 -0
  170. package/dist/nosql/doris/index.js +169 -0
  171. package/dist/nosql/druid/index.js +162 -0
  172. package/dist/nosql/dynamodb/index.js +937 -0
  173. package/dist/nosql/elasticsearch/index.js +377 -0
  174. package/dist/nosql/etcd/index.js +502 -0
  175. package/dist/nosql/eventhubs/index.js +213 -0
  176. package/dist/nosql/eventstore/index.js +254 -0
  177. package/dist/nosql/faunadb/index.js +188 -0
  178. package/dist/nosql/firestore/index.js +177 -0
  179. package/dist/nosql/fluree/index.js +148 -0
  180. package/dist/nosql/fuseki/index.js +170 -0
  181. package/dist/nosql/gcs/index.js +172 -0
  182. package/dist/nosql/gearman/index.js +160 -0
  183. package/dist/nosql/geode/index.js +196 -0
  184. package/dist/nosql/graphdb/index.js +169 -0
  185. package/dist/nosql/graylog/index.js +188 -0
  186. package/dist/nosql/gridgain/index.js +171 -0
  187. package/dist/nosql/hazelcast/index.js +162 -0
  188. package/dist/nosql/hbase/index.js +230 -0
  189. package/dist/nosql/ignite/index.js +173 -0
  190. package/dist/nosql/immudb/index.js +184 -0
  191. package/dist/nosql/index.js +232 -0
  192. package/dist/nosql/infinispan/index.js +200 -0
  193. package/dist/nosql/influxdb/index.js +0 -0
  194. package/dist/nosql/kafka/index.js +234 -0
  195. package/dist/nosql/keyspaces/index.js +189 -0
  196. package/dist/nosql/kinesis/index.js +253 -0
  197. package/dist/nosql/leveldb/index.js +153 -0
  198. package/dist/nosql/lmdb/index.js +160 -0
  199. package/dist/nosql/loki/index.js +201 -0
  200. package/dist/nosql/marklogic/index.js +204 -0
  201. package/dist/nosql/materialize/index.js +145 -0
  202. package/dist/nosql/meilisearch/index.js +154 -0
  203. package/dist/nosql/memcached/index.js +223 -0
  204. package/dist/nosql/milvus/index.js +410 -0
  205. package/dist/nosql/minio/index.js +265 -0
  206. package/dist/nosql/momento/index.js +179 -0
  207. package/dist/nosql/mongodb/index.js +461 -0
  208. package/dist/nosql/nats/index.js +247 -0
  209. package/dist/nosql/nedb/index.js +164 -0
  210. package/dist/nosql/neo4j/index.js +450 -0
  211. package/dist/nosql/neptune/index.js +470 -0
  212. package/dist/nosql/nsq/index.js +200 -0
  213. package/dist/nosql/opensearch/index.js +186 -0
  214. package/dist/nosql/orientdb/index.js +175 -0
  215. package/dist/nosql/papertrail/index.js +200 -0
  216. package/dist/nosql/pinecone/index.js +0 -0
  217. package/dist/nosql/pinot/index.js +133 -0
  218. package/dist/nosql/pouchdb/index.js +172 -0
  219. package/dist/nosql/prometheus/index.js +174 -0
  220. package/dist/nosql/provendb/index.js +147 -0
  221. package/dist/nosql/pubsub/index.js +187 -0
  222. package/dist/nosql/pulsar/index.js +232 -0
  223. package/dist/nosql/qdrant/index.js +295 -0
  224. package/dist/nosql/qldb/index.js +192 -0
  225. package/dist/nosql/r2/index.js +281 -0
  226. package/dist/nosql/rabbitmq/index.js +237 -0
  227. package/dist/nosql/ravendb/index.js +175 -0
  228. package/dist/nosql/redis/index.js +607 -0
  229. package/dist/nosql/redpanda/index.js +237 -0
  230. package/dist/nosql/resque/index.js +203 -0
  231. package/dist/nosql/rethinkdb/index.js +232 -0
  232. package/dist/nosql/rocksdb/index.js +152 -0
  233. package/dist/nosql/rockset/index.js +126 -0
  234. package/dist/nosql/s3/index.js +298 -0
  235. package/dist/nosql/scylladb/index.js +178 -0
  236. package/dist/nosql/signoz/index.js +227 -0
  237. package/dist/nosql/sns/index.js +201 -0
  238. package/dist/nosql/solr/index.js +231 -0
  239. package/dist/nosql/splunk/index.js +227 -0
  240. package/dist/nosql/sqs/index.js +246 -0
  241. package/dist/nosql/stardog/index.js +169 -0
  242. package/dist/nosql/starrocks/index.js +170 -0
  243. package/dist/nosql/store.js +2 -0
  244. package/dist/nosql/sumologic/index.js +213 -0
  245. package/dist/nosql/surrealdb/index.js +179 -0
  246. package/dist/nosql/terminusdb/index.js +151 -0
  247. package/dist/nosql/tigergraph/index.js +469 -0
  248. package/dist/nosql/typesense/index.js +148 -0
  249. package/dist/nosql/unqlite/index.js +157 -0
  250. package/dist/nosql/upstash/index.js +167 -0
  251. package/dist/nosql/vercel-kv/index.js +166 -0
  252. package/dist/nosql/victoriametrics/index.js +213 -0
  253. package/dist/nosql/virtuoso/index.js +169 -0
  254. package/dist/nosql/weaviate/index.js +276 -0
  255. package/dist/operators/index.js +108 -0
  256. package/dist/operators.js +2690 -0
  257. package/dist/prisma-migrate/index.js +38 -0
  258. package/dist/prisma-migrate/migration-generator.js +125 -0
  259. package/dist/prisma-migrate/model-generator.js +172 -0
  260. package/dist/prisma-migrate/relations.js +100 -0
  261. package/dist/prisma-migrate/schema-parser.js +167 -0
  262. package/dist/prisma-migrate/type-mapper.js +40 -0
  263. package/dist/prorm.js +6832 -0
  264. package/dist/query-builders/cte-builder.js +80 -0
  265. package/dist/query-builders/functions/aggregate.js +390 -0
  266. package/dist/query-builders/functions/conditional.js +503 -0
  267. package/dist/query-builders/functions/datetime.js +695 -0
  268. package/dist/query-builders/functions/fulltext.js +439 -0
  269. package/dist/query-builders/functions/index.js +93 -0
  270. package/dist/query-builders/functions/json.js +427 -0
  271. package/dist/query-builders/functions/math.js +399 -0
  272. package/dist/query-builders/functions/string.js +518 -0
  273. package/dist/query-builders/functions/window.js +328 -0
  274. package/dist/query-builders/include-builder.js +323 -0
  275. package/dist/query-builders/index-expression-builder.js +242 -0
  276. package/dist/query-builders/index.js +161 -0
  277. package/dist/query-builders/insert-builder.js +164 -0
  278. package/dist/query-builders/model-helpers.js +38 -0
  279. package/dist/query-builders/order-limit-builder.js +239 -0
  280. package/dist/query-builders/sql-compiler.js +2040 -0
  281. package/dist/query-builders/subquery-builder.js +152 -0
  282. package/dist/query-builders/update-builder.js +182 -0
  283. package/dist/query-builders/view-builder.js +218 -0
  284. package/dist/query-builders/where-builder.js +1532 -0
  285. package/dist/query-interface.js +622 -0
  286. package/dist/query-optimizers/batch-optimizer.js +426 -0
  287. package/dist/query-optimizers/explain-plans.js +415 -0
  288. package/dist/query-optimizers/index.js +51 -0
  289. package/dist/query-optimizers/prepared-statement-cache.js +423 -0
  290. package/dist/query-optimizers/query-hints.js +438 -0
  291. package/dist/query-optimizers/query-optimizer.js +278 -0
  292. package/dist/query-optimizers/slow-query-logger.js +271 -0
  293. package/dist/replica-manager.js +507 -0
  294. package/dist/schema/index.js +15 -0
  295. package/dist/schema/migration-generator.js +374 -0
  296. package/dist/schema/schema-differ.js +549 -0
  297. package/dist/schema/types.js +6 -0
  298. package/dist/sql-constants.js +300 -0
  299. package/dist/sqlite-advanced.js +1047 -0
  300. package/dist/streams/index.js +12 -0
  301. package/dist/streams/transforms.js +178 -0
  302. package/dist/transaction.js +456 -0
  303. package/dist/types/index.js +180 -0
  304. package/dist/user-management.js +127 -0
  305. package/dist/utils/date.js +300 -0
  306. package/dist/utils/index.js +1079 -0
  307. package/dist/utils/string.js +161 -0
  308. package/dist/validators/index.js +19 -0
  309. package/dist/validators/validator.js +911 -0
  310. package/package.json +190 -0
package/dist/cli.js ADDED
@@ -0,0 +1,2200 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * CLI Tools - Command-line interface for the ORM. This is the single,
5
+ * canonical CLI implementation exposed via the published `orm-cli` bin.
6
+ * Provides commands: init, migration:create, migration:run, migration:revert,
7
+ * migration:status, model:create, seeder:create, db:*, diagram:*
8
+ *
9
+ * Usage:
10
+ * orm-cli init
11
+ * orm-cli migration:create -- --name <migration-name>
12
+ * orm-cli migration:run
13
+ * orm-cli migration:revert
14
+ * orm-cli migration:status
15
+ * orm-cli model:create --dialect sqlite --database ./mydb.sqlite
16
+ *
17
+ * Model Generator (programmatic):
18
+ * CLI.model('User', { name: 'STRING', email: 'STRING' })
19
+ */
20
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
21
+ if (k2 === undefined) k2 = k;
22
+ var desc = Object.getOwnPropertyDescriptor(m, k);
23
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
24
+ desc = { enumerable: true, get: function() { return m[k]; } };
25
+ }
26
+ Object.defineProperty(o, k2, desc);
27
+ }) : (function(o, m, k, k2) {
28
+ if (k2 === undefined) k2 = k;
29
+ o[k2] = m[k];
30
+ }));
31
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
32
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
33
+ }) : function(o, v) {
34
+ o["default"] = v;
35
+ });
36
+ var __importStar = (this && this.__importStar) || (function () {
37
+ var ownKeys = function(o) {
38
+ ownKeys = Object.getOwnPropertyNames || function (o) {
39
+ var ar = [];
40
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
41
+ return ar;
42
+ };
43
+ return ownKeys(o);
44
+ };
45
+ return function (mod) {
46
+ if (mod && mod.__esModule) return mod;
47
+ var result = {};
48
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
49
+ __setModuleDefault(result, mod);
50
+ return result;
51
+ };
52
+ })();
53
+ Object.defineProperty(exports, "__esModule", { value: true });
54
+ exports.CLI = void 0;
55
+ exports.init = init;
56
+ exports.getProrm = getProrm;
57
+ exports.migrationCreate = migrationCreate;
58
+ exports.migrationRun = migrationRun;
59
+ exports.migrationRevert = migrationRevert;
60
+ exports.migrationStatus = migrationStatus;
61
+ exports.showHelp = showHelp;
62
+ exports.model = model;
63
+ exports.generateModels = generateModels;
64
+ exports.configure = configure;
65
+ exports.getOutputDir = getOutputDir;
66
+ exports.setOutputDir = setOutputDir;
67
+ exports.listModels = listModels;
68
+ exports.removeModel = removeModelFile;
69
+ exports.diagramModel = diagramModel;
70
+ exports.diagramER = diagramER;
71
+ exports.diagramMigration = diagramMigration;
72
+ exports.modelCreateCommand = modelCreateCommand;
73
+ exports.prismaImportCommand = prismaImportCommand;
74
+ exports.seederCreate = seederCreate;
75
+ exports.dbSeed = dbSeed;
76
+ exports.dbSeedUndo = dbSeedUndo;
77
+ exports.dbSeedUndoAll = dbSeedUndoAll;
78
+ exports.dbSeedStatus = dbSeedStatus;
79
+ const fs = __importStar(require("fs"));
80
+ const path = __importStar(require("path"));
81
+ const migrations_1 = require("./migrations");
82
+ const diagrams_1 = require("./diagrams");
83
+ const seeder_1 = require("./migrations/seeder");
84
+ const prorm_1 = require("./prorm");
85
+ /**
86
+ * Connect to a database and get schema information
87
+ */
88
+ async function getDbSchema(connectionString) {
89
+ // Parse connection string: mysql://user:pass@host:port/db, postgres://..., sqlite:./path
90
+ let dialect = 'sqlite';
91
+ let database = ':memory:';
92
+ let username = '';
93
+ let password = '';
94
+ let host = 'localhost';
95
+ let port = 3306;
96
+ let storage = ':memory:';
97
+ if (connectionString.startsWith('mysql://') || connectionString.startsWith('mariadb://')) {
98
+ const url = new URL(connectionString);
99
+ dialect = connectionString.startsWith('mysql') ? 'mysql' : 'mariadb';
100
+ username = url.username;
101
+ password = url.password;
102
+ host = url.hostname;
103
+ port = parseInt(url.port) || 3306;
104
+ database = url.pathname.slice(1);
105
+ }
106
+ else if (connectionString.startsWith('postgres://') ||
107
+ connectionString.startsWith('postgresql://')) {
108
+ const url = new URL(connectionString);
109
+ dialect = 'postgres';
110
+ username = url.username;
111
+ password = url.password;
112
+ host = url.hostname;
113
+ port = parseInt(url.port) || 5432;
114
+ database = url.pathname.slice(1);
115
+ }
116
+ else if (connectionString.startsWith('sqlite:')) {
117
+ dialect = 'sqlite';
118
+ storage = connectionString.replace('sqlite:', '');
119
+ if (!storage)
120
+ storage = ':memory:';
121
+ }
122
+ else {
123
+ // Assume it's a file path for sqlite
124
+ dialect = 'sqlite';
125
+ storage = connectionString;
126
+ }
127
+ const seq = new prorm_1.Prorm({
128
+ dialect,
129
+ database,
130
+ username,
131
+ password,
132
+ host,
133
+ port,
134
+ storage: dialect === 'sqlite' ? storage : undefined,
135
+ logging: false,
136
+ });
137
+ await seq.authenticate();
138
+ log(`Connected to ${dialect} database`, 'info');
139
+ // Query table information based on dialect
140
+ let tables = [];
141
+ if (dialect === 'mysql' || dialect === 'mariadb') {
142
+ const [rows] = (await seq.query(`SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = ?`, {
143
+ replacements: [database],
144
+ }));
145
+ for (const row of rows) {
146
+ const tableName = row.TABLE_NAME;
147
+ const [cols] = (await seq.query(`SELECT * FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?`, {
148
+ replacements: [database, tableName],
149
+ }));
150
+ tables.push({
151
+ name: tableName,
152
+ columns: cols.map((c) => ({
153
+ name: c.COLUMN_NAME,
154
+ type: c.COLUMN_TYPE,
155
+ allowNull: c.IS_NULLABLE === 'YES',
156
+ defaultValue: c.COLUMN_DEFAULT,
157
+ primaryKey: c.COLUMN_KEY === 'PRI',
158
+ unique: c.COLUMN_KEY === 'UNI',
159
+ autoIncrement: c.EXTRA === 'auto_increment',
160
+ })),
161
+ });
162
+ }
163
+ }
164
+ else if (dialect === 'postgres') {
165
+ const [tablesRes] = (await seq.query(`SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'`));
166
+ for (const row of tablesRes) {
167
+ const tableName = row.table_name;
168
+ const [cols] = (await seq.query(`SELECT column_name, data_type, is_nullable, column_default, character_maximum_length FROM information_schema.columns WHERE table_name = $1 AND table_schema = 'public'`, {
169
+ replacements: [tableName],
170
+ }));
171
+ tables.push({
172
+ name: tableName,
173
+ columns: cols.map((c) => ({
174
+ name: c.column_name,
175
+ type: c.data_type + (c.character_maximum_length ? `(${c.character_maximum_length})` : ''),
176
+ allowNull: c.is_nullable === 'YES',
177
+ defaultValue: c.column_default,
178
+ primaryKey: false,
179
+ unique: false,
180
+ autoIncrement: false,
181
+ })),
182
+ });
183
+ }
184
+ }
185
+ else if (dialect === 'sqlite') {
186
+ const [tablesRes] = (await seq.query(`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'`));
187
+ for (const row of tablesRes) {
188
+ const tableName = row.name;
189
+ const [cols] = (await seq.query(`PRAGMA table_info("${tableName}")`));
190
+ tables.push({
191
+ name: tableName,
192
+ columns: cols.map((c) => ({
193
+ name: c.name,
194
+ type: c.type,
195
+ allowNull: c.notnull === 0,
196
+ defaultValue: c.dflt_value,
197
+ primaryKey: c.pk === 1,
198
+ unique: false,
199
+ autoIncrement: c.autoincrement === 1,
200
+ })),
201
+ });
202
+ }
203
+ }
204
+ await seq.close();
205
+ return tables;
206
+ }
207
+ /**
208
+ * Convert database schema to ModelDiagramOptions[]
209
+ */
210
+ function schemaToModels(tables) {
211
+ return tables.map((table) => ({
212
+ name: table.name.charAt(0).toUpperCase() +
213
+ table.name.slice(1).replace(/_([a-z])/g, (_, c) => c.toUpperCase()),
214
+ tableName: table.name,
215
+ fields: Object.fromEntries(table.columns.map((col) => [
216
+ col.name,
217
+ {
218
+ type: col.type,
219
+ primaryKey: col.primaryKey,
220
+ allowNull: col.allowNull,
221
+ defaultValue: col.defaultValue,
222
+ autoIncrement: col.autoIncrement,
223
+ unique: col.unique,
224
+ },
225
+ ])),
226
+ timestamps: true,
227
+ }));
228
+ }
229
+ // ==================== Model Generator ====================
230
+ // Map of shorthand types to DataTypes
231
+ const TYPE_MAP = {
232
+ STRING: 'DataTypes.STRING(255)',
233
+ TEXT: 'DataTypes.TEXT()',
234
+ INTEGER: 'DataTypes.INTEGER()',
235
+ BIGINT: 'DataTypes.BIGINT()',
236
+ FLOAT: 'DataTypes.FLOAT()',
237
+ DOUBLE: 'DataTypes.DOUBLE()',
238
+ DECIMAL: 'DataTypes.DECIMAL(10, 2)',
239
+ BOOLEAN: 'DataTypes.BOOLEAN()',
240
+ DATE: 'DataTypes.DATE()',
241
+ DATEONLY: 'DataTypes.DATEONLY()',
242
+ TIME: 'DataTypes.TIME()',
243
+ UUID: 'DataTypes.UUID()',
244
+ JSON: 'DataTypes.JSON()',
245
+ JSONB: 'DataTypes.JSONB()',
246
+ BLOB: 'DataTypes.BLOB()',
247
+ ENUM: "DataTypes.ENUM('value1', 'value2')",
248
+ ARRAY: 'DataTypes.ARRAY(DataTypes.STRING)',
249
+ };
250
+ let modelConfig = {
251
+ outputDir: process.cwd(),
252
+ };
253
+ /**
254
+ * Configure model generator options
255
+ */
256
+ function configure(options) {
257
+ modelConfig = { ...modelConfig, ...options };
258
+ }
259
+ /**
260
+ * Get the output directory for generated models
261
+ */
262
+ function getOutputDir() {
263
+ return modelConfig.outputDir;
264
+ }
265
+ /**
266
+ * Set the output directory for generated models
267
+ */
268
+ function setOutputDir(dir) {
269
+ modelConfig.outputDir = dir;
270
+ }
271
+ /**
272
+ * Parse a type string into DataTypes expression
273
+ */
274
+ function parseModelType(typeStr) {
275
+ // Check for type with length (e.g., STRING(100))
276
+ const match = typeStr.match(/^(\w+)\((\d+)\)$/);
277
+ if (match) {
278
+ const [, type, length] = match;
279
+ if (TYPE_MAP[type]) {
280
+ return TYPE_MAP[type].replace(/\(\d+\)/, `(${length})`);
281
+ }
282
+ return `DataTypes.${type}(${length})`;
283
+ }
284
+ // Check for simple type
285
+ if (TYPE_MAP[typeStr]) {
286
+ return TYPE_MAP[typeStr];
287
+ }
288
+ // Default to STRING
289
+ return 'DataTypes.STRING(255)';
290
+ }
291
+ /**
292
+ * Generate field definitions from simple field map
293
+ */
294
+ function generateFieldDefs(fields, primaryKey, autoIncrement, allowNull, unique) {
295
+ const lines = [];
296
+ const fieldNames = Object.keys(fields);
297
+ // Add user-defined fields (skip id - it's added if primaryKey is true)
298
+ fieldNames.forEach((fieldName) => {
299
+ const typeStr = fields[fieldName];
300
+ const parsedType = parseModelType(typeStr);
301
+ const fieldDef = [];
302
+ fieldDef.push(` ${fieldName}: {`);
303
+ fieldDef.push(` type: ${parsedType},`);
304
+ // Only add allowNull if it's true (default is false)
305
+ if (allowNull) {
306
+ fieldDef.push(` allowNull: ${allowNull},`);
307
+ }
308
+ if (unique) {
309
+ fieldDef.push(` unique: true,`);
310
+ }
311
+ // Remove trailing comma and close
312
+ const lastIdx = fieldDef.length - 1;
313
+ fieldDef[lastIdx] = fieldDef[lastIdx].replace(/,$/, '');
314
+ fieldDef.push(' },');
315
+ lines.push(fieldDef.join('\n'));
316
+ });
317
+ // Prepend id field if primaryKey is enabled
318
+ if (primaryKey) {
319
+ const idField = ` id: {
320
+ type: DataTypes.INTEGER(),
321
+ primaryKey: true,
322
+ autoIncrement: ${autoIncrement},
323
+ },`;
324
+ lines.unshift(idField);
325
+ }
326
+ return lines.join('\n');
327
+ }
328
+ /**
329
+ * Generate the complete model file content
330
+ */
331
+ function generateModelFile(modelName, tableName, fieldDefs, timestamps, paranoid) {
332
+ const options = [];
333
+ options.push(`tableName: '${tableName}',`);
334
+ options.push(`timestamps: ${timestamps},`);
335
+ if (paranoid) {
336
+ options.push(`paranoid: true,`);
337
+ }
338
+ return `import { DataTypes, Model } from 'ts-prorm-orm';
339
+
340
+ /**
341
+ * ${modelName} model
342
+ * Generated by CLI model generator
343
+ */
344
+ export interface ${modelName}Attributes {
345
+ id?: number;
346
+ }
347
+
348
+ export interface ${modelName}Instance extends Model, ${modelName}Attributes {
349
+ // Instance methods can be added here
350
+ }
351
+
352
+ const ${modelName} = (prorm: any) => {
353
+ const ${modelName}Model = prorm.define('${modelName}', {
354
+ ${fieldDefs}
355
+ }, {
356
+ ${options.map((opt) => ` ${opt}`).join('\n')}
357
+ });
358
+
359
+ // Define associations
360
+ // ${modelName}Model.associate = (models: any) => {
361
+ // ${modelName}Model.hasMany(models.Post, { foreignKey: 'userId', as: 'posts' });
362
+ // ${modelName}Model.belongsTo(models.Other, { foreignKey: 'otherId', as: 'other' });
363
+ // };
364
+
365
+ return ${modelName}Model;
366
+ };
367
+
368
+ export { ${modelName} };
369
+ export default ${modelName};
370
+ `;
371
+ }
372
+ /**
373
+ * Generate a model file from model name and fields
374
+ * @param modelName - The name of the model (e.g., 'User')
375
+ * @param fields - Object defining fields (e.g., { name: 'STRING', email: 'STRING' })
376
+ * @param options - Optional model configuration
377
+ */
378
+ function model(modelName, fields, options = {}) {
379
+ const { tableName = modelName.toLowerCase() + 's', timestamps = true, paranoid = false, primaryKey = true, autoIncrement = true, allowNull = false, unique = false, } = options;
380
+ // Build field definitions
381
+ const fieldDefs = generateFieldDefs(fields, primaryKey, autoIncrement, allowNull, unique);
382
+ // Generate the model file content
383
+ const modelContent = generateModelFile(modelName, tableName, fieldDefs, timestamps, paranoid);
384
+ // Determine output path
385
+ const outputPath = path.join(modelConfig.outputDir, `${modelName}.ts`);
386
+ // Write the file
387
+ fs.writeFileSync(outputPath, modelContent, 'utf-8');
388
+ console.log(`Model "${modelName}" created at: ${outputPath}`);
389
+ return outputPath;
390
+ }
391
+ /**
392
+ * Generate multiple models at once
393
+ */
394
+ function generateModels(definitions) {
395
+ const generatedFiles = [];
396
+ for (const [modelName, fields] of Object.entries(definitions)) {
397
+ const filePath = model(modelName, fields);
398
+ generatedFiles.push(filePath);
399
+ }
400
+ return generatedFiles;
401
+ }
402
+ /**
403
+ * Get list of generated model files in the output directory
404
+ */
405
+ function listModels() {
406
+ const outputDir = modelConfig.outputDir;
407
+ if (!fs.existsSync(outputDir)) {
408
+ return [];
409
+ }
410
+ return fs
411
+ .readdirSync(outputDir)
412
+ .filter((file) => file.endsWith('.ts') && file !== 'index.ts')
413
+ .sort();
414
+ }
415
+ /**
416
+ * Remove a generated model file
417
+ */
418
+ function removeModelFile(modelName) {
419
+ const filePath = path.join(modelConfig.outputDir, `${modelName}.ts`);
420
+ if (fs.existsSync(filePath)) {
421
+ fs.unlinkSync(filePath);
422
+ console.log(`Model "${modelName}" removed from: ${filePath}`);
423
+ return true;
424
+ }
425
+ console.log(`Model "${modelName}" not found at: ${filePath}`);
426
+ return false;
427
+ }
428
+ /**
429
+ * CLI object for chained usage: CLI.model('User', { ... })
430
+ */
431
+ const CLI = {
432
+ model,
433
+ generateModels,
434
+ configure,
435
+ getOutputDir,
436
+ setOutputDir,
437
+ listModels,
438
+ removeModel: removeModelFile,
439
+ };
440
+ exports.CLI = CLI;
441
+ // Default configuration
442
+ const defaultConfig = {
443
+ migrationsPath: path.join(process.cwd(), 'migrations'),
444
+ seedersPath: path.join(process.cwd(), 'seeders'),
445
+ verbose: true,
446
+ };
447
+ /**
448
+ * Get project root directory
449
+ */
450
+ function getProjectRoot() {
451
+ return process.cwd();
452
+ }
453
+ /**
454
+ * Create directory if it doesn't exist
455
+ */
456
+ function createDirectory(dirPath) {
457
+ if (!fs.existsSync(dirPath)) {
458
+ fs.mkdirSync(dirPath, { recursive: true });
459
+ console.log(`Created: ${dirPath}`);
460
+ }
461
+ else {
462
+ console.log(`Exists: ${dirPath}`);
463
+ }
464
+ }
465
+ /**
466
+ * Create config file
467
+ */
468
+ function createConfigFile(configDir) {
469
+ const configContent = `import { Prorm } from 'ts-prorm-orm';
470
+
471
+ // Zero-config default: SQLite. Set DB_DIALECT (and related DB_* env vars)
472
+ // to switch to mysql, postgres, or mariadb.
473
+ const prorm = new Prorm({
474
+ dialect: (process.env.DB_DIALECT as any) || 'sqlite',
475
+ storage: process.env.DB_STORAGE || './database.sqlite',
476
+ host: process.env.DB_HOST || 'localhost',
477
+ port: parseInt(process.env.DB_PORT || '3306'),
478
+ database: process.env.DB_NAME || 'myapp',
479
+ username: process.env.DB_USER || 'root',
480
+ password: process.env.DB_PASSWORD || '',
481
+ logging: console.log,
482
+ define: {
483
+ timestamps: true,
484
+ underscored: false,
485
+ },
486
+ });
487
+
488
+ export default prorm;
489
+ `;
490
+ const configPath = path.join(configDir, 'config.ts');
491
+ if (!fs.existsSync(configPath)) {
492
+ fs.writeFileSync(configPath, configContent);
493
+ console.log(`Created: ${configPath}`);
494
+ }
495
+ else {
496
+ console.log(`Exists: ${configPath}`);
497
+ }
498
+ }
499
+ /**
500
+ * Create models index file
501
+ */
502
+ function createModelsIndexFile(modelsDir) {
503
+ const indexContent = `import { DataTypes, Model, Prorm } from 'ts-prorm-orm';
504
+ import prorm from '../config/config';
505
+
506
+ export { prorm, DataTypes, Model };
507
+
508
+ // Import models and define associations here
509
+ // Example:
510
+ // import { User } from './user';
511
+ // User.hasMany(Post);
512
+ // Post.belongsTo(User);
513
+
514
+ export const models = {
515
+ // Add your models here
516
+ };
517
+ `;
518
+ const indexPath = path.join(modelsDir, 'index.ts');
519
+ if (!fs.existsSync(indexPath)) {
520
+ fs.writeFileSync(indexPath, indexContent);
521
+ console.log(`Created: ${indexPath}`);
522
+ }
523
+ else {
524
+ console.log(`Exists: ${indexPath}`);
525
+ }
526
+ }
527
+ /**
528
+ * Create migrations index file
529
+ */
530
+ function createMigrationsIndexFile(migrationsDir) {
531
+ const indexContent = `// Migration files go here
532
+ // Each migration should export 'up' and 'down' functions
533
+ //
534
+ // Example migration:
535
+ // export async function up(queryInterface: any) {
536
+ // await queryInterface.createTable('users', {
537
+ // id: {
538
+ // type: DataTypes.INTEGER,
539
+ // autoIncrement: true,
540
+ // primaryKey: true,
541
+ // },
542
+ // name: {
543
+ // type: DataTypes.STRING,
544
+ // allowNull: false,
545
+ // },
546
+ // createdAt: {
547
+ // type: DataTypes.DATE,
548
+ // allowNull: false,
549
+ // },
550
+ // updatedAt: {
551
+ // type: DataTypes.DATE,
552
+ // allowNull: false,
553
+ // },
554
+ // });
555
+ // }
556
+ //
557
+ // export async function down(queryInterface: any) {
558
+ // await queryInterface.dropTable('users');
559
+ // }
560
+ `;
561
+ const indexPath = path.join(migrationsDir, 'index.ts');
562
+ if (!fs.existsSync(indexPath)) {
563
+ fs.writeFileSync(indexPath, indexContent);
564
+ console.log(`Created: ${indexPath}`);
565
+ }
566
+ else {
567
+ console.log(`Exists: ${indexPath}`);
568
+ }
569
+ }
570
+ /**
571
+ * Initialize a new Prorm project
572
+ */
573
+ function init(options = {}) {
574
+ const projectRoot = options.directory || getProjectRoot();
575
+ console.log(`Initializing Prorm project in: ${projectRoot}`);
576
+ console.log('----------------------------------------');
577
+ const configDir = path.join(projectRoot, 'config');
578
+ const modelsDir = path.join(projectRoot, 'models');
579
+ const migrationsDir = path.join(projectRoot, 'migrations');
580
+ createDirectory(configDir);
581
+ createDirectory(modelsDir);
582
+ createDirectory(migrationsDir);
583
+ createConfigFile(configDir);
584
+ createModelsIndexFile(modelsDir);
585
+ createMigrationsIndexFile(migrationsDir);
586
+ console.log('----------------------------------------');
587
+ console.log('Prorm project initialized successfully!');
588
+ console.log('');
589
+ console.log('Next steps:');
590
+ console.log(' 1. Update config/config.ts with your database credentials');
591
+ console.log(' 2. Add your models in the models/ directory');
592
+ console.log(' 3. Run migrations using your migration tool');
593
+ }
594
+ // ============================================
595
+ // Migration CLI Commands
596
+ // ============================================
597
+ /**
598
+ * Print colored console output
599
+ */
600
+ function log(message, type = 'info') {
601
+ const colors = {
602
+ info: '\x1b[36m', // Cyan
603
+ success: '\x1b[32m', // Green
604
+ error: '\x1b[31m', // Red
605
+ warning: '\x1b[33m', // Yellow
606
+ };
607
+ const reset = '\x1b[0m';
608
+ console.log(`${colors[type]}${message}${reset}`);
609
+ }
610
+ /**
611
+ * Command: db:create
612
+ * Creates the target database if it does not exist
613
+ */
614
+ async function dbCreate() {
615
+ const dbDialect = process.env.DB_DIALECT || process.env.DIALECT || 'sqlite';
616
+ const dbName = process.env.DB_NAME || process.env.DB_DATABASE || 'database.db';
617
+ const dbUser = process.env.DB_USER || process.env.DB_USERNAME || '';
618
+ const dbPass = process.env.DB_PASSWORD || process.env.DB_PASS || '';
619
+ const dbHost = process.env.DB_HOST || 'localhost';
620
+ const dbPort = parseInt(process.env.DB_PORT || '5432', 10);
621
+ log(`Creating database: ${dbName}...`, 'info');
622
+ try {
623
+ switch (dbDialect) {
624
+ case 'sqlite':
625
+ // SQLite - just verify we can create the file
626
+ const sqlitePath = path.isAbsolute(dbName) ? dbName : path.join(process.cwd(), dbName);
627
+ if (!fs.existsSync(sqlitePath)) {
628
+ fs.writeFileSync(sqlitePath, ''); // Create empty file
629
+ log(`Created SQLite database: ${sqlitePath}`, 'success');
630
+ }
631
+ else {
632
+ log(`Database already exists: ${sqlitePath}`, 'warning');
633
+ }
634
+ break;
635
+ case 'postgres':
636
+ case 'postgresql': {
637
+ // PostgreSQL - connect to postgres system db and create
638
+ const adminSeq = new prorm_1.Prorm({
639
+ dialect: 'postgres',
640
+ database: 'postgres',
641
+ username: dbUser,
642
+ password: dbPass,
643
+ host: dbHost,
644
+ port: dbPort,
645
+ logging: false,
646
+ });
647
+ await adminSeq.query(`CREATE DATABASE ${dbName}`);
648
+ await adminSeq.close();
649
+ log(`Created PostgreSQL database: ${dbName}`, 'success');
650
+ break;
651
+ }
652
+ case 'mysql': {
653
+ // MySQL - connect to mysql system db and create
654
+ const adminSeq = new prorm_1.Prorm({
655
+ dialect: 'mysql',
656
+ database: 'mysql',
657
+ username: dbUser,
658
+ password: dbPass,
659
+ host: dbHost,
660
+ port: dbPort,
661
+ logging: false,
662
+ });
663
+ await adminSeq.query(`CREATE DATABASE \`${dbName}\``);
664
+ await adminSeq.close();
665
+ log(`Created MySQL database: ${dbName}`, 'success');
666
+ break;
667
+ }
668
+ case 'mariadb': {
669
+ // MariaDB - similar to MySQL
670
+ const adminSeq = new prorm_1.Prorm({
671
+ dialect: 'mariadb',
672
+ database: 'mysql',
673
+ username: dbUser,
674
+ password: dbPass,
675
+ host: dbHost,
676
+ port: dbPort,
677
+ logging: false,
678
+ });
679
+ await adminSeq.query(`CREATE DATABASE \`${dbName}\``);
680
+ await adminSeq.close();
681
+ log(`Created MariaDB database: ${dbName}`, 'success');
682
+ break;
683
+ }
684
+ default:
685
+ throw new Error(`Unsupported dialect: ${dbDialect}`);
686
+ }
687
+ }
688
+ catch (error) {
689
+ if (error.message?.includes('already exists')) {
690
+ log(`Database already exists: ${dbName}`, 'warning');
691
+ }
692
+ else {
693
+ log(`Error creating database: ${error.message}`, 'error');
694
+ throw error;
695
+ }
696
+ }
697
+ }
698
+ /**
699
+ * Command: db:drop
700
+ * Drops the target database
701
+ */
702
+ async function dbDrop() {
703
+ const dbDialect = process.env.DB_DIALECT || process.env.DIALECT || 'sqlite';
704
+ const dbName = process.env.DB_NAME || process.env.DB_DATABASE || 'database.db';
705
+ const dbUser = process.env.DB_USER || process.env.DB_USERNAME || '';
706
+ const dbPass = process.env.DB_PASSWORD || process.env.DB_PASS || '';
707
+ const dbHost = process.env.DB_HOST || 'localhost';
708
+ const dbPort = parseInt(process.env.DB_PORT || '5432', 10);
709
+ log(`Dropping database: ${dbName}...`, 'info');
710
+ try {
711
+ switch (dbDialect) {
712
+ case 'sqlite':
713
+ // SQLite - just delete the file
714
+ const sqlitePath = path.isAbsolute(dbName) ? dbName : path.join(process.cwd(), dbName);
715
+ if (fs.existsSync(sqlitePath)) {
716
+ fs.unlinkSync(sqlitePath);
717
+ log(`Dropped SQLite database: ${sqlitePath}`, 'success');
718
+ }
719
+ else {
720
+ log(`Database does not exist: ${sqlitePath}`, 'warning');
721
+ }
722
+ break;
723
+ case 'postgres':
724
+ case 'postgresql': {
725
+ // PostgreSQL - connect to postgres system db and drop
726
+ const adminSeq = new prorm_1.Prorm({
727
+ dialect: 'postgres',
728
+ database: 'postgres',
729
+ username: dbUser,
730
+ password: dbPass,
731
+ host: dbHost,
732
+ port: dbPort,
733
+ logging: false,
734
+ });
735
+ // Terminate all connections to the database first
736
+ await adminSeq
737
+ .query(`
738
+ SELECT pg_terminate_backend(pg_stat_activity.pid)
739
+ FROM pg_stat_activity
740
+ WHERE pg_stat_activity.datname = '${dbName}'
741
+ AND pid <> pg_backend_pid()
742
+ `)
743
+ .catch(() => {
744
+ /* ignore errors */
745
+ });
746
+ await adminSeq.query(`DROP DATABASE IF EXISTS ${dbName}`);
747
+ await adminSeq.close();
748
+ log(`Dropped PostgreSQL database: ${dbName}`, 'success');
749
+ break;
750
+ }
751
+ case 'mysql': {
752
+ // MySQL - connect to mysql system db and drop
753
+ const adminSeq = new prorm_1.Prorm({
754
+ dialect: 'mysql',
755
+ database: 'mysql',
756
+ username: dbUser,
757
+ password: dbPass,
758
+ host: dbHost,
759
+ port: dbPort,
760
+ logging: false,
761
+ });
762
+ await adminSeq.query(`DROP DATABASE IF EXISTS \`${dbName}\``);
763
+ await adminSeq.close();
764
+ log(`Dropped MySQL database: ${dbName}`, 'success');
765
+ break;
766
+ }
767
+ case 'mariadb': {
768
+ // MariaDB - similar to MySQL
769
+ const adminSeq = new prorm_1.Prorm({
770
+ dialect: 'mariadb',
771
+ database: 'mysql',
772
+ username: dbUser,
773
+ password: dbPass,
774
+ host: dbHost,
775
+ port: dbPort,
776
+ logging: false,
777
+ });
778
+ await adminSeq.query(`DROP DATABASE IF EXISTS \`${dbName}\``);
779
+ await adminSeq.close();
780
+ log(`Dropped MariaDB database: ${dbName}`, 'success');
781
+ break;
782
+ }
783
+ default:
784
+ throw new Error(`Unsupported dialect: ${dbDialect}`);
785
+ }
786
+ }
787
+ catch (error) {
788
+ log(`Error dropping database: ${error.message}`, 'error');
789
+ throw error;
790
+ }
791
+ }
792
+ /**
793
+ * Load Prorm instance from environment or config
794
+ */
795
+ async function getProrm() {
796
+ // Try to load from config file or environment
797
+ const configPaths = [
798
+ path.join(process.cwd(), 'config', 'database.js'),
799
+ path.join(process.cwd(), 'config', 'database.ts'),
800
+ path.join(process.cwd(), 'database.js'),
801
+ path.join(process.cwd(), 'database.ts'),
802
+ path.join(process.cwd(), '.env'),
803
+ ];
804
+ let prorm = null;
805
+ // Check for environment variables first
806
+ const databaseUrl = process.env.DATABASE_URL || process.env.DATABASE;
807
+ const dbDialect = process.env.DB_DIALECT || process.env.DIALECT || 'sqlite';
808
+ const dbName = process.env.DB_NAME || process.env.DB_DATABASE || 'database.db';
809
+ const dbUser = process.env.DB_USER || process.env.DB_USERNAME || '';
810
+ const dbPass = process.env.DB_PASSWORD || process.env.DB_PASS || '';
811
+ const dbHost = process.env.DB_HOST || 'localhost';
812
+ const dbPort = parseInt(process.env.DB_PORT || '5432', 10);
813
+ if (databaseUrl) {
814
+ // Parse database URL (e.g., postgres://user:pass@localhost:5432/db)
815
+ const url = new URL(databaseUrl);
816
+ // Normalize protocol to a supported Dialect (e.g. 'postgresql' -> 'postgres')
817
+ const urlDialect = url.protocol.replace(':', '');
818
+ prorm = new prorm_1.Prorm({
819
+ dialect: (urlDialect === 'postgresql' ? 'postgres' : urlDialect),
820
+ database: url.pathname.replace('/', ''),
821
+ username: url.username,
822
+ password: url.password,
823
+ host: url.hostname,
824
+ port: parseInt(url.port, 10) || 5432,
825
+ logging: false,
826
+ });
827
+ }
828
+ else {
829
+ // Create based on dialect
830
+ switch (dbDialect) {
831
+ case 'sqlite':
832
+ prorm = new prorm_1.Prorm({
833
+ dialect: 'sqlite',
834
+ storage: dbName,
835
+ logging: false,
836
+ });
837
+ break;
838
+ case 'postgres':
839
+ case 'postgresql':
840
+ prorm = new prorm_1.Prorm({
841
+ dialect: 'postgres',
842
+ host: dbHost,
843
+ port: dbPort,
844
+ database: dbName,
845
+ username: dbUser,
846
+ password: dbPass,
847
+ logging: false,
848
+ });
849
+ break;
850
+ case 'mysql':
851
+ prorm = new prorm_1.Prorm({
852
+ dialect: 'mysql',
853
+ host: dbHost,
854
+ port: dbPort,
855
+ database: dbName,
856
+ username: dbUser,
857
+ password: dbPass,
858
+ logging: false,
859
+ });
860
+ break;
861
+ case 'mariadb':
862
+ prorm = new prorm_1.Prorm({
863
+ dialect: 'mariadb',
864
+ host: dbHost,
865
+ port: dbPort,
866
+ database: dbName,
867
+ username: dbUser,
868
+ password: dbPass,
869
+ logging: false,
870
+ });
871
+ break;
872
+ default:
873
+ throw new Error(`Unsupported dialect: ${dbDialect}`);
874
+ }
875
+ }
876
+ // Establish the connection, then test it. `authenticate()` requires
877
+ // `connect()` to have been called first (it throws "Database connection
878
+ // not established" otherwise), so both steps are needed here.
879
+ try {
880
+ await prorm.connect();
881
+ await prorm.authenticate();
882
+ log('Database connection established', 'success');
883
+ }
884
+ catch (error) {
885
+ log(`Failed to connect to database: ${error}`, 'error');
886
+ throw error;
887
+ }
888
+ return prorm;
889
+ }
890
+ /**
891
+ * Ensure migrations directory exists
892
+ */
893
+ function ensureMigrationsDir(migrationsPath) {
894
+ if (!fs.existsSync(migrationsPath)) {
895
+ fs.mkdirSync(migrationsPath, { recursive: true });
896
+ log(`Created migrations directory: ${migrationsPath}`, 'success');
897
+ }
898
+ }
899
+ /**
900
+ * Command: migration:create
901
+ * Creates a new migration file
902
+ */
903
+ async function migrationCreate(name, config) {
904
+ if (!name) {
905
+ log('Error: Migration name is required. Usage: --name <migration-name>', 'error');
906
+ process.exit(1);
907
+ }
908
+ ensureMigrationsDir(config.migrationsPath);
909
+ const filepath = (0, migrations_1.createMigration)(name, { path: config.migrationsPath });
910
+ log(`Migration created successfully: ${path.basename(filepath)}`, 'success');
911
+ }
912
+ /**
913
+ * Command: migration:run
914
+ * Runs pending migrations
915
+ */
916
+ async function migrationRun(options, config) {
917
+ const prorm = await getProrm();
918
+ const migrator = (0, migrations_1.createMigrator)({
919
+ prorm,
920
+ path: config.migrationsPath,
921
+ });
922
+ try {
923
+ // Sync migrations table
924
+ await migrator.syncMigrationsTable();
925
+ const migrationName = options.name;
926
+ if (migrationName) {
927
+ // Run specific migration
928
+ log(`Running migration: ${migrationName}...`, 'info');
929
+ const result = await migrator.up({ migration: migrationName });
930
+ if (result.executed > 0) {
931
+ log(`Migration "${migrationName}" executed successfully`, 'success');
932
+ }
933
+ else {
934
+ log(`Migration "${migrationName}" already applied or not found`, 'warning');
935
+ }
936
+ }
937
+ else {
938
+ // Run all pending migrations
939
+ const pending = await migrator.pending();
940
+ if (pending.length === 0) {
941
+ log('No pending migrations to run', 'info');
942
+ return;
943
+ }
944
+ log(`Running ${pending.length} pending migration(s)...`, 'info');
945
+ const result = await migrator.up();
946
+ log(`Executed ${result.executed} migration(s)`, 'success');
947
+ if (config.verbose && result.migrations.length > 0) {
948
+ result.migrations.forEach((name) => {
949
+ console.log(` - ${name}`);
950
+ });
951
+ }
952
+ }
953
+ }
954
+ finally {
955
+ await prorm.close();
956
+ }
957
+ }
958
+ /**
959
+ * Command: migration:revert
960
+ * Reverts migrations
961
+ */
962
+ async function migrationRevert(options, config) {
963
+ const prorm = await getProrm();
964
+ const migrator = (0, migrations_1.createMigrator)({
965
+ prorm,
966
+ path: config.migrationsPath,
967
+ });
968
+ try {
969
+ // Sync migrations table
970
+ await migrator.syncMigrationsTable();
971
+ const migrationName = options.name;
972
+ const steps = options.steps ? parseInt(options.steps, 10) : undefined;
973
+ if (migrationName) {
974
+ // Revert specific migration
975
+ log(`Reverting migration: ${migrationName}...`, 'info');
976
+ const result = await migrator.down({ migration: migrationName });
977
+ if (result.reverted > 0) {
978
+ log(`Migration "${migrationName}" reverted successfully`, 'success');
979
+ }
980
+ else {
981
+ log(`Migration "${migrationName}" not found or not applied`, 'warning');
982
+ }
983
+ }
984
+ else if (steps !== undefined) {
985
+ // Revert N migrations
986
+ log(`Reverting ${steps} migration(s)...`, 'info');
987
+ const result = await migrator.down({ steps });
988
+ log(`Reverted ${result.reverted} migration(s)`, 'success');
989
+ if (config.verbose && result.migrations.length > 0) {
990
+ result.migrations.forEach((name) => {
991
+ console.log(` - ${name}`);
992
+ });
993
+ }
994
+ }
995
+ else {
996
+ // Revert last migration
997
+ const executed = await migrator.executed();
998
+ if (executed.length === 0) {
999
+ log('No migrations to revert', 'info');
1000
+ return;
1001
+ }
1002
+ log('Reverting last migration...', 'info');
1003
+ const result = await migrator.down();
1004
+ log(`Reverted ${result.reverted} migration(s)`, 'success');
1005
+ if (config.verbose && result.migrations.length > 0) {
1006
+ result.migrations.forEach((name) => {
1007
+ console.log(` - ${name}`);
1008
+ });
1009
+ }
1010
+ }
1011
+ }
1012
+ finally {
1013
+ await prorm.close();
1014
+ }
1015
+ }
1016
+ /**
1017
+ * Command: migration:status
1018
+ * Shows migration status
1019
+ */
1020
+ async function migrationStatus(config) {
1021
+ const prorm = await getProrm();
1022
+ const migrator = (0, migrations_1.createMigrator)({
1023
+ prorm,
1024
+ path: config.migrationsPath,
1025
+ });
1026
+ try {
1027
+ // Sync migrations table
1028
+ await migrator.syncMigrationsTable();
1029
+ const statuses = await migrator.status();
1030
+ if (statuses.length === 0) {
1031
+ log('No migrations found', 'warning');
1032
+ return;
1033
+ }
1034
+ console.log('\nMigration Status:');
1035
+ console.log('================');
1036
+ const pending = statuses.filter((s) => s.status === 'pending');
1037
+ const executed = statuses.filter((s) => s.status === 'up');
1038
+ // Show executed migrations first
1039
+ if (executed.length > 0) {
1040
+ console.log('\nExecuted Migrations:');
1041
+ executed.forEach((s) => {
1042
+ const batch = s.batch ? `[Batch ${s.batch}]` : '';
1043
+ console.log(` [UP] ${s.name} ${batch}`);
1044
+ });
1045
+ }
1046
+ // Show pending migrations
1047
+ if (pending.length > 0) {
1048
+ console.log('\nPending Migrations:');
1049
+ pending.forEach((s) => {
1050
+ console.log(` [DOWN] ${s.name}`);
1051
+ });
1052
+ }
1053
+ console.log('\n----------------');
1054
+ console.log(`Total: ${statuses.length} (${executed.length} executed, ${pending.length} pending)`);
1055
+ console.log('');
1056
+ }
1057
+ finally {
1058
+ await prorm.close();
1059
+ }
1060
+ }
1061
+ // ==================== Seeder CLI Functions ====================
1062
+ /**
1063
+ * Ensure seeders directory exists
1064
+ */
1065
+ function ensureSeedersDir(seedersPath) {
1066
+ if (!fs.existsSync(seedersPath)) {
1067
+ fs.mkdirSync(seedersPath, { recursive: true });
1068
+ log('Created seeders directory: ' + seedersPath, 'success');
1069
+ }
1070
+ }
1071
+ /**
1072
+ * Command: seeder:create
1073
+ * Creates a new seeder file
1074
+ */
1075
+ async function seederCreate(name, config) {
1076
+ if (!name) {
1077
+ log('Error: Seeder name is required. Usage: --name <seeder-name>', 'error');
1078
+ process.exit(1);
1079
+ }
1080
+ ensureSeedersDir(config.seedersPath);
1081
+ const timestamp = Date.now();
1082
+ const filepath = config.seedersPath + '/' + timestamp + '-' + name + '.js';
1083
+ const tmpl = 'module.exports = { up: async (q,s)=>{}, down: async (q,s)=>{} };';
1084
+ fs.writeFileSync(filepath, tmpl);
1085
+ log('Seeder created: ' + filepath, 'success');
1086
+ }
1087
+ /**
1088
+ * Command: db:seed
1089
+ * Runs all pending seeders, or a single one when --name is given.
1090
+ * --env <name> restricts to seeders whose `env` field matches (or have none).
1091
+ * --force (only valid together with --name) re-runs a seeder even if it was
1092
+ * already recorded as executed.
1093
+ */
1094
+ async function dbSeed(options, config) {
1095
+ const prorm = await getProrm();
1096
+ const sr = new seeder_1.SeederRunner(prorm, { seedersPath: config.seedersPath });
1097
+ try {
1098
+ await sr.ensureSeedsTable();
1099
+ const ran = await sr.runSeeders(config.seedersPath, {
1100
+ only: options.name,
1101
+ env: options.env || process.env.NODE_ENV,
1102
+ force: Boolean(options.force),
1103
+ });
1104
+ log('Executed ' + ran.length + ' seeder(s)', 'success');
1105
+ if (config.verbose && ran.length > 0) {
1106
+ ran.forEach(function (name) {
1107
+ console.log(' - ' + name);
1108
+ });
1109
+ }
1110
+ }
1111
+ finally {
1112
+ await prorm.close();
1113
+ }
1114
+ }
1115
+ /**
1116
+ * Command: db:seed:undo
1117
+ * Undoes a single seeder: the one named by --name, or the most recently
1118
+ * executed one if no --name is given. Use db:seed:undo:all to undo every
1119
+ * executed seeder in reverse order.
1120
+ */
1121
+ async function dbSeedUndo(options, config) {
1122
+ const prorm = await getProrm();
1123
+ const sr = new seeder_1.SeederRunner(prorm, { seedersPath: config.seedersPath });
1124
+ try {
1125
+ await sr.ensureSeedsTable();
1126
+ if (options.name) {
1127
+ await sr.undoSeeder(options.name);
1128
+ log('Seeder undone: ' + options.name, 'success');
1129
+ }
1130
+ else {
1131
+ const executed = await sr.getExecutedSeeds();
1132
+ if (executed.length === 0) {
1133
+ log('No executed seeders to undo', 'info');
1134
+ }
1135
+ else {
1136
+ const last = executed[executed.length - 1];
1137
+ await sr.undoSeeder();
1138
+ log('Seeder undone: ' + last, 'success');
1139
+ }
1140
+ }
1141
+ }
1142
+ finally {
1143
+ await prorm.close();
1144
+ }
1145
+ }
1146
+ /**
1147
+ * Command: db:seed:undo:all
1148
+ * Undoes every executed seeder, in reverse execution order.
1149
+ */
1150
+ async function dbSeedUndoAll(config) {
1151
+ const prorm = await getProrm();
1152
+ const sr = new seeder_1.SeederRunner(prorm, { seedersPath: config.seedersPath });
1153
+ try {
1154
+ await sr.ensureSeedsTable();
1155
+ await sr.undoAllSeeders();
1156
+ log('All seeders undone', 'success');
1157
+ }
1158
+ finally {
1159
+ await prorm.close();
1160
+ }
1161
+ }
1162
+ /**
1163
+ * Command: db:seed:status
1164
+ * Shows seeder status
1165
+ */
1166
+ async function dbSeedStatus(config) {
1167
+ const prorm = await getProrm();
1168
+ const sr = new seeder_1.SeederRunner(prorm, { seedersPath: config.seedersPath });
1169
+ try {
1170
+ await sr.ensureSeedsTable();
1171
+ const statuses = await sr.getSeedStatus();
1172
+ console.log('\nSeeder Status:');
1173
+ console.log('=============');
1174
+ const pending = statuses.filter((s) => s.status === 'pending');
1175
+ const executed = statuses.filter((s) => s.status === 'executed');
1176
+ if (executed.length > 0) {
1177
+ console.log('\nExecuted Seeders:');
1178
+ executed.forEach((s) => console.log(' [UP] ' + s.name));
1179
+ }
1180
+ if (pending.length > 0) {
1181
+ console.log('\nPending Seeders:');
1182
+ pending.forEach((s) => console.log(' [DOWN] ' + s.name));
1183
+ }
1184
+ console.log('\n----------------');
1185
+ console.log('Total: ' +
1186
+ statuses.length +
1187
+ ' (' +
1188
+ executed.length +
1189
+ ' executed, ' +
1190
+ pending.length +
1191
+ ' pending)');
1192
+ }
1193
+ finally {
1194
+ await prorm.close();
1195
+ }
1196
+ }
1197
+ // ─── Diagram CLI handlers ────────────────────────────────────────────────────
1198
+ /**
1199
+ * Parse named CLI flags from an argv array.
1200
+ * --flag value → { flag: 'value' }
1201
+ * --flag → { flag: true }
1202
+ */
1203
+ function parseFlags(args) {
1204
+ const flags = {};
1205
+ for (let i = 0; i < args.length; i++) {
1206
+ const a = args[i];
1207
+ if (a.startsWith('--')) {
1208
+ const key = a.slice(2);
1209
+ const next = args[i + 1];
1210
+ if (next && !next.startsWith('--')) {
1211
+ flags[key] = next;
1212
+ i++;
1213
+ }
1214
+ else {
1215
+ flags[key] = true;
1216
+ }
1217
+ }
1218
+ }
1219
+ return flags;
1220
+ }
1221
+ /**
1222
+ * Command: diagram:model
1223
+ *
1224
+ * Generates a single-model SVG card.
1225
+ *
1226
+ * Reads model spec from --file <json> or falls back to a minimal stub
1227
+ * using --name <ModelName>.
1228
+ *
1229
+ * cli diagram:model --file ./User.json --out ./diagrams/User.svg
1230
+ * cli diagram:model --name Order --out ./diagrams/Order.svg
1231
+ */
1232
+ async function diagramModel(args) {
1233
+ const flags = parseFlags(args);
1234
+ const file = flags.file;
1235
+ const outArg = flags.out;
1236
+ const name = flags.name;
1237
+ const theme = flags.theme;
1238
+ const color = flags.color;
1239
+ const db = flags.db;
1240
+ const table = flags.table;
1241
+ let opts;
1242
+ // Option 1: Generate from database
1243
+ if (db) {
1244
+ try {
1245
+ const tables = await getDbSchema(db);
1246
+ const models = schemaToModels(tables);
1247
+ if (table) {
1248
+ // Generate single table diagram
1249
+ const model = models.find((m) => m.tableName === table || m.name.toLowerCase() === table.toLowerCase());
1250
+ if (!model) {
1251
+ log(`Table "${table}" not found in database`, 'error');
1252
+ process.exit(1);
1253
+ }
1254
+ opts = model;
1255
+ }
1256
+ else if (name) {
1257
+ const model = models.find((m) => m.name.toLowerCase() === name.toLowerCase());
1258
+ if (!model) {
1259
+ log(`Model "${name}" not found in database`, 'error');
1260
+ process.exit(1);
1261
+ }
1262
+ opts = model;
1263
+ }
1264
+ else {
1265
+ log('diagram:model --db requires --table <name> or --name <ModelName>', 'error');
1266
+ process.exit(1);
1267
+ }
1268
+ }
1269
+ catch (err) {
1270
+ log(`Database connection failed: ${err.message}`, 'error');
1271
+ process.exit(1);
1272
+ }
1273
+ }
1274
+ else if (file) {
1275
+ // Option 2: Load from JSON file
1276
+ const specPath = path.resolve(file);
1277
+ if (!fs.existsSync(specPath)) {
1278
+ log(`Spec file not found: ${specPath}`, 'error');
1279
+ process.exit(1);
1280
+ }
1281
+ opts = JSON.parse(fs.readFileSync(specPath, 'utf-8'));
1282
+ log(`Loaded model spec from ${specPath}`, 'info');
1283
+ }
1284
+ else if (name) {
1285
+ // Option 3: Minimal stub
1286
+ opts = { name, fields: {} };
1287
+ log(`No --file provided — rendering empty model "${name}"`, 'warning');
1288
+ }
1289
+ else {
1290
+ log('diagram:model requires --file <path> or --db <connection-string>', 'error');
1291
+ process.exit(1);
1292
+ }
1293
+ // Apply colorful options from CLI
1294
+ if (theme && ['light', 'dark'].includes(theme)) {
1295
+ opts.theme = theme;
1296
+ log(`Using theme: ${theme}`, 'info');
1297
+ }
1298
+ if (color) {
1299
+ opts.color = color;
1300
+ log(`Using accent color: ${color}`, 'info');
1301
+ }
1302
+ const defaultOut = path.join(process.cwd(), 'diagrams', `${opts.name}.svg`);
1303
+ const outPath = outArg ? path.resolve(outArg) : defaultOut;
1304
+ const diag = new diagrams_1.ModelDiagram(opts);
1305
+ const saved = diag.save(outPath);
1306
+ log(`✔ Model diagram saved → ${saved}`, 'success');
1307
+ }
1308
+ /**
1309
+ * Command: diagram:er
1310
+ *
1311
+ * Generates a full ER diagram SVG from a schema JSON file.
1312
+ *
1313
+ * Schema JSON format:
1314
+ * {
1315
+ * "title": "My Schema",
1316
+ * "columns": 3,
1317
+ * "models": [ <model-spec>, ... ]
1318
+ * }
1319
+ *
1320
+ * cli diagram:er --file ./schema.json --out ./diagrams/er.svg
1321
+ * cli diagram:er --file ./schema.json --title "Blog Schema" --columns 2
1322
+ */
1323
+ async function diagramER(args) {
1324
+ const flags = parseFlags(args);
1325
+ const file = flags.file;
1326
+ const outArg = flags.out;
1327
+ const titleFlag = flags.title;
1328
+ const colsFlag = flags.columns;
1329
+ const theme = flags.theme;
1330
+ const color = flags.color;
1331
+ const db = flags.db;
1332
+ let schema;
1333
+ // Option 1: Generate from database
1334
+ if (db) {
1335
+ try {
1336
+ const tables = await getDbSchema(db);
1337
+ const models = schemaToModels(tables);
1338
+ schema = {
1339
+ title: 'Database Schema',
1340
+ columns: parseInt(colsFlag ?? '', 10) || 3,
1341
+ models,
1342
+ };
1343
+ log(`Generated ER diagram from database (${models.length} tables)`, 'info');
1344
+ }
1345
+ catch (err) {
1346
+ log(`Database connection failed: ${err.message}`, 'error');
1347
+ process.exit(1);
1348
+ }
1349
+ }
1350
+ else if (file) {
1351
+ // Option 2: Load from JSON file
1352
+ const schemaPath = path.resolve(file);
1353
+ if (!fs.existsSync(schemaPath)) {
1354
+ log(`Schema file not found: ${schemaPath}`, 'error');
1355
+ process.exit(1);
1356
+ }
1357
+ const raw = fs.readFileSync(schemaPath, 'utf-8');
1358
+ schema = JSON.parse(raw);
1359
+ log(`Loaded ER schema from ${schemaPath} (${schema.models.length} models)`, 'info');
1360
+ }
1361
+ else {
1362
+ log('diagram:er requires --file <schema.json> or --db <connection-string>', 'error');
1363
+ process.exit(1);
1364
+ }
1365
+ // Allow CLI overrides
1366
+ if (titleFlag)
1367
+ schema.title = titleFlag;
1368
+ if (colsFlag)
1369
+ schema.columns = parseInt(colsFlag, 10);
1370
+ if (theme)
1371
+ schema.theme = theme;
1372
+ if (color)
1373
+ schema.color = color;
1374
+ const defaultOut = path.join(process.cwd(), 'diagrams', 'er-diagram.svg');
1375
+ const outPath = outArg ? path.resolve(outArg) : defaultOut;
1376
+ const er = new diagrams_1.ERDiagram(schema);
1377
+ const saved = er.save(outPath);
1378
+ log(`✔ ER diagram saved → ${saved}`, 'success');
1379
+ log(` Models rendered: ${schema.models.map((m) => m.name).join(', ')}`, 'info');
1380
+ }
1381
+ /**
1382
+ * Command: diagram:migration
1383
+ *
1384
+ * Generates a migration diagram SVG showing schema changes.
1385
+ *
1386
+ * Migration JSON format:
1387
+ * {
1388
+ * "title": "Add email verification",
1389
+ * "description": "Add email verification fields to users",
1390
+ * "timestamp": "2024-01-15T10:30:00Z",
1391
+ * "changes": [
1392
+ * { "table": "users", "type": "modify", "changes": [
1393
+ * { "name": "emailVerified", "type": "BOOLEAN", "action": "add" },
1394
+ * { "name": "verifiedAt", "type": "DATE", "action": "add" }
1395
+ * ]},
1396
+ * { "table": "sessions", "type": "add", "changes": [
1397
+ * { "name": "id", "type": "UUID", "action": "add" },
1398
+ * { "name": "userId", "type": "UUID", "action": "add" }
1399
+ * ]}
1400
+ * ]
1401
+ * }
1402
+ *
1403
+ * cli diagram:migration --file ./migration.json --out ./diagrams/migration.svg
1404
+ * cli diagram:migration --file ./migration.json --title "User Migration"
1405
+ */
1406
+ function diagramMigration(args) {
1407
+ const flags = parseFlags(args);
1408
+ const file = flags.file;
1409
+ const outArg = flags.out;
1410
+ const titleFlag = flags.title;
1411
+ const descFlag = flags.description;
1412
+ if (!file) {
1413
+ log('diagram:migration requires --file <migration.json>', 'error');
1414
+ log('Migration format: { "title":"…", "description":"…", "changes": [ { "table":"...", "type":"add|modify|remove", "changes": [...] } ] }', 'info');
1415
+ process.exit(1);
1416
+ }
1417
+ const migrationPath = path.resolve(file);
1418
+ if (!fs.existsSync(migrationPath)) {
1419
+ log(`Migration file not found: ${migrationPath}`, 'error');
1420
+ process.exit(1);
1421
+ }
1422
+ const raw = fs.readFileSync(migrationPath, 'utf-8');
1423
+ const opts = JSON.parse(raw);
1424
+ log(`Loaded migration from ${migrationPath} (${opts.changes.length} table changes)`, 'info');
1425
+ // CLI overrides
1426
+ if (titleFlag)
1427
+ opts.title = titleFlag;
1428
+ if (descFlag)
1429
+ opts.description = descFlag;
1430
+ const defaultOut = path.join(process.cwd(), 'diagrams', 'migration.svg');
1431
+ const outPath = outArg ? path.resolve(outArg) : defaultOut;
1432
+ const diag = new diagrams_1.MigrationDiagram(opts);
1433
+ const saved = diag.save(outPath);
1434
+ log(`✔ Migration diagram saved → ${saved}`, 'success');
1435
+ log(` Changes: ${opts.changes.map((c) => `${c.type} ${c.table}`).join(', ')}`, 'info');
1436
+ }
1437
+ /**
1438
+ * Extended visualizer generators — each renders its own JSON spec to SVG.
1439
+ * Every one accepts `theme`/`color`/(most) `title` in its options, so the
1440
+ * generic handler wires the matching CLI flags through.
1441
+ */
1442
+ const EXTRA_DIAGRAMS = {
1443
+ class: diagrams_1.ClassDiagram,
1444
+ relational: diagrams_1.RelationalDiagram,
1445
+ sequence: diagrams_1.SequenceDiagram,
1446
+ state: diagrams_1.StateDiagram,
1447
+ dependency: diagrams_1.DependencyDiagram,
1448
+ schemadoc: diagrams_1.SchemaDocDiagram,
1449
+ index: diagrams_1.IndexDiagram,
1450
+ flow: diagrams_1.FlowDiagram,
1451
+ tree: diagrams_1.TreeDiagram,
1452
+ chen: diagrams_1.ChenDiagram,
1453
+ package: diagrams_1.PackageDiagram,
1454
+ gantt: diagrams_1.GanttDiagram,
1455
+ };
1456
+ /**
1457
+ * Command: diagram:<type> for the extended visualizers (class, relational,
1458
+ * sequence, state, dependency, schemadoc, index, flow, tree, chen, package,
1459
+ * gantt). Reads the generator's options from --file <json>, applies
1460
+ * --theme/--color/--title, and writes the SVG to --out.
1461
+ *
1462
+ * cli diagram:class --file ./classes.json --out ./diagrams/classes.svg
1463
+ * cli diagram:relational --file ./schema.json --out ./diagrams/erd.svg --theme dark
1464
+ * cli diagram:gantt --file ./plan.json --out ./diagrams/plan.svg --color emerald
1465
+ */
1466
+ function diagramGeneric(type, args) {
1467
+ const Ctor = EXTRA_DIAGRAMS[type];
1468
+ if (!Ctor) {
1469
+ log(`Unknown diagram type: ${type}`, 'error');
1470
+ process.exit(1);
1471
+ }
1472
+ const flags = parseFlags(args);
1473
+ const file = flags.file;
1474
+ const outArg = flags.out;
1475
+ const theme = flags.theme;
1476
+ const color = flags.color;
1477
+ const title = flags.title;
1478
+ if (!file) {
1479
+ log(`diagram:${type} requires --file <spec.json>`, 'error');
1480
+ process.exit(1);
1481
+ }
1482
+ const specPath = path.resolve(file);
1483
+ if (!fs.existsSync(specPath)) {
1484
+ log(`Spec file not found: ${specPath}`, 'error');
1485
+ process.exit(1);
1486
+ }
1487
+ const opts = JSON.parse(fs.readFileSync(specPath, 'utf-8'));
1488
+ log(`Loaded ${type} spec from ${specPath}`, 'info');
1489
+ if (theme && ['light', 'dark'].includes(theme)) {
1490
+ opts.theme = theme;
1491
+ log(`Using theme: ${theme}`, 'info');
1492
+ }
1493
+ if (color) {
1494
+ opts.color = color;
1495
+ log(`Using accent color: ${color}`, 'info');
1496
+ }
1497
+ if (title && opts.title === undefined)
1498
+ opts.title = title;
1499
+ const defaultOut = path.join(process.cwd(), 'diagrams', `${type}.svg`);
1500
+ const outPath = outArg ? path.resolve(outArg) : defaultOut;
1501
+ const saved = new Ctor(opts).save(outPath);
1502
+ log(`✔ ${type} diagram saved → ${saved}`, 'success');
1503
+ }
1504
+ /**
1505
+ * Map SQL column types to DataTypes (used by model:create introspection)
1506
+ */
1507
+ function mapColumnType(sqlType) {
1508
+ const typeUpper = sqlType.toUpperCase();
1509
+ const varcharMatch = typeUpper.match(/VARCHAR\((\d+)\)/);
1510
+ if (varcharMatch) {
1511
+ return `DataTypes.STRING(${varcharMatch[1]})`;
1512
+ }
1513
+ const charMatch = typeUpper.match(/CHAR\((\d+)\)/);
1514
+ if (charMatch) {
1515
+ return `DataTypes.CHAR(${charMatch[1]})`;
1516
+ }
1517
+ const decimalMatch = typeUpper.match(/DECIMAL\((\d+),(\d+)\)/);
1518
+ if (decimalMatch) {
1519
+ return `DataTypes.DECIMAL(${decimalMatch[1]}, ${decimalMatch[2]})`;
1520
+ }
1521
+ const floatMatch = typeUpper.match(/FLOAT\((\d+),(\d+)\)/);
1522
+ if (floatMatch) {
1523
+ return `DataTypes.FLOAT({ precision: ${floatMatch[1]}, decimals: ${floatMatch[2]} })`;
1524
+ }
1525
+ if (typeUpper.includes('INT')) {
1526
+ if (typeUpper.includes('TINYINT'))
1527
+ return 'DataTypes.TINYINT()';
1528
+ if (typeUpper.includes('SMALLINT'))
1529
+ return 'DataTypes.SMALLINT()';
1530
+ if (typeUpper.includes('MEDIUMINT'))
1531
+ return 'DataTypes.MEDIUMINT()';
1532
+ if (typeUpper.includes('BIGINT'))
1533
+ return 'DataTypes.BIGINT()';
1534
+ return 'DataTypes.INTEGER()';
1535
+ }
1536
+ if (typeUpper === 'TEXT' || typeUpper.includes('TEXT')) {
1537
+ if (typeUpper.includes('TINY'))
1538
+ return 'DataTypes.TEXT("tiny")';
1539
+ if (typeUpper.includes('MEDIUM'))
1540
+ return 'DataTypes.TEXT("medium")';
1541
+ if (typeUpper.includes('LONG'))
1542
+ return 'DataTypes.TEXT("long")';
1543
+ return 'DataTypes.TEXT()';
1544
+ }
1545
+ if (typeUpper === 'FLOAT' || typeUpper.includes('FLOAT'))
1546
+ return 'DataTypes.FLOAT()';
1547
+ if (typeUpper === 'DOUBLE' || typeUpper.includes('DOUBLE'))
1548
+ return 'DataTypes.DOUBLE()';
1549
+ if (typeUpper.includes('REAL') || typeUpper.includes('PRECISION'))
1550
+ return 'DataTypes.FLOAT()';
1551
+ if (typeUpper.includes('BOOLEAN') || typeUpper.includes('BOOL') || typeUpper === 'INTEGER') {
1552
+ return 'DataTypes.BOOLEAN()';
1553
+ }
1554
+ if (typeUpper.includes('DATETIME') || typeUpper.includes('TIMESTAMP')) {
1555
+ return 'DataTypes.DATE()';
1556
+ }
1557
+ if (typeUpper === 'DATE')
1558
+ return 'DataTypes.DATEONLY()';
1559
+ if (typeUpper === 'TIME')
1560
+ return 'DataTypes.TIME()';
1561
+ if (typeUpper.includes('UUID'))
1562
+ return 'DataTypes.UUID()';
1563
+ if (typeUpper.includes('JSON')) {
1564
+ if (typeUpper.includes('JSONB'))
1565
+ return 'DataTypes.JSONB(true)';
1566
+ return 'DataTypes.JSON(true)';
1567
+ }
1568
+ if (typeUpper.includes('BLOB') || typeUpper.includes('BINARY') || typeUpper.includes('BYTEA')) {
1569
+ if (typeUpper.includes('TINY'))
1570
+ return 'DataTypes.TINYBLOB()';
1571
+ if (typeUpper.includes('MEDIUM'))
1572
+ return 'DataTypes.MEDIUMBLOB()';
1573
+ if (typeUpper.includes('LONG'))
1574
+ return 'DataTypes.LONGBLOB()';
1575
+ return 'DataTypes.BLOB()';
1576
+ }
1577
+ const enumMatch = sqlType.match(/enum\(['"](.+?)['"]/i);
1578
+ if (enumMatch) {
1579
+ const values = enumMatch[1]
1580
+ .split("','")
1581
+ .map((v) => `'${v}'`)
1582
+ .join(', ');
1583
+ return `DataTypes.ENUM(${values})`;
1584
+ }
1585
+ return 'DataTypes.STRING';
1586
+ }
1587
+ /**
1588
+ * Generate an introspected model file's content from table column metadata.
1589
+ */
1590
+ function generateIntrospectedModelFile(tableName, columns, options) {
1591
+ const modelName = options?.modelName || toPascalCase(tableName);
1592
+ const timestamps = options?.timestamps ?? true;
1593
+ const underscored = options?.underscored ?? false;
1594
+ const columnDefinitions = [];
1595
+ const attributes = [];
1596
+ for (const [columnName, colInfo] of Object.entries(columns)) {
1597
+ attributes.push(columnName);
1598
+ const dataTypeStr = mapColumnType(colInfo.type);
1599
+ const parts = [`type: ${dataTypeStr}`];
1600
+ if (!colInfo.allowNull) {
1601
+ parts.push('allowNull: false');
1602
+ }
1603
+ if (colInfo.defaultValue !== null && colInfo.defaultValue !== undefined) {
1604
+ const defaultVal = colInfo.defaultValue;
1605
+ if (typeof defaultVal === 'string') {
1606
+ if (defaultVal.toLowerCase() === 'null') {
1607
+ // Don't add null as default
1608
+ }
1609
+ else if (defaultVal.includes('(')) {
1610
+ parts.push(`defaultValue: prorm.literal('${defaultVal}')`);
1611
+ }
1612
+ else {
1613
+ parts.push(`defaultValue: '${defaultVal}'`);
1614
+ }
1615
+ }
1616
+ else if (typeof defaultVal === 'number') {
1617
+ parts.push(`defaultValue: ${defaultVal}`);
1618
+ }
1619
+ else if (typeof defaultVal === 'boolean') {
1620
+ parts.push(`defaultValue: ${defaultVal}`);
1621
+ }
1622
+ }
1623
+ if (colInfo.primaryKey) {
1624
+ parts.push('primaryKey: true');
1625
+ }
1626
+ if (colInfo.autoIncrement) {
1627
+ parts.push('autoIncrement: true');
1628
+ }
1629
+ columnDefinitions.push(` ${columnName}: { ${parts.join(', ')} }`);
1630
+ }
1631
+ const tableNameStr = underscored ? toSnakeCase(tableName) : tableName;
1632
+ return `/**
1633
+ * Auto-generated model for ${tableName}
1634
+ * Generated by CLI model:create command
1635
+ */
1636
+
1637
+ import { DataTypes, Prorm } from 'ts-prorm-orm';
1638
+
1639
+ /**
1640
+ * Attributes for the ${modelName} model.
1641
+ */
1642
+ export interface ${modelName}Attributes {
1643
+ ${attributes.map((attr) => `${attr}?: any;`).join('\n ')}
1644
+ }
1645
+
1646
+ /**
1647
+ * Registered ${modelName} model. Populated by init${modelName}().
1648
+ */
1649
+ export let ${modelName}: ReturnType<Prorm['define']>;
1650
+
1651
+ /**
1652
+ * Register the ${modelName} model with a Prorm instance.
1653
+ */
1654
+ export function init${modelName}(prorm: Prorm): ReturnType<Prorm['define']> {
1655
+ ${modelName} = prorm.define<${modelName}Attributes>(
1656
+ '${modelName}',
1657
+ {
1658
+ ${columnDefinitions.join(',\n')},
1659
+ },
1660
+ {
1661
+ tableName: '${tableNameStr}',
1662
+ ${timestamps ? 'timestamps: true,' : 'timestamps: false,'}
1663
+ ${underscored ? 'underscored: true,' : ''}
1664
+ }
1665
+ );
1666
+
1667
+ return ${modelName};
1668
+ }
1669
+
1670
+ export default init${modelName};
1671
+ `;
1672
+ }
1673
+ /**
1674
+ * Convert string to PascalCase
1675
+ */
1676
+ function toPascalCase(str) {
1677
+ return str
1678
+ .replace(/[^a-zA-Z0-9]+(.)/g, (_, chr) => chr.toUpperCase())
1679
+ .replace(/^[a-z]/, (chr) => chr.toUpperCase());
1680
+ }
1681
+ /**
1682
+ * Convert string to snake_case
1683
+ */
1684
+ function toSnakeCase(str) {
1685
+ return str
1686
+ .replace(/([A-Z])/g, '_$1')
1687
+ .replace(/[^a-zA-Z0-9]+/g, '_')
1688
+ .replace(/^_|_$/g, '')
1689
+ .toLowerCase();
1690
+ }
1691
+ /**
1692
+ * Parse command line arguments for model:create (DB introspection)
1693
+ */
1694
+ function parseModelCreateArgs(args) {
1695
+ const options = {
1696
+ dialect: 'sqlite',
1697
+ database: '',
1698
+ outDir: './models',
1699
+ };
1700
+ for (let i = 0; i < args.length; i++) {
1701
+ const arg = args[i];
1702
+ const nextArg = args[i + 1];
1703
+ if (arg === '--dialect' && nextArg) {
1704
+ options.dialect = nextArg;
1705
+ i++;
1706
+ }
1707
+ else if (arg === '--database' && nextArg) {
1708
+ options.database = nextArg;
1709
+ i++;
1710
+ }
1711
+ else if (arg === '--username' && nextArg) {
1712
+ options.username = nextArg;
1713
+ i++;
1714
+ }
1715
+ else if (arg === '--password' && nextArg) {
1716
+ options.password = nextArg;
1717
+ i++;
1718
+ }
1719
+ else if (arg === '--host' && nextArg) {
1720
+ options.host = nextArg;
1721
+ i++;
1722
+ }
1723
+ else if (arg === '--port' && nextArg) {
1724
+ options.port = parseInt(nextArg, 10);
1725
+ i++;
1726
+ }
1727
+ else if (arg === '--storage' && nextArg) {
1728
+ options.storage = nextArg;
1729
+ i++;
1730
+ }
1731
+ else if (arg === '--out-dir' && nextArg) {
1732
+ options.outDir = nextArg;
1733
+ i++;
1734
+ }
1735
+ else if (arg === '--table' && nextArg) {
1736
+ options.table = nextArg;
1737
+ i++;
1738
+ }
1739
+ }
1740
+ return options;
1741
+ }
1742
+ /**
1743
+ * Command: model:create
1744
+ * Generate model files from live database table introspection.
1745
+ */
1746
+ async function modelCreateCommand(args) {
1747
+ const options = parseModelCreateArgs(args);
1748
+ if (!options.database) {
1749
+ log('Error: --database is required', 'error');
1750
+ log('Usage: orm-cli model:create --dialect sqlite --database ./mydb.sqlite', 'info');
1751
+ process.exit(1);
1752
+ }
1753
+ log(`\n=== Model Generator ===`, 'info');
1754
+ log(`Dialect: ${options.dialect}`, 'info');
1755
+ log(`Database: ${options.database}`, 'info');
1756
+ log(`Output: ${options.outDir}\n`, 'info');
1757
+ const prormConfig = {
1758
+ dialect: options.dialect,
1759
+ logging: false,
1760
+ };
1761
+ if (options.dialect === 'sqlite') {
1762
+ prormConfig.storage = options.storage || options.database;
1763
+ }
1764
+ else {
1765
+ prormConfig.host = options.host || 'localhost';
1766
+ prormConfig.database = options.database;
1767
+ prormConfig.username = options.username || 'root';
1768
+ prormConfig.password = options.password || '';
1769
+ if (options.port) {
1770
+ prormConfig.port = options.port;
1771
+ }
1772
+ }
1773
+ let prorm;
1774
+ try {
1775
+ prorm = new prorm_1.Prorm(prormConfig);
1776
+ await prorm.connect();
1777
+ log('Database connection established.\n', 'success');
1778
+ }
1779
+ catch (error) {
1780
+ log(`Error: Unable to connect to database: ${error.message}`, 'error');
1781
+ process.exit(1);
1782
+ }
1783
+ try {
1784
+ const tables = await prorm.showTables();
1785
+ const targetTables = options.table ? [options.table] : tables;
1786
+ if (targetTables.length === 0) {
1787
+ log('No tables found in database.', 'warning');
1788
+ await prorm.close();
1789
+ return;
1790
+ }
1791
+ log(`Found ${targetTables.length} table(s): ${targetTables.join(', ')}\n`, 'info');
1792
+ const outDir = path.resolve(options.outDir || './models');
1793
+ if (!fs.existsSync(outDir)) {
1794
+ fs.mkdirSync(outDir, { recursive: true });
1795
+ log(`Created output directory: ${outDir}\n`, 'success');
1796
+ }
1797
+ const generatedFiles = [];
1798
+ for (const tableName of targetTables) {
1799
+ if (!tables.includes(tableName)) {
1800
+ log(`Warning: Table '${tableName}' does not exist, skipping.`, 'warning');
1801
+ continue;
1802
+ }
1803
+ log(`Generating model for table: ${tableName}`, 'info');
1804
+ const tableDescription = await prorm.describeTable(tableName);
1805
+ const modelContent = generateIntrospectedModelFile(tableName, tableDescription);
1806
+ const modelName = toPascalCase(tableName);
1807
+ const fileName = `${modelName}.ts`;
1808
+ const filePath = path.join(outDir, fileName);
1809
+ fs.writeFileSync(filePath, modelContent, 'utf-8');
1810
+ generatedFiles.push(fileName);
1811
+ log(` Created: ${filePath}`, 'success');
1812
+ }
1813
+ log(`\n=== Generation Complete ===\n`, 'success');
1814
+ log(`Generated ${generatedFiles.length} model file(s):`, 'info');
1815
+ for (const file of generatedFiles) {
1816
+ console.log(` - ${file}`);
1817
+ }
1818
+ if (generatedFiles.length > 0) {
1819
+ const indexContent = generatedFiles
1820
+ .map((file) => {
1821
+ const modelName = file.replace('.ts', '');
1822
+ return `export { ${modelName}, init${modelName} } from './${modelName}';`;
1823
+ })
1824
+ .join('\n');
1825
+ fs.writeFileSync(path.join(outDir, 'index.ts'), indexContent, 'utf-8');
1826
+ log(`\nCreated index file: ${path.join(outDir, 'index.ts')}`, 'success');
1827
+ }
1828
+ console.log('\n');
1829
+ }
1830
+ catch (error) {
1831
+ log(`Error during model generation: ${error.message}`, 'error');
1832
+ process.exit(1);
1833
+ }
1834
+ finally {
1835
+ await prorm.close();
1836
+ log('Database connection closed.', 'info');
1837
+ }
1838
+ }
1839
+ // ─── End model:create ────────────────────────────────────────────────────────
1840
+ /**
1841
+ * Command: prisma:import
1842
+ * Converts a Prisma schema.prisma file into prorm model files + an initial
1843
+ * migration. Pure translation only (src/prisma-migrate/) — this command
1844
+ * just handles reading the input file and writing the generated output to
1845
+ * disk, and reports the unsupported-feature errors the converter throws
1846
+ * (implicit many-to-many relations, composite foreign keys, unmapped
1847
+ * Prisma scalar types) as a normal CLI error rather than a stack trace.
1848
+ */
1849
+ async function prismaImportCommand(options) {
1850
+ const schemaPath = options.schema || './schema.prisma';
1851
+ const modelsOut = options['models-out'] || './models';
1852
+ const migrationsOut = options['migrations-out'] || './migrations';
1853
+ if (!fs.existsSync(schemaPath)) {
1854
+ log(`Error: schema file not found at ${schemaPath}. Usage: prisma:import --schema <path> [--models-out <dir>] [--migrations-out <dir>]`, 'error');
1855
+ process.exit(1);
1856
+ return;
1857
+ }
1858
+ const { convertPrismaSchema } = require('./prisma-migrate');
1859
+ const schemaSource = fs.readFileSync(schemaPath, 'utf-8');
1860
+ let result;
1861
+ try {
1862
+ result = convertPrismaSchema(schemaSource);
1863
+ }
1864
+ catch (error) {
1865
+ log(`Error converting schema: ${error.message}`, 'error');
1866
+ process.exit(1);
1867
+ return;
1868
+ }
1869
+ fs.mkdirSync(modelsOut, { recursive: true });
1870
+ for (const model of result.models) {
1871
+ fs.writeFileSync(path.join(modelsOut, `${model.name}.ts`), model.source);
1872
+ log(`Created model: ${path.join(modelsOut, `${model.name}.ts`)}`, 'success');
1873
+ }
1874
+ fs.writeFileSync(path.join(modelsOut, 'index.ts'), result.modelsIndex);
1875
+ log(`Created model registry: ${path.join(modelsOut, 'index.ts')}`, 'success');
1876
+ fs.mkdirSync(migrationsOut, { recursive: true });
1877
+ const migrationFileName = `${Date.now()}-prisma-import.js`;
1878
+ fs.writeFileSync(path.join(migrationsOut, migrationFileName), result.migration);
1879
+ log(`Created migration: ${path.join(migrationsOut, migrationFileName)}`, 'success');
1880
+ log(`Converted ${result.models.length} model(s) from ${schemaPath}. Review the generated files — see the comment ` +
1881
+ 'at the top of the migration file for what was translated on a best-effort basis.', 'info');
1882
+ }
1883
+ /**
1884
+ * Print the full CLI usage/help text to the console
1885
+ */
1886
+ function showHelp() {
1887
+ console.log(`
1888
+ ORM CLI Tools
1889
+ =============
1890
+
1891
+ Usage:
1892
+ orm-cli <command> [options]
1893
+
1894
+ Commands:
1895
+ init Initialize a new project (config, models, migrations, seeders)
1896
+ model:create Generate model files from live database introspection
1897
+ migration:create Create a new migration file
1898
+ migration:run Run pending migrations
1899
+ migration:revert Revert migrations
1900
+ migration:status Show migration status
1901
+ seeder:create Create a new seeder file
1902
+ db:seed Run pending seeders (--name to run just one, --env to
1903
+ scope by environment, --force to re-run with --name)
1904
+ db:seed:undo Undo one seeder (--name <n>, or the last run if omitted)
1905
+ db:seed:undo:all Undo all executed seeders, in reverse order
1906
+ db:seed:status Show seeder status
1907
+ diagram:model Generate an SVG model diagram
1908
+ diagram:er Generate an SVG ER diagram for multiple models
1909
+ diagram:migration Generate an SVG migration diagram
1910
+ diagram:class Generate a UML class diagram (--file spec.json)
1911
+ diagram:relational Generate a crow's-foot relational diagram (--file spec.json)
1912
+ diagram:sequence Generate a UML sequence diagram (--file spec.json)
1913
+ diagram:state Generate a state-machine diagram (--file spec.json)
1914
+ diagram:dependency Generate a dependency-graph diagram (--file spec.json)
1915
+ diagram:schemadoc Generate a data-dictionary sheet (--file spec.json)
1916
+ diagram:index Generate an index/constraint map (--file spec.json)
1917
+ diagram:flow Generate a flowchart (--file spec.json)
1918
+ diagram:tree Generate a tree / mind-map (--file spec.json)
1919
+ diagram:chen Generate a Chen-notation ER diagram (--file spec.json)
1920
+ diagram:package Generate a UML package diagram (--file spec.json)
1921
+ diagram:gantt Generate a gantt / timeline diagram (--file spec.json)
1922
+ prisma:import Convert a Prisma schema.prisma into prorm models + an initial migration
1923
+
1924
+ Options:
1925
+ --name <name> Migration/model name
1926
+ --steps <n> Number of migrations to revert
1927
+ --path <path> Custom migrations directory path
1928
+ --directory <path> Project directory (for init)
1929
+ --verbose Show detailed output
1930
+ --help, -h Show this help message
1931
+
1932
+ model:create options:
1933
+ --dialect <dialect> Database dialect: sqlite, mysql, postgres, mariadb (default: sqlite)
1934
+ --database <name> Database name or file path (required)
1935
+ --username <user> Database username (mysql/mariadb/postgres)
1936
+ --password <pass> Database password (mysql/mariadb/postgres)
1937
+ --host <host> Database host (default: localhost)
1938
+ --port <port> Database port
1939
+ --storage <path> SQLite storage path
1940
+ --out-dir <path> Output directory for models (default: ./models)
1941
+ --table <name> Specific table to generate a model for (default: all tables)
1942
+
1943
+ Diagram options (diagram:model / diagram:er / diagram:migration):
1944
+ --file <path> JSON spec file (model/ER schema/migration)
1945
+ --db <conn-string> Database connection string (generates diagram from DB)
1946
+ --table <name> Table name for model diagram (with --db)
1947
+ --out <path> Output SVG file path (default: ./diagrams/<name>.svg)
1948
+ --title <title> ER/migration diagram title
1949
+ --columns <n> Number of columns in ER grid layout (default: 3)
1950
+ --theme <theme> Theme: light or dark (default: light)
1951
+ --color <color> Accent color: blue, violet, emerald, amber, pink, sky, indigo, purple, red, teal, green
1952
+ --description <desc> Migration description (diagram:migration only)
1953
+
1954
+ prisma:import options:
1955
+ --schema <path> Path to schema.prisma (default: ./schema.prisma)
1956
+ --models-out <path> Output directory for generated model files (default: ./models)
1957
+ --migrations-out <path> Output directory for the generated migration (default: ./migrations)
1958
+
1959
+ Connection string formats:
1960
+ mysql://user:pass@host:port/database
1961
+ postgres://user:pass@host:port/database
1962
+ sqlite:/path/to/database.db
1963
+ sqlite: (for in-memory database)
1964
+
1965
+ Examples:
1966
+ # Initialize a new project
1967
+ cli init
1968
+ cli init --directory ./my-project
1969
+
1970
+ # Generate models from an existing database (introspection)
1971
+ cli model:create --dialect sqlite --database ./mydb.sqlite
1972
+ cli model:create --dialect mysql --database mydb --username root --password secret --table users
1973
+
1974
+ # Create a new migration
1975
+ cli migration:create -- --name create-users-table
1976
+
1977
+ # Convert an existing Prisma project's schema.prisma to prorm
1978
+ cli prisma:import --schema ./schema.prisma --models-out ./src/models --migrations-out ./migrations
1979
+
1980
+ # Run all pending migrations
1981
+ cli migration:run
1982
+
1983
+ # Run a specific migration
1984
+ cli migration:run -- --name 20240315120000-create-users-table
1985
+
1986
+ # Revert the last migration
1987
+ cli migration:revert
1988
+
1989
+ # Revert a specific migration
1990
+ cli migration:revert -- --name 20240315120000-create-users-table
1991
+
1992
+ # Revert last 3 migrations
1993
+ cli migration:revert -- --steps 3
1994
+
1995
+ # Show migration status
1996
+ cli migration:status
1997
+
1998
+ # Use custom migrations path
1999
+ cli migration:run -- --path ./db/migrations
2000
+
2001
+ # Generate a model diagram from a JSON spec
2002
+ cli diagram:model --file ./User.json --out ./diagrams/User.svg
2003
+
2004
+ # Generate an ER diagram from a schema JSON (multiple models)
2005
+ cli diagram:er --file ./schema.json --out ./diagrams/er.svg
2006
+
2007
+ # Inline single-model diagram (no JSON file needed)
2008
+ cli diagram:model --name User --out ./diagrams/User.svg
2009
+
2010
+ # Generate diagram from database (for documentation)
2011
+ cli diagram:er --db sqlite:./database.db --out ./diagrams/schema.svg
2012
+ cli diagram:model --db mysql://root:pass@localhost:3306/mydb --table users --out ./diagrams/User.svg
2013
+ cli diagram:er --db postgres://user:pass@localhost:5432/mydb --title "Production DB" --color violet
2014
+
2015
+ # Extended visualizers — each reads its own JSON spec (--file) and writes SVG (--out)
2016
+ cli diagram:class --file ./classes.json --out ./diagrams/classes.svg
2017
+ cli diagram:relational --file ./schema.json --out ./diagrams/erd.svg --theme dark
2018
+ cli diagram:sequence --file ./flow.json --out ./diagrams/sequence.svg
2019
+ cli diagram:state --file ./lifecycle.json --out ./diagrams/state.svg
2020
+ cli diagram:dependency --file ./deps.json --out ./diagrams/deps.svg
2021
+ cli diagram:schemadoc --file ./schema.json --out ./diagrams/data-dictionary.svg
2022
+ cli diagram:index --file ./indexes.json --out ./diagrams/indexes.svg
2023
+ cli diagram:flow --file ./pipeline.json --out ./diagrams/flow.svg
2024
+ cli diagram:tree --file ./tree.json --out ./diagrams/tree.svg
2025
+ cli diagram:chen --file ./entities.json --out ./diagrams/chen.svg
2026
+ cli diagram:package --file ./packages.json --out ./diagrams/packages.svg
2027
+ cli diagram:gantt --file ./plan.json --out ./diagrams/plan.svg --color emerald
2028
+
2029
+ # JSON spec format for a single model (User.json):
2030
+ # {
2031
+ # "name": "User",
2032
+ # "tableName": "users",
2033
+ # "fields": {
2034
+ # "id": { "type": "INTEGER", "primaryKey": true, "autoIncrement": true },
2035
+ # "email": { "type": "VARCHAR(255)", "unique": true, "allowNull": false }
2036
+ # },
2037
+ # "associations": [
2038
+ # { "type": "hasMany", "target": "Post", "foreignKey": "userId" }
2039
+ # ],
2040
+ # "timestamps": true
2041
+ # }
2042
+ #
2043
+ # JSON spec format for an ER diagram (schema.json):
2044
+ # {
2045
+ # "title": "My App Schema",
2046
+ # "columns": 3,
2047
+ # "models": [ <model-spec>, <model-spec>, ... ]
2048
+ # }
2049
+
2050
+ Environment Variables:
2051
+ DATABASE_URL Full database URL (e.g., postgres://user:pass@localhost/db)
2052
+ DB_DIALECT Database dialect: sqlite, postgres, mysql, mariadb
2053
+ DB_NAME Database name
2054
+ DB_USER Database username
2055
+ DB_PASSWORD Database password
2056
+ DB_HOST Database host (default: localhost)
2057
+ DB_PORT Database port (default: 5432)
2058
+ `);
2059
+ }
2060
+ /**
2061
+ * Parse command-line arguments for migration commands
2062
+ */
2063
+ function parseMigrationArgs(args) {
2064
+ const command = args[0] || 'help';
2065
+ const options = {};
2066
+ // Start at 1, not 2: args[0] is the command itself (e.g. 'migration:create'),
2067
+ // so the first real flag begins at args[1]. Starting at 2 silently dropped
2068
+ // the first flag after the command (e.g. `migration:create --name foo`
2069
+ // lost both `--name` and its value `foo`).
2070
+ for (let i = 1; i < args.length; i++) {
2071
+ const arg = args[i];
2072
+ if (arg.startsWith('--')) {
2073
+ const key = arg.slice(2);
2074
+ const nextArg = args[i + 1];
2075
+ if (nextArg && !nextArg.startsWith('--')) {
2076
+ options[key] = nextArg;
2077
+ i++;
2078
+ }
2079
+ else {
2080
+ options[key] = true;
2081
+ }
2082
+ }
2083
+ else if (arg.startsWith('-')) {
2084
+ const key = arg.slice(1);
2085
+ options[key] = true;
2086
+ }
2087
+ }
2088
+ return { command, options };
2089
+ }
2090
+ /**
2091
+ * Main entry point
2092
+ */
2093
+ function main() {
2094
+ const args = process.argv.slice(2);
2095
+ const command = args[0];
2096
+ // Handle init command (original behavior)
2097
+ if (command === 'init') {
2098
+ const dirIndex = args.indexOf('--directory');
2099
+ const directory = dirIndex !== -1 ? args[dirIndex + 1] : undefined;
2100
+ init({ directory });
2101
+ return;
2102
+ }
2103
+ // Handle help
2104
+ if (command === '--help' || command === '-h' || !command) {
2105
+ showHelp();
2106
+ return;
2107
+ }
2108
+ // Build config
2109
+ const config = {
2110
+ ...defaultConfig,
2111
+ migrationsPath: (args.includes('--path') ? args[args.indexOf('--path') + 1] : undefined) ||
2112
+ defaultConfig.migrationsPath,
2113
+ verbose: args.includes('--verbose'),
2114
+ };
2115
+ // Parse migration command
2116
+ const { command: migrationCommand, options } = parseMigrationArgs(args);
2117
+ // Execute migration command asynchronously
2118
+ (async () => {
2119
+ try {
2120
+ switch (migrationCommand) {
2121
+ case 'model:create':
2122
+ await modelCreateCommand(args.slice(1));
2123
+ break;
2124
+ case 'migration:create':
2125
+ await migrationCreate(options.name, config);
2126
+ break;
2127
+ case 'migration:run':
2128
+ await migrationRun(options, config);
2129
+ break;
2130
+ case 'migration:revert':
2131
+ await migrationRevert(options, config);
2132
+ break;
2133
+ case 'migration:status':
2134
+ await migrationStatus(config);
2135
+ break;
2136
+ case 'db:create':
2137
+ await dbCreate();
2138
+ break;
2139
+ case 'db:drop':
2140
+ await dbDrop();
2141
+ break;
2142
+ case 'seeder:create':
2143
+ await seederCreate(options.name, config);
2144
+ break;
2145
+ case 'db:seed':
2146
+ await dbSeed(options, config);
2147
+ break;
2148
+ case 'db:seed:undo':
2149
+ await dbSeedUndo(options, config);
2150
+ break;
2151
+ case 'db:seed:undo:all':
2152
+ await dbSeedUndoAll(config);
2153
+ break;
2154
+ case 'db:seed:status':
2155
+ await dbSeedStatus(config);
2156
+ break;
2157
+ case 'diagram:model':
2158
+ await diagramModel(args);
2159
+ break;
2160
+ case 'diagram:er':
2161
+ await diagramER(args);
2162
+ break;
2163
+ case 'diagram:migration':
2164
+ await diagramMigration(args);
2165
+ break;
2166
+ case 'diagram:class':
2167
+ case 'diagram:relational':
2168
+ case 'diagram:sequence':
2169
+ case 'diagram:state':
2170
+ case 'diagram:dependency':
2171
+ case 'diagram:schemadoc':
2172
+ case 'diagram:index':
2173
+ case 'diagram:flow':
2174
+ case 'diagram:tree':
2175
+ case 'diagram:chen':
2176
+ case 'diagram:package':
2177
+ case 'diagram:gantt':
2178
+ diagramGeneric(command.slice('diagram:'.length), args);
2179
+ break;
2180
+ case 'prisma:import':
2181
+ await prismaImportCommand(options);
2182
+ break;
2183
+ default:
2184
+ if (command) {
2185
+ log(`Unknown command: ${command}`, 'error');
2186
+ }
2187
+ showHelp();
2188
+ process.exit(1);
2189
+ }
2190
+ }
2191
+ catch (error) {
2192
+ log(`Error: ${error.message}`, 'error');
2193
+ process.exit(1);
2194
+ }
2195
+ })();
2196
+ }
2197
+ // Run if executed directly (not when imported as a module)
2198
+ if (require.main === module) {
2199
+ main();
2200
+ }