feat(plugin-tools): first version of the package with some nice plugin utilities
This commit is contained in:
parent
cccdfb817d
commit
cdafd05c20
5 changed files with 193 additions and 0 deletions
5
.changeset/proud-ducks-refuse.md
Normal file
5
.changeset/proud-ducks-refuse.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@emigrate/plugin-tools': minor
|
||||
---
|
||||
|
||||
First version of the @emigrate/plugin-tools package which contains some nice to have utilities when building and using Emigrate plugins
|
||||
36
packages/plugin-tools/package.json
Normal file
36
packages/plugin-tools/package.json
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"name": "@emigrate/plugin-tools",
|
||||
"version": "0.0.0",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"description": "",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.js",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
},
|
||||
"./types": {
|
||||
"import": "./dist/types.js",
|
||||
"types": "./dist/types.d.ts"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc --pretty",
|
||||
"build:watch": "tsc --pretty --watch"
|
||||
},
|
||||
"keywords": [
|
||||
"emigrate",
|
||||
"plugin",
|
||||
"migrations",
|
||||
"types"
|
||||
],
|
||||
"author": "Aboviq AB <dev@aboviq.com> (https://www.aboviq.com)",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@emigrate/tsconfig": "workspace:*"
|
||||
}
|
||||
}
|
||||
56
packages/plugin-tools/src/index.ts
Normal file
56
packages/plugin-tools/src/index.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import { type GeneratorPlugin, type StoragePlugin } from './types.js';
|
||||
|
||||
export const createStoragePlugin = (initialize: StoragePlugin['initialize']): StoragePlugin => {
|
||||
return {
|
||||
type: 'storage',
|
||||
initialize,
|
||||
};
|
||||
};
|
||||
|
||||
export const createGeneratorPlugin = (generate: GeneratorPlugin['generate']): GeneratorPlugin => {
|
||||
return {
|
||||
type: 'generator',
|
||||
generate,
|
||||
};
|
||||
};
|
||||
|
||||
export const isGeneratorPlugin = (plugin: any): plugin is GeneratorPlugin => {
|
||||
if (!plugin || typeof plugin !== 'object') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (plugin.type === 'generator') {
|
||||
return typeof plugin.generate === 'function';
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
export const isStoragePlugin = (plugin: any): plugin is StoragePlugin => {
|
||||
if (!plugin || typeof plugin !== 'object') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (plugin.type === 'storage') {
|
||||
return typeof plugin.initialize === 'function';
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a timestamp string in the format YYYYMMDDHHmmssmmm based on the current time (UTC)
|
||||
*
|
||||
* Can be used to prefix migration filenames so that they are executed in the correct order
|
||||
*
|
||||
* @returns A timestamp string in the format YYYYMMDDHHmmssmmm
|
||||
*/
|
||||
export const getTimestampPrefix = () => new Date().toISOString().replaceAll(/[-:ZT.]/g, '');
|
||||
|
||||
/**
|
||||
* A utility function to sanitize a migration name so that it can be used as a filename
|
||||
*
|
||||
* @param name A migration name to sanitize
|
||||
* @returns A sanitized migration name that can be used as a filename
|
||||
*/
|
||||
export const sanitizeMigrationName = (name: string) => name.replaceAll(/[\W/\\:|*?'"<>]/g, '_').trim();
|
||||
88
packages/plugin-tools/src/types.ts
Normal file
88
packages/plugin-tools/src/types.ts
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
export type MigrationStatus = 'failed' | 'done';
|
||||
|
||||
export type MigrationHistoryEntry = {
|
||||
name: string;
|
||||
status: MigrationStatus;
|
||||
date: Date;
|
||||
error?: unknown;
|
||||
};
|
||||
|
||||
export type Storage = {
|
||||
/**
|
||||
* Acquire a lock on the given migrations.
|
||||
*
|
||||
* To best support concurrent migrations (e.g. when multiple services are deployed at the same time and want to migrate the same database)
|
||||
* the plugin should try to lock all migrations at once (i.e. in a transaction) and ignore migrations that are already locked (or done).
|
||||
* The successfully locked migrations should be returned and are the migrations that will be executed.
|
||||
*
|
||||
* If one of the migrations to lock is in a failed state, the plugin should throw an error to abort the migration attempt.
|
||||
*
|
||||
* @returns The migrations that were successfully locked.
|
||||
*/
|
||||
lock(migrations: string[]): Promise<string[]>;
|
||||
/**
|
||||
* The unlock method is called after all migrations have been executed or when the process is interrupted (e.g. by a SIGTERM or SIGINT signal).
|
||||
*
|
||||
* Depending on the plugin implementation, the unlock method is usually a no-op for already succeeded or failed migrations.
|
||||
*
|
||||
* @param migrations The previously successfully locked migrations that should now be unlocked.
|
||||
*/
|
||||
unlock(migrations: string[]): Promise<void>;
|
||||
/**
|
||||
* Get the history of previously executed migrations.
|
||||
*
|
||||
* For failed migrations, the error property should be set.
|
||||
* Emigrate will not sort the history entries, so the plugin should return the entries in the order they were executed.
|
||||
* The order doesn't affect the execution of migrations, but it does affect the order in which the history is displayed in the CLI.
|
||||
* Migrations that have not yet been executed will always be run in alphabetical order.
|
||||
*
|
||||
* The history has two purposes:
|
||||
* 1. To determine which migrations have already been executed.
|
||||
* 2. To list the migration history in the CLI.
|
||||
*/
|
||||
getHistory(): AsyncIterable<MigrationHistoryEntry>;
|
||||
/**
|
||||
* Called when a migration has been successfully executed.
|
||||
*
|
||||
* @param migration The name of the migration that should be marked as done.
|
||||
*/
|
||||
onSuccess(migration: string): Promise<void>;
|
||||
/**
|
||||
* Called when a migration has failed.
|
||||
*
|
||||
* @param migration The name of the migration that should be marked as failed.
|
||||
* @param error The error that caused the migration to fail.
|
||||
*/
|
||||
onError(migration: string, error: Error): Promise<void>;
|
||||
};
|
||||
|
||||
export type StoragePlugin = {
|
||||
type: 'storage';
|
||||
initialize(): Promise<Storage>;
|
||||
};
|
||||
|
||||
export type MigrationFile = {
|
||||
/**
|
||||
* The complete filename of the migration file, including the extension.
|
||||
*
|
||||
* Migrations that have not yet been executed will be run in alphabetical order, so preferably prefix the filename with a timestamp (and avoid unix timestamp and prefer something more human readable).
|
||||
*/
|
||||
filename: string;
|
||||
/**
|
||||
* The content of the migration file.
|
||||
*/
|
||||
content: string;
|
||||
};
|
||||
|
||||
export type GeneratorPlugin = {
|
||||
type: 'generator';
|
||||
/**
|
||||
* Used to generate a new migration file.
|
||||
*
|
||||
* @param name The name of the migration that should be generated (provided as arguments to the CLI)
|
||||
* @returns The generated migration file.
|
||||
*/
|
||||
generate(name: string): Promise<MigrationFile>;
|
||||
};
|
||||
|
||||
export type Plugin = StoragePlugin | GeneratorPlugin;
|
||||
8
packages/plugin-tools/tsconfig.json
Normal file
8
packages/plugin-tools/tsconfig.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"extends": "@emigrate/tsconfig/build.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue