Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
fe3b995
feat(jobnik-openapi): unify branded-id generation in jobnik-openapi
CptSchnitz Sep 16, 2026
5f5d529
feat(jobnik-sdk): consume jobnik-openapi types directly
CptSchnitz Sep 16, 2026
db8e803
feat(jobnik-manager): brand job ids end-to-end
CptSchnitz Sep 16, 2026
3e73e82
feat(jobnik-manager): brand stage ids end-to-end
CptSchnitz Sep 16, 2026
c72fec6
feat(jobnik-manager): brand task ids end-to-end
CptSchnitz Sep 16, 2026
55345b4
fix(jobnik-openapi,jobnik-manager): address code review findings
CptSchnitz Sep 16, 2026
de63f33
fix(jobnik-sdk): satisfy import-x/order for jobnik-openapi imports
CptSchnitz Sep 17, 2026
467c03f
refactor(jobnik-openapi,jobnik-sdk,jobnik-manager): unify generated e…
CptSchnitz Sep 17, 2026
483a7cd
fix(jobnik-sdk): sync lockfile after openapi-helpers devDependency re…
CptSchnitz Sep 22, 2026
b292a7e
refactor(jobnik-manager): brand ids at the Prisma query boundary inst…
CptSchnitz Sep 22, 2026
9044e0c
refactor(jobnik-sdk): merge copy:openapi and copy:errors into one cop…
CptSchnitz Sep 22, 2026
acd7990
fix(jobnik-sdk): mark generated openapi-errors import as unresolved i…
CptSchnitz Sep 22, 2026
8ce5f32
refactor(jobnik-openapi): rename parameter in generateTypes transform…
CptSchnitz Sep 22, 2026
1b3a062
chore(global): knip fixes
CptSchnitz Sep 22, 2026
4b8a2dd
fix(jobnik-sdk): restore ignoreUnresolved for generated openapi-error…
CptSchnitz Sep 24, 2026
ac45492
fix(jobnik-manager): restore @prismaClient in knip ignoreDependencies
CptSchnitz Sep 24, 2026
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
17 changes: 17 additions & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Jobnik

A job orchestration system: Jobs are composed of Stages, which are composed of Tasks.

## Language

**Branded ID**:
A nominally-typed wrapper (`JobId`, `StageId`, `TaskId`) around a plain `string` id, generated as part of the OpenAPI types. Its purpose is to catch a job/stage/task id being passed where a different kind of id is expected — a compile-time guard against mixing up ids, not a runtime check that the id exists or is well-formed (that's the OpenAPI request validator's job).
_Avoid_: validated ID, typed ID

**API model**:
A type derived directly from the OpenAPI spec (`components['schemas'][...]`), describing the wire contract of a request or response.
_Avoid_: DTO, schema type

**Persistence model**:
The Prisma-derived shape of a row as stored in the database. Deliberately not required to mirror the API model — the two are distinct representations of the same entity, and Prisma is expected to be replaced later.
_Avoid_: DB model, entity (when meaning the Prisma type specifically)
2 changes: 1 addition & 1 deletion apps/jobnik-manager/eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,5 @@ const customConfig = {
};

export default defineConfig(vitestConfig, tsBaseConfig, customConfig, {
ignores: ['src/db/prisma/generated', 'src/common/generated', 'vitest.config.mts'],
ignores: ['src/db/prisma/generated', 'vitest.config.mts'],
});
2 changes: 0 additions & 2 deletions apps/jobnik-manager/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@
"assets:copy": "copyfiles -f ./config/* ./dist/config && copyfiles ./package.json dist && copyfiles -u 4 \"./src/db/prisma/migrations/**/*\" ./dist/db/prisma/migrations && copyfiles -f ./src/db/prisma/schema.prisma ./dist/db/prisma",
"prisma:copy": "node ./scripts/copyDirectories.mjs ./src/db/prisma/generated ./dist/db/prisma/generated",
"clean": "rimraf dist",
"generate:openapi-errors": "node scripts/generateOpenApiErrors.mts",
"generate:check": "npm run generate:openapi-errors && git diff --exit-code -- src/common/generated/errors.ts",
"type-check": "tsc --noEmit",
"migration:validate": "npx prisma validate --schema ./src/db/prisma/schema.prisma ",
"migration:format": "npx prisma format --check --schema ./src/db/prisma/schema.prisma ",
Expand Down
10 changes: 0 additions & 10 deletions apps/jobnik-manager/scripts/generateOpenApiErrors.mts

This file was deleted.

