-
Notifications
You must be signed in to change notification settings - Fork 0
Concepts: pluggable envelopes
Many services do not speak bare JSON: legacy APIs wrap every payload in an envelope (SOAP, JSON-RPC, a vendor {status, result} shape) and signal failures inside it. double-meh does not force problem+json — or any standard — on such services. Instead it exposes a small set of composable seams, and an envelope plugin is just those seams registered together, conventionally shipped as an install function (installX(io, options?) — the same shape as installZlibEncoders, installSW, installChannel).
| Seam | Direction | Registration |
|---|---|---|
| MIME alias | request |
io.mimeTypes.acme = 'application/vnd.acme+json' — makes as: 'acme' set the Content-Type. |
| Data processor | request |
io.registerData({match(data, options), encode(data, headers, options)}) — wraps the outgoing body; runs before the JSON fast path and may set its own headers. |
| MIME processor | response |
io.registerMime({match(contentType, response), decode(response, options)}) — parses/unwraps by content type; runs on success and error bodies alike (an error's output feeds BadStatus.problem). |
| Response inspector | response |
io.inspect.response(fn, match?) — post-decode envelope rework; the tool when the service uses a shared MIME type (plain application/json) that must not be hijacked globally. |
BadStatus.problem |
errors | the parsed error envelope, whatever the service calls it — see the envelope page. |
Everything downstream composes untouched: unwrapped payloads cache, dedupe, retry, and paginate like any other data.
const ACME_MIME = 'application/vnd.acme+json';
export const installAcme = io => {
io.mimeTypes.acme = ACME_MIME;
io.registerData({
match: (_data, options) => options.as === 'acme',
encode: (data, headers) => {
headers.set('Content-Type', ACME_MIME);
return JSON.stringify({v: 1, payload: data});
}
});
io.registerMime({
match: contentType => contentType.startsWith(ACME_MIME),
decode: async (response, options) => {
const doc = JSON.parse(await response.text());
// an envelope-level fault rides a 2xx: reject it like a bad status, fault as the data
if (doc.fault) throw new io.BadStatus(response, doc.fault, undefined, options);
return doc.payload;
}
});
return io;
};installAcme(io);
await io.post('/acme/echo', {name: 'unit-1'}, {as: 'acme'}); // sends {v: 1, payload: {...}}
const units = await io.get('/acme/units'); // callers see the payload, not the envelope-
HTTP-level — a non-2xx with a typed body: nothing to do. The MIME processor's decode runs on the error body, and the result lands on
BadStatus.dataandBadStatus.problemautomatically. -
Envelope-level — a fault member inside a 2xx (the SOAP/JSON-RPC habit): throw
io.BadStatus(response, fault, undefined, options)from the decode.IOErrors pass through the error mapping unwrapped, so callers get the sameinstanceof io.BadStatushandling as a real 4xx/5xx, with the transport status preserved and the fault on.data/.problem. One nuance: a 2xx fault envelope is cacheable like any 2xx, so repeats throw consistently until the entry is invalidated — servers that fault inside 2xx should sendCache-Controlaccordingly, and the plugin's callers can passcache: falsewhere that bites.
An XML envelope needs an XML parser, which double-meh deliberately does not ship (zero dependencies; browsers have DOMParser, CLIs do not). The seams above are the integration surface: a SOAP plugin is a data processor building the request envelope, a MIME processor matching application/soap+xml / text/xml that parses and unwraps Body — and throws BadStatus on Fault — plus an as alias. It is planned as a separate project on this surface.
- The pipeline
-
Requests & options —
asand the MIME alias registry -
Response envelope —
BadStatus.problemand the error taxonomy - Cookbook: requests & responses — response inspectors
Guides
Concepts
Services
Modules
- fetch transport
- keys & URLs
- helpers
- records
- sse
- storage backends
- compression encoders
- Service Worker integration
Cookbook