Skip to content

Commit df25ee6

Browse files
Merge pull request #85 from oliver-oloughlin/patch/delete-undelivered
added missing deleteUndelivered method + fixed cron job timing
2 parents b514b23 + 3d9e2ee commit df25ee6

File tree

6 files changed

+140
-52
lines changed

6 files changed

+140
-52
lines changed

README.md

+22-7
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ like atomic operations and queue listeners.
5555
- [enqueue()](#enqueue-1)
5656
- [listenQueue()](#listenqueue-1)
5757
- [findUndelivered()](#findundelivered-1)
58+
- [deleteUndelivered()](#deleteundelivered)
5859
- [cron()](#cron)
5960
- [atomic()](#atomic)
6061
- [Atomic Operations](#atomic-operations)
@@ -653,23 +654,37 @@ const doc2 = await db.findUndelivered("undelivered_id", {
653654
})
654655
```
655656

657+
### deleteUndelivered()
658+
659+
Delete an undelivered document entry by id.
660+
661+
```ts
662+
await db.deleteUndelivered("id")
663+
```
664+
656665
### cron()
657666

658667
Create a cron job that will run on interval, either indefinitely or until an
659-
exit condition is met. If no interval is set, the next job will run immediately
660-
after the previous has finished. Like with queue listeners, there can be
661-
multiple cron jobs defined.
668+
exit condition is met. Interval defaults to 1 second if not set. Like with queue
669+
listeners, there can be multiple cron jobs defined.
662670

663671
```ts
664-
// Will repeat indeefinitely without delay
672+
// Will repeat indefinitely with 1 second interval
665673
db.cron(() => console.log("Hello World!"))
666674

667675
// First job starts with a 10 second delay, after that there is a 5 second delay between jobs
668-
// Will terminate after the 10th run (count starts at 0), or if the job returns n < 0.25
669-
db.cron(() => Math.random(), {
676+
db.cron(() => console.log("I terminate after running 10 times"), {
677+
// Delay before the first job is invoked
670678
startDelay: 10_000,
679+
680+
// Fixed interval
671681
interval: 5_000,
672-
exit: ({ count, result }) => count >= 9 || result < 0.25,
682+
683+
// If this is set it will override the fixed interval
684+
setInterval: ({ count }) => count * 500
685+
686+
// Count starts at 0 and is given before the current job is run
687+
exit: ({ count }) => count === 10,
673688
})
674689
```
675690

src/collection.ts

+15
Original file line numberDiff line numberDiff line change
@@ -643,6 +643,21 @@ export class Collection<
643643
})
644644
}
645645

646+
/**
647+
* Delete an undelivered document entry by id from the collection queue.
648+
*
649+
* @example
650+
* ```ts
651+
* db.users.deleteUndelivered("id")
652+
* ```
653+
*
654+
* @param id - Id of undelivered document.
655+
*/
656+
async deleteUndelivered(id: KvId) {
657+
const key = extendKey(this._keys.baseKey, UNDELIVERED_KEY_PREFIX, id)
658+
await this.kv.delete(key)
659+
}
660+
646661
/** PROTECTED METHODS */
647662

648663
/**

src/constants.ts

+3
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,6 @@ export const ATOMIC_OPERATION_MUTATION_LIMIT = 20
1717
export const GET_MANY_KEY_LIMIT = 10
1818

1919
export const LARGE_COLLECTION_STRING_LIMIT = 25_000
20+
21+
// Time constants
22+
export const DEFAULT_CRON_INTERVAL = 1_000

src/kvdex.ts

+66-25
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,11 @@ import {
2626
prepareEnqueue,
2727
} from "./utils.ts"
2828
import { AtomicBuilder } from "./atomic_builder.ts"
29-
import { KVDEX_KEY_PREFIX, UNDELIVERED_KEY_PREFIX } from "./constants.ts"
29+
import {
30+
DEFAULT_CRON_INTERVAL,
31+
KVDEX_KEY_PREFIX,
32+
UNDELIVERED_KEY_PREFIX,
33+
} from "./constants.ts"
3034

3135
/**
3236
* Create a new database instance.
@@ -284,45 +288,67 @@ export class KvDex<const T extends Schema<SchemaDefinition>> {
284288
})
285289
}
286290

291+
/**
292+
* Delete an undelivered document entry by id from the database queue.
293+
*
294+
* @example
295+
* ```ts
296+
* db.deleteUndelivered("id")
297+
* ```
298+
*
299+
* @param id - Id of undelivered document.
300+
*/
301+
async deleteUndelivered(id: KvId) {
302+
const key = extendKey([KVDEX_KEY_PREFIX], UNDELIVERED_KEY_PREFIX, id)
303+
await this.kv.delete(key)
304+
}
305+
287306
/**
288307
* Create a cron job that will repeat at a given interval.
289308
*
290-
* If no interval is set the next job will start immediately after the previous has finished.
309+
* Interval defaults to 1 second if not set.
291310
*
292-
* Will repeat indefinitely if no exit predicate is set.
311+
* Will repeat indefinitely if no exit condition is set.
293312
*
294313
* @example
295314
* ```ts
296315
* // Will repeat indeefinitely without delay
297316
* db.cron(() => console.log("Hello World!"))
298317
*
299318
* // First job starts with a 10 second delay, after that there is a 5 second delay between jobs
300-
* // Will terminate after the 10th run (count starts at 0), or if the job returns n < 0.25
301-
* db.cron(() => Math.random(), {
319+
* db.cron(() => console.log("I terminate after the 10th run"), {
320+
* // Delay before the first job is invoked
302321
* startDelay: 10_000,
322+
*
323+
* // Fixed interval
303324
* interval: 5_000,
304-
* exit: ({ count, result }) => count >= 9 || result < 0.25,
325+
*
326+
* // If this is set it will override the fixed interval
327+
* setInterval: ({ count }) => count * 500
328+
*
329+
* // Count starts at 0 and is given before the current job is run
330+
* exit: ({ count }) => count === 10,
305331
* })
306332
* ```
307333
*
308334
* @param job - Work that will be run for each job interval.
309335
* @param options - Cron options.
310336
*/
311-
async cron<T1>(
312-
job: () => T1,
313-
options?: CronOptions<Awaited<T1>>,
337+
async cron(
338+
job: (msg: CronMessage) => unknown,
339+
options?: CronOptions,
314340
) {
315341
// Create cron handler id
316342
const id = crypto.randomUUID()
317343

318344
// Create cron job enqueuer
319345
const enqueue = async (
320-
cronMsg: CronMessage,
346+
msg: CronMessage,
321347
delay: number | undefined,
322348
) => {
323349
// Enqueue cron job until delivered
324-
for (let i = 0; i < (options?.retries ?? 10); i++) {
325-
await this.enqueue(cronMsg, {
350+
for (let i = 0; i <= (options?.retries ?? 10); i++) {
351+
await this.enqueue(msg, {
326352
idsIfUndelivered: [id],
327353
delay,
328354
topic: id,
@@ -334,41 +360,56 @@ export class KvDex<const T extends Schema<SchemaDefinition>> {
334360
if (doc === null) {
335361
break
336362
}
363+
364+
// Delete undelivered entry before retrying
365+
await this.deleteUndelivered(id)
337366
}
338367
}
339368

340369
// Add cron job listener
341370
this.listenQueue<CronMessage>(async (msg) => {
342-
// Run cron job
343-
const result = await job()
344-
345371
// Check if exit criteria is met, end repeating cron job if true
346-
const exit = await options?.exitOn?.(
347-
{ count: msg.count, result },
348-
) ?? false
372+
const exit = await options?.exitOn?.(msg) ?? false
349373

350374
if (exit) {
351375
return
352376
}
353377

354378
// Set the next interval
355379
const interval = options?.setInterval
356-
? await options?.setInterval!({ count: msg.count, result })
357-
: options?.interval
358-
359-
// Enqueue next cron job
360-
await enqueue({
361-
count: msg.count + 1,
362-
}, interval)
380+
? await options?.setInterval!(msg)
381+
: options?.interval ?? DEFAULT_CRON_INTERVAL
382+
383+
await allFulfilled([
384+
// Enqueue next job
385+
enqueue({
386+
count: msg.count + 1,
387+
previousInterval: interval,
388+
isFirstJob: false,
389+
enqueueTimestamp: new Date(),
390+
}, interval),
391+
392+
// Run cron job
393+
job(msg),
394+
])
363395
}, { topic: id })
364396

365397
// Enqueue first cron job
366398
await enqueue({
367399
count: 0,
400+
previousInterval: options?.startDelay ?? 0,
401+
isFirstJob: true,
402+
enqueueTimestamp: new Date(),
368403
}, options?.startDelay)
369404
}
370405
}
371406

407+
/*************************/
408+
/* */
409+
/* UTILITY FUNCTIONS */
410+
/* */
411+
/**************************/
412+
372413
/**
373414
* Create a database schema from schema definition.
374415
*

src/types.ts

+17-14
Original file line numberDiff line numberDiff line change
@@ -29,23 +29,19 @@ export type CommitResult<T1 extends KvValue> = {
2929
export type IdGenerator<T extends KvValue> = (data: T) => KvId
3030

3131
// Cron types
32-
export type CronOptions<T> = {
32+
export type CronOptions = {
3333
/**
3434
* Interval in milliseconds for cron job.
3535
*
36-
* If not set, job will repeat without delay.
36+
* @default 1000 // Defaults to 1 second
3737
*/
3838
interval?: number
3939

4040
/** Conditionally set the next interval in milliseconds. */
41-
setInterval?: (
42-
{ count, result }: { count: number; result: T },
43-
) => number | Promise<number>
41+
setInterval?: (msg: CronMessage) => number | Promise<number>
4442

4543
/** Exit predicate used to end cron job. */
46-
exitOn?: (
47-
{ count, result }: { count: number; result: T },
48-
) => boolean | Promise<boolean>
44+
exitOn?: (msg: CronMessage) => boolean | Promise<boolean>
4945

5046
/**
5147
* Delay before running the first job.
@@ -65,14 +61,21 @@ export type CronOptions<T> = {
6561
}
6662

6763
export type CronMessage = {
64+
/** Job number, starts at 0. */
6865
count: number
69-
}
7066

71-
export type ParsedCronMessage = {
72-
ok: true
73-
msg: CronMessage
74-
} | {
75-
ok: false
67+
/** Previously set interval. */
68+
previousInterval: number
69+
70+
/** Indicates whether the current job is the first to be run. */
71+
isFirstJob: boolean
72+
73+
/**
74+
* Timestamp of enqueue.
75+
*
76+
* Equal to the start time of the previous job.
77+
*/
78+
enqueueTimestamp: Date
7679
}
7780

7881
// Atomic Builder Types

tests/db/cron.test.ts

+17-6
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,28 @@ Deno.test("db - cron", async (t) => {
99

1010
let count1 = 0
1111
let count2 = 0
12+
let count3 = 0
1213

13-
db.cron(() => count1++, { exitOn: ({ count }) => count === 2 })
14+
db.cron(() => count1++, {
15+
interval: 10,
16+
exitOn: ({ count }) => count === 2,
17+
})
1418

15-
db.cron(() => count2++, { exitOn: ({ count }) => count === 5 })
19+
db.cron(() => count2++, {
20+
interval: 10,
21+
exitOn: ({ isFirstJob }) => isFirstJob,
22+
})
1623

17-
await sleep(1_000)
24+
db.cron(() => count3++, {
25+
interval: 10,
26+
exitOn: ({ previousInterval }) => previousInterval > 0,
27+
})
1828

19-
console.log(count1, count2)
29+
await sleep(1_000)
2030

21-
assert(count1 === 3)
22-
assert(count2 === 6)
31+
assert(count1 === 2)
32+
assert(count2 === 0)
33+
assert(count3 === 1)
2334

2435
kv.close()
2536
})

0 commit comments

Comments
 (0)