@bubltec/mycota-jobs 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/eventbridge.adapter.d.ts +35 -0
- package/dist/eventbridge.adapter.d.ts.map +1 -0
- package/dist/eventbridge.adapter.js +56 -0
- package/dist/eventbridge.adapter.js.map +1 -0
- package/dist/fake.adapter.d.ts +21 -0
- package/dist/fake.adapter.d.ts.map +1 -0
- package/dist/fake.adapter.js +61 -0
- package/dist/fake.adapter.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/jobs.spec.d.ts +2 -0
- package/dist/jobs.spec.d.ts.map +1 -0
- package/dist/jobs.spec.js +60 -0
- package/dist/jobs.spec.js.map +1 -0
- package/dist/ports.d.ts +27 -0
- package/dist/ports.d.ts.map +1 -0
- package/dist/ports.js +2 -0
- package/dist/ports.js.map +1 -0
- package/package.json +30 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Job, JobScheduler, ScheduleJobInput } from './ports.js';
|
|
2
|
+
/**
|
|
3
|
+
* Narrow EventBridge Scheduler surface. The consuming app passes the AWS
|
|
4
|
+
* client (or a test double). Schedules target a queue/Lambda the app owns.
|
|
5
|
+
*/
|
|
6
|
+
export interface SchedulerClient {
|
|
7
|
+
createSchedule(input: {
|
|
8
|
+
name: string;
|
|
9
|
+
runAt: string;
|
|
10
|
+
payload: string;
|
|
11
|
+
}): Promise<void>;
|
|
12
|
+
deleteSchedule(name: string): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
export interface EventBridgeJobSchedulerOptions {
|
|
15
|
+
/** Prefix applied to schedule names (EventBridge: [0-9a-zA-Z-_.]{1,64}). */
|
|
16
|
+
namePrefix?: string;
|
|
17
|
+
}
|
|
18
|
+
declare function scheduleName(prefix: string, id: string): string;
|
|
19
|
+
declare function atExpression(runAt: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* One-time schedules via EventBridge Scheduler `at()`. Does not poll — AWS
|
|
22
|
+
* invokes the target at `runAt`. `get` only sees jobs this process scheduled.
|
|
23
|
+
*/
|
|
24
|
+
export declare class EventBridgeJobScheduler implements JobScheduler {
|
|
25
|
+
private readonly client;
|
|
26
|
+
private readonly options;
|
|
27
|
+
private readonly local;
|
|
28
|
+
private seq;
|
|
29
|
+
constructor(client: SchedulerClient, options?: EventBridgeJobSchedulerOptions);
|
|
30
|
+
schedule(input: ScheduleJobInput): Promise<Job>;
|
|
31
|
+
cancel(id: string): Promise<void>;
|
|
32
|
+
get(id: string): Promise<Job | undefined>;
|
|
33
|
+
}
|
|
34
|
+
export { atExpression, scheduleName };
|
|
35
|
+
//# sourceMappingURL=eventbridge.adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eventbridge.adapter.d.ts","sourceRoot":"","sources":["../src/eventbridge.adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEtE;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,cAAc,CAAC,KAAK,EAAE;QACpB,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,MAAM,CAAC;KACjB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7C;AAED,MAAM,WAAW,8BAA8B;IAC7C,4EAA4E;IAC5E,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,iBAAS,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAGxD;AAED,iBAAS,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAG3C;AAED;;;GAGG;AACH,qBAAa,uBAAwB,YAAW,YAAY;IAKxD,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAL1B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA0B;IAChD,OAAO,CAAC,GAAG,CAAK;gBAGG,MAAM,EAAE,eAAe,EACvB,OAAO,GAAE,8BAAmC;IAGzD,QAAQ,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,GAAG,CAAC;IAqB/C,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOjC,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,GAAG,SAAS,CAAC;CAIhD;AAED,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
function scheduleName(prefix, id) {
|
|
2
|
+
const raw = `${prefix}${id}`.replace(/[^0-9a-zA-Z-_.]/g, '-');
|
|
3
|
+
return raw.slice(0, 64);
|
|
4
|
+
}
|
|
5
|
+
function atExpression(runAt) {
|
|
6
|
+
const iso = new Date(runAt).toISOString().replace(/\.\d{3}Z$/, '');
|
|
7
|
+
return `at(${iso})`;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* One-time schedules via EventBridge Scheduler `at()`. Does not poll — AWS
|
|
11
|
+
* invokes the target at `runAt`. `get` only sees jobs this process scheduled.
|
|
12
|
+
*/
|
|
13
|
+
export class EventBridgeJobScheduler {
|
|
14
|
+
client;
|
|
15
|
+
options;
|
|
16
|
+
local = new Map();
|
|
17
|
+
seq = 0;
|
|
18
|
+
constructor(client, options = {}) {
|
|
19
|
+
this.client = client;
|
|
20
|
+
this.options = options;
|
|
21
|
+
}
|
|
22
|
+
async schedule(input) {
|
|
23
|
+
if (!input.name)
|
|
24
|
+
throw new Error('job name is required');
|
|
25
|
+
this.seq += 1;
|
|
26
|
+
const id = `job_${this.seq}_${Date.now()}`;
|
|
27
|
+
const job = {
|
|
28
|
+
id,
|
|
29
|
+
name: input.name,
|
|
30
|
+
payload: input.payload,
|
|
31
|
+
runAt: input.runAt ?? new Date().toISOString(),
|
|
32
|
+
status: 'scheduled',
|
|
33
|
+
attempts: 0,
|
|
34
|
+
};
|
|
35
|
+
await this.client.createSchedule({
|
|
36
|
+
name: scheduleName(this.options.namePrefix ?? 'mycota-', job.id),
|
|
37
|
+
runAt: atExpression(job.runAt),
|
|
38
|
+
payload: JSON.stringify({ id: job.id, name: job.name, payload: job.payload, runAt: job.runAt }),
|
|
39
|
+
});
|
|
40
|
+
this.local.set(job.id, job);
|
|
41
|
+
return job;
|
|
42
|
+
}
|
|
43
|
+
async cancel(id) {
|
|
44
|
+
const job = this.local.get(id);
|
|
45
|
+
if (!job)
|
|
46
|
+
throw new Error(`job ${id} not found`);
|
|
47
|
+
await this.client.deleteSchedule(scheduleName(this.options.namePrefix ?? 'mycota-', id));
|
|
48
|
+
job.status = 'cancelled';
|
|
49
|
+
}
|
|
50
|
+
async get(id) {
|
|
51
|
+
const job = this.local.get(id);
|
|
52
|
+
return job ? { ...job } : undefined;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export { atExpression, scheduleName };
|
|
56
|
+
//# sourceMappingURL=eventbridge.adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eventbridge.adapter.js","sourceRoot":"","sources":["../src/eventbridge.adapter.ts"],"names":[],"mappings":"AAoBA,SAAS,YAAY,CAAC,MAAc,EAAE,EAAU;IAC9C,MAAM,GAAG,GAAG,GAAG,MAAM,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,kBAAkB,EAAE,GAAG,CAAC,CAAC;IAC9D,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAC1B,CAAC;AAED,SAAS,YAAY,CAAC,KAAa;IACjC,MAAM,GAAG,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;IACnE,OAAO,MAAM,GAAG,GAAG,CAAC;AACtB,CAAC;AAED;;;GAGG;AACH,MAAM,OAAO,uBAAuB;IAKf;IACA;IALF,KAAK,GAAG,IAAI,GAAG,EAAe,CAAC;IACxC,GAAG,GAAG,CAAC,CAAC;IAEhB,YACmB,MAAuB,EACvB,UAA0C,EAAE;QAD5C,WAAM,GAAN,MAAM,CAAiB;QACvB,YAAO,GAAP,OAAO,CAAqC;IAC5D,CAAC;IAEJ,KAAK,CAAC,QAAQ,CAAC,KAAuB;QACpC,IAAI,CAAC,KAAK,CAAC,IAAI;YAAE,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;QACzD,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;QACd,MAAM,EAAE,GAAG,OAAO,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;QAC3C,MAAM,GAAG,GAAQ;YACf,EAAE;YACF,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YAC9C,MAAM,EAAE,WAAW;YACnB,QAAQ,EAAE,CAAC;SACZ,CAAC;QACF,MAAM,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC;YAC/B,IAAI,EAAE,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,IAAI,SAAS,EAAE,GAAG,CAAC,EAAE,CAAC;YAChE,KAAK,EAAE,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC;YAC9B,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC;SAChG,CAAC,CAAC;QACH,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;QAC5B,OAAO,GAAG,CAAC;IACb,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,EAAU;QACrB,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC/B,IAAI,CAAC,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;QACjD,MAAM,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,IAAI,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC;QACzF,GAAG,CAAC,MAAM,GAAG,WAAW,CAAC;IAC3B,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,EAAU;QAClB,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC/B,OAAO,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,GAAG,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;IACtC,CAAC;CACF;AAED,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Job, JobHandler, JobScheduler, ScheduleJobInput } from './ports.js';
|
|
2
|
+
export interface FakeJobSchedulerOptions {
|
|
3
|
+
clock?: () => Date;
|
|
4
|
+
maxAttempts?: number;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* In-memory scheduler. `processDue` is the local stand-in for EventBridge
|
|
8
|
+
* invoking the worker at `runAt`.
|
|
9
|
+
*/
|
|
10
|
+
export declare class FakeJobScheduler implements JobScheduler {
|
|
11
|
+
private readonly options;
|
|
12
|
+
readonly jobs: Map<string, Job>;
|
|
13
|
+
private seq;
|
|
14
|
+
constructor(options?: FakeJobSchedulerOptions);
|
|
15
|
+
schedule(input: ScheduleJobInput): Promise<Job>;
|
|
16
|
+
cancel(id: string): Promise<void>;
|
|
17
|
+
get(id: string): Promise<Job | undefined>;
|
|
18
|
+
processDue(handler: JobHandler, now?: Date): Promise<number>;
|
|
19
|
+
private now;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=fake.adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fake.adapter.d.ts","sourceRoot":"","sources":["../src/fake.adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,UAAU,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAElF,MAAM,WAAW,uBAAuB;IACtC,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;GAGG;AACH,qBAAa,gBAAiB,YAAW,YAAY;IAIvC,OAAO,CAAC,QAAQ,CAAC,OAAO;IAHpC,QAAQ,CAAC,IAAI,mBAA0B;IACvC,OAAO,CAAC,GAAG,CAAK;gBAEa,OAAO,GAAE,uBAA4B;IAE5D,QAAQ,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,GAAG,CAAC;IAe/C,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOjC,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,GAAG,SAAS,CAAC;IAKzC,UAAU,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,OAAa,GAAG,OAAO,CAAC,MAAM,CAAC;IAoBxE,OAAO,CAAC,GAAG;CAGZ"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory scheduler. `processDue` is the local stand-in for EventBridge
|
|
3
|
+
* invoking the worker at `runAt`.
|
|
4
|
+
*/
|
|
5
|
+
export class FakeJobScheduler {
|
|
6
|
+
options;
|
|
7
|
+
jobs = new Map();
|
|
8
|
+
seq = 0;
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
this.options = options;
|
|
11
|
+
}
|
|
12
|
+
async schedule(input) {
|
|
13
|
+
if (!input.name)
|
|
14
|
+
throw new Error('job name is required');
|
|
15
|
+
this.seq += 1;
|
|
16
|
+
const job = {
|
|
17
|
+
id: `job_fake_${this.seq}`,
|
|
18
|
+
name: input.name,
|
|
19
|
+
payload: input.payload,
|
|
20
|
+
runAt: input.runAt ?? this.now().toISOString(),
|
|
21
|
+
status: 'scheduled',
|
|
22
|
+
attempts: 0,
|
|
23
|
+
};
|
|
24
|
+
this.jobs.set(job.id, job);
|
|
25
|
+
return job;
|
|
26
|
+
}
|
|
27
|
+
async cancel(id) {
|
|
28
|
+
const job = this.jobs.get(id);
|
|
29
|
+
if (!job)
|
|
30
|
+
throw new Error(`job ${id} not found`);
|
|
31
|
+
if (job.status === 'succeeded' || job.status === 'cancelled')
|
|
32
|
+
return;
|
|
33
|
+
job.status = 'cancelled';
|
|
34
|
+
}
|
|
35
|
+
async get(id) {
|
|
36
|
+
const job = this.jobs.get(id);
|
|
37
|
+
return job ? { ...job } : undefined;
|
|
38
|
+
}
|
|
39
|
+
async processDue(handler, now = this.now()) {
|
|
40
|
+
const maxAttempts = this.options.maxAttempts ?? 5;
|
|
41
|
+
const due = [...this.jobs.values()].filter((job) => job.status === 'scheduled' && new Date(job.runAt).getTime() <= now.getTime());
|
|
42
|
+
let ran = 0;
|
|
43
|
+
for (const job of due) {
|
|
44
|
+
job.status = 'running';
|
|
45
|
+
job.attempts += 1;
|
|
46
|
+
try {
|
|
47
|
+
await handler({ ...job });
|
|
48
|
+
job.status = 'succeeded';
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
job.status = job.attempts >= maxAttempts ? 'failed' : 'scheduled';
|
|
52
|
+
}
|
|
53
|
+
ran += 1;
|
|
54
|
+
}
|
|
55
|
+
return ran;
|
|
56
|
+
}
|
|
57
|
+
now() {
|
|
58
|
+
return this.options.clock?.() ?? new Date();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=fake.adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fake.adapter.js","sourceRoot":"","sources":["../src/fake.adapter.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type { Job, JobHandler, JobScheduler, JobStatus, ScheduleJobInput } from './ports.js';
|
|
2
|
+
export { FakeJobScheduler, type FakeJobSchedulerOptions } from './fake.adapter.js';
|
|
3
|
+
export { EventBridgeJobScheduler, atExpression, scheduleName, type EventBridgeJobSchedulerOptions, type SchedulerClient, } from './eventbridge.adapter.js';
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,GAAG,EAAE,UAAU,EAAE,YAAY,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC7F,OAAO,EAAE,gBAAgB,EAAE,KAAK,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AACnF,OAAO,EACL,uBAAuB,EACvB,YAAY,EACZ,YAAY,EACZ,KAAK,8BAA8B,EACnC,KAAK,eAAe,GACrB,MAAM,0BAA0B,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAgC,MAAM,mBAAmB,CAAC;AACnF,OAAO,EACL,uBAAuB,EACvB,YAAY,EACZ,YAAY,GAGb,MAAM,0BAA0B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jobs.spec.d.ts","sourceRoot":"","sources":["../src/jobs.spec.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { EventBridgeJobScheduler, atExpression } from './eventbridge.adapter.js';
|
|
3
|
+
import { FakeJobScheduler } from './fake.adapter.js';
|
|
4
|
+
describe('FakeJobScheduler', () => {
|
|
5
|
+
it('runs due jobs and retries failures until maxAttempts', async () => {
|
|
6
|
+
const scheduler = new FakeJobScheduler({
|
|
7
|
+
clock: () => new Date('2026-09-01T12:00:00.000Z'),
|
|
8
|
+
maxAttempts: 2,
|
|
9
|
+
});
|
|
10
|
+
await scheduler.schedule({
|
|
11
|
+
name: 'campaign.publish',
|
|
12
|
+
payload: { beat: 'announce' },
|
|
13
|
+
runAt: '2026-09-01T11:00:00.000Z',
|
|
14
|
+
});
|
|
15
|
+
await scheduler.schedule({
|
|
16
|
+
name: 'campaign.publish',
|
|
17
|
+
payload: { beat: 'day_of' },
|
|
18
|
+
runAt: '2026-09-02T20:00:00.000Z',
|
|
19
|
+
});
|
|
20
|
+
const ran = await scheduler.processDue(async (job) => {
|
|
21
|
+
if (job.payload && typeof job.payload === 'object' && 'beat' in job.payload) {
|
|
22
|
+
throw new Error('boom');
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
expect(ran).toBe(1);
|
|
26
|
+
expect((await scheduler.get('job_fake_1'))?.status).toBe('scheduled');
|
|
27
|
+
await scheduler.processDue(async () => {
|
|
28
|
+
throw new Error('boom');
|
|
29
|
+
});
|
|
30
|
+
expect((await scheduler.get('job_fake_1'))?.status).toBe('failed');
|
|
31
|
+
expect((await scheduler.get('job_fake_2'))?.status).toBe('scheduled');
|
|
32
|
+
});
|
|
33
|
+
it('cancel is a no-op after success', async () => {
|
|
34
|
+
const scheduler = new FakeJobScheduler();
|
|
35
|
+
const job = await scheduler.schedule({ name: 'ping', payload: {} });
|
|
36
|
+
await scheduler.processDue(async () => undefined);
|
|
37
|
+
await scheduler.cancel(job.id);
|
|
38
|
+
expect((await scheduler.get(job.id))?.status).toBe('succeeded');
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
describe('EventBridgeJobScheduler', () => {
|
|
42
|
+
it('creates an at() schedule and cancels by name', async () => {
|
|
43
|
+
const client = {
|
|
44
|
+
createSchedule: vi.fn(async () => undefined),
|
|
45
|
+
deleteSchedule: vi.fn(async () => undefined),
|
|
46
|
+
};
|
|
47
|
+
const scheduler = new EventBridgeJobScheduler(client, { namePrefix: 'sloth-' });
|
|
48
|
+
const job = await scheduler.schedule({
|
|
49
|
+
name: 'campaign.publish',
|
|
50
|
+
payload: { eventId: 'e1' },
|
|
51
|
+
runAt: '2026-09-01T20:00:00.000Z',
|
|
52
|
+
});
|
|
53
|
+
expect(client.createSchedule).toHaveBeenCalledWith(expect.objectContaining({
|
|
54
|
+
runAt: atExpression('2026-09-01T20:00:00.000Z'),
|
|
55
|
+
}));
|
|
56
|
+
await scheduler.cancel(job.id);
|
|
57
|
+
expect(client.deleteSchedule).toHaveBeenCalled();
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
//# sourceMappingURL=jobs.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jobs.spec.js","sourceRoot":"","sources":["../src/jobs.spec.ts"],"names":[],"mappings":"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"}
|
package/dist/ports.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export type JobStatus = 'scheduled' | 'running' | 'succeeded' | 'failed' | 'cancelled';
|
|
2
|
+
export interface ScheduleJobInput {
|
|
3
|
+
name: string;
|
|
4
|
+
payload: unknown;
|
|
5
|
+
/** ISO-8601. Defaults to now — the next `processDue` (fake) or the worker (prod) runs it. */
|
|
6
|
+
runAt?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface Job {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
payload: unknown;
|
|
12
|
+
runAt: string;
|
|
13
|
+
status: JobStatus;
|
|
14
|
+
attempts: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Delayed work: campaign beats, payout retries, token refresh. SQS delay is
|
|
18
|
+
* capped at 15 minutes, so production uses EventBridge Scheduler `at()`, not
|
|
19
|
+
* a delayed SQS send. Local/tests use FakeJobScheduler.processDue.
|
|
20
|
+
*/
|
|
21
|
+
export interface JobScheduler {
|
|
22
|
+
schedule(input: ScheduleJobInput): Promise<Job>;
|
|
23
|
+
cancel(id: string): Promise<void>;
|
|
24
|
+
get(id: string): Promise<Job | undefined>;
|
|
25
|
+
}
|
|
26
|
+
export type JobHandler = (job: Job) => Promise<void>;
|
|
27
|
+
//# sourceMappingURL=ports.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ports.d.ts","sourceRoot":"","sources":["../src/ports.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,GAAG,WAAW,CAAC;AAEvF,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,6FAA6F;IAC7F,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,GAAG;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,SAAS,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAChD,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,GAAG,SAAS,CAAC,CAAC;CAC3C;AAED,MAAM,MAAM,UAAU,GAAG,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC"}
|
package/dist/ports.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ports.js","sourceRoot":"","sources":["../src/ports.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bubltec/mycota-jobs",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc -b",
|
|
21
|
+
"typecheck": "tsc -b --noEmit",
|
|
22
|
+
"lint": "oxlint -c ../../.oxlintrc.json src",
|
|
23
|
+
"test": "vitest run"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/node": "^22.10.0",
|
|
27
|
+
"typescript": "^5.8.0",
|
|
28
|
+
"vitest": "^2.1.0"
|
|
29
|
+
}
|
|
30
|
+
}
|