@zerotal/orm 1.0.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 (87) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/LICENSE +21 -0
  3. package/README.md +170 -0
  4. package/package.json +58 -0
  5. package/src/casts/Cast.ts +200 -0
  6. package/src/commands/DbSeedCommand.ts +71 -0
  7. package/src/commands/MakeFactoryCommand.ts +59 -0
  8. package/src/commands/MakeMigrationCommand.ts +109 -0
  9. package/src/commands/MakeModelCommand.ts +83 -0
  10. package/src/commands/MakeSeederCommand.ts +50 -0
  11. package/src/commands/MigrateCommand.ts +60 -0
  12. package/src/commands/MigrateFreshCommand.ts +41 -0
  13. package/src/commands/MigrateGenerateCommand.ts +110 -0
  14. package/src/commands/MigrateRollbackCommand.ts +43 -0
  15. package/src/commands/MigrateStatusCommand.ts +49 -0
  16. package/src/commands/_loadMigrations.ts +34 -0
  17. package/src/commands/index.ts +30 -0
  18. package/src/config.ts +182 -0
  19. package/src/conventions.ts +67 -0
  20. package/src/db/DB.ts +486 -0
  21. package/src/db/NPlusOneDetector.ts +176 -0
  22. package/src/db/QueryBuilder.ts +2458 -0
  23. package/src/db/ReadWriteRouter.ts +96 -0
  24. package/src/db/TransactionContext.ts +13 -0
  25. package/src/db/dialects/MysqlDialect.ts +57 -0
  26. package/src/db/dialects/PostgresDialect.ts +55 -0
  27. package/src/db/dialects/SqliteDialect.ts +54 -0
  28. package/src/db/dialects/index.ts +25 -0
  29. package/src/db/dialects/types.ts +67 -0
  30. package/src/db/resolver.ts +30 -0
  31. package/src/db/sql-types.ts +12 -0
  32. package/src/db/types.ts +296 -0
  33. package/src/errors/MassAssignmentError.ts +25 -0
  34. package/src/errors/MigrationError.ts +18 -0
  35. package/src/errors/ModelNotFoundError.ts +21 -0
  36. package/src/errors/NPlusOneError.ts +6 -0
  37. package/src/errors/RelationNotLoadedError.ts +19 -0
  38. package/src/errors/StateError.ts +18 -0
  39. package/src/errors/TransactionError.ts +13 -0
  40. package/src/errors/UnsupportedDialectError.ts +18 -0
  41. package/src/errors/index.ts +7 -0
  42. package/src/events.ts +112 -0
  43. package/src/global.d.ts +17 -0
  44. package/src/implicitBinding.ts +73 -0
  45. package/src/index.ts +255 -0
  46. package/src/model/BaseModel.ts +2499 -0
  47. package/src/model/ModelQueryBuilder.ts +1808 -0
  48. package/src/model/Observer.ts +73 -0
  49. package/src/model/OrmContext.ts +71 -0
  50. package/src/model/ReactiveProxy.ts +53 -0
  51. package/src/model/SoftDeletes.ts +108 -0
  52. package/src/model/State.ts +290 -0
  53. package/src/model/decorators/_metadata.ts +211 -0
  54. package/src/model/decorators/_registerRelation.ts +20 -0
  55. package/src/model/decorators/belongsTo.ts +38 -0
  56. package/src/model/decorators/column.ts +278 -0
  57. package/src/model/decorators/hasMany.ts +34 -0
  58. package/src/model/decorators/hasManyThrough.ts +50 -0
  59. package/src/model/decorators/hasOne.ts +34 -0
  60. package/src/model/decorators/hasOneThrough.ts +40 -0
  61. package/src/model/decorators/manyToMany.ts +55 -0
  62. package/src/model/decorators/morphMany.ts +38 -0
  63. package/src/model/decorators/morphOne.ts +38 -0
  64. package/src/model/decorators/morphTo.ts +51 -0
  65. package/src/model/decorators/morphToMany.ts +49 -0
  66. package/src/model/decorators/morphedByMany.ts +46 -0
  67. package/src/model/decorators/table.ts +124 -0
  68. package/src/model/hooks/HookRegistry.ts +110 -0
  69. package/src/model/mixins.ts +536 -0
  70. package/src/model/payload.ts +114 -0
  71. package/src/model/relations/RelationRegistry.ts +184 -0
  72. package/src/observability.ts +210 -0
  73. package/src/provider/DatabaseProvider.ts +266 -0
  74. package/src/schema/Blueprint.ts +900 -0
  75. package/src/schema/ColumnDefinition.ts +517 -0
  76. package/src/schema/Migration.ts +34 -0
  77. package/src/schema/MigrationCodegen.ts +108 -0
  78. package/src/schema/MigrationRunner.ts +351 -0
  79. package/src/schema/ModelInspector.ts +133 -0
  80. package/src/schema/Schema.ts +140 -0
  81. package/src/schema/SchemaDiffer.ts +137 -0
  82. package/src/schema/SchemaInspector.ts +164 -0
  83. package/src/schema/__test_migrations__/001_create_test_table.ts +15 -0
  84. package/src/schema/autoMigrate.ts +154 -0
  85. package/src/schema/index.ts +28 -0
  86. package/src/seeding/Seeder.ts +46 -0
  87. package/src/support/identifiers.ts +62 -0
