Skip to content

Commit d2ddc2a

Browse files
committed
add scheduler
1 parent 0508341 commit d2ddc2a

6 files changed

Lines changed: 248 additions & 9 deletions

File tree

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@cubetiq/hookme-sdk",
3-
"version": "0.0.6",
3+
"version": "0.0.7",
44
"description": "A simple way to send webhook request to the server",
55
"main": "dist/index.js",
66
"private": false,

src/hookme.ts

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
import { HookmeClientService } from './service';
1+
import { HookmeClientService, SchedulerClientService } from './service';
22
import { PostWebhookFailedException } from './exceptions';
33
import { Logs } from './logger';
4-
import { HookmeClientOptions, WebhookRequest, WebhookResponse } from './model';
4+
import { HookmeClientOptions, ScheduleJob, ScheduleJobResponse, WebhookRequest, WebhookResponse } from './model';
55
import { AxiosError } from 'axios';
66
import { IStore } from './store';
77
import { generatedID } from './util';
88

99
export class HookmeClient {
10-
static readonly version = '0.0.6';
11-
static readonly versionCode = '6';
10+
static readonly version = '0.0.7';
11+
static readonly versionCode = '7';
1212
static readonly userAgent = `${HookmeClient.name}:sdk-ts/${HookmeClient.version}-${HookmeClient.versionCode}`;
1313

1414
private store?: IStore;
@@ -65,7 +65,7 @@ export class HookmeClient {
6565

6666
private startProcessEmitQueue(seconds: number): void {
6767
Logs.d(`[startProcessEmitQueue] processing emit queue with interval: ${seconds} seconds`);
68-
68+
6969
// create a new promise to avoid blocking the main thread
7070
new Promise(async (resolve) => {
7171
while (true) {
@@ -269,6 +269,35 @@ export class HookmeClient {
269269
}
270270
}
271271

272+
// create a scheduled webhook endpoint (to be trigger in the future) and backed by hookme scheduler
273+
async schedule(key: string, job: ScheduleJob): Promise<ScheduleJobResponse> {
274+
Logs.d(`[schedule] scheduling job with key: ${key}`);
275+
276+
const resp = await SchedulerClientService.schedule(
277+
this.options.url!,
278+
this.options.tenantId ? this.options.tenantId : 'default',
279+
this.options.apiKey ? this.options.apiKey : '',
280+
key,
281+
job
282+
);
283+
284+
Logs.d(`[schedule] scheduled job response: ${JSON.stringify(resp.data)}`);
285+
return ScheduleJobResponse.fromJson(resp.data);
286+
}
287+
288+
async unschedule(key: string): Promise<void> {
289+
Logs.d(`[unschedule] unscheduling job with key: ${key}`);
290+
291+
const resp = await SchedulerClientService.unschedule(
292+
this.options.url!,
293+
this.options.tenantId ? this.options.tenantId : 'default',
294+
this.options.apiKey ? this.options.apiKey : '',
295+
key,
296+
);
297+
298+
Logs.d(`[unschedule] unscheduled job response: ${JSON.stringify(resp.data)}`);
299+
}
300+
272301
getVersionInfo(): string {
273302
return HookmeClient.userAgent;
274303
}

src/model.ts

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,4 +149,113 @@ export class WebhookResponse {
149149
static fromJson(json: any): WebhookResponse {
150150
return new WebhookResponse(json.id, json.status, new Date(json.created_at), json.updated_at ? new Date(json.updated_at) : null, json.error);
151151
}
152+
}
153+
154+
export type ScheduleJobType = 'cron' | 'interval';
155+
156+
// This cron expression is with seconds (e.g. '*/5 * * * * *' for every 5 seconds)
157+
export const CronExpression = {
158+
Every5Seconds: '*/5 * * * * *',
159+
Every10Seconds: '*/10 * * * * *',
160+
Every30Seconds: '*/30 * * * * *',
161+
Every1Minute: '0 * * * * *',
162+
Every5Minutes: '*/5 * * * * *',
163+
Every10Minutes: '*/10 * * * * *',
164+
Every30Minutes: '*/30 * * * * *',
165+
Every1Hour: '0 0 * * * *',
166+
Every6Hours: '0 0 */6 * * *',
167+
Every12Hours: '0 0 */12 * * *',
168+
}
169+
170+
export class ScheduleJob {
171+
webhook_url: string;
172+
webhook_data: any;
173+
type: ScheduleJobType;
174+
// cron e.g. '*/5 * * * * *' for every 5 seconds or '0 0 12 * * *' for every day at 12:00 PM
175+
// interval e.g. '5s' for every 5 seconds or '1h' for every 1 hour (unit: s, m, h, d, w)
176+
schedule: string;
177+
tz?: string;
178+
179+
constructor(webhook_url: string, webhook_data: any, type: ScheduleJobType, schedule: string, tz?: string) {
180+
this.webhook_url = webhook_url;
181+
this.webhook_data = webhook_data;
182+
this.type = type;
183+
this.schedule = schedule;
184+
this.tz = tz;
185+
}
186+
187+
static builder(): ScheduleJobBuilder {
188+
return new ScheduleJobBuilder();
189+
}
190+
}
191+
192+
class ScheduleJobBuilder {
193+
private _webhook_url: string;
194+
private _webhook_data: any;
195+
private _type: ScheduleJobType;
196+
private _schedule: string;
197+
private _tz?: string;
198+
199+
constructor() {
200+
this._webhook_url = '';
201+
this._webhook_data = undefined;
202+
this._type = 'cron';
203+
this._schedule = '';
204+
this._tz = undefined;
205+
}
206+
207+
webhook_url(webhook_url: string): ScheduleJobBuilder {
208+
this._webhook_url = webhook_url;
209+
return this;
210+
}
211+
212+
webhook_data(webhook_data: any): ScheduleJobBuilder {
213+
this._webhook_data = webhook_data;
214+
return this;
215+
}
216+
217+
type(type: ScheduleJobType): ScheduleJobBuilder {
218+
this._type = type;
219+
return this;
220+
}
221+
222+
schedule(schedule: string): ScheduleJobBuilder {
223+
this._schedule = schedule;
224+
return this;
225+
}
226+
227+
tz(tz: string): ScheduleJobBuilder {
228+
this._tz = tz;
229+
return this;
230+
}
231+
232+
build(): ScheduleJob {
233+
return new ScheduleJob(this._webhook_url, this._webhook_data, this._type, this._schedule, this._tz);
234+
}
235+
}
236+
237+
export class ScheduleJobResponse {
238+
key: string;
239+
job_id: string;
240+
job_status: number; // 100: not started, 200: scheduled, 300: running, 400: failed, 500: terminated, 600: completed
241+
type: ScheduleJobType;
242+
next_run?: Date;
243+
last_run?: Date;
244+
created_at?: Date;
245+
updated_at?: Date;
246+
247+
constructor(key: string, job_id: string, job_status: number, type: ScheduleJobType, next_run?: Date, last_run?: Date, created_at?: Date, updated_at?: Date) {
248+
this.key = key;
249+
this.job_id = job_id;
250+
this.job_status = job_status;
251+
this.type = type;
252+
this.next_run = next_run;
253+
this.last_run = last_run;
254+
this.created_at = created_at;
255+
this.updated_at = updated_at;
256+
}
257+
258+
static fromJson(json: any): ScheduleJobResponse {
259+
return new ScheduleJobResponse(json.key, json.job_id, json.job_status, json.type, json.next_run ? new Date(json.next_run) : undefined, json.last_run ? new Date(json.last_run) : undefined, new Date(json.created_at), json.updated_at ? new Date(json.updated_at) : undefined);
260+
}
152261
}

src/service.ts

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import axios, { AxiosResponse } from 'axios';
22
import { HookmeClient } from './hookme';
3-
import { WebhookRequest } from './model';
3+
import { ScheduleJob, WebhookRequest } from './model';
44

55
export class HookmeClientService {
66
static async post(
@@ -32,3 +32,66 @@ export class HookmeClientService {
3232
return response;
3333
}
3434
}
35+
36+
export class SchedulerClientService {
37+
static async schedule(
38+
url: string,
39+
tenantId: string,
40+
apiKey: string,
41+
key: string,
42+
job: ScheduleJob,
43+
): Promise<AxiosResponse> {
44+
45+
if (!key) {
46+
throw new Error('key is required');
47+
}
48+
49+
if (!url) {
50+
throw new Error('url is required');
51+
}
52+
53+
if (!tenantId) {
54+
throw new Error('tenantId is required');
55+
}
56+
57+
const response = await axios.post(`${url}/api/v1/${tenantId}/scheduler/${key}`, job, {
58+
headers: {
59+
'User-Agent': HookmeClient.userAgent,
60+
'x-api-key': apiKey,
61+
'x-request-id': new Date().getTime().toString(),
62+
},
63+
});
64+
65+
return response;
66+
}
67+
68+
static async unschedule(
69+
url: string,
70+
tenantId: string,
71+
apiKey: string,
72+
key: string,
73+
): Promise<AxiosResponse> {
74+
75+
if (!key) {
76+
throw new Error('key is required');
77+
}
78+
79+
if (!url) {
80+
throw new Error('url is required');
81+
}
82+
83+
if (!tenantId) {
84+
throw new Error('tenantId is required');
85+
}
86+
87+
const response = await axios.delete(`${url}/api/v1/${tenantId}/scheduler/${key}`, {
88+
headers: {
89+
'User-Agent': HookmeClient.userAgent,
90+
'x-api-key': apiKey,
91+
'x-request-id': new Date().getTime().toString(),
92+
},
93+
});
94+
95+
return response;
96+
}
97+
}

tests/scheduler.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { CronExpression, HookmeClient, ScheduleJob } from '../src/index';
2+
3+
const sdk = HookmeClient.local();
4+
5+
test('Hookme client sdk should be defined', () => {
6+
expect(sdk).toBeDefined();
7+
});
8+
9+
test('Hookme client sdk should be able to schedule a job', async () => {
10+
const job = ScheduleJob.builder()
11+
.webhook_url("https://01a99e31-5c70-40b6-bf68-b7349ade168d-webhook-lt.ctdn.net")
12+
.webhook_data({
13+
name: "Sambo Chea"
14+
})
15+
.type("cron")
16+
.schedule(CronExpression.Every10Seconds)
17+
.tz("Asia/Phnom_Penh")
18+
.build();
19+
20+
const response = await sdk.schedule("mywebhook-job-1", job);
21+
console.log("Request: ", job);
22+
console.log("Response: ", response);
23+
24+
expect(job.webhook_url).toBeDefined();
25+
expect(job.webhook_data).not.toBeNull();
26+
27+
expect(response).toBeDefined();
28+
expect(response).not.toBeNull();
29+
expect(response).not.toBe('');
30+
expect(response?.key).toBeDefined();
31+
expect(response?.key).not.toBeNull();
32+
expect(response?.key).not.toBe('');
33+
expect(response?.job_status).toBeDefined();
34+
expect(response?.job_status).not.toBeNull();
35+
expect(response?.created_at).toBeDefined();
36+
expect(response?.created_at).not.toBeNull();
37+
expect(response?.created_at).not.toBe('');
38+
});

0 commit comments

Comments
 (0)