Skip to content

Commit 790b7bd

Browse files
committed
[DE-339] Implement async jobs management
1 parent d50cee3 commit 790b7bd

File tree

3 files changed

+213
-0
lines changed

3 files changed

+213
-0
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ This driver uses semantic versioning:
2222

2323
- Implemented logging API (DE-144, DE-145, DE-146, DE-147)
2424

25+
- Implemented async jobs management (DE-339)
26+
2527
## [8.4.1] - 2023-09-15
2628

2729
### Fixed

src/database.ts

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import {
4444
Graph,
4545
GraphInfo,
4646
} from "./graph";
47+
import { Job } from "./job";
4748
import { Blob } from "./lib/blob";
4849
import { DATABASE_NOT_FOUND } from "./lib/codes";
4950
import { toForm } from "./lib/multipart";
@@ -5906,4 +5907,96 @@ export class Database {
59065907
});
59075908
}
59085909
//#endregion
5910+
//#region async jobs
5911+
/**
5912+
* Returns a {@link job.Job} instance for the given `jobId`.
5913+
*
5914+
* @param jobId - ID of the async job.
5915+
*
5916+
* @example
5917+
* ```js
5918+
* const db = new Database();
5919+
* const job = db.job("12345");
5920+
* ```
5921+
*/
5922+
job(jobId: string): Job {
5923+
return new Job(this, jobId);
5924+
}
5925+
5926+
/**
5927+
* Returns a list of the IDs of all currently pending async jobs.
5928+
*
5929+
* @example
5930+
* ```js
5931+
* const db = new Database();
5932+
* const pendingJobs = await db.listPendingJobs();
5933+
* console.log(pendingJobs); // e.g. ["12345", "67890"]
5934+
* ```
5935+
*/
5936+
listPendingJobs(): Promise<string[]> {
5937+
return this.request(
5938+
{
5939+
path: "/_api/job/pending",
5940+
},
5941+
(res) => res.body
5942+
);
5943+
}
5944+
5945+
/**
5946+
* Returns a list of the IDs of all currently available completed async jobs.
5947+
*
5948+
* @example
5949+
* ```js
5950+
* const db = new Database();
5951+
* const completedJobs = await db.listCompletedJobs();
5952+
* console.log(completedJobs); // e.g. ["12345", "67890"]
5953+
* ```
5954+
*/
5955+
listCompletedJobs(): Promise<string[]> {
5956+
return this.request(
5957+
{
5958+
path: "/_api/job/done",
5959+
},
5960+
(res) => res.body
5961+
);
5962+
}
5963+
5964+
/**
5965+
* Deletes the results of all completed async jobs created before the given
5966+
* threshold.
5967+
*
5968+
* @param threshold - The expiration timestamp in milliseconds.
5969+
*
5970+
* @example
5971+
* ```js
5972+
* const db = new Database();
5973+
* const ONE_WEEK = 7 * 24 * 60 * 60 * 1000;
5974+
* await db.deleteExpiredJobResults(Date.now() - ONE_WEEK);
5975+
* // all job results older than a week have been deleted
5976+
* ```
5977+
*/
5978+
deleteExpiredJobResults(threshold: number): Promise<void> {
5979+
return this.request(
5980+
{
5981+
method: "DELETE",
5982+
path: `/_api/job/expired`,
5983+
qs: { stamp: threshold / 1000 },
5984+
},
5985+
() => undefined
5986+
);
5987+
}
5988+
5989+
/**
5990+
* Deletes the results of all completed async jobs.
5991+
*/
5992+
deleteAllJobResults(): Promise<void> {
5993+
return this.request(
5994+
{
5995+
method: "DELETE",
5996+
path: `/_api/job/all`,
5997+
},
5998+
() => undefined
5999+
);
6000+
}
6001+
//#endregion
59096002
}

src/job.ts

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { Database } from "./database";
2+
3+
/**
4+
* Represents an async job in a {@link database.Database}.
5+
*/
6+
export class Job<T = any> {
7+
protected _id: string;
8+
protected _db: Database;
9+
protected _loaded: boolean = false;
10+
protected _result: T | undefined;
11+
12+
/**
13+
* @internal
14+
*/
15+
constructor(db: Database, id: string) {
16+
this._db = db;
17+
this._id = id;
18+
}
19+
20+
/**
21+
* Whether the job's results have been loaded. If set to `true`, the job's
22+
* result can be accessed from {@link Job.result}.
23+
*/
24+
get isLoaded(): boolean {
25+
return this._loaded;
26+
}
27+
28+
/**
29+
* The job's result if it has been loaded or `undefined` otherwise.
30+
*/
31+
get result(): T | undefined {
32+
return this._result;
33+
}
34+
35+
/**
36+
* Loads the job's result from the database if it is not already loaded.
37+
*
38+
* @example
39+
* ```js
40+
* // poll for the job to complete
41+
* while (!job.isLoaded) {
42+
* await timeout(1000);
43+
* const result = await job.load();
44+
* console.log(result);
45+
* }
46+
* // job result is now loaded and can also be accessed from job.result
47+
* console.log(job.result);
48+
* ```
49+
*/
50+
async load(): Promise<T | undefined> {
51+
if (!this.isLoaded) {
52+
const res = await this._db.request(
53+
{
54+
method: "PUT",
55+
path: `/_api/job/${this._id}`,
56+
},
57+
false
58+
);
59+
if (res.statusCode !== 204) {
60+
this._loaded = true;
61+
this._result = res.body;
62+
}
63+
}
64+
return this._result;
65+
}
66+
67+
/**
68+
* Cancels the job if it is still running. Note that it may take some time to
69+
* actually cancel the job.
70+
*/
71+
cancel(): Promise<void> {
72+
return this._db.request(
73+
{
74+
method: "PUT",
75+
path: `/_api/job/${this._id}/cancel`,
76+
},
77+
() => undefined
78+
);
79+
}
80+
81+
/**
82+
* Deletes the result if it has not already been retrieved or deleted.
83+
*/
84+
deleteResult(): Promise<void> {
85+
return this._db.request(
86+
{
87+
method: "DELETE",
88+
path: `/_api/job/${this._id}`,
89+
},
90+
() => undefined
91+
);
92+
}
93+
94+
/**
95+
* Fetches the job's completion state.
96+
*
97+
* Returns `true` if the job has completed, `false` otherwise.
98+
*
99+
* @example
100+
* ```js
101+
* // poll for the job to complete
102+
* while (!(await job.getCompleted())) {
103+
* await timeout(1000);
104+
* }
105+
* // job result is now available and can be loaded
106+
* await job.load();
107+
* console.log(job.result);
108+
* ```
109+
*/
110+
getCompleted(): Promise<boolean> {
111+
return this._db.request(
112+
{
113+
path: `/_api/job/${this._id}`,
114+
},
115+
(res) => res.statusCode !== 204
116+
);
117+
}
118+
}

0 commit comments

Comments
 (0)