@@ -243,6 +243,74 @@ kj::Promise<void> WorkerQueue::send(
243243 .attach (context.registerPendingEvent ());
244244};
245245
246+ jsg::Promise<WorkerQueue::SendResponse> WorkerQueue::sendWithResponse (jsg::Lock& js,
247+ jsg::JsValue body,
248+ jsg::Optional<SendOptions> options,
249+ const jsg::TypeHandler<SendResponse>& responseHandler) {
250+ auto & context = IoContext::current ();
251+
252+ JSG_REQUIRE (!body.isUndefined (), TypeError, " Message body cannot be undefined" );
253+
254+ auto headers = kj::HttpHeaders (context.getHeaderTable ());
255+ headers.set (kj::HttpHeaderId::CONTENT_TYPE, MimeType::OCTET_STREAM.toString ());
256+
257+ kj::Maybe<kj::StringPtr> contentType;
258+ KJ_IF_SOME (opts, options) {
259+ KJ_IF_SOME (type, opts.contentType ) {
260+ auto validatedType = validateContentType (type);
261+ headers.addPtrPtr (HDR_MSG_FORMAT, validatedType);
262+ contentType = validatedType;
263+ }
264+ KJ_IF_SOME (secs, opts.delaySeconds ) {
265+ headers.addPtr (HDR_MSG_DELAY, kj::str (secs));
266+ }
267+ }
268+
269+ Serialized serialized;
270+ KJ_IF_SOME (type, contentType) {
271+ serialized = serialize (js, body, type, SerializeArrayBufferBehavior::DEEP_COPY);
272+ } else if (workerd::FeatureFlags::get (js).getQueuesJsonMessages ()) {
273+ headers.addPtrPtr (" X-Msg-Fmt" , IncomingQueueMessage::ContentType::JSON);
274+ serialized = serialize (
275+ js, body, IncomingQueueMessage::ContentType::JSON, SerializeArrayBufferBehavior::DEEP_COPY);
276+ } else {
277+ serialized = serializeV8 (js, body);
278+ }
279+
280+ auto client = context.getHttpClient (subrequestChannel, true , kj::none, " queue_send" _kjc);
281+ auto req = client->request (
282+ kj::HttpMethod::POST, " https://fake-host/message" _kjc, headers, serialized.data .size ());
283+
284+ const auto & headerIds = context.getHeaderIds ();
285+ const auto exposeErrorCodes = workerd::FeatureFlags::get (js).getQueueExposeErrorCodes ();
286+
287+ static constexpr auto handleSend = [](auto req, auto serialized, auto client, auto & headerIds,
288+ bool exposeErrorCodes) -> kj::Promise<kj::String> {
289+ co_await req.body ->write (serialized.data );
290+ auto response = co_await req.response ;
291+
292+ if (exposeErrorCodes) {
293+ JSG_REQUIRE (response.statusCode == 200 , Error, buildQueueErrorMessage (response, headerIds));
294+ } else {
295+ JSG_REQUIRE (
296+ response.statusCode == 200 , Error, kj::str (" Queue send failed: " , response.statusText ));
297+ }
298+
299+ auto responseBody = co_await response.body ->readAllBytes ();
300+ co_return kj::str (responseBody.asChars ());
301+ };
302+
303+ auto promise =
304+ handleSend (kj::mv (req), kj::mv (serialized), kj::mv (client), headerIds, exposeErrorCodes);
305+
306+ return context.awaitIo (
307+ js, kj::mv (promise), [&responseHandler](jsg::Lock& js, kj::String text) -> SendResponse {
308+ auto parsed = jsg::JsValue::fromJson (js, text);
309+ return JSG_REQUIRE_NONNULL (
310+ responseHandler.tryUnwrap (js, parsed), Error, " Failed to parse queue send response" , text);
311+ });
312+ }
313+
246314kj::Promise<void > WorkerQueue::sendBatch (jsg::Lock& js,
247315 jsg::Sequence<MessageSendRequest> batch,
248316 jsg::Optional<SendBatchOptions> options) {
@@ -394,6 +462,120 @@ jsg::Promise<WorkerQueue::Metrics> WorkerQueue::metrics(
394462 });
395463}
396464
465+ jsg::Promise<WorkerQueue::SendBatchResponse> WorkerQueue::sendBatchWithResponse (jsg::Lock& js,
466+ jsg::Sequence<MessageSendRequest> batch,
467+ jsg::Optional<SendBatchOptions> options,
468+ const jsg::TypeHandler<SendBatchResponse>& responseHandler) {
469+ auto & context = IoContext::current ();
470+
471+ JSG_REQUIRE (batch.size () > 0 , TypeError, " sendBatch() requires at least one message" );
472+
473+ size_t totalSize = 0 ;
474+ size_t largestMessage = 0 ;
475+ auto messageCount = batch.size ();
476+ auto builder = kj::heapArrayBuilder<SerializedWithOptions>(messageCount);
477+ for (auto & message: batch) {
478+ auto body = message.body .getHandle (js);
479+ JSG_REQUIRE (!body.isUndefined (), TypeError, " Message body cannot be undefined" );
480+
481+ SerializedWithOptions item;
482+ KJ_IF_SOME (secs, message.delaySeconds ) {
483+ item.delaySeconds = secs;
484+ }
485+
486+ KJ_IF_SOME (contentType, message.contentType ) {
487+ item.contentType = validateContentType (contentType);
488+ item.body = serialize (js, body, contentType, SerializeArrayBufferBehavior::SHALLOW_REFERENCE);
489+ } else if (workerd::FeatureFlags::get (js).getQueuesJsonMessages ()) {
490+ item.contentType = IncomingQueueMessage::ContentType::JSON;
491+ item.body = serialize (js, body, IncomingQueueMessage::ContentType::JSON,
492+ SerializeArrayBufferBehavior::SHALLOW_REFERENCE);
493+ } else {
494+ item.body = serializeV8 (js, body);
495+ }
496+
497+ builder.add (kj::mv (item));
498+ totalSize += builder.back ().body .data .size ();
499+ largestMessage = kj::max (largestMessage, builder.back ().body .data .size ());
500+ }
501+ auto serializedBodies = builder.finish ();
502+
503+ auto estimatedSize = (totalSize + 2 ) / 3 * 4 + messageCount * 64 + 32 ;
504+ kj::Vector<char > bodyBuilder (estimatedSize);
505+ bodyBuilder.addAll (" {\" messages\" :[" _kj);
506+ for (size_t i = 0 ; i < messageCount; ++i) {
507+ bodyBuilder.addAll (" {\" body\" :\" " _kj);
508+ bodyBuilder.addAll (kj::encodeBase64 (serializedBodies[i].body .data ));
509+ bodyBuilder.add (' "' );
510+
511+ KJ_IF_SOME (contentType, serializedBodies[i].contentType ) {
512+ bodyBuilder.addAll (" ,\" contentType\" :\" " _kj);
513+ bodyBuilder.addAll (contentType);
514+ bodyBuilder.add (' "' );
515+ }
516+
517+ KJ_IF_SOME (delaySecs, serializedBodies[i].delaySeconds ) {
518+ bodyBuilder.addAll (" ,\" delaySecs\" : " _kj);
519+ bodyBuilder.addAll (kj::str (delaySecs));
520+ }
521+
522+ bodyBuilder.addAll (" }" _kj);
523+ if (i < messageCount - 1 ) {
524+ bodyBuilder.add (' ,' );
525+ }
526+ }
527+ bodyBuilder.addAll (" ]}" _kj);
528+ bodyBuilder.add (' \0 ' );
529+ KJ_DASSERT (bodyBuilder.size () <= estimatedSize);
530+ kj::String body (bodyBuilder.releaseAsArray ());
531+ KJ_DASSERT (jsg::JsValue::fromJson (js, body).isObject ());
532+
533+ auto client = context.getHttpClient (subrequestChannel, true , kj::none, " queue_send" _kjc);
534+
535+ auto headers = kj::HttpHeaders (context.getHeaderTable ());
536+ headers.addPtr (" CF-Queue-Batch-Count" _kj, kj::str (messageCount));
537+ headers.addPtr (" CF-Queue-Batch-Bytes" _kj, kj::str (totalSize));
538+ headers.addPtr (" CF-Queue-Largest-Msg" _kj, kj::str (largestMessage));
539+ headers.set (kj::HttpHeaderId::CONTENT_TYPE, MimeType::JSON.toString ());
540+
541+ KJ_IF_SOME (opts, options) {
542+ KJ_IF_SOME (secs, opts.delaySeconds ) {
543+ headers.addPtr (HDR_MSG_DELAY, kj::str (secs));
544+ }
545+ }
546+
547+ auto req =
548+ client->request (kj::HttpMethod::POST, " https://fake-host/batch" _kjc, headers, body.size ());
549+
550+ const auto & headerIds = context.getHeaderIds ();
551+ const auto exposeErrorCodes = workerd::FeatureFlags::get (js).getQueueExposeErrorCodes ();
552+ static constexpr auto handleWrite = [](auto req, auto body, auto client, auto & headerIds,
553+ bool exposeErrorCodes) -> kj::Promise<kj::String> {
554+ co_await req.body ->write (body.asBytes ());
555+ auto response = co_await req.response ;
556+
557+ if (exposeErrorCodes) {
558+ JSG_REQUIRE (response.statusCode == 200 , Error, buildQueueErrorMessage (response, headerIds));
559+ } else {
560+ JSG_REQUIRE (response.statusCode == 200 , Error,
561+ kj::str (" Queue sendBatch failed: " , response.statusText ));
562+ }
563+
564+ auto responseBody = co_await response.body ->readAllBytes ();
565+ co_return kj::str (responseBody.asChars ());
566+ };
567+
568+ auto promise =
569+ handleWrite (kj::mv (req), kj::mv (body), kj::mv (client), headerIds, exposeErrorCodes);
570+
571+ return context.awaitIo (
572+ js, kj::mv (promise), [&responseHandler](jsg::Lock& js, kj::String text) -> SendBatchResponse {
573+ auto parsed = jsg::JsValue::fromJson (js, text);
574+ return JSG_REQUIRE_NONNULL (
575+ responseHandler.tryUnwrap (js, parsed), Error, " Failed to parse queue send response" , text);
576+ });
577+ }
578+
397579QueueMessage::QueueMessage (
398580 jsg::Lock& js, rpc::QueueMessage::Reader message, IoPtr<QueueEventResult> result)
399581 : id(kj::str(message.getId())),
0 commit comments