refactor(audience-core): replace Transport interface with HttpSend + structured TransportResult#2839
Merged
Merged
Conversation
…structured TransportResult
The Transport interface had one production implementation (httpTransport)
and was being bypassed anyway: ConsentManager imported httpSend directly
instead of going through the injected transport. Tests had to mock at
two different levels (jest.fn for queue, jest.mock for consent).
Collapse the abstraction:
- Delete Transport interface and httpTransport wrapper.
- Export type HttpSend = typeof httpSend so tests can mock by injection.
- MessageQueue takes a send: HttpSend constructor parameter.
- createConsentManager takes a send: HttpSend parameter (new second arg)
and stops importing httpSend directly. Same instance flows to both,
so the transport layer is finally uniform.
- httpSend returns TransportResult { ok, error? } instead of boolean.
TransportError carries { status, endpoint, body, cause } for both
HTTP failures and network failures.
- New errors.ts module hosts TransportError + TransportResult to keep
types.ts focused on wire shapes.
- Removes the batchSize() helper / payload-shape inspection that lived
in transport.ts. The transport no longer cares what it's sending.
Behaviour is unchanged: queue still drops messages on result.ok, still
retries on failure, still flushes via keepalive on page unload. The
HttpSend contract is documented as never-rejects so floating promises
in flushUnload and notifyBackend are safe.
Test mocks updated to match. The old jest.mock('./transport') in
consent.test.ts is gone — fakes are passed by injection now.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
View your CI Pipeline Execution ↗ for commit 518db0e
☁️ Nx Cloud last updated this comment at |
✅ Pixel Bundle Size — @imtbl/pixel
Budget: 10.00 KB gzipped (warn at 8.00 KB) |
bkbooth
reviewed
Apr 9, 2026
Addresses review feedback (bkbooth): stack traces were lost because
TransportError was a plain interface. Instances now carry a native
stack and integrate with devtools / Sentry / @imtbl/metrics without
custom wrapping.
- Convert TransportError from interface to class extends Error.
- Use ES2022 Error.cause via super(msg, { cause }) — the existing
.cause field comes for free, source-compatible with consumers.
- status / endpoint / body become readonly class fields; name and
message set so default stringification is useful.
- Two construction sites in transport.ts switch to new TransportError.
- Collapse the causeError workaround in the catch branch: the
TransportError IS an Error, so trackError consumes it directly and
the original error is preserved via cause chaining.
Tests: transport.test.ts uses toMatchObject + toBeInstanceOf and adds
a lock-in test for the Error contract (instanceof Error, name, stack).
queue.test.ts fail fixture constructs via new TransportError({...}).
No behaviour change visible to studios — field access on error.status /
endpoint / body / cause is identical (cause is native Error.cause,
accessed the same way).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
bkbooth
approved these changes
Apr 9, 2026
ImmutableJeffrey
added a commit
that referenced
this pull request
Apr 9, 2026
The squash merge of PR #2839 changed TransportError from an interface to a class extending Error. Update test fixtures and the partial-success detection in httpSend to use `new TransportError(...)` instead of plain object literals. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ImmutableJeffrey
added a commit
that referenced
this pull request
Apr 9, 2026
The squash merge of PR #2839 changed TransportError from an interface to a class extending Error. Update test fixtures and the partial-success detection in httpSend to use `new TransportError(...)` instead of plain object literals. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Replaces the
Transportinterface in@imtbl/audience-corewith anHttpSendfunction type and injects it intocreateConsentManager, so consent and queue both use the same transport mechanism. Removes thehttpTransportwrapper, thebatchSize()payload-shape leak intransport.ts, and the divergent test mocking strategies (consent.test.tsno longer needsjest.mock('./transport', …)).httpSendnow returnsTransportResult({ ok, error? }) instead ofPromise<boolean>, with a typedTransportErrorcarrying{ status, endpoint, body, cause }. BothMessageQueue.flushUnloadandConsentManager's consent PUT remain fire-and-forget — the newHttpSendcontract documents that implementations must not reject.TransportErrorandTransportResultlive in a newcore/src/errors.tsmodule sotypes.tsstays focused on wire shapes.Pure refactor. No behaviour change visible to studios.
Linear
Test plan
pnpm --filter @imtbl/audience-core --filter @imtbl/audience --filter @imtbl/pixel test— all pass (112 core + 52 sdk + 68 pixel)pnpm --filter @imtbl/audience-core --filter @imtbl/audience --filter @imtbl/pixel typecheck— cleanpnpm --filter @imtbl/audience-core --filter @imtbl/audience --filter @imtbl/pixel lint— cleanWhat's next
Two follow-up PRs build on this branch (separate Linear tickets to come):
feat(audience-core): unified AudienceError surface in core + fix partial-success silent dropfeat(audience+pixel): wire onError callback end-to-end through web SDK and pixel🤖 Generated with Claude Code