From 5d4ff093a21c072553b2cac6c799d3efa3cb84c0 Mon Sep 17 00:00:00 2001 From: Ben Holmes Date: Fri, 23 Feb 2024 17:01:33 -0500 Subject: [PATCH] Improved error logging from config (#10207) * fix: better error messaging on seed() * chore: collection -> table for errors * chore: changeset --- .changeset/young-pears-brake.md | 5 ++ packages/db/src/core/errors.ts | 18 ++++-- packages/db/src/core/integration/index.ts | 9 +-- packages/db/src/core/queries.ts | 71 +++++++++++++++-------- 4 files changed, 67 insertions(+), 36 deletions(-) create mode 100644 .changeset/young-pears-brake.md diff --git a/.changeset/young-pears-brake.md b/.changeset/young-pears-brake.md new file mode 100644 index 000000000000..30ee4acd79ff --- /dev/null +++ b/.changeset/young-pears-brake.md @@ -0,0 +1,5 @@ +--- +"@astrojs/db": patch +--- + +Improve error messaging when seeding invalid data. diff --git a/packages/db/src/core/errors.ts b/packages/db/src/core/errors.ts index 4346f2355721..2ac406b31681 100644 --- a/packages/db/src/core/errors.ts +++ b/packages/db/src/core/errors.ts @@ -10,8 +10,8 @@ export const MISSING_PROJECT_ID_ERROR = `${red('▶ Directory not linked.')} To link this directory to an Astro Studio project, run ${cyan('astro db link')}\n`; -export const STUDIO_CONFIG_MISSING_WRITABLE_COLLECTIONS_ERROR = (collectionName: string) => `${red( - `▶ Writable collection ${bold(collectionName)} requires Astro Studio or the ${yellow( +export const STUDIO_CONFIG_MISSING_WRITABLE_TABLE_ERROR = (tableName: string) => `${red( + `▶ Writable table ${bold(tableName)} requires Astro Studio or the ${yellow( 'unsafeWritable' )} option.` )} @@ -34,10 +34,20 @@ export const MIGRATIONS_NOT_INITIALIZED = `${yellow( '▶ No migrations found!' )}\n\n To scaffold your migrations folder, run\n ${cyan('astro db sync')}\n`; -export const SEED_WRITABLE_IN_PROD_ERROR = (collectionName: string) => { +export const SEED_WRITABLE_IN_PROD_ERROR = (tableName: string) => { return `${red( `Writable tables should not be seeded in production with data().` )} You can seed ${bold( - collectionName + tableName )} in development mode only using the "mode" flag. See the docs for more: https://www.notion.so/astroinc/astrojs-db-README-dcf6fa10de9a4f528be56cee96e8c054?pvs=4#278aed3fc37e4cec80240d1552ff6ac5`; }; + +export const SEED_ERROR = (tableName: string, error: string) => { + return `${red(`Error seeding table ${bold(tableName)}:`)}\n\n${error}`; +}; + +export const SEED_EMPTY_ARRAY_ERROR = (tableName: string) => { + // Drizzle error says "values() must be called with at least one value." + // This is specific to db.insert(). Prettify for seed(). + return SEED_ERROR(tableName, `Empty array was passed. seed() must receive at least one value.`); +}; diff --git a/packages/db/src/core/integration/index.ts b/packages/db/src/core/integration/index.ts index c9b7aefe7946..2ea1b74c78ef 100644 --- a/packages/db/src/core/integration/index.ts +++ b/packages/db/src/core/integration/index.ts @@ -8,10 +8,7 @@ import { DB_PATH } from '../consts.js'; import { createLocalDatabaseClient } from '../../runtime/db-client.js'; import { astroConfigWithDbSchema, type DBTables } from '../types.js'; import { type VitePlugin } from '../utils.js'; -import { - STUDIO_CONFIG_MISSING_WRITABLE_COLLECTIONS_ERROR, - UNSAFE_WRITABLE_WARNING, -} from '../errors.js'; +import { STUDIO_CONFIG_MISSING_WRITABLE_TABLE_ERROR, UNSAFE_WRITABLE_WARNING } from '../errors.js'; import { errorMap } from './error-map.js'; import { dirname } from 'path'; import { fileURLToPath } from 'url'; @@ -76,9 +73,7 @@ function astroDBIntegration(): AstroIntegration { const foundWritableCollection = Object.entries(tables).find(([, c]) => c.writable); const writableAllowed = studio || unsafeWritable; if (!writableAllowed && foundWritableCollection) { - logger.error( - STUDIO_CONFIG_MISSING_WRITABLE_COLLECTIONS_ERROR(foundWritableCollection[0]) - ); + logger.error(STUDIO_CONFIG_MISSING_WRITABLE_TABLE_ERROR(foundWritableCollection[0])); process.exit(1); } // Using writable tables with the opt-in flag. Warn them to let them diff --git a/packages/db/src/core/queries.ts b/packages/db/src/core/queries.ts index 82c0fe3f85af..9fd1003f6cd5 100644 --- a/packages/db/src/core/queries.ts +++ b/packages/db/src/core/queries.ts @@ -14,10 +14,15 @@ import { bold } from 'kleur/colors'; import { type SQL, sql, getTableName } from 'drizzle-orm'; import { SQLiteAsyncDialect, type SQLiteInsert } from 'drizzle-orm/sqlite-core'; import type { AstroIntegrationLogger } from 'astro'; -import type { DBUserConfig } from '../core/types.js'; +import type { + ColumnsConfig, + DBUserConfig, + MaybeArray, + ResolvedCollectionConfig, +} from '../core/types.js'; import { hasPrimaryKey } from '../runtime/index.js'; import { isSerializedSQL } from '../runtime/types.js'; -import { SEED_WRITABLE_IN_PROD_ERROR } from './errors.js'; +import { SEED_EMPTY_ARRAY_ERROR, SEED_ERROR, SEED_WRITABLE_IN_PROD_ERROR } from './errors.js'; const sqlite = new SQLiteAsyncDialect(); @@ -51,40 +56,56 @@ export async function seedData({ logger?: AstroIntegrationLogger; mode: 'dev' | 'build'; }) { + const dataFns = Array.isArray(data) ? data : [data]; try { - const dataFns = Array.isArray(data) ? data : [data]; for (const dataFn of dataFns) { await dataFn({ - seed: async ({ table, writable }, values) => { - if (writable && mode === 'build' && process.env.ASTRO_DB_TEST_ENV !== '1') { - (logger ?? console).error(SEED_WRITABLE_IN_PROD_ERROR(getTableName(table))); - process.exit(1); + seed: async (config, values) => { + seedErrorChecks(mode, config, values); + try { + await db.insert(config.table).values(values as any); + } catch (e) { + const msg = e instanceof Error ? e.message : String(e); + throw new Error(SEED_ERROR(getTableName(config.table), msg)); } - await db.insert(table).values(values as any); }, - seedReturning: async ({ table, writable }, values) => { - if (writable && mode === 'build' && process.env.ASTRO_DB_TEST_ENV !== '1') { - (logger ?? console).error(SEED_WRITABLE_IN_PROD_ERROR(getTableName(table))); - process.exit(1); - } - let result: SQLiteInsert = db - .insert(table) - .values(values as any) - .returning(); - if (!Array.isArray(values)) { - result = result.get(); + seedReturning: async (config, values) => { + seedErrorChecks(mode, config, values); + try { + let result: SQLiteInsert = db + .insert(config.table) + .values(values as any) + .returning(); + if (!Array.isArray(values)) { + result = result.get(); + } + return result; + } catch (e) { + const msg = e instanceof Error ? e.message : String(e); + throw new Error(SEED_ERROR(getTableName(config.table), msg)); } - return result; }, db, mode, }); } - } catch (error) { - (logger ?? console).error( - `Failed to seed data. Did you update to match recent schema changes?` - ); - (logger ?? console).error(error as string); + } catch (e) { + if (!(e instanceof Error)) throw e; + (logger ?? console).error(e.message); + } +} + +function seedErrorChecks( + mode: 'dev' | 'build', + { table, writable }: ResolvedCollectionConfig, + values: MaybeArray +) { + const tableName = getTableName(table); + if (writable && mode === 'build' && process.env.ASTRO_DB_TEST_ENV !== '1') { + throw new Error(SEED_WRITABLE_IN_PROD_ERROR(tableName)); + } + if (Array.isArray(values) && values.length === 0) { + throw new Error(SEED_EMPTY_ARRAY_ERROR(tableName)); } }