2 changes: 1 addition & 1 deletion apps/jobnik-manager/src/api/v1/jobs/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import httpStatus from 'http-status-codes';
import { injectable, inject } from 'tsyringe';
import { HttpError } from '@map-colonies/error-express-handler';
import type { TypedRequestHandlers } from 'jobnik-openapi';
import { IllegalJobStatusTransitionError, JobNotInFiniteStateError, JobNotFoundError } from 'jobnik-openapi';
import { SERVICES, successMessages } from '@common/constants';
import { SamePriorityChangeError } from '@src/jobs/models/errors';
import { IllegalJobStatusTransitionError, JobNotInFiniteStateError, JobNotFoundError } from '@src/common/generated/errors';
import { type JobFindCriteriaArg } from '@src/jobs/models/models';
import { JobManager } from '@src/jobs/models/manager';

Expand Down
2 changes: 1 addition & 1 deletion apps/jobnik-manager/src/api/v1/stages/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import httpStatus from 'http-status-codes';
import { injectable, inject } from 'tsyringe';
import { HttpError } from '@map-colonies/error-express-handler';
import type { TypedRequestHandlers } from 'jobnik-openapi';
import { IllegalStageStatusTransitionError, JobInFiniteStateError, JobNotFoundError, StageNotFoundError } from 'jobnik-openapi';
import { SERVICES, successMessages } from '@common/constants';
import { IllegalStageStatusTransitionError, JobInFiniteStateError, JobNotFoundError, StageNotFoundError } from '@src/common/generated/errors';
import { StageManager } from '@src/stages/models/manager';
import type { StageFindCriteriaArg } from '@src/stages/models/models';

Expand Down
4 changes: 2 additions & 2 deletions apps/jobnik-manager/src/api/v1/tasks/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import httpStatus from 'http-status-codes';
import { injectable, inject } from 'tsyringe';
import { HttpError } from '@map-colonies/error-express-handler';
import type { TypedRequestHandlers } from 'jobnik-openapi';
import { SERVICES, successMessages } from '@common/constants';
import {
IllegalJobStatusTransitionError,
IllegalStageStatusTransitionError,
Expand All @@ -14,7 +13,8 @@ import {
StageNotFoundError,
TaskStatusUpdateFailedError,
TaskNotFoundError,
} from '@src/common/generated/errors';
} from 'jobnik-openapi';
import { SERVICES, successMessages } from '@common/constants';
import { TaskManager } from '@src/tasks/models/manager';
import { type TasksFindCriteriaArg } from '@src/tasks/models/models';

Expand Down
19 changes: 10 additions & 9 deletions apps/jobnik-manager/src/jobs/models/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ import type { Tracer } from '@opentelemetry/api';
import { trace } from '@opentelemetry/api';
import { withSpanAsyncV4 } from '@map-colonies/tracing-utils';
import { INFRA_CONVENTIONS } from '@map-colonies/semantic-conventions';
import type { JobId } from 'jobnik-openapi';
import { IllegalJobStatusTransitionError, JobNotInFiniteStateError, JobNotFoundError } from 'jobnik-openapi';
import type { PrismaClient, Priority } from '@prismaClient';
import { Prisma, JobOperationStatus } from '@prismaClient';
import { SERVICES } from '@common/constants';
import { convertArrayPrismaStageToStageResponse } from '@src/stages/models/helper';
import { illegalStatusTransitionErrorMessage, prismaKnownErrors } from '@common/errors';
import { type PrismaTransaction } from '@src/db/types';
import { resolveTraceContext } from '@src/common/utils/tracingHelpers';
import { IllegalJobStatusTransitionError, JobNotInFiniteStateError, JobNotFoundError } from '@src/common/generated/errors';
import { ATTR_MESSAGING_MESSAGE_CONVERSATION_ID } from '@src/common/semconv';
import { paginate } from '@src/common/utils/pagination';
import { errorMessages as jobsErrorMessages, SamePriorityChangeError } from './errors';
Expand Down Expand Up @@ -88,7 +89,7 @@ export class JobManager {
}

