Skip to content
Draft
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
807 changes: 645 additions & 162 deletions package-lock.json

Large diffs are not rendered by default.

14 changes: 7 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@
"@map-colonies/schemas": "^1.13.0",
"@map-colonies/telemetry": "^10.1.0",
"@opentelemetry/api": "^1.9.0",
"@prisma/adapter-pg": "^6.19.0",
"@prisma/client": "^6.19.0",
"@prisma/instrumentation": "^6.19.0",
"@prisma/adapter-pg": "^7.0.1",
"@prisma/client": "^7.0.1",
"@prisma/instrumentation": "^7.0.1",
"compression": "^1.8.1",
"date-fns": "^4.1.0",
"express": "^4.21.2",
Expand Down Expand Up @@ -101,14 +101,14 @@
"jest-openapi": "^0.14.2",
"prettier": "^3.3.3",
"pretty-quick": "^4.1.1",
"prisma": "^6.19.0",
"prisma-json-types-generator": "^3.6.2",
"prisma": "^7.0.1",
"prisma-json-types-generator": "^4.0.0-beta.1",
"rimraf": "^6.1.0",
"supertest": "^7.1.4",
"tsc-alias": "^1.8.11",
"type-fest": "^4.38.0",
"typescript": "^5.9.3",
"zx": "^8.8.5",
"vitest": "^3.1.4"
"vitest": "^3.1.4",
"zx": "^8.8.5"
}
}
15 changes: 15 additions & 0 deletions prisma.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import path from 'node:path';
import { defineConfig } from 'prisma/config';

// DATABASE_URL is only needed for Prisma migrations
// At runtime, the PrismaClient uses the adapter configured in createConnection.ts
// Use an obviously placeholder URL for client generation if DATABASE_URL is not present
const migrateUrl = process.env.DATABASE_URL ?? 'postgresql://prisma-migrations-only:not-a-real-connection@localhost:5432/dummy?schema=public';

export default defineConfig({
earlyAccess: true,
schema: path.join('src', 'db', 'prisma', 'schema.prisma'),
migrate: {
url: migrateUrl,
},
});
14 changes: 13 additions & 1 deletion src/common/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,20 @@ export const prismaKnownErrors = {
recordNotFound: 'P2025',
} as const;

// XState machine state names (uppercase)
type XStateMachineState =
| 'CREATED'
| 'PENDING'
| 'IN_PROGRESS'
| 'COMPLETED'
| 'FAILED'
| 'ABORTED'
| 'PAUSED'
| 'WAITING'
| 'RETRIED';

