marianozunino / morpheus

Morpheus is a modern, open-source database migration tool for Neo4j. It is designed to be a simple, intuitive tool for managing database migrations. The project is inspired by Michael Simons' tool for Java.
MIT License
19 stars 4 forks source link
cli database database-migrations migration migrations neo4j nestjs nodejs typescript

Morpheus

build-deploy License: MIT

Coverage Status

npm type definitions Downloads/week Version Nest Logo

About

Morpheus is a modern, open-source database migration tool for Neo4j. It is designed to be a simple, intuitive tool for managing database migrations. The project is inspired by Michael Simons' tool for Java.

Quick Start

npm install -g morpheus4j
morpheus init         # Create config file
morpheus create user-nodes  # Creates V1_0_0__user-nodes.cypher
morpheus migrate     # Run migrations

Prerequisites

Migration Files

Migration files:

Example migration file V1_0_0__create_users.cypher:

CREATE CONSTRAINT user_email IF NOT EXISTS FOR (u:User) REQUIRE u.email IS UNIQUE;

CREATE (u:User {
  email: 'admin@example.com',
  name: 'Admin User',
  created_at: datetime()
});

File Naming Convention

Migration files follow this pattern:

Example: V1_0_0__create-user-constraints.cypher

Environment Variables

Morpheus supports the following environment variables:

Best Practices

Troubleshooting

Usage

$ npm install -g morpheus4j
$ morpheus COMMAND
running command...
$ morpheus (--version)
morpheus4j/4.2.0 linux-x64 node-v20.13.1
$ morpheus --help [COMMAND]
USAGE
  $ morpheus COMMAND
...

Commands

morpheus autocomplete [SHELL]

Display autocomplete installation instructions.

USAGE
  $ morpheus autocomplete [SHELL] [-r]

ARGUMENTS
  SHELL  (zsh|bash|powershell) Shell type

FLAGS
  -r, --refresh-cache  Refresh cache (ignores displaying instructions)

DESCRIPTION
  Display autocomplete installation instructions.

EXAMPLES
  $ morpheus autocomplete

  $ morpheus autocomplete bash

  $ morpheus autocomplete zsh

  $ morpheus autocomplete powershell

  $ morpheus autocomplete --refresh-cache

See code: @oclif/plugin-autocomplete

morpheus clean

Clean up migration-related database objects

USAGE
  $ morpheus clean [--json] [--debug] [--drop-constraints] [-c <value>] [-m <value>] [-h <value>] [-p
    <value>] [-s <value>] [-P <value>] [-u <value>] [-d <value>]

FLAGS
  -P, --password=<value>        Neo4j password. Env: 'MORPHEUS_PASSWORD'
  -c, --configFile=<value>      Path to the morpheus file. ./morpheus.json by default
  -d, --database=<value>        Neo4j database. Env: 'MORPHEUS_DATABASE'
  -h, --host=<value>            Neo4j host. Env: 'MORPHEUS_HOST'
  -m, --migrationsPath=<value>  Migrations path. Env: 'MORPHEUS_MIGRATIONS_PATH'
  -p, --port=<value>            Neo4j port. Env: 'MORPHEUS_PORT'
  -s, --scheme=<value>          Neo4j scheme. Env: 'MORPHEUS_SCHEME'
  -u, --username=<value>        Neo4j username. Env: 'MORPHEUS_USERNAME'
      --drop-constraints        Additionally remove all Morpheus-related database constraints

GLOBAL FLAGS
  --debug  Enable debug logging
  --json   Format output as json.

DESCRIPTION
  Clean up migration-related database objects

  Removes all Morpheus migration metadata including nodes, relationships, and optionally constraints.
  Use with caution as this will reset the migration history.

EXAMPLES
  $ morpheus clean

  $ morpheus clean --drop-constraints

  $ morpheus clean --config ./custom-config.json

See code: src/commands/clean.ts

morpheus create NAME

Generate a new timestamped migration file with boilerplate code

USAGE
  $ morpheus create NAME [--json] [-c <value>] [-m <value>]

ARGUMENTS
  NAME  Name of the migration (will be prefixed with a semver number)

FLAGS
  -c, --configFile=<value>      Path to the morpheus file. ./morpheus.json by default
  -m, --migrationsPath=<value>  Migrations path. Env: 'MORPHEUS_MIGRATIONS_PATH'

GLOBAL FLAGS
  --json  Format output as json.

DESCRIPTION
  Generate a new timestamped migration file with boilerplate code

EXAMPLES
  $ morpheus create add-user-nodes

  $ morpheus create update-relationships -m ~/path/to/migrations

  $ morpheus create update-relationships --config ./custom-config.json

See code: src/commands/create.ts

morpheus info

Info up migration-related database objects

USAGE
  $ morpheus info [--json] [--debug] [-c <value>] [-m <value>] [-h <value>] [-p <value>] [-s <value>] [-P
    <value>] [-u <value>] [-d <value>]

