Skip to content

Commit

Permalink
fix: use id type directly for job class generics
Browse files Browse the repository at this point in the history
  • Loading branch information
yujiosaka committed Oct 25, 2023
1 parent c841069 commit 00ec519
Show file tree
Hide file tree
Showing 9 changed files with 45 additions and 56 deletions.
21 changes: 17 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import type { Duration } from "date-fns";
import type { Types } from "mongoose";
import type Job from "./job";
import type { JobLockId } from "./job-lock";
import JobRunner from "./job-runner";
import type BaseJobStore from "./job-store";

/**
* @public
*/
export type { default as BaseJobLock, JobLockId } from "./job-lock";
export type { default as BaseJobLock } from "./job-lock";

/**
* @public
Expand Down Expand Up @@ -69,6 +69,19 @@ export enum Source {
Postgres = "Postgres",
}

/**
* @public
*/
export type JobLockId<S extends Source> = S extends Source.Mongodb
? Types.ObjectId
: S extends Source.Redis
? string
: S extends Source.Mysql
? string
: S extends Source.Postgres
? string
: never;

/**
* @public
*/
Expand Down Expand Up @@ -104,7 +117,7 @@ export default class Cronyx<S extends Source> {
this.#timezone = options.timezone;
}

async requestJobExec(options: RequestJobOptions, task: (job: Job<S>) => Promise<void>): Promise<void> {
async requestJobExec(options: RequestJobOptions, task: (job: Job<JobLockId<S>>) => Promise<void>): Promise<void> {
const jobRunner = new JobRunner(this.#jobStore, options.jobName, options.jobInterval, {
timezone: this.#timezone,
requiredJobNames: options.requiredJobNames,
Expand All @@ -116,7 +129,7 @@ export default class Cronyx<S extends Source> {
return await jobRunner.requestJobExec(task);
}

async requestJobStart(options: RequestJobOptions): Promise<Job<S> | null> {
async requestJobStart(options: RequestJobOptions): Promise<Job<JobLockId<S>> | null> {
const jobRunner = new JobRunner(this.#jobStore, options.jobName, options.jobInterval, {
timezone: this.#timezone,
requiredJobNames: options.requiredJobNames,
Expand Down
20 changes: 2 additions & 18 deletions src/job-lock/index.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,8 @@
import type { Types } from "mongoose";
import type { Source } from "..";

/**
* @public
*/
export type JobLockId<S extends Source> = S extends Source.Mongodb
? Types.ObjectId
: S extends Source.Redis
? string
: S extends Source.Mysql
? string
: S extends Source.Postgres
? string
: never;

/**
* @public
*/
export default interface BaseJobLock<T> {
_id: T | null;
export default interface BaseJobLock<I> {
_id: I | null;
jobName: string;
jobInterval: number;
jobIntervalEndedAt: Date;
Expand Down
20 changes: 9 additions & 11 deletions src/job-runner.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import type { Duration } from "date-fns";
import { differenceInMilliseconds } from "date-fns";
import type { Source } from ".";
import { CronyxError } from "./error";
import Job from "./job";
import type BaseJobLock from "./job-lock";
import type { JobLockId } from "./job-lock";
import MockJobLock from "./job-lock/mock";
import type BaseJobStore from "./job-store";
import { addInterval, getLastDeactivatedJobIntervalEndedAt, log, subInterval } from "./util";
Expand All @@ -21,8 +19,8 @@ type JobRunnerOptions = {
/**
* @internal
*/
export default class JobRunner<S extends Source> {
#jobStore: BaseJobStore<JobLockId<S>>;
export default class JobRunner<I> {
#jobStore: BaseJobStore<I>;
#timezone: string;
#jobName: string;
#jobInterval: Duration | string | number;
Expand All @@ -33,7 +31,7 @@ export default class JobRunner<S extends Source> {
#jobIntervalStartedAt: Date | undefined;

constructor(
jobStore: BaseJobStore<JobLockId<S>>,
jobStore: BaseJobStore<I>,
jobName: string,
jobInterval: Duration | string | number,
options?: JobRunnerOptions,
Expand All @@ -49,7 +47,7 @@ export default class JobRunner<S extends Source> {
this.#jobIntervalStartedAt = options?.jobIntervalStartedAt;
}

async requestJobExec(task: (job: Job<S>) => Promise<void>): Promise<void> {
async requestJobExec(task: (job: Job<I>) => Promise<void>): Promise<void> {
const job = await this.requestJobStart();
if (!job) return;

Expand All @@ -62,7 +60,7 @@ export default class JobRunner<S extends Source> {
}
}

async requestJobStart(): Promise<Job<S> | null> {
async requestJobStart(): Promise<Job<I> | null> {
if (this.#jobIntervalStartedAt) {
if (!this.#noLock) throw new CronyxError("Should enable `noLock` when `jobIntervalStartedAt` is passed");

Expand Down Expand Up @@ -99,7 +97,7 @@ export default class JobRunner<S extends Source> {
return new Job(this.#jobStore, jobLock);
}

let jobLock: BaseJobLock<JobLockId<S>> | null;
let jobLock: BaseJobLock<I> | null;
try {
jobLock = await this.#jobStore.activateJobLock(this.#jobName, jobInterval, jobIntervalEndedAt, retryIntervalStartedAt);
} catch (error) {
Expand All @@ -114,8 +112,8 @@ export default class JobRunner<S extends Source> {
return new Job(this.#jobStore, jobLock);
}

async #ensureLastJobLock(requestedAt: Date): Promise<BaseJobLock<JobLockId<S>>> {
let lastJobLock: BaseJobLock<JobLockId<S>> | null;
async #ensureLastJobLock(requestedAt: Date): Promise<BaseJobLock<I>> {
let lastJobLock: BaseJobLock<I> | null;
try {
lastJobLock = await this.#jobStore.fetchLastJobLock(this.#jobName);
} catch (error) {
Expand All @@ -135,7 +133,7 @@ export default class JobRunner<S extends Source> {

async #areRequiredJobsFulfilled(jobIntervalEndedAt: Date): Promise<boolean> {
for (const requiredJobName of this.#requiredJobNames) {
let requiredJobLock: BaseJobLock<JobLockId<S>> | null;
let requiredJobLock: BaseJobLock<I> | null;

try {
requiredJobLock = await this.#jobStore.fetchLastJobLock(requiredJobName);
Expand Down
10 changes: 5 additions & 5 deletions src/job-store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ import type BaseJobLock from "../job-lock";
/**
* @public
*/
export default interface BaseJobStore<T> {
export default interface BaseJobStore<I> {
close(): Promise<void>;
fetchLastJobLock(jobName: string): Promise<BaseJobLock<T> | null>;
fetchLastJobLock(jobName: string): Promise<BaseJobLock<I> | null>;
activateJobLock(
jobName: string,
jobInterval: number,
jobIntervalEndedAt: Date,
retryIntervalStartedAt: Date,
): Promise<BaseJobLock<T> | null>;
deactivateJobLock(jobName: string, jobId: T): Promise<BaseJobLock<T>>;
removeJobLock(jobName: string, jobId: T): Promise<void>;
): Promise<BaseJobLock<I> | null>;
deactivateJobLock(jobName: string, jobId: I): Promise<BaseJobLock<I>>;
removeJobLock(jobName: string, jobId: I): Promise<void>;
}
12 changes: 5 additions & 7 deletions src/job.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,28 @@
import { subMilliseconds } from "date-fns";
import type { Source } from ".";
import { CronyxError } from "./error";
import type BaseJobLock from "./job-lock";
import type { JobLockId } from "./job-lock";
import type BaseJobStore from "./job-store";
import { log } from "./util";

/**
* @public
*/
export default class Job<S extends Source> {
export default class Job<I> {
#jobName: string;
#jobStore: BaseJobStore<JobLockId<S>>;
#jobLock: BaseJobLock<JobLockId<S>> | null;
#jobStore: BaseJobStore<I>;
#jobLock: BaseJobLock<I> | null;
#pendingPromise: Promise<void> | null = null;

/**
* @internal
*/
constructor(jobStore: BaseJobStore<JobLockId<S>>, jobLock: BaseJobLock<JobLockId<S>>) {
constructor(jobStore: BaseJobStore<I>, jobLock: BaseJobLock<I>) {
this.#jobName = jobLock.jobName;
this.#jobStore = jobStore;
this.#jobLock = jobLock;
}

get id(): JobLockId<S> | null {
get id(): I | null {
if (!this.#jobLock || !this.#jobLock.isActive) throw new CronyxError(`Job is not active for ${this.#jobName}`);

return this.#jobLock._id;
Expand Down
2 changes: 1 addition & 1 deletion src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export function subInterval(date: Date, interval: Duration | string | number, ti
/**
* @internal
*/
export function getLastDeactivatedJobIntervalEndedAt<T>(lastJobLock: BaseJobLock<T>) {
export function getLastDeactivatedJobIntervalEndedAt<I>(lastJobLock: BaseJobLock<I>) {
return lastJobLock.isActive
? subMilliseconds(lastJobLock.jobIntervalEndedAt, lastJobLock.jobInterval)
: lastJobLock.jobIntervalEndedAt;
Expand Down
3 changes: 1 addition & 2 deletions test/job-runner.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { afterEach, beforeEach, describe, expect, Mock, mock, spyOn, test } from "bun:test";
import { addMilliseconds, subMilliseconds } from "date-fns";
import type { JobLockId, Source } from "../src";
import Job from "../src/job";
import RedisJobLock from "../src/job-lock/redis";
import JobRunner from "../src/job-runner";
Expand Down Expand Up @@ -29,7 +28,7 @@ describe("JobRunner", () => {
const unfulfilledJobLock = { ...fulfilledJobLock, jobName: "unfilfilledJobName", jobIntervalEndedAt };
const fulfilledActiveJobLock = { ...fulfilledJobLock, jobName: "fulfilledActiveJobName", isActive: true };

let jobStore: BaseJobStore<JobLockId<Source>>;
let jobStore: BaseJobStore<string>;
let failureTask: Mock<() => Promise<void>>;
let successTask: Mock<() => Promise<void>>;
let finish: Mock<() => Promise<void>>;
Expand Down
8 changes: 3 additions & 5 deletions test/job-store/shared.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
import { afterEach, beforeEach, describe, expect, setSystemTime, test } from "bun:test";
import { addHours, subMilliseconds } from "date-fns";
import type { Source } from "../../src";
import type BaseJobLock from "../../src/job-lock";
import type { JobLockId } from "../../src/job-lock";
import type BaseJobStore from "../../src/job-store";

export function testBehavesLikeJobStore<S extends Source>(getJobStore: () => BaseJobStore<JobLockId<S>>) {
export function testBehavesLikeJobStore<I>(getJobStore: () => BaseJobStore<I>) {
const jobName = "jobName";
const jobIntervalStartedAt = new Date();
const jobIntervalEndedAt = addHours(jobIntervalStartedAt, 1);
const jobInterval = 1000 * 60 * 60; // 1 hour
const retryInterval = 1000 * 60 * 60 * 2; // 2 hours

let jobStore: BaseJobStore<JobLockId<S>>;
let jobStore: BaseJobStore<I>;
let retryIntervalStartedAt: Date;

beforeEach(() => {
Expand All @@ -31,7 +29,7 @@ export function testBehavesLikeJobStore<S extends Source>(getJobStore: () => Bas
});

describe("after activating a job lock", () => {
let jobLock: BaseJobLock<JobLockId<S>>;
let jobLock: BaseJobLock<I>;

beforeEach(async () => {
jobLock = (await jobStore.activateJobLock(jobName, jobInterval, jobIntervalEndedAt, retryIntervalStartedAt))!;
Expand Down
5 changes: 2 additions & 3 deletions test/job.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { beforeEach, describe, expect, mock, test } from "bun:test";
import { addMilliseconds, subMilliseconds } from "date-fns";
import type { JobLockId, Source } from "../src";
import Job from "../src/job";
import RedisJobLock from "../src/job-lock/redis";
import type BaseJobStore from "../src/job-store";
Expand All @@ -20,8 +19,8 @@ describe.each([[false], [true]])("Job", (noLock) => {
const activatedJobLock = { ...lastJobLock, jobIntervalEndedAt, isActive: true, _id: noLock ? null : lastJobLock._id };
const deactivatedJobLock = { ...activatedJobLock, isActive: false, updatedAt: addMilliseconds(now, 1) };

let jobStore: BaseJobStore<JobLockId<Source>>;
let job: Job<Source>;
let jobStore: BaseJobStore<string>;
let job: Job<string>;

beforeEach(() => {
jobStore = {
Expand Down

0 comments on commit 00ec519

Please sign in to comment.