@veap/database 0.1.0 → 0.1.2

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 (63) hide show
  1. package/README.md +3 -2
  2. package/dist/casts.js +1 -1
  3. package/dist/cli/commands/make-migration.d.ts +13 -0
  4. package/dist/cli/commands/make-migration.js +24 -0
  5. package/dist/cli/commands/migrate-rollback.d.ts +14 -0
  6. package/dist/cli/commands/migrate-rollback.js +41 -0
  7. package/dist/cli/commands/migrate-status.d.ts +16 -0
  8. package/dist/cli/commands/migrate-status.js +79 -0
  9. package/dist/cli/commands/migrate.d.ts +13 -0
  10. package/dist/cli/commands/migrate.js +40 -0
  11. package/dist/cli/confirmation.d.ts +14 -0
  12. package/dist/cli/confirmation.js +26 -0
  13. package/dist/cli/migrations.d.ts +20 -0
  14. package/dist/cli/migrations.js +41 -0
  15. package/dist/composition.d.ts +15 -0
  16. package/dist/composition.js +26 -0
  17. package/dist/connection.d.ts +1 -1
  18. package/dist/connection.js +9 -5
  19. package/dist/factory.d.ts +1 -1
  20. package/dist/factory.js +1 -1
  21. package/dist/index.d.ts +18 -16
  22. package/dist/index.js +18 -16
  23. package/dist/migration-provider.d.ts +10 -0
  24. package/dist/migration-provider.js +50 -6
  25. package/dist/migrations/generator.d.ts +11 -2
  26. package/dist/migrations/generator.js +40 -5
  27. package/dist/migrations/index.d.ts +3 -3
  28. package/dist/migrations/index.js +3 -3
  29. package/dist/migrations/runner.d.ts +28 -3
  30. package/dist/migrations/runner.js +45 -29
  31. package/dist/migrations/schema-builder.js +1 -1
  32. package/dist/model.d.ts +4 -4
  33. package/dist/model.js +25 -11
  34. package/dist/provider.d.ts +2 -1
  35. package/dist/provider.js +62 -8
  36. package/dist/query-builder.d.ts +6 -6
  37. package/dist/query-builder.js +40 -6
  38. package/dist/relations/belongs-to-many.d.ts +3 -3
  39. package/dist/relations/belongs-to-many.js +2 -2
  40. package/dist/relations/belongs-to.d.ts +3 -3
  41. package/dist/relations/belongs-to.js +1 -1
  42. package/dist/relations/eager-loader.d.ts +1 -1
  43. package/dist/relations/eager-loader.js +3 -1
  44. package/dist/relations/has-many.d.ts +3 -3
  45. package/dist/relations/has-many.js +1 -1
  46. package/dist/relations/has-one.d.ts +3 -3
  47. package/dist/relations/has-one.js +1 -1
  48. package/dist/relations/index.d.ts +11 -11
  49. package/dist/relations/index.js +11 -11
  50. package/dist/relations/morph-many.d.ts +3 -3
  51. package/dist/relations/morph-many.js +2 -2
  52. package/dist/relations/morph-one.d.ts +3 -3
  53. package/dist/relations/morph-one.js +2 -2
  54. package/dist/relations/morph-to-many.d.ts +3 -3
  55. package/dist/relations/morph-to-many.js +3 -3
  56. package/dist/relations/morph-to.d.ts +3 -3
  57. package/dist/relations/morph-to.js +3 -3
  58. package/dist/relations/relation.d.ts +2 -2
  59. package/dist/scopes.d.ts +2 -2
  60. package/dist/seeder.js +2 -2
  61. package/dist/tokens.d.ts +10 -0
  62. package/dist/tokens.js +8 -0
  63. package/package.json +10 -2
