effect-mq 0.3.2 → 0.4.1
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/README.md +102 -14
- package/dist/Job.d.ts +131 -9
- package/dist/Job.d.ts.map +1 -1
- package/dist/Job.js +81 -5
- package/dist/Job.js.map +1 -1
- package/dist/JobStore.d.ts +63 -2
- package/dist/JobStore.d.ts.map +1 -1
- package/dist/JobStore.js.map +1 -1
- package/dist/MemoryJobStore.d.ts.map +1 -1
- package/dist/MemoryJobStore.js +159 -119
- package/dist/MemoryJobStore.js.map +1 -1
- package/dist/Worker.d.ts +18 -0
- package/dist/Worker.d.ts.map +1 -1
- package/dist/Worker.js +37 -9
- package/dist/Worker.js.map +1 -1
- package/dist/drizzle-postgres/DrizzleJobStore.d.ts.map +1 -1
- package/dist/drizzle-postgres/DrizzleJobStore.js +239 -49
- package/dist/drizzle-postgres/DrizzleJobStore.js.map +1 -1
- package/dist/drizzle-postgres/schema.d.ts +35 -27
- package/dist/drizzle-postgres/schema.d.ts.map +1 -1
- package/dist/drizzle-postgres/schema.js +8 -2
- package/dist/drizzle-postgres/schema.js.map +1 -1
- package/dist/redis/RedisJobStore.d.ts.map +1 -1
- package/dist/redis/RedisJobStore.js +173 -36
- package/dist/redis/RedisJobStore.js.map +1 -1
- package/dist/redis/scripts.d.ts +24 -1
- package/dist/redis/scripts.d.ts.map +1 -1
- package/dist/redis/scripts.js +126 -21
- package/dist/redis/scripts.js.map +1 -1
- package/dist/testing/conformance.d.ts.map +1 -1
- package/dist/testing/conformance.js +266 -0
- package/dist/testing/conformance.js.map +1 -1
- package/package.json +1 -1
- package/src/Job.ts +268 -13
- package/src/JobStore.ts +77 -2
- package/src/MemoryJobStore.ts +102 -53
- package/src/Worker.ts +61 -9
- package/src/drizzle-postgres/DrizzleJobStore.ts +273 -66
- package/src/drizzle-postgres/schema.ts +33 -25
- package/src/redis/RedisJobStore.ts +236 -47
- package/src/redis/scripts.ts +153 -21
- package/src/testing/conformance.ts +350 -0
|
@@ -37,6 +37,7 @@ const baseRequest = (
|
|
|
37
37
|
keep: undefined,
|
|
38
38
|
timeoutMs: undefined,
|
|
39
39
|
dedupe: undefined,
|
|
40
|
+
trace: undefined,
|
|
40
41
|
delayMs: 0,
|
|
41
42
|
...overrides
|
|
42
43
|
})
|
|
@@ -832,6 +833,310 @@ export const jobStoreConformance = (
|
|
|
832
833
|
})
|
|
833
834
|
))
|
|
834
835
|
|
|
836
|
+
const minutelySchedule = (): JobStore.ScheduleRecord => ({
|
|
837
|
+
key: JobStore.ScheduleKey("TestJob/minutely"),
|
|
838
|
+
jobName: "TestJob",
|
|
839
|
+
queue: QueueName("default"),
|
|
840
|
+
cron: undefined,
|
|
841
|
+
tz: undefined,
|
|
842
|
+
everyMs: 60_000,
|
|
843
|
+
payload: { n: 7 },
|
|
844
|
+
metadata: {},
|
|
845
|
+
priority: 0,
|
|
846
|
+
attemptsMax: 1,
|
|
847
|
+
backoff: undefined,
|
|
848
|
+
keep: undefined,
|
|
849
|
+
timeoutMs: undefined,
|
|
850
|
+
nextRunAt: 60_000
|
|
851
|
+
})
|
|
852
|
+
|
|
853
|
+
it.effect("tickSchedule fires a slot exactly once and advances atomically", () =>
|
|
854
|
+
withStore((store) =>
|
|
855
|
+
Effect.gen(function*() {
|
|
856
|
+
const schedule = minutelySchedule()
|
|
857
|
+
yield* store.upsertSchedule(schedule)
|
|
858
|
+
yield* TestClock.adjust(60_000)
|
|
859
|
+
const request = baseRequest({
|
|
860
|
+
id: JobId("sched/TestJob/minutely/60000"),
|
|
861
|
+
payload: { n: 7 }
|
|
862
|
+
})
|
|
863
|
+
|
|
864
|
+
expect(yield* store.tickSchedule(schedule.key, 60_000, 120_000, request)).toBe(true)
|
|
865
|
+
expect((yield* store.listSchedules())[0]?.nextRunAt).toBe(120_000)
|
|
866
|
+
expect((yield* store.counts()).waiting).toBe(1)
|
|
867
|
+
|
|
868
|
+
// A concurrent sweeper holding the same stale expectation loses the
|
|
869
|
+
// CAS: nothing fires, nothing advances further.
|
|
870
|
+
expect(yield* store.tickSchedule(schedule.key, 60_000, 180_000, request)).toBe(false)
|
|
871
|
+
expect((yield* store.listSchedules())[0]?.nextRunAt).toBe(120_000)
|
|
872
|
+
expect((yield* store.counts()).waiting).toBe(1)
|
|
873
|
+
|
|
874
|
+
const claim = yield* store.claim(claimOptions())
|
|
875
|
+
assert(claim._tag === "Claimed")
|
|
876
|
+
expect(claim.job.id).toBe("sched/TestJob/minutely/60000")
|
|
877
|
+
expect(claim.job.payload).toEqual({ n: 7 })
|
|
878
|
+
})
|
|
879
|
+
))
|
|
880
|
+
|
|
881
|
+
it.effect("a stale tick cannot re-fire a slot whose job was pruned", () =>
|
|
882
|
+
withStore((store) =>
|
|
883
|
+
Effect.gen(function*() {
|
|
884
|
+
const schedule = minutelySchedule()
|
|
885
|
+
yield* store.upsertSchedule(schedule)
|
|
886
|
+
yield* TestClock.adjust(60_000)
|
|
887
|
+
const slotId = JobId("sched/TestJob/minutely/60000")
|
|
888
|
+
const request = baseRequest({ id: slotId, payload: { n: 7 } })
|
|
889
|
+
expect(yield* store.tickSchedule(schedule.key, 60_000, 120_000, request)).toBe(true)
|
|
890
|
+
|
|
891
|
+
// Retention prunes the slot job — the old non-atomic design would
|
|
892
|
+
// now let a stale sweeper re-insert it. The CAS must still refuse.
|
|
893
|
+
expect(yield* store.remove(slotId)).toBe(true)
|
|
894
|
+
expect(yield* store.tickSchedule(schedule.key, 60_000, 180_000, request)).toBe(false)
|
|
895
|
+
expect((yield* store.counts()).waiting).toBe(0)
|
|
896
|
+
expect((yield* store.listSchedules())[0]?.nextRunAt).toBe(120_000)
|
|
897
|
+
})
|
|
898
|
+
))
|
|
899
|
+
|
|
900
|
+
it.effect("tickSchedule advances without firing when the slot job already exists", () =>
|
|
901
|
+
withStore((store) =>
|
|
902
|
+
Effect.gen(function*() {
|
|
903
|
+
const schedule = minutelySchedule()
|
|
904
|
+
yield* store.upsertSchedule(schedule)
|
|
905
|
+
yield* TestClock.adjust(60_000)
|
|
906
|
+
const slotId = JobId("sched/TestJob/minutely/60000")
|
|
907
|
+
// A pre-0.4 worker crashed between its enqueue and advance: the
|
|
908
|
+
// slot row exists but nextRunAt is stale. The tick must advance the
|
|
909
|
+
// schedule (or it stays due forever) yet report nothing new fired.
|
|
910
|
+
yield* store.enqueue(baseRequest({ id: slotId, payload: { n: 7 } }))
|
|
911
|
+
const request = baseRequest({ id: slotId, payload: { n: 7 } })
|
|
912
|
+
expect(yield* store.tickSchedule(schedule.key, 60_000, 120_000, request)).toBe(false)
|
|
913
|
+
expect((yield* store.listSchedules())[0]?.nextRunAt).toBe(120_000)
|
|
914
|
+
expect((yield* store.counts()).waiting).toBe(1)
|
|
915
|
+
})
|
|
916
|
+
))
|
|
917
|
+
|
|
918
|
+
it.effect("tickSchedule rejects requests without an id and unknown keys", () =>
|
|
919
|
+
withStore((store) =>
|
|
920
|
+
Effect.gen(function*() {
|
|
921
|
+
const missing = yield* Effect.flip(
|
|
922
|
+
store.tickSchedule(JobStore.ScheduleKey("TestJob/minutely"), 60_000, 120_000, baseRequest())
|
|
923
|
+
)
|
|
924
|
+
expect(missing._tag).toBe("JobStoreError")
|
|
925
|
+
expect(
|
|
926
|
+
yield* store.tickSchedule(
|
|
927
|
+
JobStore.ScheduleKey("ghost"),
|
|
928
|
+
0,
|
|
929
|
+
60_000,
|
|
930
|
+
baseRequest({ id: JobId("sched/ghost/0") })
|
|
931
|
+
)
|
|
932
|
+
).toBe(false)
|
|
933
|
+
expect((yield* store.counts()).waiting).toBe(0)
|
|
934
|
+
})
|
|
935
|
+
))
|
|
936
|
+
|
|
937
|
+
it.effect("tickSchedule wakes parked takers", () =>
|
|
938
|
+
withStore((store) =>
|
|
939
|
+
Effect.gen(function*() {
|
|
940
|
+
const schedule = minutelySchedule()
|
|
941
|
+
yield* store.upsertSchedule(schedule)
|
|
942
|
+
const empty = yield* store.claim(claimOptions())
|
|
943
|
+
assert(empty._tag === "Empty")
|
|
944
|
+
const waiter = yield* Effect.forkChild(
|
|
945
|
+
store.awaitWake([QueueName("default")], empty.wakeToken)
|
|
946
|
+
)
|
|
947
|
+
yield* Effect.yieldNow
|
|
948
|
+
yield* TestClock.adjust(60_000)
|
|
949
|
+
const request = baseRequest({
|
|
950
|
+
id: JobId("sched/TestJob/minutely/60000"),
|
|
951
|
+
payload: { n: 7 }
|
|
952
|
+
})
|
|
953
|
+
expect(yield* store.tickSchedule(schedule.key, 60_000, 120_000, request)).toBe(true)
|
|
954
|
+
yield* Fiber.join(waiter)
|
|
955
|
+
const claim = yield* store.claim(claimOptions({ token: "t-2" }))
|
|
956
|
+
assert(claim._tag === "Claimed")
|
|
957
|
+
})
|
|
958
|
+
))
|
|
959
|
+
|
|
960
|
+
it.effect("enqueueMany inserts a batch with positional results", () =>
|
|
961
|
+
withStore((store) =>
|
|
962
|
+
Effect.gen(function*() {
|
|
963
|
+
const results = yield* store.enqueueMany([
|
|
964
|
+
baseRequest({ payload: { n: 1 } }),
|
|
965
|
+
baseRequest({ payload: { n: 2 }, delayMs: 60_000 }),
|
|
966
|
+
baseRequest({ id: JobId("batch-3"), payload: { n: 3 }, priority: 5 })
|
|
967
|
+
])
|
|
968
|
+
expect(results).toHaveLength(3)
|
|
969
|
+
expect(results.map((result) => result.duplicate)).toEqual([false, false, false])
|
|
970
|
+
expect(new Set(results.map((result) => result.id)).size).toBe(3)
|
|
971
|
+
expect(results[2]?.id).toBe("batch-3")
|
|
972
|
+
|
|
973
|
+
const counts = yield* store.counts()
|
|
974
|
+
expect(counts.waiting).toBe(2)
|
|
975
|
+
expect(counts.delayed).toBe(1)
|
|
976
|
+
|
|
977
|
+
const explicit = yield* store.getJob(JobId("batch-3"))
|
|
978
|
+
assert(Option.isSome(explicit))
|
|
979
|
+
expect(explicit.value.payload).toEqual({ n: 3 })
|
|
980
|
+
expect(explicit.value.priority).toBe(5)
|
|
981
|
+
|
|
982
|
+
const delayedId = results[1]?.id
|
|
983
|
+
assert(delayedId !== undefined)
|
|
984
|
+
const delayed = yield* store.getJob(delayedId)
|
|
985
|
+
assert(Option.isSome(delayed))
|
|
986
|
+
expect(delayed.value.state).toBe("delayed")
|
|
987
|
+
expect(delayed.value.runAt).toBe(60_000)
|
|
988
|
+
|
|
989
|
+
// Priority order survives the batch path.
|
|
990
|
+
const claim = yield* store.claim(claimOptions())
|
|
991
|
+
assert(claim._tag === "Claimed")
|
|
992
|
+
expect(claim.job.id).toBe("batch-3")
|
|
993
|
+
})
|
|
994
|
+
))
|
|
995
|
+
|
|
996
|
+
it.effect("enqueueMany reports duplicates positionally without clobbering", () =>
|
|
997
|
+
withStore((store) =>
|
|
998
|
+
Effect.gen(function*() {
|
|
999
|
+
yield* store.enqueue(baseRequest({ id: JobId("dup-1"), payload: { n: 0 } }))
|
|
1000
|
+
const results = yield* store.enqueueMany([
|
|
1001
|
+
baseRequest({ payload: { n: 1 } }),
|
|
1002
|
+
baseRequest({ id: JobId("dup-1"), payload: { n: 99 } }),
|
|
1003
|
+
baseRequest({ payload: { n: 2 } })
|
|
1004
|
+
])
|
|
1005
|
+
expect(results.map((result) => result.duplicate)).toEqual([false, true, false])
|
|
1006
|
+
expect(results[1]?.id).toBe("dup-1")
|
|
1007
|
+
const kept = yield* store.getJob(JobId("dup-1"))
|
|
1008
|
+
assert(Option.isSome(kept))
|
|
1009
|
+
expect(kept.value.payload).toEqual({ n: 0 })
|
|
1010
|
+
expect((yield* store.counts()).waiting).toBe(3)
|
|
1011
|
+
})
|
|
1012
|
+
))
|
|
1013
|
+
|
|
1014
|
+
it.effect("enqueueMany resolves intra-batch repeats of one id like separate enqueues", () =>
|
|
1015
|
+
withStore((store) =>
|
|
1016
|
+
Effect.gen(function*() {
|
|
1017
|
+
const results = yield* store.enqueueMany([
|
|
1018
|
+
baseRequest({ id: JobId("same"), payload: { n: 1 } }),
|
|
1019
|
+
baseRequest({ id: JobId("same"), payload: { n: 2 } })
|
|
1020
|
+
])
|
|
1021
|
+
expect(results.map((result) => result.duplicate)).toEqual([false, true])
|
|
1022
|
+
expect(results[1]?.id).toBe("same")
|
|
1023
|
+
expect((yield* store.counts()).waiting).toBe(1)
|
|
1024
|
+
const job = yield* store.getJob(JobId("same"))
|
|
1025
|
+
assert(Option.isSome(job))
|
|
1026
|
+
expect(job.value.payload).toEqual({ n: 1 })
|
|
1027
|
+
})
|
|
1028
|
+
))
|
|
1029
|
+
|
|
1030
|
+
it.effect("enqueueMany applies per-item dedup policies in order", () =>
|
|
1031
|
+
withStore((store) =>
|
|
1032
|
+
Effect.gen(function*() {
|
|
1033
|
+
const dedupe = { key: "k", ttlMs: undefined, extend: false, replace: false }
|
|
1034
|
+
const results = yield* store.enqueueMany([
|
|
1035
|
+
baseRequest({ payload: { n: 1 }, dedupe }),
|
|
1036
|
+
baseRequest({ payload: { n: 2 } }),
|
|
1037
|
+
baseRequest({ payload: { n: 3 }, dedupe })
|
|
1038
|
+
])
|
|
1039
|
+
expect(results.map((result) => result.duplicate)).toEqual([false, false, true])
|
|
1040
|
+
expect(results[2]?.id).toBe(results[0]?.id)
|
|
1041
|
+
expect((yield* store.counts()).waiting).toBe(2)
|
|
1042
|
+
})
|
|
1043
|
+
))
|
|
1044
|
+
|
|
1045
|
+
it.effect("enqueueMany with an empty batch returns an empty array", () =>
|
|
1046
|
+
withStore((store) =>
|
|
1047
|
+
Effect.gen(function*() {
|
|
1048
|
+
expect(yield* store.enqueueMany([])).toEqual([])
|
|
1049
|
+
})
|
|
1050
|
+
))
|
|
1051
|
+
|
|
1052
|
+
// Pins the hand-duplicated field plumbing in batch/tick insert paths
|
|
1053
|
+
// (positional Lua ARGV strides, multi-row VALUES lists): every persisted
|
|
1054
|
+
// field must come out identical to the single-enqueue path. A swapped
|
|
1055
|
+
// pair of ARGVs or a dropped column fails this even though the simpler
|
|
1056
|
+
// tests (payload/priority/state only) stay green.
|
|
1057
|
+
it.effect("batch and tick inserts persist every field exactly like enqueue", () =>
|
|
1058
|
+
withStore((store) =>
|
|
1059
|
+
Effect.gen(function*() {
|
|
1060
|
+
const richRequest = (id: string): JobStore.EnqueueRequest =>
|
|
1061
|
+
baseRequest({
|
|
1062
|
+
id: JobId(id),
|
|
1063
|
+
payload: { big: 1234567890123456, nested: { arr: [1, 2, 3] } },
|
|
1064
|
+
metadata: { tenant: "acme", region: "us" },
|
|
1065
|
+
priority: 3,
|
|
1066
|
+
attemptsMax: 4,
|
|
1067
|
+
backoff: { _tag: "fixed", delayMs: 2_000 },
|
|
1068
|
+
keep: { completed: { count: 2, ageMs: undefined } },
|
|
1069
|
+
timeoutMs: 9_000,
|
|
1070
|
+
trace: { traceId: "trace-1", spanId: "span-1", sampled: true, delayed: false }
|
|
1071
|
+
})
|
|
1072
|
+
yield* store.enqueue(richRequest("rich-single"))
|
|
1073
|
+
expect(yield* store.enqueueMany([richRequest("rich-batch")]))
|
|
1074
|
+
.toEqual([{ id: "rich-batch", duplicate: false }])
|
|
1075
|
+
const schedule = {
|
|
1076
|
+
...minutelySchedule(),
|
|
1077
|
+
key: JobStore.ScheduleKey("TestJob/parity"),
|
|
1078
|
+
nextRunAt: 0
|
|
1079
|
+
}
|
|
1080
|
+
yield* store.upsertSchedule(schedule)
|
|
1081
|
+
expect(yield* store.tickSchedule(schedule.key, 0, 60_000, richRequest("rich-tick")))
|
|
1082
|
+
.toBe(true)
|
|
1083
|
+
|
|
1084
|
+
const project = (job: JobStore.JobRecord) => ({
|
|
1085
|
+
name: job.name,
|
|
1086
|
+
queue: job.queue,
|
|
1087
|
+
state: job.state,
|
|
1088
|
+
payload: job.payload,
|
|
1089
|
+
metadata: job.metadata,
|
|
1090
|
+
priority: job.priority,
|
|
1091
|
+
attemptsMax: job.attemptsMax,
|
|
1092
|
+
attemptsMade: job.attemptsMade,
|
|
1093
|
+
backoff: job.backoff,
|
|
1094
|
+
keep: job.keep,
|
|
1095
|
+
timeoutMs: job.timeoutMs,
|
|
1096
|
+
cancelRequested: job.cancelRequested,
|
|
1097
|
+
trace: job.trace,
|
|
1098
|
+
runAt: job.runAt,
|
|
1099
|
+
enqueuedAt: job.enqueuedAt
|
|
1100
|
+
})
|
|
1101
|
+
const single = yield* store.getJob(JobId("rich-single"))
|
|
1102
|
+
assert(Option.isSome(single))
|
|
1103
|
+
const expected = project(single.value)
|
|
1104
|
+
// The reference row itself must carry the rich fields — otherwise
|
|
1105
|
+
// three empty rows would compare equal and prove nothing.
|
|
1106
|
+
expect(expected.backoff).toEqual({ _tag: "fixed", delayMs: 2_000 })
|
|
1107
|
+
expect(expected.keep).toEqual({ completed: { count: 2 } })
|
|
1108
|
+
expect(expected.timeoutMs).toBe(9_000)
|
|
1109
|
+
expect(expected.trace).toEqual({
|
|
1110
|
+
traceId: "trace-1",
|
|
1111
|
+
spanId: "span-1",
|
|
1112
|
+
sampled: true,
|
|
1113
|
+
delayed: false
|
|
1114
|
+
})
|
|
1115
|
+
expect(expected.payload).toEqual({ big: 1234567890123456, nested: { arr: [1, 2, 3] } })
|
|
1116
|
+
for (const id of [JobId("rich-batch"), JobId("rich-tick")]) {
|
|
1117
|
+
const job = yield* store.getJob(id)
|
|
1118
|
+
assert(Option.isSome(job))
|
|
1119
|
+
expect(project(job.value)).toEqual(expected)
|
|
1120
|
+
}
|
|
1121
|
+
})
|
|
1122
|
+
))
|
|
1123
|
+
|
|
1124
|
+
it.effect("enqueueMany wakes parked takers", () =>
|
|
1125
|
+
withStore((store) =>
|
|
1126
|
+
Effect.gen(function*() {
|
|
1127
|
+
const empty = yield* store.claim(claimOptions())
|
|
1128
|
+
assert(empty._tag === "Empty")
|
|
1129
|
+
const waiter = yield* Effect.forkChild(
|
|
1130
|
+
store.awaitWake([QueueName("default")], empty.wakeToken)
|
|
1131
|
+
)
|
|
1132
|
+
yield* Effect.yieldNow
|
|
1133
|
+
yield* store.enqueueMany([baseRequest()])
|
|
1134
|
+
yield* Fiber.join(waiter)
|
|
1135
|
+
const claim = yield* store.claim(claimOptions({ token: "t-2" }))
|
|
1136
|
+
assert(claim._tag === "Claimed")
|
|
1137
|
+
})
|
|
1138
|
+
))
|
|
1139
|
+
|
|
835
1140
|
it.effect("completion wins over a pending cancel request", () =>
|
|
836
1141
|
withStore((store) =>
|
|
837
1142
|
Effect.gen(function*() {
|
|
@@ -1293,6 +1598,51 @@ export const jobStoreConformance = (
|
|
|
1293
1598
|
})
|
|
1294
1599
|
))
|
|
1295
1600
|
|
|
1601
|
+
it.effect("cancelByDedupe cancels pending keyed jobs and is idempotent", () =>
|
|
1602
|
+
withStore((store) =>
|
|
1603
|
+
Effect.gen(function*() {
|
|
1604
|
+
const dedupe = { key: "emp-1", ttlMs: undefined, extend: false, replace: false }
|
|
1605
|
+
// Unknown key: nothing pending, no error.
|
|
1606
|
+
expect(yield* store.cancelByDedupe("TestJob", "emp-1")).toBe(false)
|
|
1607
|
+
|
|
1608
|
+
// Delayed keyed job: cancelled terminally.
|
|
1609
|
+
const delayed = yield* store.enqueue(baseRequest({ dedupe, delayMs: 60_000 }))
|
|
1610
|
+
expect(yield* store.cancelByDedupe("TestJob", "emp-1")).toBe(true)
|
|
1611
|
+
const job = yield* store.getJob(delayed.id)
|
|
1612
|
+
assert(Option.isSome(job))
|
|
1613
|
+
expect(job.value.state).toBe("cancelled")
|
|
1614
|
+
expect(yield* store.cancelByDedupe("TestJob", "emp-1")).toBe(false)
|
|
1615
|
+
|
|
1616
|
+
// Active keyed job: flagged for the heartbeat.
|
|
1617
|
+
const active = yield* store.enqueue(baseRequest({ dedupe, payload: { n: 2 } }))
|
|
1618
|
+
const claim = yield* store.claim(claimOptions())
|
|
1619
|
+
assert(claim._tag === "Claimed")
|
|
1620
|
+
expect(yield* store.cancelByDedupe("TestJob", "emp-1")).toBe(true)
|
|
1621
|
+
const flagged = yield* store.getJob(active.id)
|
|
1622
|
+
assert(Option.isSome(flagged))
|
|
1623
|
+
expect(flagged.value.cancelRequested).toBe(true)
|
|
1624
|
+
|
|
1625
|
+
// Name scoping: same key under another name is untouched.
|
|
1626
|
+
expect(yield* store.cancelByDedupe("OtherJob", "emp-1")).toBe(false)
|
|
1627
|
+
})
|
|
1628
|
+
))
|
|
1629
|
+
|
|
1630
|
+
it.effect("the producer's trace context round-trips on the record", () =>
|
|
1631
|
+
withStore((store) =>
|
|
1632
|
+
Effect.gen(function*() {
|
|
1633
|
+
const trace = { traceId: "trace-abc", spanId: "span-def", sampled: true, delayed: false }
|
|
1634
|
+
const { id } = yield* store.enqueue(baseRequest({ trace }))
|
|
1635
|
+
const job = yield* store.getJob(id)
|
|
1636
|
+
assert(Option.isSome(job))
|
|
1637
|
+
expect(job.value.trace).toEqual(trace)
|
|
1638
|
+
|
|
1639
|
+
const bare = yield* store.enqueue(baseRequest({ payload: { n: 2 } }))
|
|
1640
|
+
const none = yield* store.getJob(bare.id)
|
|
1641
|
+
assert(Option.isSome(none))
|
|
1642
|
+
expect(none.value.trace).toBeUndefined()
|
|
1643
|
+
})
|
|
1644
|
+
))
|
|
1645
|
+
|
|
1296
1646
|
it.effect("delayed jobs still promote to waiting while their queue is paused", () =>
|
|
1297
1647
|
withStore((store) =>
|
|
1298
1648
|
Effect.gen(function*() {
|