effect-mq 0.6.0 → 0.7.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.
@@ -528,21 +528,153 @@ export const jobStoreConformance = (
528
528
  withStore((store) =>
529
529
  Effect.gen(function*() {
530
530
  // No clock adjustment: every record shares one enqueuedAt, so
531
- // ordering and the cursor fall back entirely to the id tie-break.
531
+ // ordering and the cursor fall back entirely to the id tie-break
532
+ // in both directions.
532
533
  for (let i = 0; i < 7; i++) {
533
534
  yield* store.enqueue(baseRequest({ payload: { n: i } }))
534
535
  }
535
- const seen = new Set<string>()
536
- let cursor: string | undefined
537
- do {
538
- const page: JobStore.ListResult = yield* store.list({ limit: 3, cursor })
539
- for (const item of page.items) {
540
- expect(seen.has(item.id)).toBe(false)
541
- seen.add(item.id)
536
+ for (const order of ["desc", "asc"] as const) {
537
+ const seen: Array<string> = []
538
+ let cursor: string | undefined
539
+ do {
540
+ const page: JobStore.ListResult = yield* store.list({ limit: 3, order, cursor })
541
+ for (const item of page.items) {
542
+ expect(seen.includes(item.id)).toBe(false)
543
+ seen.push(item.id)
544
+ }
545
+ cursor = page.cursor
546
+ } while (cursor !== undefined)
547
+ expect(seen.length).toBe(7)
548
+ // Opposite directions walk exact mirror orders.
549
+ if (order === "asc") {
550
+ const descAll = yield* store.list({ limit: 7, order: "desc" })
551
+ expect(seen).toEqual(descAll.items.map((item) => item.id).toReversed())
542
552
  }
543
- cursor = page.cursor
544
- } while (cursor !== undefined)
545
- expect(seen.size).toBe(7)
553
+ }
554
+ })
555
+ ))
556
+
557
+ it.effect("list orders by enqueuedAt ascending on request, cursor included", () =>
558
+ withStore((store) =>
559
+ Effect.gen(function*() {
560
+ const ids: Array<JobStore.JobId> = []
561
+ for (let i = 0; i < 3; i++) {
562
+ const { id } = yield* store.enqueue(baseRequest({ payload: { n: i } }))
563
+ ids.push(id)
564
+ yield* TestClock.adjust(1_000)
565
+ }
566
+ const first = yield* store.list({ orderBy: "enqueuedAt", order: "asc", limit: 2 })
567
+ expect(first.items.map((job) => job.id)).toEqual([ids[0], ids[1]])
568
+ assert(first.cursor !== undefined)
569
+ const second = yield* store.list({
570
+ orderBy: "enqueuedAt",
571
+ order: "asc",
572
+ limit: 2,
573
+ cursor: first.cursor
574
+ })
575
+ expect(second.items.map((job) => job.id)).toEqual([ids[2]])
576
+ expect(second.cursor).toBeUndefined()
577
+ })
578
+ ))
579
+
580
+ it.effect("list orders delayed jobs by runAt within a queue", () =>
581
+ withStore((store) =>
582
+ Effect.gen(function*() {
583
+ const late = yield* store.enqueue(baseRequest({ payload: { n: 1 }, delayMs: 30_000 }))
584
+ const soon = yield* store.enqueue(baseRequest({ payload: { n: 2 }, delayMs: 5_000 }))
585
+ const middle = yield* store.enqueue(baseRequest({ payload: { n: 3 }, delayMs: 10_000 }))
586
+ // An immediate job in the same queue must not appear.
587
+ yield* store.enqueue(baseRequest({ payload: { n: 4 } }))
588
+
589
+ const upcoming = yield* store.list({
590
+ queue: QueueName("default"),
591
+ states: ["delayed"],
592
+ orderBy: "runAt",
593
+ order: "asc",
594
+ limit: 2
595
+ })
596
+ expect(upcoming.items.map((job) => job.id)).toEqual([soon.id, middle.id])
597
+ assert(upcoming.cursor !== undefined)
598
+ const rest = yield* store.list({
599
+ queue: QueueName("default"),
600
+ states: ["delayed"],
601
+ orderBy: "runAt",
602
+ order: "asc",
603
+ limit: 2,
604
+ cursor: upcoming.cursor
605
+ })
606
+ expect(rest.items.map((job) => job.id)).toEqual([late.id])
607
+ expect(rest.cursor).toBeUndefined()
608
+ })
609
+ ))
610
+
611
+ it.effect("list orders terminal jobs by finishedAt, with and without a name", () =>
612
+ withStore((store) =>
613
+ Effect.gen(function*() {
614
+ // Enqueue in one order, finish in the REVERSE order, so finishedAt
615
+ // ordering and enqueuedAt ordering disagree — a driver that quietly
616
+ // ignores orderBy fails here instead of passing by coincidence.
617
+ const enqueueAs = (name: string, queue?: string) => {
618
+ const request = queue === undefined
619
+ ? baseRequest({ name })
620
+ : baseRequest({ name, queue: QueueName(queue) })
621
+ return Effect.map(store.enqueue(request), (result) => result.id)
622
+ }
623
+ const first = yield* enqueueAs("TestJob")
624
+ const second = yield* enqueueAs("OtherJob")
625
+ const third = yield* enqueueAs("TestJob")
626
+ const elsewhere = yield* enqueueAs("TestJob", "other")
627
+ const claims = new Map<JobStore.JobId, string>()
628
+ for (const token of ["t-1", "t-2", "t-3"]) {
629
+ const claim = yield* store.claim(claimOptions({
630
+ names: ["TestJob", "OtherJob"],
631
+ token
632
+ }))
633
+ assert(claim._tag === "Claimed")
634
+ claims.set(claim.job.id, token)
635
+ }
636
+ const otherClaim = yield* store.claim(claimOptions({
637
+ queue: QueueName("other"),
638
+ token: "t-4"
639
+ }))
640
+ assert(otherClaim._tag === "Claimed")
641
+ claims.set(elsewhere, "t-4")
642
+ for (const id of [elsewhere, third, second, first]) {
643
+ const token = claims.get(id)
644
+ assert(token !== undefined)
645
+ yield* store.ack(id, token, {
646
+ _tag: id === second ? "Fail" : "Complete",
647
+ exit: undefined
648
+ })
649
+ yield* TestClock.adjust(1_000)
650
+ }
651
+
652
+ // Across terminal states, newest FINISHED first: the reverse of
653
+ // enqueue order.
654
+ const recent = yield* store.list({
655
+ states: ["completed", "failed"],
656
+ orderBy: "finishedAt",
657
+ order: "desc"
658
+ })
659
+ expect(recent.items.map((job) => job.id)).toEqual([first, second, third, elsewhere])
660
+
661
+ // Scoped to one name and state.
662
+ const byName = yield* store.list({
663
+ name: "TestJob",
664
+ states: ["completed"],
665
+ orderBy: "finishedAt",
666
+ order: "desc"
667
+ })
668
+ expect(byName.items.map((job) => job.id)).toEqual([first, third, elsewhere])
669
+
670
+ // The queue filter applies on top of the finishedAt route.
671
+ const byQueue = yield* store.list({
672
+ queue: QueueName("other"),
673
+ states: ["completed"],
674
+ orderBy: "finishedAt",
675
+ order: "desc"
676
+ })
677
+ expect(byQueue.items.map((job) => job.id)).toEqual([elsewhere])
546
678
  })
547
679
  ))
548
680