Skip to content

feat(queue): add helix-shared-queue base package - #1272

Merged
tripodsan merged 5 commits into
mainfrom
add-helix-shared-queue
Sep 11, 2026
Merged

feat(queue): add helix-shared-queue base package#1272
tripodsan merged 5 commits into
mainfrom
add-helix-shared-queue

Conversation

@tripodsan

@tripodsan tripodsan commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds @adobe/helix-shared-queue, a pluggable queue abstraction (QueueService.queue(name)Queue, backed by an injectable QueueBackend/backendFactory), mirroring @adobe/helix-shared-storage's Storage/Bucket pattern so a concrete provider package (e.g. a future helix-shared-queue-sqs) can be swapped in without forking BatchedQueueClient (@adobe/helix-admin-support).
  • Queue#send/receive/delete generalize BatchedQueueClient's send/long-poll-receive/batch-delete semantics; groupId/dedupId are named generically (not SQS's MessageGroupId/MessageDeduplicationId) so a future Azure Service Bus backend can map them onto session id + native dedup.
  • sendBatch/receiveBatch/deleteBatch are fully mandatory on AbstractQueueBackend with no generic defaults, since batching limits, long-poll call shape, and ack-token shape are all provider-specific. This base package has zero cloud SDK dependencies.
  • isSwapped(message)/deserialize(message) (added after the initial version of this PR, once two concrete backends surfaced the need): a backend that spills oversized messages to blob storage should not have receive() transparently fetch every spilled message's real content — that forces an I/O round-trip even when the caller only needed a couple of cheap fields off the message (e.g. helix-indexer's notify() reads just owner/repo without ever touching storage). These two methods let a caller check cheaply (no I/O) and fetch only when actually needed, identically regardless of which backend is plugged in. Both have generic defaults on AbstractQueueBackend (isSwapped always false, deserialize returns the message unchanged), since spillover support is optional.
  • Updates root CLAUDE.md with a package entry.

Addresses #1269. Companion issues #1270 (SQS backend) and #1271 (Azure Service Bus backend) are now implemented in #1274 and #1275 respectively, both depending on this PR.

Test plan

  • npm run lint (workspace-wide, 19 projects) passes
  • npm test (workspace-wide, 19 projects) passes
  • New package hits 100% line/branch/statement/function coverage (.nycrc.json gate), including isSwapped()/deserialize()'s delegation and generic-default behavior
  • Manually verified the public API shape end-to-end with a fake backendFactory (new QueueService({ backendFactory }).queue('name').send([...]))

🤖 Generated with Claude Code

Introduces a pluggable queue abstraction (QueueService -> Queue, backed
by an injectable QueueBackend) mirroring helix-shared-storage's
Storage/Bucket/backendFactory pattern, so a concrete provider package
(e.g. a future helix-shared-queue-sqs) can be swapped in without
forking BatchedQueueClient (@adobe/helix-admin-support). This base
package ships no cloud SDK; sendBatch/receiveBatch/deleteBatch are
fully backend-owned since batching limits, long-poll shape, and
ack-token shape are all provider-specific.

Addresses #1269.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

This PR will trigger a minor release when merged.

tripodsan and others added 3 commits September 11, 2026 10:28
… spillover

receive() no longer promises transparent dereferencing of spilled
messages -- that was forcing an I/O fetch for every swapped message
regardless of whether the caller actually needed the full body,
defeating an optimization existing consumers rely on (e.g. reading
just owner/repo off a message without ever touching blob storage).

Add isSwapped(message)/deserialize(message) to AbstractQueueBackend
(generic defaults: always false / return unchanged, since spillover
support is optional) and Queue (thin delegation), so callers opt into
the blob fetch only when they need the real body, and this works
identically regardless of which backend is plugged in.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ntion

Queue#backend/#log and QueueService#log/#backendFactory/#closed become
real private class fields -- neither is subclassed at the instance
level (QueueServiceSqs/QueueServiceServiceBus only override the static
fromContext), so nothing needs prototype-chain access to them.

AbstractQueueBackend._wrapError stays _-prefixed: it's called via
this._wrapError(...) from concrete backend subclasses' own method
bodies, and true #private members aren't reachable across that
inheritance boundary -- documented inline.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sugar over send([message]) unwrapping the single resulting messageId, for
callers that only ever send one message at a time (e.g. helix-api-service's
Job.js, which currently does new SQSClient().send(new SendMessageCommand(...))
directly and reads result.MessageId).
@tripodsan
tripodsan merged commit 567d658 into main Sep 11, 2026
6 checks passed
@tripodsan
tripodsan deleted the add-helix-shared-queue branch September 11, 2026 12:27
@tripodsan

Copy link
Copy Markdown
Contributor Author

🎉 This PR is included in version 9.0.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

@tripodsan

Copy link
Copy Markdown
Contributor Author

🎉 This PR is included in version @adobe/helix-shared-queue-v1.0.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants