Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 31 additions & 3 deletions packages/shared/src/utils/testing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,42 @@ export function mergeDeep<T extends ObjectType>(target: T, source: DeepPartial<T
}

/**
* Type guard to check if a value is an object
* @param item The value to check
* @returns True if the value is an object, false otherwise
* Determines whether the provided value is a non-null object that is not an array.
*
* This type guard checks that the value is of type "object", is not null, and explicitly excludes arrays.
* It is useful for ensuring a value conforms to an object shape before performing object-specific operations.
*
* @param item - The value to check.
* @returns True if `item` is a non-null object and not an array; otherwise, false.
*
* @example
* ```typescript
* const value: unknown = { key: 'value' };
* if (isObject(value)) {
* // Within this block, TypeScript treats `value` as an object.
* }
* ```
*/
function isObject(item: unknown): item is ObjectType {
return item !== null && typeof item === "object" && !Array.isArray(item);
}

/**
* Determines whether the provided value can be parsed as a valid JSON string.
*
* This function attempts to parse the input as a JSON string. If parsing is successful,
* it returns `true` (indicating that the input conforms to JSON structure and is thus considered a valid JSON object).
* If parsing fails, it logs the error to the console and returns `false`.
*
* @param item - The value to test for valid JSON structure.
* @returns `true` if the input can be successfully parsed as JSON; otherwise, `false`.
*
* @example
* const jsonString = '{"name": "Alice", "age": 30}';
* if (isJSON(jsonString)) {
* // jsonString is a valid JSON and now inferred to be of ObjectType
* }
*/
export function isJSON(item: unknown): item is ObjectType {
try {
JSON.parse(item as string);
Expand Down
21 changes: 21 additions & 0 deletions scripts/bootstrap/src/schemas/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,17 @@ const env = validationSchema.safeParse(process.env);

export type DbEnvConfig = z.infer<typeof dbEnvSchema>;

/**
* Retrieves the validated database configuration from environment variables.
*
* This function uses the `dbEnvSchema` to safely parse and validate the environment variables
* provided in `process.env`. If the validation fails, it logs a detailed error message and throws
* an exception to halt further execution. On successful validation, it returns the configuration
* structured as defined by `DbEnvConfig`.
*
* @returns The validated database configuration.
* @throws Error when the environment variables do not conform to the expected schema.
*/
export function getDatabaseConfigFromEnv(): DbEnvConfig {
const result = dbEnvSchema.safeParse(process.env);

Expand All @@ -64,6 +75,16 @@ export function getDatabaseConfigFromEnv(): DbEnvConfig {
return result.data;
}

/**
* Retrieves validated environment variables.
*
* This function checks whether the environment variables have been successfully validated using `validationSchema`.
* If the validation fails, it logs the formatted error details and throws an exception to prevent further execution
* with invalid configurations. On successful validation, it returns the structured environment settings.
*
* @returns The validated environment variables conforming to `validationSchema`.
* @throws Error if the environment variables do not pass validation.
*/
export function getEnv(): z.infer<typeof validationSchema> {
if (!env.success) {
console.error("❌ Invalid environment variables:", env.error.format());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
import { Kysely, sql } from "kysely";

/**
* Executes the "up" migration to create caching tables in the database.
*
* This function creates two tables:
*
* - **priceCache**: Stores pricing data with the following columns:
* - `tokenCode` (text): Not nullable.
* - `timestampMs` (bigint): Not nullable.
* - `priceUsd` (decimal(36, 18)): Not nullable.
* - `createdAt` (timestamptz): Defaults to the current timestamp.
* A composite primary key is defined on `tokenCode` and `timestampMs`.
*
* - **metadataCache**: Stores metadata with the following columns:
* - `id` (text): Not nullable.
* - `metadata` (jsonb): Nullable, allowing null values.
* - `createdAt` (timestamptz): Defaults to the current timestamp.
* A primary key is defined on the `id` column.
*
* @param db - The Kysely database instance used to execute the migration queries.
* @returns A promise that resolves when the migration is complete.
*/
export async function up(db: Kysely<unknown>): Promise<void> {
// Create pricing cache table
await db.schema
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,31 @@ import { getSchemaName } from "../../utils/index.js";
* The only argument for the functions is an instance of Kysely<any>. It's important to use Kysely<any> and not Kysely<YourDatabase>.
* ref: https://kysely.dev/docs/migrations#migration-files
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
/**
* Applies the database schema upgrade migration using the Kysely schema builder.
*
* This function performs a series of asynchronous schema modifications to support new application features.
* It creates custom enum types, tables, indexes, foreign key constraints, and a SQL function for project search.
*
* Schema changes include:
* - Creating the "project_type" enum type with values "canonical" and "linked".
* - Creating the "projects" table with various columns and a primary key constraint on (id, chainId), along with an index on metadata.
* - Creating tables for pending and confirmed project roles, including the "pending_project_roles" and "project_roles" tables.
* - Creating the "rounds" table with multiple columns for managing rounds and associated indexes for performance.
* - Creating tables for pending and confirmed round roles, including the "pending_round_roles" and "round_roles" tables.
* - Creating the "application_status" enum type and the "applications" table with foreign key constraints to projects and rounds.
* - Creating the "applications_payouts" table with a foreign key linking to applications.
* - Creating the "donations" table with corresponding indexes for optimized queries.
* - Creating the "legacy_projects" table for legacy project data.
* - Defining a custom SQL function for project search.
*
* @param db - The Kysely database instance used to execute the schema migration commands. The database's schema
* property is used to dynamically reference qualified table names.
*
* @returns A promise that resolves once the schema migration is successfully applied.
*
* @throws An error if any of the schema creation or alteration commands fail.
*/
export async function up(db: Kysely<any>): Promise<void> {
const BIGINT_TYPE = sql`decimal(78,0)`;
const ADDRESS_TYPE = "text";
Expand Down
38 changes: 28 additions & 10 deletions scripts/migrations/src/utils/kysely.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,23 @@ export const getSchemaName = (schema: SchemaModule): string => {
};

/**
* Applies all migrations to the database up to the latest version.
* Applies all available migrations to the database up to the latest version.
*
* @param config - The migration configuration.
* @param config.db - The Kysely database instance.
* @param config.schema - The schema to use for the migrations. Should be the same as the schema used in the Kysely database instance.
* @returns The migration results.
* This function ensures that the specified schema exists and then uses file-based migrations to update the
* database. It creates a migrator using the provided database instance and migration configurations, including
* dynamically constructed migration table names based on the given domain. Each migration's status is logged,
* and in case of an error during the migration process, the error is logged and re-thrown.
*
* @param config - The migration configuration object.
* @param config.db - The Kysely database instance used to execute migration queries.
* @param config.schema - The name of the schema to apply migrations, which should match the database schema.
* @param config.migrationsFolder - The folder path containing migration files.
* @param config.domain - The domain string used to dynamically name the migration tables.
* @param logger - The logger instance for logging migration statuses and errors.
*
* @returns A promise that resolves to an array of migration result objects if migrations are applied, or undefined otherwise.
*
* @throws If an error occurs during the migration process, the error is logged and thrown.
*/
export async function migrateToLatest<T>(
config: MigrationConfig<T>,
Expand Down Expand Up @@ -77,12 +88,19 @@ export async function migrateToLatest<T>(
}

/**
* Resets the database by rolling back all migrations.
* Resets the database by rolling back all applied migrations.
*
* This function initializes a Migrator with a file migration provider and reverts the database
* state by rolling back all migrations. The migration and lock table names are dynamically constructed
* using the provided domain (formatted as "<domain>_migrations" and "<domain>_migrations_lock").
*
* @param config - The migration configuration.
* @param config.db - The Kysely database instance.
* @param config.schema - The schema to use for the migrations. Should be the same as the schema used in the Kysely database instance.
* @returns The migration results.
* @param config - The migration configuration, including:
* - db: The Kysely database instance.
* - migrationsFolder: The folder path containing migration files.
* - domain: The domain used to namespace the migration tables.
* @param logger - The logger instance for recording migration events and errors.
* @returns A promise that resolves to an array of migration results if successful, or undefined.
* @throws Will throw an error if the migration reset process encounters an issue.
*/
export async function resetDatabase<T>(
config: MigrationConfig<T>,
Expand Down