@@ -0,0 +1,109 @@
1
+ import { Command, Str } from "@zerotal/core";
2
+
3
+ export type MigrationFileResult = {
4
+ path: string;
5
+ created: boolean;
6
+ };
7
+
8
+ /**
9
+ * Converts a migration name (e.g. `create_posts_table`) into a PascalCase
10
+ * class name (e.g. `CreatePostsTable`) for use in the generated stub.
11
+ */
12
+ export function toMigrationClassName(name: string): string {
13
+ return Str.pascalCase(name);
14
+ }
15
+
16
+ /**
17
+ * Returns the source text of a blank migration file whose default-exported
18
+ * class is named `className`.
19
+ */
20
+ export function migrationStub(className: string): string {
21
+ return `import { Migration, Schema } from '@zerotal/orm';
22
+
23
+ export default class ${className} extends Migration {
24
+ override async up(): Promise<void> {
25
+ await Schema.create('table_name', (table) => {
26
+ table.increments('id');
27
+ table.timestamps();
28
+ });
29
+ }
30
+
31
+ override async down(): Promise<void> {
32
+ await Schema.drop('table_name');
33
+ }
34
+ }
35
+ `;
36
+ }
37
+
38
+ /**
39
+ * Lists the existing `database/migrations/*.ts` file paths, used to derive the
40
+ * next zero-padded numeric prefix.
41
+ *
42
+ * @internal
43
+ */
44
+ async function listMigrationFiles(): Promise<string[]> {
45
+ const glob = new Bun.Glob("database/migrations/*.ts");
46
+ const files: string[] = [];
47
+ for await (const file of glob.scan({ cwd: process.cwd() })) {
48
+ files.push(file);
49
+ }
50
+ return files;
51
+ }
52
+
53
+ /**
54
+ * Writes a new numbered migration file for `name` under `database/migrations/`,
55
+ * returning its path and whether it was created (`false` if one already exists).
56
+ */
57
+ export async function createMigrationFile(name: string): Promise<MigrationFileResult> {
58
+ const files = await listMigrationFiles();
59
+ const nextNumber = files.length + 1;
60
+ const prefix = String(nextNumber).padStart(3, "0");
61
+ const fileName = `${prefix}_${name}.ts`;
62
+ const path = `database/migrations/${fileName}`;
63
+
64
+ if (await Bun.file(path).exists()) {
65
+ return { path, created: false };
66
+ }
67
+
68
+ // Bun.write() creates any missing parent directories, so no mkdir is needed.
69
+ await Bun.write(path, migrationStub(toMigrationClassName(name)));
70
+ return { path, created: true };
71
+ }
72
+
73
+ /**
74
+ * Scaffolds a new migration file (`bun zt make:migration`).
75
+ *
76
+ * Writes a blank migration with `up()`/`down()` stubs to
77
+ * `database/migrations/`, prefixed with the next zero-padded sequence number.
78
+ *
79
+ * @example
80
+ * ```bash
81
+ * bun zt make:migration create_posts_table
82
+ * ```
83
+ *
84
+ * @category Scaffolding (make:*)
85
+ */
86
+ export class MakeMigrationCommand extends Command {
87
+ static commandName = "make:migration";
88
+ static description = "Create a new migration file";
89
+ static needsApp = false;
90
+ static args = [
91
+ {
92
+ name: "name",
93
+ required: true,
94
+ description: "Migration name (e.g. create_posts_table)",
95
+ },
96
+ ];
97
+
98
+ async run(): Promise<void> {
99
+ const name = this.args["name"]!;
100
+ const result = await createMigrationFile(name);
101
+
102
+ if (!result.created) {
103
+ this.error(`File already exists: ${result.path}`);
104
+ return;
105
+ }
106
+
107
+ this.info(`Created: ${result.path}`);
108
+ }
109
+ }
@@ -0,0 +1,83 @@
1
+ import { Command, Str } from "@zerotal/core";
2
+ import { createMigrationFile } from "./MakeMigrationCommand.ts";
3
+
4
+ /**
5
+ * Returns the source text of a model class named `name` bound to `tableName`.
6
+ */
7
+ export function modelStub(name: string, tableName: string): string {
8
+ return `import { BaseModel, column, table } from '@zerotal/orm';
9
+
10
+ @table('${tableName}').withTimestamps()
11
+ export class ${name} extends BaseModel {
12
+ // Models guard every attribute by default. List the columns that may be
13
+ // mass-assigned from user input via create() / fill().
14
+ static override fillable: string[] = ['name'];
15
+
16
+ @column() name!: string;
17
+ }
18
+ `;
19
+ }
20
+
21
+ /**
22
+ * Scaffolds a new model class (`bun zt make:model`).
23
+ *
24
+ * Writes an `app/models/<Name>.ts` file containing a `BaseModel` subclass whose
25
+ * table name is the snake-cased model name. Passing `--migration` (`-m`) also
26
+ * generates a matching `create_<name>_table` migration.
27
+ *
28
+ * @example
29
+ * ```bash
30
+ * bun zt make:model Post
31
+ * bun zt make:model Post --migration
32
+ * ```
33
+ *
34
+ * @category Scaffolding (make:*)
35
+ */
36
+ export class MakeModelCommand extends Command {
37
+ static commandName = "make:model";
38
+ static description = "Create a new model class";
39
+ static needsApp = false;
40
+ static args = [
41
+ {
42
+ name: "name",
43
+ required: true,
44
+ description: "Model name (e.g. User)",
45
+ },
46
+ ];
47
+ static flags = [
48
+ {
49
+ name: "migration",
50
+ short: "m",
51
+ type: "boolean" as const,
52
+ description: "Also create a migration file",
53
+ default: false,
54
+ },
55
+ ];
56
+
57
+ async run(): Promise<void> {
58
+ const name = this.args["name"]!;
59
+ const path = `app/models/${name}.ts`;
60
+
61
+ if (await Bun.file(path).exists()) {
62
+ this.error(`File already exists: ${path}`);
63
+ return;
64
+ }
65
+
66
+ const tableName = Str.snakeCase(name);
67
+
68
+ // Bun.write() creates any missing parent directories, so no mkdir is needed.
69
+ await Bun.write(path, modelStub(name, tableName));
70
+ this.info(`Created: ${path}`);
71
+
72
+ const withMigration = this.flags["migration"] as boolean;
73
+ if (withMigration) {
74
+ const migrationName = `create_${Str.snakeCase(name)}_table`;
75
+ const result = await createMigrationFile(migrationName);
76
+ if (!result.created) {
77
+ this.error(`File already exists: ${result.path}`);
78
+ return;
79
+ }
80
+ this.info(`Created: ${result.path}`);
81
+ }
82
+ }
83
+ }
@@ -0,0 +1,50 @@
1
+ import { Command } from "@zerotal/core";
2
+
3
+ /**
4
+ * Scaffolds a new database seeder class (`bun zt make:seeder`).
5
+ *
6
+ * Writes `database/seeders/<Name>.ts` containing a `Seeder` subclass with an
7
+ * empty `run()` method to be filled in with seeding logic.
8
+ *
9
+ * @example
10
+ * ```bash
11
+ * bun zt make:seeder UserSeeder
12
+ * ```
13
+ *
14
+ * @category Scaffolding (make:*)
15
+ */
16
+ export class MakeSeederCommand extends Command {
17
+ static commandName = "make:seeder";
18
+ static description = "Create a new database seeder class";
19
+ static needsApp = false;
20
+ static args = [
21
+ { name: "name", required: true, description: "Seeder class name (e.g. UserSeeder)" },
22
+ ];
23
+
24
+ async run(): Promise<void> {
25
+ const name = this.args["name"]!;
26
+ const path = `database/seeders/${name}.ts`;
27
+
28
+ if (await Bun.file(path).exists()) {
29
+ this.error(`File already exists: ${path}`);
30
+ return;
31
+ }
32
+
33
+ await Bun.write(path, seederStub(name));
34
+ this.info(`Created: ${path}`);
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Returns the source text of a seeder class file named `name`.
40
+ */
41
+ export function seederStub(name: string): string {
42
+ return `import { Seeder } from '@zerotal/orm';
43
+
44
+ export class ${name} extends Seeder {
45
+ async run(): Promise<void> {
46
+ // Seed your database here
47
+ }
48
+ }
49
+ `;
50
+ }
@@ -0,0 +1,60 @@
1
+ import { Command } from "@zerotal/core";
2
+ import type { MigrationEntry } from "../schema/MigrationRunner.ts";
3
+ import { MigrationRunner } from "../schema/MigrationRunner.ts";
4
+ import { _getConnection } from "../db/DB.ts";
5
+ import { loadMigrations } from "./_loadMigrations.ts";
6
+
7
+ /**
8
+ * Runs all pending database migrations (`bun zt migrate`).
9
+ *
10
+ * Loads every migration under `database/migrations/`, then applies those not
11
+ * yet run. Passing `--fresh` first drops all tables and re-runs every
12
+ * migration from scratch.
13
+ *
14
+ * @example
15
+ * ```bash
16
+ * bun zt migrate
17
+ * bun zt migrate --fresh
18
+ * ```
19
+ *
20
+ * @category Migrations
21
+ */
22
+ export class MigrateCommand extends Command {
23
+ static commandName = "migrate";
24
+ static aliases = ["db:migrate"];
25
+ static description = "Run all pending database migrations";
26
+ static needsApp = true;
27
+ static flags = [
28
+ {
29
+ name: "fresh",
30
+ type: "boolean" as const,
31
+ description: "Drop all tables and re-run all migrations",
32
+ default: false,
33
+ },
34
+ ];
35
+
36
+ async run(): Promise<void> {
37
+ const fresh = this.flags["fresh"] as boolean;
38
+ const records = await loadMigrations();
39
+ const entries: MigrationEntry[] = records.map((r) => ({
40
+ name: r.name,
41
+ migration: r.instance,
42
+ }));
43
+
44
+ const runner = new MigrationRunner({ connection: _getConnection() });
45
+
46
+ if (fresh) {
47
+ await runner.reset(entries);
48
+ }
49
+
50
+ const ran = await runner.run(entries);
51
+
52
+ if (ran.length === 0) {
53
+ this.info("Nothing to migrate.");
54
+ return;
55
+ }
56
+
57
+ this.info(`Migrated ${ran.length} migration(s).`);
58
+ this.table(ran.map((name) => [name, "ran"]));
59
+ }
60
+ }
@@ -0,0 +1,41 @@
1
+ import { Command } from "@zerotal/core";
2
+ import type { MigrationEntry } from "../schema/MigrationRunner.ts";
3
+ import { MigrationRunner } from "../schema/MigrationRunner.ts";
4
+ import { _getConnection } from "../db/DB.ts";
5
+ import { loadMigrations } from "./_loadMigrations.ts";
6
+
7
+ /**
8
+ * Rolls back every migration, then re-runs them from scratch (`bun zt migrate:fresh`).
9
+ *
10
+ * Resets the database by reversing all applied migrations and then re-applying
11
+ * the full set, giving a clean, fully-migrated schema in one step.
12
+ *
13
+ * @example
14
+ * ```bash
15
+ * bun zt migrate:fresh
16
+ * ```
17
+ *
18
+ * @category Migrations
19
+ */
20
+ export class MigrateFreshCommand extends Command {
21
+ static commandName = "migrate:fresh";
22
+ static description = "Roll back every migration, then re-run them from scratch";
23
+ static needsApp = true;
24
+
25
+ async run(): Promise<void> {
26
+ const records = await loadMigrations();
27
+ const entries: MigrationEntry[] = records.map((r) => ({
28
+ name: r.name,
29
+ migration: r.instance,
30
+ }));
31
+
32
+ const runner = new MigrationRunner({ connection: _getConnection() });
33
+ await runner.reset(entries);
34
+ const ran = await runner.run(entries);
35
+
36
+ this.info(`Database refreshed — ran ${ran.length} migration(s).`);
37
+ if (ran.length > 0) {
38
+ this.table(ran.map((name) => [name, "migrated"]));
39
+ }
40
+ }
41
+ }
@@ -0,0 +1,110 @@
1
+ import path from "node:path";
2
+ import { Command } from "@zerotal/core";
3
+ import { ModelInspector } from "../schema/ModelInspector.ts";
4
+ import { SchemaDiffer } from "../schema/SchemaDiffer.ts";
5
+ import { generateMigrationContent } from "../schema/MigrationCodegen.ts";
6
+ import { toMigrationClassName } from "./MakeMigrationCommand.ts";
7
+
8
+ /**
9
+ * Lists the existing `database/migrations/*.ts` file paths, used to derive the
10
+ * next zero-padded numeric prefix for the generated migration.
11
+ *
12
+ * @internal
13
+ */
14
+ async function listMigrationFiles(): Promise<string[]> {
15
+ const glob = new Bun.Glob("database/migrations/*.ts");
16
+ const files: string[] = [];
17
+ for await (const file of glob.scan({ cwd: process.cwd() })) {
18
+ files.push(file);
19
+ }
20
+ return files;
21
+ }
22
+
23
+ /**
24
+ * Auto-generates a migration from model schema changes (`bun zt migrate:generate`).
25
+ *
26
+ * Loads the model files matching the `--models` glob, diffs their `@column()`
27
+ * schemas against the live database, and writes a numbered migration under
28
+ * `database/migrations/` describing the new tables and columns. Reports "No
29
+ * schema changes detected" when the models and database already agree.
30
+ *
31
+ * @example
32
+ * ```bash
33
+ * bun zt migrate:generate create_posts_table
34
+ * bun zt migrate:generate add_slug_to_posts --models "app/models/**\/*.ts"
35
+ * ```
36
+ *
37
+ * @category Migrations
38
+ */
39
+ export class MigrateGenerateCommand extends Command {
40
+ static override commandName = "migrate:generate";
41
+ static override description = "Auto-generate a migration from model schema changes";
42
+ static override needsApp = false;
43
+
44
+ static override args = [
45
+ {
46
+ name: "name",
47
+ required: true,
48
+ description: "Migration name (e.g. create_posts_table)",
49
+ },
50
+ ];
51
+
52
+ static override flags = [
53
+ {
54
+ name: "models",
55
+ type: "string" as const,
56
+ description: "Glob pattern for model files to inspect",
57
+ default: "app/models/**/*.ts",
58
+ },
59
+ ];
60
+
61
+ async run(): Promise<void> {
62
+ const name = this.args["name"]!;
63
+ const modelsPattern = (this.flags["models"] as string | undefined) ?? "app/models/**/*.ts";
64
+
65
+ // ── 1. Import model files ────────────────────────────────────────────────
66
+ this.dim(`Scanning models: ${modelsPattern}`);
67
+ await ModelInspector.load(modelsPattern);
68
+
69
+ const schemas = ModelInspector.all();
70
+ if (schemas.length === 0) {
71
+ this.warn(`No models found matching: ${modelsPattern}`);
72
+ this.dim("Ensure models use @column() decorators and set static table.");
73
+ return;
74
+ }
75
+ this.dim(`Found ${schemas.length} model(s)`);
76
+
77
+ // ── 2. Diff against live DB ──────────────────────────────────────────────
78
+ const diff = await SchemaDiffer.diff(schemas);
79
+
80
+ if (SchemaDiffer.isEmpty(diff)) {
81
+ this.info("No schema changes detected — nothing to generate.");
82
+ return;
83
+ }
84
+
85
+ // ── 3. Generate migration content ────────────────────────────────────────
86
+ const className = toMigrationClassName(name);
87
+ const content = generateMigrationContent(className, diff);
88
+
89
+ // ── 4. Write the migration file ──────────────────────────────────────────
90
+ const existing = await listMigrationFiles();
91
+ const nextNumber = existing.length + 1;
92
+ const prefix = String(nextNumber).padStart(3, "0");
93
+ const filePath = path.join("database", "migrations", `${prefix}_${name}.ts`);
94
+ const absPath = path.resolve(process.cwd(), filePath);
95
+
96
+ // Bun.write() creates any missing parent directories, so no mkdir is needed.
97
+ await Bun.write(absPath, content);
98
+
99
+ // ── 5. Report ────────────────────────────────────────────────────────────
100
+ this.info(`Generated: ${filePath}`);
101
+
102
+ if (diff.newTables.length > 0) {
103
+ this.line(` + New tables: ${diff.newTables.map((t) => t.schema.table).join(", ")}`);
104
+ }
105
+ if (diff.newColumns.length > 0) {
106
+ const cols = diff.newColumns.map((c) => `${c.table}.${c.column.name}`);
107
+ this.line(` + New columns: ${cols.join(", ")}`);
108
+ }
109
+ }
110
+ }
@@ -0,0 +1,43 @@
1
+ import { Command } from "@zerotal/core";
2
+ import type { MigrationEntry } from "../schema/MigrationRunner.ts";
3
+ import { MigrationRunner } from "../schema/MigrationRunner.ts";
4
+ import { _getConnection } from "../db/DB.ts";
5
+ import { loadMigrations } from "./_loadMigrations.ts";
6
+
7
+ /**
8
+ * Rolls back the most recent migration batch (`bun zt migrate:rollback`).
9
+ *
10
+ * Reverses the migrations applied in the last batch by calling their `down()`
11
+ * methods; reports "Nothing to roll back." when no batch remains.
12
+ *
13
+ * @example
14
+ * ```bash
15
+ * bun zt migrate:rollback
16
+ * ```
17
+ *
18
+ * @category Migrations
19
+ */
20
+ export class MigrateRollbackCommand extends Command {
21
+ static commandName = "migrate:rollback";
22
+ static description = "Roll back the most recent migration batch";
23
+ static needsApp = true;
24
+
25
+ async run(): Promise<void> {
26
+ const records = await loadMigrations();
27
+ const entries: MigrationEntry[] = records.map((r) => ({
28
+ name: r.name,
29
+ migration: r.instance,
30
+ }));
31
+
32
+ const runner = new MigrationRunner({ connection: _getConnection() });
33
+ const rolledBack = await runner.rollback(entries);
34
+
35
+ if (rolledBack.length === 0) {
36
+ this.info("Nothing to roll back.");
37
+ return;
38
+ }
39
+
40
+ this.info(`Rolled back ${rolledBack.length} migration(s).`);
41
+ this.table(rolledBack.map((name) => [name, "rolled back"]));
42
+ }
43
+ }
@@ -0,0 +1,49 @@
1
+ import { Command } from "@zerotal/core";
2
+ import type { MigrationEntry } from "../schema/MigrationRunner.ts";
3
+ import { MigrationRunner } from "../schema/MigrationRunner.ts";
4
+ import { _getConnection } from "../db/DB.ts";
5
+ import { loadMigrations } from "./_loadMigrations.ts";
6
+
7
+ /**
8
+ * Shows the status of each migration (`bun zt migrate:status`).
9
+ *
10
+ * Prints a table listing every known migration and whether it has run, along
11
+ * with its batch number and the timestamp it was applied.
12
+ *
13
+ * @example
14
+ * ```bash
15
+ * bun zt migrate:status
16
+ * ```
17
+ *
18
+ * @category Migrations
19
+ */
20
+ export class MigrateStatusCommand extends Command {
21
+ static commandName = "migrate:status";
22
+ static description = "Show the status of each migration";
23
+ static needsApp = true;
24
+
25
+ async run(): Promise<void> {
26
+ const records = await loadMigrations();
27
+ const entries: MigrationEntry[] = records.map((r) => ({
28
+ name: r.name,
29
+ migration: r.instance,
30
+ }));
31
+
32
+ const runner = new MigrationRunner({ connection: _getConnection() });
33
+ const statuses = await runner.status(entries);
34
+
35
+ const rows: [string, string][] = [
36
+ ["name", "ran | batch | ranAt"],
37
+ ...statuses.map((status): [string, string] => {
38
+ if (!status.ran) {
39
+ return [status.name, "no | - | -"];
40
+ }
41
+ const batch = status.batch ?? "-";
42
+ const ranAt = status.ranAt ? status.ranAt.toISOString() : "-";
43
+ return [status.name, `yes | ${batch} | ${ranAt}`];
44
+ }),
45
+ ];
46
+
47
+ this.table(rows);
48
+ }
49
+ }
@@ -0,0 +1,34 @@
1
+ import type { MigrationRecord } from "../schema/MigrationRunner.ts";
2
+
3
+ /**
4
+ * Globs `database/migrations/*.ts`, imports each file, and returns a
5
+ * {@link MigrationRecord} array of instantiated migrations. Files are sorted by
6
+ * filename so `001_` runs before `002_`; files without a default export are
7
+ * skipped.
8
+ *
9
+ * Shared by the `migrate`, `migrate:fresh`, `migrate:rollback`, and
10
+ * `migrate:status` commands.
11
+ *
12
+ * @internal
13
+ */
14
+ export async function loadMigrations(): Promise<MigrationRecord[]> {
15
+ const glob = new Bun.Glob("database/migrations/*.ts");
16
+ const files: string[] = [];
17
+
18
+ for await (const file of glob.scan({ cwd: process.cwd() })) {
19
+ files.push(file);
20
+ }
21
+
22
+ files.sort();
23
+
24
+ const records: MigrationRecord[] = [];
25
+ for (const file of files) {
26
+ const mod = await import(`${process.cwd()}/${file}`);
27
+ const Ctor = mod.default as (new () => MigrationRecord["instance"]) | undefined;
28
+ if (!Ctor) continue;
29
+ const name = file.replace(/^database\/migrations\//, "").replace(/\.ts$/, "");
30
+ records.push({ name, instance: new Ctor() });
31
+ }
32
+
33
+ return records;
34
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * CLI commands for the Zerotal ORM: database migrations and code scaffolding.
3
+ *
4
+ * This entry point bundles the `Command`-derived classes that back the
5
+ * ORM's `bun zt` sub-commands — running, refreshing, rolling back, and
6
+ * inspecting migrations, seeding the database, and generating models,
7
+ * migrations, factories, and seeders. Register these with the CLI runner to
8
+ * expose the `migrate:*`, `db:*`, and `make:*` commands in an application.
9
+ *
10
+ * @example
11
+ * ```bash
12
+ * # Run all pending migrations
13
+ * bun zt migrate
14
+ *
15
+ * # Scaffold a new model class
16
+ * bun zt make:model Post
17
+ * ```
18
+ *
19
+ * @packageDocumentation
20
+ */
21
+ export { MigrateCommand } from "./MigrateCommand.ts";
22
+ export { MigrateRollbackCommand } from "./MigrateRollbackCommand.ts";
23
+ export { MigrateStatusCommand } from "./MigrateStatusCommand.ts";
24
+ export { MigrateFreshCommand } from "./MigrateFreshCommand.ts";
25
+ export { MakeMigrationCommand } from "./MakeMigrationCommand.ts";
26
+ export { MigrateGenerateCommand } from "./MigrateGenerateCommand.ts";
27
+ export { MakeModelCommand } from "./MakeModelCommand.ts";
28
+ export { DbSeedCommand } from "./DbSeedCommand.ts";
29
+ export { MakeSeederCommand } from "./MakeSeederCommand.ts";
30
+ export { MakeFactoryCommand } from "./MakeFactoryCommand.ts";