diff --git a/.changeset/fast-olives-bathe.md b/.changeset/fast-olives-bathe.md new file mode 100644 index 0000000..d873bfe --- /dev/null +++ b/.changeset/fast-olives-bathe.md @@ -0,0 +1,5 @@ +--- +'@emigrate/plugin-tools': minor +--- + +When serializing errors take all "own properties" into account to be able to serialize errors thrown by the `mysql2` package for instance without losing any information diff --git a/.changeset/few-hounds-retire.md b/.changeset/few-hounds-retire.md new file mode 100644 index 0000000..a705f75 --- /dev/null +++ b/.changeset/few-hounds-retire.md @@ -0,0 +1,8 @@ +--- +'@emigrate/plugin-tools': patch +'@emigrate/storage-fs': patch +'@emigrate/mysql': patch +'@emigrate/cli': patch +--- + +Serialization of errors now happens inside storage plugins because it makes more sense and the types are easier to work with this way diff --git a/packages/mysql/src/index.ts b/packages/mysql/src/index.ts index 23852ab..82b1c96 100644 --- a/packages/mysql/src/index.ts +++ b/packages/mysql/src/index.ts @@ -20,7 +20,7 @@ import { type GenerateMigrationFunction, type GeneratorPlugin, } from '@emigrate/plugin-tools/types'; -import { getTimestampPrefix, sanitizeMigrationName } from '@emigrate/plugin-tools'; +import { getTimestampPrefix, sanitizeMigrationName, serializeError } from '@emigrate/plugin-tools'; const defaultTable = 'migrations'; @@ -213,7 +213,7 @@ export const createMysqlStorage = ({ table = defaultTable, connection }: MysqlSt status: row.status, date: new Date(row.date), // FIXME: Migrate the migrations table to support the error column - error: row.status === 'failed' ? new Error('Unknown error reason') : undefined, + error: row.status === 'failed' ? serializeError(new Error('Unknown error reason')) : undefined, }; } }, diff --git a/packages/plugin-tools/src/index.ts b/packages/plugin-tools/src/index.ts index 0615d2f..ce08988 100644 --- a/packages/plugin-tools/src/index.ts +++ b/packages/plugin-tools/src/index.ts @@ -11,12 +11,16 @@ import { } from './types.js'; export const serializeError = (error: Error): SerializedError => { - return { + const properties: Record = { name: error.name, - message: error.message, - stack: error.stack, - cause: error.cause instanceof Error ? serializeError(error.cause) : error.cause, }; + + for (const key of Object.getOwnPropertyNames(error)) { + const value = error[key as keyof Error]; + properties[key] = value instanceof Error ? serializeError(value) : value; + } + + return properties as SerializedError; }; export const isGeneratorPlugin = (plugin: any): plugin is GeneratorPlugin => { diff --git a/packages/plugin-tools/src/types.ts b/packages/plugin-tools/src/types.ts index fd7c19b..ce77ed6 100644 --- a/packages/plugin-tools/src/types.ts +++ b/packages/plugin-tools/src/types.ts @@ -5,7 +5,8 @@ export type StringOrModule = string | T | (() => Awaitable) | (() => Await export type MigrationStatus = 'failed' | 'done' | 'pending'; export type SerializedError = { - name: string; + [key: string]: unknown; + name?: string; message: string; stack?: string; cause?: unknown; @@ -73,7 +74,7 @@ export type Storage = { * @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: MigrationMetadataFinished, error: SerializedError): Promise; + onError(migration: MigrationMetadataFinished, error: Error): Promise; /** * Called when the command is finished or aborted (e.g. by a SIGTERM or SIGINT signal). * diff --git a/packages/storage-fs/src/index.ts b/packages/storage-fs/src/index.ts index c31e2f2..1c643ec 100644 --- a/packages/storage-fs/src/index.ts +++ b/packages/storage-fs/src/index.ts @@ -1,6 +1,7 @@ import fs from 'node:fs/promises'; import path from 'node:path'; import process from 'node:process'; +import { serializeError } from '@emigrate/plugin-tools'; import { type SerializedError, type EmigrateStorage, type MigrationStatus } from '@emigrate/plugin-tools/types'; export type StorageFsOptions = { @@ -26,7 +27,7 @@ export default function storageFs({ filename }: StorageFsOptions): EmigrateStora let lastUpdate: Promise = Promise.resolve(); - const update = async (migration: string, status: MigrationStatus, error?: SerializedError) => { + const update = async (migration: string, status: MigrationStatus, error?: Error) => { lastUpdate = lastUpdate.then(async () => { const history = await read(); @@ -35,7 +36,7 @@ export default function storageFs({ filename }: StorageFsOptions): EmigrateStora [migration]: { status, date: new Date().toISOString(), - error, + error: error ? serializeError(error) : undefined, }, };