@withSpanAsyncV4
public async getJobById(jobId: string, includeStages?: boolean): Promise<JobModel> {
public async getJobById(jobId: JobId, includeStages?: boolean): Promise<JobModel> {
trace.getActiveSpan()?.setAttributes({
[ATTR_MESSAGING_MESSAGE_CONVERSATION_ID]: jobId,
});
Expand All @@ -103,7 +104,7 @@ export class JobManager {
}

@withSpanAsyncV4
public async updateUserMetadata(jobId: string, userMetadata: Record<string, unknown>): Promise<void> {
public async updateUserMetadata(jobId: JobId, userMetadata: Record<string, unknown>): Promise<void> {
trace.getActiveSpan()?.setAttributes({
[ATTR_MESSAGING_MESSAGE_CONVERSATION_ID]: jobId,
});
Expand All @@ -128,7 +129,7 @@ export class JobManager {
}

@withSpanAsyncV4
public async updatePriority(jobId: string, priority: Priority): Promise<void> {
public async updatePriority(jobId: JobId, priority: Priority): Promise<void> {
trace.getActiveSpan()?.setAttributes({
[ATTR_MESSAGING_MESSAGE_CONVERSATION_ID]: jobId,
[INFRA_CONVENTIONS.infra.jobnik.job.priority]: priority,
Expand Down Expand Up @@ -157,7 +158,7 @@ export class JobManager {
}

@withSpanAsyncV4
public async updateStatus(jobId: string, status: JobOperationStatus, tx?: PrismaTransaction): Promise<void> {
public async updateStatus(jobId: JobId, status: JobOperationStatus, tx?: PrismaTransaction): Promise<void> {
trace.getActiveSpan()?.setAttributes({
[ATTR_MESSAGING_MESSAGE_CONVERSATION_ID]: jobId,
[INFRA_CONVENTIONS.infra.jobnik.job.status]: status,
Expand All @@ -173,7 +174,7 @@ export class JobManager {
}

@withSpanAsyncV4
public async deleteJob(jobId: string): Promise<void> {
public async deleteJob(jobId: JobId): Promise<void> {
trace.getActiveSpan()?.setAttributes({
[ATTR_MESSAGING_MESSAGE_CONVERSATION_ID]: jobId,
});
Expand Down Expand Up @@ -207,7 +208,7 @@ export class JobManager {
*/
@withSpanAsyncV4
public async getJobEntityById<IncludeStages extends boolean = false>(
jobId: string,
jobId: JobId,
options: { includeStages?: IncludeStages; tx?: PrismaTransaction } = {}
): Promise<JobPrismaObject<IncludeStages> | null> {
const spanActive = trace.getActiveSpan();
Expand All @@ -229,7 +230,7 @@ export class JobManager {
}

@withSpanAsyncV4
private async executeUpdateStatus(jobId: string, status: JobOperationStatus, tx: PrismaTransaction): Promise<void> {
private async executeUpdateStatus(jobId: JobId, status: JobOperationStatus, tx: PrismaTransaction): Promise<void> {
const job = await this.getJobEntityById(jobId, { tx });

if (!job) {
Expand Down Expand Up @@ -279,6 +280,6 @@ export class JobManager {
stages: Array.isArray(stage) ? convertArrayPrismaStageToStageResponse(stage) : undefined,
};

return Object.assign(rest, transformedFields);
return Object.assign(rest, transformedFields) as unknown as JobModel;
}
}
3 changes: 2 additions & 1 deletion apps/jobnik-manager/src/stages/DAL/stageRepository.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { inject, Lifecycle, scoped } from 'tsyringe';
import { type Logger } from '@map-colonies/js-logger';
import type { StageId } from 'jobnik-openapi';
import { Prisma, PrismaClient } from '@prismaClient';
import { SERVICES } from '@src/common/constants';
import { PrismaTransaction } from '@src/db/types';
Expand All @@ -13,7 +14,7 @@ export class StageRepository {
@inject(SERVICES.PRISMA) private readonly prisma: PrismaClient
) {}

public async updateStageSummary(stageId: string, summaryPayload: UpdateSummaryCount, tx: PrismaTransaction): Promise<StageSummary> {
public async updateStageSummary(stageId: StageId, summaryPayload: UpdateSummaryCount, tx: PrismaTransaction): Promise<StageSummary> {
const addStatus = summaryCountsMapper[summaryPayload.add.status];
const addCount = summaryPayload.add.count;

Expand Down
2 changes: 1 addition & 1 deletion apps/jobnik-manager/src/stages/models/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ function convertPrismaToStageResponse(prismaObjects: StagePrismaObject): StageMo
tasks: Array.isArray(task) ? convertArrayPrismaTaskToTaskResponse(task) : undefined,
tracestate: tracestate ?? undefined,
};
return Object.assign(rest, transformedFields);
return Object.assign(rest, transformedFields) as StageModel;
}

/**
Expand Down
Loading
Loading