package/README.md CHANGED
@@ -1,5 +1,6 @@
1
- [![CI](https://github.com/veapjs/veap-database/actions/workflows/ci.yml/badge.svg)](https://github.com/veapjs/veap-database/actions/workflows/ci.yml)
2
- [![Release](https://github.com/veapjs/veap-database/actions/workflows/release.yml/badge.svg)](https://github.com/veapjs/veap-database/actions/workflows/release.yml)
1
+ [![CI](https://github.com/veapjs/veap-database/actions/workflows/CI.yml/badge.svg)](https://github.com/veapjs/veap-database/actions/workflows/CI.yml)
2
+ [![Release](https://github.com/veapjs/veap-database/actions/workflows/RELEASE.yml/badge.svg)](https://github.com/veapjs/veap-database/actions/workflows/RELEASE.yml)
3
+ [![Version](https://img.shields.io/npm/v/@veap/database?style=flat&colorA=000000&colorB=000000)](https://www.npmjs.com/package/@veap/database)
3
4
 
4
5
  # @veap/database
5
6
 
package/dist/casts.js CHANGED
@@ -57,7 +57,7 @@ export function serializeForStorage(value, type, clientType) {
57
57
  }
58
58
  switch (type) {
59
59
  case "json":
60
- return JSON.stringify(value);
60
+ return typeof value === "string" ? value : JSON.stringify(value);
61
61
  case "boolean":
62
62
  if (clientType?.includes("sqlite")) {
63
63
  return value ? 1 : 0;
@@ -0,0 +1,13 @@
1
+ import { Command, type CommandInput, type Output } from "@veap/kernel/cli";
2
+ /**
3
+ * `veap make:migration {name}` — scaffolds a migration in the host project.
4
+ *
5
+ * The file lands in the project the console was started in (by default
6
+ * `<project>/migrations`) and the directory's `index.ts` is regenerated, so the
7
+ * new migration is picked up by `.withMigrations(appMigrations)`.
8
+ */
9
+ export declare class MakeMigrationCommand extends Command {
10
+ static signature: string;
11
+ static description: string;
12
+ handle(input: CommandInput, output: Output): Promise<number>;
13
+ }
@@ -0,0 +1,24 @@
1
+ import path from "node:path";
2
+ import { Command } from "@veap/kernel/cli";
3
+ import { generateMigration } from "../../migrations/generator.js";
4
+ /**
5
+ * `veap make:migration {name}` — scaffolds a migration in the host project.
6
+ *
7
+ * The file lands in the project the console was started in (by default
8
+ * `<project>/migrations`) and the directory's `index.ts` is regenerated, so the
9
+ * new migration is picked up by `.withMigrations(appMigrations)`.
10
+ */
11
+ export class MakeMigrationCommand extends Command {
12
+ static signature = "make:migration {name : Name of the migration, e.g. create_posts_table} {--dir=path : Directory relative to the project root (default: migrations)}";
13
+ static description = "Create a new migration file in the application";
14
+ async handle(input, output) {
15
+ const name = input.argument("name") ?? "";
16
+ const dir = input.option("dir");
17
+ const directory = path.resolve(this.context.root, typeof dir === "string" && dir.trim() !== "" ? dir.trim() : "migrations");
18
+ const { filePath } = await generateMigration(name, directory);
19
+ const relative = path.relative(this.context.root, filePath);
20
+ output.success(`Created migration: ${relative}`);
21
+ output.info('Run "veap migrate" to apply the pending migrations.');
22
+ return 0;
23
+ }
24
+ }
@@ -0,0 +1,14 @@
1
+ import { Command, type CommandInput, type Output } from "@veap/kernel/cli";
2
+ /**
3
+ * `veap migrate:rollback` — undoes the last batch of the scope.
4
+ *
5
+ * Runs each migration's `down()` in the reverse of the order it was applied, and
6
+ * deletes the `migrations` rows it undid, so a `veap migrate` afterwards
7
+ * re-applies exactly what was rolled back. In production the command asks first;
8
+ * `--force` answers that question for an unattended run.
9
+ */
10
+ export declare class MigrateRollbackCommand extends Command {
11
+ static signature: string;
12
+ static description: string;
13
+ handle(input: CommandInput, output: Output): Promise<number>;
14
+ }
@@ -0,0 +1,41 @@
1
+ import { Command } from "@veap/kernel/cli";
2
+ import { rollbackMigrations } from "../../migrations/runner.js";
3
+ import { confirmToProceed } from "../confirmation.js";
4
+ import { hasDatabaseConnection, resolveRegisteredMigrations, resolveScope, } from "../migrations.js";
5
+ /**
6
+ * `veap migrate:rollback` — undoes the last batch of the scope.
7
+ *
8
+ * Runs each migration's `down()` in the reverse of the order it was applied, and
9
+ * deletes the `migrations` rows it undid, so a `veap migrate` afterwards
10
+ * re-applies exactly what was rolled back. In production the command asks first;
11
+ * `--force` answers that question for an unattended run.
12
+ */
13
+ export class MigrateRollbackCommand extends Command {
14
+ static signature = "migrate:rollback {--scope=app : Migration scope to roll back}";
15
+ static description = "Roll back the last batch of application migrations";
16
+ async handle(input, output) {
17
+ const scope = resolveScope(input);
18
+ const migrations = await resolveRegisteredMigrations(this.context.container);
19
+ if (migrations.length === 0) {
20
+ output.warn("No application migrations are registered. Add them to the application with `.withMigrations(appMigrations)`.");
21
+ return 0;
22
+ }
23
+ if (!hasDatabaseConnection()) {
24
+ output.error("No database connection is available. Set DATABASE_URL (or register DatabaseServiceProvider) before rolling migrations back.");
25
+ return 1;
26
+ }
27
+ const proceed = await confirmToProceed(this.context, output, "Roll back the last batch of migrations");
28
+ if (!proceed)
29
+ return 1;
30
+ const result = await rollbackMigrations(scope, migrations);
31
+ if (result.rolledBack.length === 0) {
32
+ output.info(`Nothing to roll back for scope "${scope}".`);
33
+ return 0;
34
+ }
35
+ for (const name of result.rolledBack) {
36
+ output.success(`Rolled back: ${name}`);
37
+ }
38
+ output.info(`${result.rolledBack.length} migration(s) rolled back (scope "${scope}").`);
39
+ return 0;
40
+ }
41
+ }
@@ -0,0 +1,16 @@
1
+ import { Command, type CommandInput, type Output } from "@veap/kernel/cli";
2
+ /**
3
+ * `veap migrate:status` — what is applied and what is still pending.
4
+ *
5
+ * Compares the migrations the application registered against the rows recorded
6
+ * in the `migrations` table for the scope, so the answer is about *this*
7
+ * application, not about the whole database. Rows recorded under the scope that
8
+ * the application no longer declares are reported separately: they are the
9
+ * signature of a renamed or deleted migration file, which `migrate` would
10
+ * otherwise never mention again.
11
+ */
12
+ export declare class MigrateStatusCommand extends Command {
13
+ static signature: string;
14
+ static description: string;
15
+ handle(input: CommandInput, output: Output): Promise<number>;
16
+ }
@@ -0,0 +1,79 @@
1
+ import { Command } from "@veap/kernel/cli";
2
+ import { getKnex } from "../../connection.js";
3
+ import { hasDatabaseConnection, resolveRegisteredMigrations, resolveScope, } from "../migrations.js";
4
+ /** `executed_at` comes back as a Date, an epoch number or a string, per driver. */
5
+ function formatRanAt(value) {
6
+ if (value === null || value === undefined)
7
+ return "—";
8
+ const date = value instanceof Date
9
+ ? value
10
+ : typeof value === "number"
11
+ ? new Date(value)
12
+ : undefined;
13
+ if (date && !Number.isNaN(date.getTime())) {
14
+ return date.toISOString().replace("T", " ").slice(0, 19);
15
+ }
16
+ const text = String(value).trim();
17
+ return text === "" ? "—" : text.replace("T", " ").slice(0, 19);
18
+ }
19
+ /**
20
+ * `veap migrate:status` — what is applied and what is still pending.
21
+ *
22
+ * Compares the migrations the application registered against the rows recorded
23
+ * in the `migrations` table for the scope, so the answer is about *this*
24
+ * application, not about the whole database. Rows recorded under the scope that
25
+ * the application no longer declares are reported separately: they are the
26
+ * signature of a renamed or deleted migration file, which `migrate` would
27
+ * otherwise never mention again.
28
+ */
29
+ export class MigrateStatusCommand extends Command {
30
+ static signature = "migrate:status {--scope=app : Migration scope to inspect}";
31
+ static description = "Show the status of the application migrations";
32
+ async handle(input, output) {
33
+ const scope = resolveScope(input);
34
+ const migrations = await resolveRegisteredMigrations(this.context.container);
35
+ if (!hasDatabaseConnection()) {
36
+ output.error("No database connection is available. Set DATABASE_URL (or register DatabaseServiceProvider) before reading the migration status.");
37
+ return 1;
38
+ }
39
+ const knex = getKnex();
40
+ const recorded = (await knex.schema.hasTable("migrations"))
41
+ ? await knex("migrations")
42
+ .select("name", "batch", "executed_at")
43
+ .where("scope", scope)
44
+ : [];
45
+ const byName = new Map(recorded.map((row) => [row.name, row]));
46
+ const rows = [];
47
+ let ran = 0;
48
+ let pending = 0;
49
+ for (const migration of migrations) {
50
+ const row = byName.get(migration.name);
51
+ if (row) {
52
+ ran += 1;
53
+ rows.push([
54
+ migration.name,
55
+ "Ran",
56
+ String(row.batch),
57
+ formatRanAt(row.executed_at),
58
+ ]);
59
+ continue;
60
+ }
61
+ pending += 1;
62
+ rows.push([migration.name, "Pending", "—", "—"]);
63
+ }
64
+ if (rows.length === 0) {
65
+ output.info(`No application migrations are registered for scope "${scope}".`);
66
+ }
67
+ else {
68
+ output.table(["Migration", "Status", "Batch", "Ran at"], rows);
69
+ output.info(`${ran} ran, ${pending} pending (scope "${scope}").`);
70
+ }
71
+ const unknown = recorded.filter((row) => !migrations.some((migration) => migration.name === row.name));
72
+ if (unknown.length > 0) {
73
+ output.warn(`${unknown.length} migration(s) recorded for scope "${scope}" are not part of this application: ${unknown
74
+ .map((row) => row.name)
75
+ .join(", ")}.`);
76
+ }
77
+ return 0;
78
+ }
79
+ }
@@ -0,0 +1,13 @@
1
+ import { Command, type CommandInput, type Output } from "@veap/kernel/cli";
2
+ /**
3
+ * `veap migrate` — applies every pending application migration, in one batch.
4
+ *
5
+ * In production the command asks before touching the database; `--force` answers
6
+ * that question for an unattended run, and `--no-interaction` without `--force`
7
+ * resolves it to "no" instead of hanging.
8
+ */
9
+ export declare class MigrateCommand extends Command {
10
+ static signature: string;
11
+ static description: string;
12
+ handle(input: CommandInput, output: Output): Promise<number>;
13
+ }
@@ -0,0 +1,40 @@
1
+ import { Command } from "@veap/kernel/cli";
2
+ import { runMigrations } from "../../migrations/runner.js";
3
+ import { confirmToProceed } from "../confirmation.js";
4
+ import { hasDatabaseConnection, resolveRegisteredMigrations, resolveScope, } from "../migrations.js";
5
+ /**
6
+ * `veap migrate` — applies every pending application migration, in one batch.
7
+ *
8
+ * In production the command asks before touching the database; `--force` answers
9
+ * that question for an unattended run, and `--no-interaction` without `--force`
10
+ * resolves it to "no" instead of hanging.
11
+ */
12
+ export class MigrateCommand extends Command {
13
+ static signature = "migrate {--scope=app : Migration scope to run}";
14
+ static description = "Run the pending application migrations";
15
+ async handle(input, output) {
16
+ const scope = resolveScope(input);
17
+ const migrations = await resolveRegisteredMigrations(this.context.container);
18
+ if (migrations.length === 0) {
19
+ output.warn("No application migrations are registered. Add them to the application with `.withMigrations(appMigrations)`.");
20
+ return 0;
21
+ }
22
+ if (!hasDatabaseConnection()) {
23
+ output.error("No database connection is available. Set DATABASE_URL (or register DatabaseServiceProvider) before running migrations.");
24
+ return 1;
25
+ }
26
+ const proceed = await confirmToProceed(this.context, output, "Run the pending migrations");
27
+ if (!proceed)
28
+ return 1;
29
+ const result = await runMigrations(scope, migrations);
30
+ if (result.ran.length === 0) {
31
+ output.info(`Nothing to migrate for scope "${scope}".`);
32
+ return 0;
33
+ }
34
+ for (const name of result.ran) {
35
+ output.success(`Migrated: ${name}`);
36
+ }
37
+ output.info(`${result.ran.length} migration(s) applied in batch ${result.batch} (scope "${scope}").`);
38
+ return 0;
39
+ }
40
+ }
@@ -0,0 +1,14 @@
1
+ import type { CommandContext, Output } from "@veap/kernel/cli";
2
+ /**
3
+ * The gate every command that changes the database goes through.
4
+ *
5
+ * Outside production it stays silent: `veap migrate` on a developer machine is a
6
+ * routine step and should not need a confirmation. Inside production the command
7
+ * asks first, and `--force` is how an unattended run says yes — a pipeline runs
8
+ * `veap migrate --force`. Passing `--no-interaction` without `--force` resolves
9
+ * the prompt to its default ("no"), so an unattended run can never touch
10
+ * production data by accident, and it can never hang waiting for input either.
11
+ */
12
+ export declare function confirmToProceed(context: CommandContext, output: Output,
13
+ /** Sentence describing the action, e.g. "Run the pending migrations". */
14
+ action: string): Promise<boolean>;
@@ -0,0 +1,26 @@
1
+ /** Environments in which changing the database asks for a confirmation first. */
2
+ const GUARDED_ENVIRONMENTS = new Set(["production", "prod"]);
3
+ /**
4
+ * The gate every command that changes the database goes through.
5
+ *
6
+ * Outside production it stays silent: `veap migrate` on a developer machine is a
7
+ * routine step and should not need a confirmation. Inside production the command
8
+ * asks first, and `--force` is how an unattended run says yes — a pipeline runs
9
+ * `veap migrate --force`. Passing `--no-interaction` without `--force` resolves
10
+ * the prompt to its default ("no"), so an unattended run can never touch
11
+ * production data by accident, and it can never hang waiting for input either.
12
+ */
13
+ export async function confirmToProceed(context, output,
14
+ /** Sentence describing the action, e.g. "Run the pending migrations". */
15
+ action) {
16
+ if (context.flags.force)
17
+ return true;
18
+ if (!GUARDED_ENVIRONMENTS.has(context.env))
19
+ return true;
20
+ // No quotes around the environment name on purpose: this question is also
21
+ // rendered inside the non-TTY error message, where nested quotes read badly.
22
+ const confirmed = await output.confirm(`The application is running in ${context.env}. ${action}?`, { default: false });
23
+ if (!confirmed)
24
+ output.warn("Cancelled.");
25
+ return confirmed;
26
+ }
@@ -0,0 +1,20 @@
1
+ import type { Container } from "@veap/kernel";
2
+ import type { CommandInput } from "@veap/kernel/cli";
3
+ /**
4
+ * Tokens a host application publishes its migration list under: the explicit
5
+ * `APP_MIGRATIONS` symbol or the string alias used by older scaffolds.
6
+ */
7
+ export declare const MIGRATION_TOKENS: readonly [import("@veap/kernel").Token<any[]>, "AppMigrations"];
8
+ /** Default scope every migration command operates on. */
9
+ export declare const DEFAULT_MIGRATION_SCOPE = "app";
10
+ /** The scope a migration command was asked for (`--scope`, default `app`). */
11
+ export declare function resolveScope(input: CommandInput): string;
12
+ /**
13
+ * The migration list the host application registered, if any.
14
+ *
15
+ * Read from the container, which is where `.withMigrations(appMigrations)` puts
16
+ * it, so no command has to guess how a project declares its migrations.
17
+ */
18
+ export declare function resolveRegisteredMigrations(container: Container): Promise<any[]>;
19
+ /** `true` when `DatabaseServiceProvider` booted and a Knex instance exists. */
20
+ export declare function hasDatabaseConnection(): boolean;
@@ -0,0 +1,41 @@
1
+ import { getKnex } from "../connection.js";
2
+ import { APP_MIGRATIONS } from "../tokens.js";
3
+ /**
4
+ * Tokens a host application publishes its migration list under: the explicit
5
+ * `APP_MIGRATIONS` symbol or the string alias used by older scaffolds.
6
+ */
7
+ export const MIGRATION_TOKENS = [APP_MIGRATIONS, "AppMigrations"];
8
+ /** Default scope every migration command operates on. */
9
+ export const DEFAULT_MIGRATION_SCOPE = "app";
10
+ /** The scope a migration command was asked for (`--scope`, default `app`). */
11
+ export function resolveScope(input) {
12
+ const scope = input.option("scope");
13
+ return typeof scope === "string" && scope.trim() !== ""
14
+ ? scope.trim()
15
+ : DEFAULT_MIGRATION_SCOPE;
16
+ }
17
+ /**
18
+ * The migration list the host application registered, if any.
19
+ *
20
+ * Read from the container, which is where `.withMigrations(appMigrations)` puts
21
+ * it, so no command has to guess how a project declares its migrations.
22
+ */
23
+ export async function resolveRegisteredMigrations(container) {
24
+ for (const token of MIGRATION_TOKENS) {
25
+ if (!container.has(token))
26
+ continue;
27
+ const migrations = await container.resolve(token);
28
+ if (Array.isArray(migrations) && migrations.length > 0)
29
+ return migrations;
30
+ }
31
+ return [];
32
+ }
33
+ /** `true` when `DatabaseServiceProvider` booted and a Knex instance exists. */
34
+ export function hasDatabaseConnection() {
35
+ try {
36
+ return Boolean(getKnex());
37
+ }
38
+ catch {
39
+ return false;
40
+ }
41
+ }
@@ -0,0 +1,15 @@
1
+ import { ServiceProvider } from "@veap/kernel";
2
+ declare module "@veap/kernel" {
3
+ interface ApplicationBuilder {
4
+ /**
5
+ * Registers host application migrations with IoC and configures MigrationServiceProvider.
6
+ */
7
+ withMigrations(migrations: any[]): this;
8
+ }
9
+ }
10
+ declare class MigrationsConfigProvider extends ServiceProvider {
11
+ private migrations;
12
+ constructor(container: any, migrations?: any[]);
13
+ register(): void;
14
+ }
15
+ export { MigrationsConfigProvider };
@@ -0,0 +1,26 @@
1
+ import { ApplicationBuilder, ServiceProvider } from "@veap/kernel";
2
+ import { MigrationServiceProvider } from "./migration-provider.js";
3
+ import { APP_MIGRATIONS } from "./tokens.js";
4
+ class MigrationsConfigProvider extends ServiceProvider {
5
+ migrations;
6
+ constructor(container, migrations = []) {
7
+ super(container);
8
+ this.migrations = migrations;
9
+ }
10
+ register() {
11
+ this.container.register({
12
+ token: APP_MIGRATIONS,
13
+ useValue: this.migrations,
14
+ });
15
+ this.container.register({
16
+ token: "AppMigrations",
17
+ useValue: this.migrations,
18
+ });
19
+ }
20
+ }
21
+ if (!ApplicationBuilder.prototype.withMigrations) {
22
+ ApplicationBuilder.prototype.withMigrations = function (migrations) {
23
+ return this.use((c) => new MigrationsConfigProvider(c, migrations)).use(MigrationServiceProvider);
24
+ };
25
+ }
26
+ export { MigrationsConfigProvider };
@@ -1,6 +1,6 @@
1
1
  import { AsyncLocalStorage } from "node:async_hooks";
2
2
  import { type Knex } from "knex";
3
- import type { DatabaseConfig, PostgresSslConfig, PostgresSslOptions, TransactionOptions } from "./types";
3
+ import type { DatabaseConfig, PostgresSslConfig, PostgresSslOptions, TransactionOptions } from "./types.js";
4
4
  /**
5
5
  * AsyncLocalStorage holding the currently active Knex transaction for the asynchronous execution context.
6
6
  */
@@ -2,8 +2,8 @@ import { AsyncLocalStorage } from "node:async_hooks";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import knex from "knex";
5
- import { DatabaseError } from "./errors";
6
- import { debug, warn } from "./logger";
5
+ import { DatabaseError } from "./errors.js";
6
+ import { debug, warn } from "./logger.js";
7
7
  const globalForDb = globalThis;
8
8
  /**
9
9
  * AsyncLocalStorage holding the currently active Knex transaction for the asynchronous execution context.
@@ -90,9 +90,13 @@ export function resolveSqliteFilename(filename) {
90
90
  }
91
91
  if (!clean.startsWith(":memory:")) {
92
92
  try {
93
- fs.mkdirSync(path.dirname(path.resolve(clean)), {
94
- recursive: true,
95
- });
93
+ if (typeof process !== "undefined" && process.versions?.node) {
94
+ /* turbopackIgnore: true */
95
+ const resolvedPath = path.resolve(clean);
96
+ fs.mkdirSync(path.dirname(resolvedPath), {
97
+ recursive: true,
98
+ });
99
+ }
96
100
  }
97
101
  catch {
98
102
  // Read-only filesystem etc. Let the driver surface its own error.
package/dist/factory.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Model } from "./model";
1
+ import type { Model } from "./model.js";
2
2
  export declare abstract class Factory<M extends Model = any> {
3
3
  private static get registry();
4
4
  /**
package/dist/factory.js CHANGED
@@ -1,4 +1,4 @@
1
- import { DatabaseError } from "./errors";
1
+ import { DatabaseError } from "./errors.js";
2
2
  export class Factory {
3
3
  static get registry() {
4
4
  const g = globalThis;
package/dist/index.d.ts CHANGED
@@ -1,17 +1,19 @@
1
1
  export { default as knex, type Knex } from "knex";
2
- export * from "./casts";
3
- export * from "./connection";
4
- export * from "./errors";
5
- export * from "./events";
6
- export * from "./factory";
7
- export * from "./logger";
8
- export * from "./migration-provider";
9
- export * from "./migrations/index";
10
- export * from "./model";
11
- export * from "./provider";
12
- export * from "./query-builder";
13
- export * from "./relations/index";
14
- export * from "./scopes";
15
- export * from "./seeder";
16
- export * from "./types";
17
- export * from "./utils";
2
+ export * from "./casts.js";
3
+ export * from "./composition.js";
4
+ export * from "./connection.js";
5
+ export * from "./errors.js";
6
+ export * from "./events.js";
7
+ export * from "./factory.js";
8
+ export * from "./logger.js";
9
+ export * from "./migration-provider.js";
10
+ export * from "./migrations/index.js";
11
+ export * from "./model.js";
12
+ export * from "./provider.js";
13
+ export * from "./query-builder.js";
14
+ export * from "./relations/index.js";
15
+ export * from "./scopes.js";
16
+ export * from "./seeder.js";
17
+ export * from "./tokens.js";
18
+ export * from "./types.js";
19
+ export * from "./utils.js";
package/dist/index.js CHANGED
@@ -1,17 +1,19 @@
1
1
  export { default as knex } from "knex";
2
- export * from "./casts";
3
- export * from "./connection";
4
- export * from "./errors";
5
- export * from "./events";
6
- export * from "./factory";
7
- export * from "./logger";
8
- export * from "./migration-provider";
9
- export * from "./migrations/index";
10
- export * from "./model";
11
- export * from "./provider";
12
- export * from "./query-builder";
13
- export * from "./relations/index";
14
- export * from "./scopes";
15
- export * from "./seeder";
16
- export * from "./types";
17
- export * from "./utils";
2
+ export * from "./casts.js";
3
+ export * from "./composition.js";
4
+ export * from "./connection.js";
5
+ export * from "./errors.js";
6
+ export * from "./events.js";
7
+ export * from "./factory.js";
8
+ export * from "./logger.js";
9
+ export * from "./migration-provider.js";
10
+ export * from "./migrations/index.js";
11
+ export * from "./model.js";
12
+ export * from "./provider.js";
13
+ export * from "./query-builder.js";
14
+ export * from "./relations/index.js";
15
+ export * from "./scopes.js";
16
+ export * from "./seeder.js";
17
+ export * from "./tokens.js";
18
+ export * from "./types.js";
19
+ export * from "./utils.js";
@@ -1,5 +1,15 @@
1
1
  import { ServiceProvider } from "@veap/kernel";
2
+ import type { CommandEntry } from "@veap/kernel/cli";
2
3
  export declare class MigrationServiceProvider extends ServiceProvider {
4
+ private migrations?;
5
+ constructor(container: any, migrations?: any[]);
6
+ /**
7
+ * Migration commands, harvested by the console for the host application:
8
+ * `veap make:migration` scaffolds one, `veap migrate` applies the pending
9
+ * ones, `veap migrate:status` reports them and `veap migrate:rollback` undoes
10
+ * the last batch.
11
+ */
12
+ commands(): CommandEntry[];
3
13
  register(): void;
4
14
  boot(): Promise<void>;
5
15
  }
@@ -1,15 +1,59 @@
1
1
  import { ServiceProvider } from "@veap/kernel";
2
- import { runMigrations } from "./migrations/runner";
2
+ import { MakeMigrationCommand } from "./cli/commands/make-migration.js";
3
+ import { MigrateCommand } from "./cli/commands/migrate.js";
4
+ import { MigrateRollbackCommand } from "./cli/commands/migrate-rollback.js";
5
+ import { MigrateStatusCommand } from "./cli/commands/migrate-status.js";
6
+ import { runMigrations } from "./migrations/runner.js";
7
+ import { APP_MIGRATIONS } from "./tokens.js";
3
8
  export class MigrationServiceProvider extends ServiceProvider {
9
+ migrations;
10
+ constructor(container, migrations) {
11
+ super(container);
12
+ this.migrations = migrations;
13
+ }
14
+ /**
15
+ * Migration commands, harvested by the console for the host application:
16
+ * `veap make:migration` scaffolds one, `veap migrate` applies the pending
17
+ * ones, `veap migrate:status` reports them and `veap migrate:rollback` undoes
18
+ * the last batch.
19
+ */
20
+ commands() {
21
+ return [
22
+ MakeMigrationCommand,
23
+ MigrateCommand,
24
+ MigrateStatusCommand,
25
+ MigrateRollbackCommand,
26
+ ];
27
+ }
4
28
  register() {
5
- // No bindings needed
29
+ if (this.migrations?.length) {
30
+ this.container.register({
31
+ token: APP_MIGRATIONS,
32
+ useValue: this.migrations,
33
+ });
34
+ this.container.register({
35
+ token: "AppMigrations",
36
+ useValue: this.migrations,
37
+ });
38
+ }
6
39
  }
7
40
  async boot() {
8
- if (this.container.has("AppMigrations")) {
9
- const appMigrations = await this.container.resolve("AppMigrations");
10
- if (appMigrations?.length) {
11
- await runMigrations("app", appMigrations);
41
+ // Under the console (`VEAP_CLI=1`) migrations are an explicit action: the
42
+ // `veap migrate` command runs them, and every other command — `list`
43
+ // included — must leave the database untouched.
44
+ if (process.env.VEAP_CLI === "1")
45
+ return;
46
+ let appMigrations = this.migrations;
47
+ if (!appMigrations?.length) {
48
+ if (this.container.has(APP_MIGRATIONS)) {
49
+ appMigrations = await this.container.resolve(APP_MIGRATIONS);
12
50
  }
51
+ else if (this.container.has("AppMigrations")) {
52
+ appMigrations = await this.container.resolve("AppMigrations");
53
+ }
54
+ }
55
+ if (appMigrations?.length) {
56
+ await runMigrations("app", appMigrations);
13
57
  }
14
58
  }
15
59
  }
@@ -1,4 +1,13 @@
1
- export declare function generateMigration(name: string, targetDir?: string): Promise<{
1
+ export interface GeneratedMigration {
2
+ /** Timestamped file name without the extension, e.g. `20261014120000_create_posts_table`. */
2
3
  fileName: string;
4
+ /** Absolute path of the written migration file. */
3
5
  filePath: string;
4
- }>;
6
+ }
7
+ /**
8
+ * Writes a new migration file and regenerates the directory's `index.ts`.
9
+ *
10
+ * Used by the `veap make:migration` command; kept free of the console so it can
11
+ * be called from anywhere (tests, generators, plugins).
12
+ */
13
+ export declare function generateMigration(name: string, targetDir?: string): Promise<GeneratedMigration>;