FLAGS
  -P, --password=<value>        Neo4j password. Env: 'MORPHEUS_PASSWORD'
  -c, --configFile=<value>      Path to the morpheus file. ./morpheus.json by default
  -d, --database=<value>        Neo4j database. Env: 'MORPHEUS_DATABASE'
  -h, --host=<value>            Neo4j host. Env: 'MORPHEUS_HOST'
  -m, --migrationsPath=<value>  Migrations path. Env: 'MORPHEUS_MIGRATIONS_PATH'
  -p, --port=<value>            Neo4j port. Env: 'MORPHEUS_PORT'
  -s, --scheme=<value>          Neo4j scheme. Env: 'MORPHEUS_SCHEME'
  -u, --username=<value>        Neo4j username. Env: 'MORPHEUS_USERNAME'

GLOBAL FLAGS
  --debug  Enable debug logging
  --json   Format output as json.

DESCRIPTION
  Info up migration-related database objects

  Removes all Morpheus migration metadata including nodes, relationships, and optionally constraints.
  Use with caution as this will reset the migration history.

EXAMPLES
  $ morpheus info

  $ morpheus info --config ./custom-config.json

See code: src/commands/info.ts

morpheus init

Initialize a new Morpheus configuration file with database connection settings

USAGE
  $ morpheus init [-c <value>] [-f]

FLAGS
  -c, --configFile=<value>  Path to the morpheus file. ./morpheus.json by default
  -f, --force               Overwrite existing configuration file if it exists

DESCRIPTION
  Initialize a new Morpheus configuration file with database connection settings

EXAMPLES
  $ morpheus init

  $ morpheus init --force

  $ morpheus init --config ./custom-path/morpheus.json

  $ morpheus init --config .config.json --force

See code: src/commands/init.ts

morpheus migrate

Execute pending database migrations in sequential order

USAGE
  $ morpheus migrate [--json] [--debug] [-c <value>] [-m <value>] [-h <value>] [-p <value>] [-s <value>] [-P
    <value>] [-u <value>] [-d <value>] [--dry-run] [--transaction-mode PER_MIGRATION|PER_STATEMENT]

FLAGS
  -P, --password=<value>           Neo4j password. Env: 'MORPHEUS_PASSWORD'
  -c, --configFile=<value>         Path to the morpheus file. ./morpheus.json by default
  -d, --database=<value>           Neo4j database. Env: 'MORPHEUS_DATABASE'
  -h, --host=<value>               Neo4j host. Env: 'MORPHEUS_HOST'
  -m, --migrationsPath=<value>     Migrations path. Env: 'MORPHEUS_MIGRATIONS_PATH'
  -p, --port=<value>               Neo4j port. Env: 'MORPHEUS_PORT'
  -s, --scheme=<value>             Neo4j scheme. Env: 'MORPHEUS_SCHEME'
  -u, --username=<value>           Neo4j username. Env: 'MORPHEUS_USERNAME'
      --dry-run                    Perform a dry run - no changes will be made to the database
      --transaction-mode=<option>  [default: PER_MIGRATION] Transaction mode
                                   <options: PER_MIGRATION|PER_STATEMENT>

GLOBAL FLAGS
  --debug  Enable debug logging
  --json   Format output as json.

DESCRIPTION
  Execute pending database migrations in sequential order

EXAMPLES
  $ morpheus migrate

  $ morpheus migrate -m ~/path/to/migrations

  $ morpheus migrate --config ./custom-config.json

  $ morpheus migrate --dry-run

  $ morpheus migrate --transaction-mode=PER_STATEMENT

See code: src/commands/migrate.ts

NestJs Integration Nest Logo

Module Usage

import { MorpheusModule, MorpheusService,  Neo4jConfig, Neo4jScheme  } from '../../dist/nestjs';
import { Module, Injectable } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';

@Module({
  imports: [MorpheusModule, ConfigModule.forRoot()],
  providers: [MigrationsService],
})
export class MigrationsModule {}

@Injectable()
export class MigrationsService {
  constructor(
    private readonly morpheusService: MorpheusService,
    private readonly configService: ConfigService,
  ) {}

  async onApplicationBootstrap() {
    // When no config is provided, the default config is used
    // -> morpheus.json
    // -> moprheus environment variables

    await this.morpheusService.cleanDatabase(); // NOTE: You probably don't want to do this, specially in production
    await this.morpheusService.runMigrations();

    // Use the ConfigService to access the environment variables
    const configs: Neo4jConfig[] = [
      {
        scheme: Neo4jScheme.BOLT,
        host: 'localhost',
        port: 7687,
        username: 'neo4j',
        password: 'password',
        migrationsPath: '../neo4j/migrations',
      },
    ];

    for (const config of configs) {
      // Clean and run migrations
      await this.morpheusService.cleanDatabase(config); // NOTE: You probably don't want to do this, specially in production
      await this.morpheusService.runMigrations(config);
    }
  }
}

How it works

The approach is simple. Morpheus will read all migrations in the neo4j/migrations directory and execute them in order.

For each migration, Morpheus will create a transaction and execute the migration. Thus a migration may contain multiple Cypher statements (each statement must end with ;).

Once a migration file is executed, Morpheus will keep track of the migration and will not execute it again.

Existing migration files that have already been executed can not be modified since they are stored in a database with their corresponding checksum (crc32).

If you want to revert a migration, create a new migration and revert the changes.

How does neo4j keep track of the migrations?

You can take a look at schema and explanation on Michael's README - there's a neat graph that shows the migration chain.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.