mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 21:15:32 +09:00
refactor(api): tidy the api and shared packages (#2733)
This commit is contained in:
1 parent
580401d2dc
commit
5afbf67a70
333 files changed
+10180
-7910
No files matched your search
@@ -3,9 +3,14 @@
|
||||
import type {LoggerInterface} from '@fluxer/logger/src/LoggerInterface';
|
||||
import type {WorkerJobOptions, WorkerJobPayload} from '@pkgs/worker/src/contracts/WorkerTypes';
|
||||
|
||||
export interface WorkerTaskAttempt {
|
||||
readonly isLastAttempt: boolean;
|
||||
}
|
||||
|
||||
export interface WorkerTaskHelpers {
|
||||
logger: LoggerInterface;
|
||||
jobId: bigint;
|
||||
attempt?: WorkerTaskAttempt;
|
||||
addJob: <TPayload extends WorkerJobPayload = WorkerJobPayload>(
|
||||
taskType: string,
|
||||
payload: TPayload,
|
||||
|
||||
@@ -24,37 +24,33 @@ export class HttpWorkerQueue implements IQueueProvider {
|
||||
this.tracing = options.tracing;
|
||||
}
|
||||
|
||||
private createTimeoutController(): AbortController {
|
||||
private async withResponse<T>(
|
||||
input: string | URL,
|
||||
init: RequestInit,
|
||||
consume: (response: Response) => Promise<T>,
|
||||
): Promise<T> {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);
|
||||
(
|
||||
controller as {
|
||||
timeoutId?: NodeJS.Timeout;
|
||||
}
|
||||
).timeoutId = timeoutId;
|
||||
return controller;
|
||||
}
|
||||
|
||||
private async fetchWithTimeout(input: string | URL, init?: RequestInit): Promise<Response> {
|
||||
const controller = this.createTimeoutController();
|
||||
try {
|
||||
const response = await fetch(input, {
|
||||
...init,
|
||||
signal: controller.signal,
|
||||
});
|
||||
return response;
|
||||
return await consume(response);
|
||||
} finally {
|
||||
const timeoutId = (
|
||||
controller as {
|
||||
timeoutId?: NodeJS.Timeout;
|
||||
}
|
||||
).timeoutId;
|
||||
if (timeoutId !== undefined) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
clearTimeout(timeoutId);
|
||||
controller.abort();
|
||||
}
|
||||
}
|
||||
|
||||
private async requireSuccess(response: Response, action: string): Promise<void> {
|
||||
if (response.ok) {
|
||||
return;
|
||||
}
|
||||
const text = await response.text();
|
||||
throw new Error(`Failed to ${action}: ${response.status} ${text}`);
|
||||
}
|
||||
|
||||
private async withOptionalSpan<T>(
|
||||
options: {
|
||||
name: string;
|
||||
@@ -100,20 +96,22 @@ export class HttpWorkerQueue implements IQueueProvider {
|
||||
run_at: options?.runAt?.toISOString(),
|
||||
max_attempts: options?.maxAttempts ?? 5,
|
||||
};
|
||||
const response = await this.fetchWithTimeout(`${this.baseUrl}/enqueue`, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
throw new Error(`Failed to enqueue job: ${response.status} ${text}`);
|
||||
}
|
||||
const jobIdResult = (await response.json()) as {
|
||||
job_id: string;
|
||||
};
|
||||
this.setSpanAttributes({'queue.job_id': jobIdResult.job_id});
|
||||
return jobIdResult.job_id;
|
||||
return this.withResponse(
|
||||
`${this.baseUrl}/enqueue`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(body),
|
||||
},
|
||||
async (response) => {
|
||||
await this.requireSuccess(response, 'enqueue job');
|
||||
const jobIdResult = (await response.json()) as {
|
||||
job_id: string;
|
||||
};
|
||||
this.setSpanAttributes({'queue.job_id': jobIdResult.job_id});
|
||||
return jobIdResult.job_id;
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -134,34 +132,32 @@ export class HttpWorkerQueue implements IQueueProvider {
|
||||
url.searchParams.set('task_types', taskTypes.join(','));
|
||||
url.searchParams.set('limit', limit.toString());
|
||||
url.searchParams.set('wait_time_ms', '0');
|
||||
const response = await this.fetchWithTimeout(url.toString(), {method: 'GET'});
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
throw new Error(`Failed to dequeue job: ${response.status} ${text}`);
|
||||
}
|
||||
this.addSpanEvent('dequeue.parse_response');
|
||||
const jobs = (await response.json()) as Array<LeasedQueueJob>;
|
||||
const jobCount = jobs?.length ?? 0;
|
||||
this.setSpanAttributes({
|
||||
'queue.jobs_returned': jobCount,
|
||||
'queue.empty': jobCount === 0,
|
||||
return this.withResponse(url, {method: 'GET'}, async (response) => {
|
||||
await this.requireSuccess(response, 'dequeue job');
|
||||
this.addSpanEvent('dequeue.parse_response');
|
||||
const jobs = (await response.json()) as Array<LeasedQueueJob>;
|
||||
const jobCount = jobs?.length ?? 0;
|
||||
this.setSpanAttributes({
|
||||
'queue.jobs_returned': jobCount,
|
||||
'queue.empty': jobCount === 0,
|
||||
});
|
||||
this.addSpanEvent('dequeue.complete');
|
||||
return jobs ?? [];
|
||||
});
|
||||
this.addSpanEvent('dequeue.complete');
|
||||
return jobs ?? [];
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async upsertCron(id: string, taskType: string, payload: WorkerJobPayload, cronExpression: string): Promise<void> {
|
||||
const response = await this.fetchWithTimeout(`${this.baseUrl}/cron`, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({id, task_type: taskType, payload, cron_expression: cronExpression}),
|
||||
});
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
throw new Error(`Failed to upsert cron job: ${response.status} ${text}`);
|
||||
}
|
||||
await this.withResponse(
|
||||
`${this.baseUrl}/cron`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({id, task_type: taskType, payload, cron_expression: cronExpression}),
|
||||
},
|
||||
(response) => this.requireSuccess(response, 'upsert cron job'),
|
||||
);
|
||||
}
|
||||
|
||||
async complete(receipt: string): Promise<void> {
|
||||
@@ -172,17 +168,16 @@ export class HttpWorkerQueue implements IQueueProvider {
|
||||
'queue.receipt': receipt,
|
||||
},
|
||||
},
|
||||
async () => {
|
||||
const response = await this.fetchWithTimeout(`${this.baseUrl}/ack`, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({receipt}),
|
||||
});
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
throw new Error(`Failed to complete job: ${response.status} ${text}`);
|
||||
}
|
||||
},
|
||||
async () =>
|
||||
this.withResponse(
|
||||
`${this.baseUrl}/ack`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({receipt}),
|
||||
},
|
||||
(response) => this.requireSuccess(response, 'complete job'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -195,48 +190,45 @@ export class HttpWorkerQueue implements IQueueProvider {
|
||||
'queue.error_message': error,
|
||||
},
|
||||
},
|
||||
async () => {
|
||||
const response = await this.fetchWithTimeout(`${this.baseUrl}/nack`, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({receipt, error}),
|
||||
});
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
throw new Error(`Failed to fail job: ${response.status} ${text}`);
|
||||
}
|
||||
},
|
||||
async () =>
|
||||
this.withResponse(
|
||||
`${this.baseUrl}/nack`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({receipt, error}),
|
||||
},
|
||||
(response) => this.requireSuccess(response, 'fail job'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
async cancelJob(jobId: string): Promise<boolean> {
|
||||
const response = await this.fetchWithTimeout(`${this.baseUrl}/job/${jobId}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
if (response.status === 404) {
|
||||
return false;
|
||||
return this.withResponse(`${this.baseUrl}/job/${jobId}`, {method: 'DELETE'}, async (response) => {
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
if (response.status === 404) {
|
||||
return false;
|
||||
}
|
||||
throw new Error(`Failed to cancel job: ${response.status} ${text}`);
|
||||
}
|
||||
throw new Error(`Failed to cancel job: ${response.status} ${text}`);
|
||||
}
|
||||
const result = (await response.json()) as {
|
||||
success: boolean;
|
||||
};
|
||||
return result.success ?? true;
|
||||
const result = (await response.json()) as {
|
||||
success: boolean;
|
||||
};
|
||||
return result.success ?? true;
|
||||
});
|
||||
}
|
||||
|
||||
async retryDeadLetterJob(jobId: string): Promise<boolean> {
|
||||
const response = await this.fetchWithTimeout(`${this.baseUrl}/retry/${jobId}`, {
|
||||
method: 'POST',
|
||||
});
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
if (response.status === 404) {
|
||||
return false;
|
||||
return this.withResponse(`${this.baseUrl}/retry/${jobId}`, {method: 'POST'}, async (response) => {
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
if (response.status === 404) {
|
||||
return false;
|
||||
}
|
||||
throw new Error(`Failed to retry job: ${response.status} ${text}`);
|
||||
}
|
||||
throw new Error(`Failed to retry job: ${response.status} ${text}`);
|
||||
}
|
||||
return true;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user