export function illegalStatusTransitionErrorMessage(
currentStatus: JobOperationStatus | StageOperationStatus | TaskOperationStatus,
currentStatus: XStateMachineState | string,
requiredStatus: JobOperationStatus | StageOperationStatus | TaskOperationStatus
): string {
return `Illegal status transition from ${currentStatus} to ${requiredStatus}`;
Expand Down
169 changes: 169 additions & 0 deletions src/common/utils/statusMapping.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
/**
* This module provides bidirectional mapping between Prisma enum values (database values)
* and API enum values (OpenAPI specification values).
*
* In Prisma 7, enum values now use the @map() database values (e.g., 'Pending', 'In-Progress')
* instead of the TypeScript enum keys (e.g., 'PENDING', 'IN_PROGRESS').
*
* The API contract uses uppercase enum values to match the OpenAPI specification.
*/

import {
JobOperationStatus as PrismaJobOperationStatus,
StageOperationStatus as PrismaStageOperationStatus,
TaskOperationStatus as PrismaTaskOperationStatus,
Priority as PrismaPriority,
} from '@prismaClient';
import type { components } from '@src/openapi';

// API types from OpenAPI specification
type ApiJobOperationStatus = components['schemas']['jobOperationStatus'];
type ApiStageOperationStatus = components['schemas']['stageOperationStatusResponse'];
type ApiTaskOperationStatus = components['schemas']['taskOperationStatusResponse'];
type ApiPriority = components['schemas']['priority'];

// Bidirectional mapping for JobOperationStatus
const jobStatusPrismaToApi: Record<PrismaJobOperationStatus, ApiJobOperationStatus> = {
[PrismaJobOperationStatus.PENDING]: 'PENDING',
[PrismaJobOperationStatus.IN_PROGRESS]: 'IN_PROGRESS',
[PrismaJobOperationStatus.COMPLETED]: 'COMPLETED',
[PrismaJobOperationStatus.FAILED]: 'FAILED',
[PrismaJobOperationStatus.ABORTED]: 'ABORTED',
[PrismaJobOperationStatus.CREATED]: 'CREATED',
[PrismaJobOperationStatus.PAUSED]: 'PAUSED',
};

const jobStatusApiToPrisma: Record<ApiJobOperationStatus, PrismaJobOperationStatus> = {
PENDING: PrismaJobOperationStatus.PENDING,
IN_PROGRESS: PrismaJobOperationStatus.IN_PROGRESS,
COMPLETED: PrismaJobOperationStatus.COMPLETED,
FAILED: PrismaJobOperationStatus.FAILED,
ABORTED: PrismaJobOperationStatus.ABORTED,
CREATED: PrismaJobOperationStatus.CREATED,
PAUSED: PrismaJobOperationStatus.PAUSED,
};

// Bidirectional mapping for StageOperationStatus
const stageStatusPrismaToApi: Record<PrismaStageOperationStatus, ApiStageOperationStatus> = {
[PrismaStageOperationStatus.PENDING]: 'PENDING',
[PrismaStageOperationStatus.IN_PROGRESS]: 'IN_PROGRESS',
[PrismaStageOperationStatus.COMPLETED]: 'COMPLETED',
[PrismaStageOperationStatus.FAILED]: 'FAILED',
[PrismaStageOperationStatus.ABORTED]: 'ABORTED',
[PrismaStageOperationStatus.WAITING]: 'WAITING',
[PrismaStageOperationStatus.CREATED]: 'CREATED',
};

const stageStatusApiToPrisma: Record<ApiStageOperationStatus, PrismaStageOperationStatus> = {
PENDING: PrismaStageOperationStatus.PENDING,
IN_PROGRESS: PrismaStageOperationStatus.IN_PROGRESS,
COMPLETED: PrismaStageOperationStatus.COMPLETED,
FAILED: PrismaStageOperationStatus.FAILED,
ABORTED: PrismaStageOperationStatus.ABORTED,
WAITING: PrismaStageOperationStatus.WAITING,
CREATED: PrismaStageOperationStatus.CREATED,
};

// Bidirectional mapping for TaskOperationStatus
const taskStatusPrismaToApi: Record<PrismaTaskOperationStatus, ApiTaskOperationStatus> = {
[PrismaTaskOperationStatus.PENDING]: 'PENDING',
[PrismaTaskOperationStatus.IN_PROGRESS]: 'IN_PROGRESS',
[PrismaTaskOperationStatus.COMPLETED]: 'COMPLETED',
[PrismaTaskOperationStatus.FAILED]: 'FAILED',
[PrismaTaskOperationStatus.CREATED]: 'CREATED',
[PrismaTaskOperationStatus.RETRIED]: 'RETRIED',
};

const taskStatusApiToPrisma: Record<ApiTaskOperationStatus, PrismaTaskOperationStatus> = {
PENDING: PrismaTaskOperationStatus.PENDING,
IN_PROGRESS: PrismaTaskOperationStatus.IN_PROGRESS,
COMPLETED: PrismaTaskOperationStatus.COMPLETED,
FAILED: PrismaTaskOperationStatus.FAILED,
CREATED: PrismaTaskOperationStatus.CREATED,
RETRIED: PrismaTaskOperationStatus.RETRIED,
};

// Bidirectional mapping for Priority
const priorityPrismaToApi: Record<PrismaPriority, ApiPriority> = {
[PrismaPriority.VERY_HIGH]: 'VERY_HIGH',
[PrismaPriority.HIGH]: 'HIGH',
[PrismaPriority.MEDIUM]: 'MEDIUM',
[PrismaPriority.LOW]: 'LOW',
[PrismaPriority.VERY_LOW]: 'VERY_LOW',
};

const priorityApiToPrisma: Record<ApiPriority, PrismaPriority> = {
VERY_HIGH: PrismaPriority.VERY_HIGH,
HIGH: PrismaPriority.HIGH,
MEDIUM: PrismaPriority.MEDIUM,
LOW: PrismaPriority.LOW,
VERY_LOW: PrismaPriority.VERY_LOW,
};

// Conversion functions with runtime validation
export function convertJobStatusToApi(prismaStatus: PrismaJobOperationStatus): ApiJobOperationStatus {
const result = jobStatusPrismaToApi[prismaStatus];
if (result === undefined) {
throw new Error(`Unknown Prisma job status: ${prismaStatus}`);
}
return result;
}

export function convertJobStatusToPrisma(apiStatus: ApiJobOperationStatus): PrismaJobOperationStatus {
const result = jobStatusApiToPrisma[apiStatus];
if (result === undefined) {
throw new Error(`Unknown API job status: ${apiStatus}`);
}
return result;
}

export function convertStageStatusToApi(prismaStatus: PrismaStageOperationStatus): ApiStageOperationStatus {
const result = stageStatusPrismaToApi[prismaStatus];
if (result === undefined) {
throw new Error(`Unknown Prisma stage status: ${prismaStatus}`);
}
return result;
}

export function convertStageStatusToPrisma(apiStatus: ApiStageOperationStatus): PrismaStageOperationStatus {
const result = stageStatusApiToPrisma[apiStatus];
if (result === undefined) {
throw new Error(`Unknown API stage status: ${apiStatus}`);
}
return result;
}

export function convertTaskStatusToApi(prismaStatus: PrismaTaskOperationStatus): ApiTaskOperationStatus {
const result = taskStatusPrismaToApi[prismaStatus];
if (result === undefined) {
throw new Error(`Unknown Prisma task status: ${prismaStatus}`);
}
return result;
}

export function convertTaskStatusToPrisma(apiStatus: ApiTaskOperationStatus): PrismaTaskOperationStatus {
const result = taskStatusApiToPrisma[apiStatus];
if (result === undefined) {
throw new Error(`Unknown API task status: ${apiStatus}`);
}
return result;
}

export function convertPriorityToApi(prismaPriority: PrismaPriority): ApiPriority {
const result = priorityPrismaToApi[prismaPriority];
if (result === undefined) {
throw new Error(`Unknown Prisma priority: ${prismaPriority}`);
}
return result;
}

export function convertPriorityToPrisma(apiPriority: ApiPriority): PrismaPriority {
const result = priorityApiToPrisma[apiPriority];
if (result === undefined) {
throw new Error(`Unknown API priority: ${apiPriority}`);
}
return result;
}

// Export types for use in other modules
export type { ApiJobOperationStatus, ApiStageOperationStatus, ApiTaskOperationStatus, ApiPriority };
1 change: 0 additions & 1 deletion src/db/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ generator json {

datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}

model Job {
Expand Down
Loading
Loading