@crvouga/mockingbird-service-resend 1.0.0 → 1.0.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/CHANGELOG.md +7 -4
- package/dist/{chunk-MAW2TLUJ.js → chunk-C4R6D4IN.js} +97 -25
- package/dist/chunk-C4R6D4IN.js.map +7 -0
- package/dist/{chunk-A4NJUMTM.js → chunk-DRZ6C7OK.js} +5 -3
- package/dist/chunk-DRZ6C7OK.js.map +7 -0
- package/dist/cli.js +2 -2
- package/dist/index.d.ts +131 -103
- package/dist/index.js +1 -1
- package/dist/server.d.ts +235 -179
- package/dist/server.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-A4NJUMTM.js.map +0 -7
- package/dist/chunk-MAW2TLUJ.js.map +0 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
# Changelog — @crvouga/mockingbird-service-resend
|
|
2
2
|
|
|
3
|
+
## 1.0.1 (2026-09-26)
|
|
4
|
+
|
|
5
|
+
### Fixes and improvements
|
|
6
|
+
|
|
7
|
+
- take the rejected-request byte count from content-length, never by reading the body ([a0e5fb2](https://github.com/crvouga/mockingbird/commit/a0e5fb2fc80ab725c3552b612f3f091fd6c857fb))
|
|
8
|
+
- reject a present body by media type, and log what a rejected request sent ([12e351a](https://github.com/crvouga/mockingbird/commit/12e351aba53cfb7b41e31a7809419455fc2902eb))
|
|
9
|
+
|
|
3
10
|
## 1.0.0 (2026-09-24)
|
|
4
11
|
|
|
5
12
|
### ⚠️ Breaking changes
|
|
6
13
|
|
|
7
14
|
- vendor branding for every service; purge internal mocks and consumer names ([f689534](https://github.com/crvouga/mockingbird/commit/f6895343543550dafcb7d76b426cae7635e03226))
|
|
8
15
|
|
|
9
|
-
### Dependencies
|
|
10
|
-
|
|
11
|
-
- `@crvouga/mockingbird-service-sqlite`
|
|
12
|
-
|
|
13
16
|
## 0.2.0 (2026-09-23)
|
|
14
17
|
|
|
15
18
|
### Features
|
|
@@ -1503,6 +1503,90 @@ var operationMetadata = (operation) => {
|
|
|
1503
1503
|
|
|
1504
1504
|
// ../core/dist/service.js
|
|
1505
1505
|
import { Hono } from "hono";
|
|
1506
|
+
|
|
1507
|
+
// ../core/dist/validation.js
|
|
1508
|
+
var requestBodyOf = (context) => {
|
|
1509
|
+
const requestBody = context.operation.operation.requestBody;
|
|
1510
|
+
return requestBody ? deref(context.document, requestBody) : void 0;
|
|
1511
|
+
};
|
|
1512
|
+
var mediaTypeMatches = (accepted, actual) => {
|
|
1513
|
+
const pattern = mediaTypeOf(accepted) ?? accepted.toLowerCase();
|
|
1514
|
+
if (pattern === actual || pattern === "*/*")
|
|
1515
|
+
return true;
|
|
1516
|
+
const [type, subtype] = pattern.split("/");
|
|
1517
|
+
const [actualType, actualSubtype] = actual.split("/");
|
|
1518
|
+
if (type !== actualType && type !== "*")
|
|
1519
|
+
return false;
|
|
1520
|
+
if (subtype === void 0 || subtype === "*")
|
|
1521
|
+
return true;
|
|
1522
|
+
if (subtype.startsWith("*+"))
|
|
1523
|
+
return actualSubtype?.endsWith(subtype.slice(1)) === true;
|
|
1524
|
+
return false;
|
|
1525
|
+
};
|
|
1526
|
+
var acceptedKeyFor = (content, mediaType) => {
|
|
1527
|
+
if (!content || mediaType === void 0)
|
|
1528
|
+
return void 0;
|
|
1529
|
+
const keys = Object.keys(content);
|
|
1530
|
+
return keys.find((key) => mediaTypeOf(key) === mediaType) ?? keys.find((key) => mediaTypeMatches(key, mediaType));
|
|
1531
|
+
};
|
|
1532
|
+
var unsupportedMediaType = (context, options = {}) => {
|
|
1533
|
+
if (context.body.kind === "empty" && options.includeEmpty !== true)
|
|
1534
|
+
return void 0;
|
|
1535
|
+
const content = requestBodyOf(context)?.content;
|
|
1536
|
+
if (!content || Object.keys(content).length === 0)
|
|
1537
|
+
return void 0;
|
|
1538
|
+
const mediaType = mediaTypeOf(context.request.headers.get("content-type"));
|
|
1539
|
+
if (acceptedKeyFor(content, mediaType) !== void 0)
|
|
1540
|
+
return void 0;
|
|
1541
|
+
return { mediaType: mediaType ?? null, accepted: Object.keys(content) };
|
|
1542
|
+
};
|
|
1543
|
+
var recorded = /* @__PURE__ */ new WeakMap();
|
|
1544
|
+
var recordedIssues = (request) => recorded.get(request);
|
|
1545
|
+
var bodyIssues = (context, contentType = "application/json") => {
|
|
1546
|
+
const issues = findBodyIssues(context, contentType);
|
|
1547
|
+
if (issues.length > 0)
|
|
1548
|
+
recorded.set(context.request, issues);
|
|
1549
|
+
return issues;
|
|
1550
|
+
};
|
|
1551
|
+
var findBodyIssues = (context, contentType) => {
|
|
1552
|
+
const resolved = requestBodyOf(context);
|
|
1553
|
+
if (!resolved)
|
|
1554
|
+
return [];
|
|
1555
|
+
const unsupported = unsupportedMediaType(context);
|
|
1556
|
+
if (unsupported) {
|
|
1557
|
+
return [
|
|
1558
|
+
{
|
|
1559
|
+
path: "",
|
|
1560
|
+
kind: "media_type",
|
|
1561
|
+
message: unsupported.mediaType === null ? "request body has no content-type" : `request body media type ${unsupported.mediaType} is not supported`
|
|
1562
|
+
}
|
|
1563
|
+
];
|
|
1564
|
+
}
|
|
1565
|
+
const key = acceptedKeyFor(resolved.content, mediaTypeOf(context.request.headers.get("content-type"))) ?? contentType;
|
|
1566
|
+
const schema = resolved.content?.[key]?.schema;
|
|
1567
|
+
if (!schema)
|
|
1568
|
+
return [];
|
|
1569
|
+
if (context.body.kind === "invalid") {
|
|
1570
|
+
return [
|
|
1571
|
+
{
|
|
1572
|
+
path: "",
|
|
1573
|
+
kind: "syntax",
|
|
1574
|
+
message: `request body is not valid ${context.body.mediaType}`
|
|
1575
|
+
}
|
|
1576
|
+
];
|
|
1577
|
+
}
|
|
1578
|
+
const value = context.body.kind === "json" || context.body.kind === "form" || context.body.kind === "text" ? context.body.value : void 0;
|
|
1579
|
+
if (value === void 0) {
|
|
1580
|
+
return resolved.required && context.body.kind === "empty" ? [{ path: "", kind: "required", message: "request body is required" }] : [];
|
|
1581
|
+
}
|
|
1582
|
+
return validateValue(context.document, resolveSchema(context.document, schema), value).map((issue) => ({ path: issue.path.join("."), kind: "schema", message: issue.message }));
|
|
1583
|
+
};
|
|
1584
|
+
|
|
1585
|
+
// ../core/dist/service.js
|
|
1586
|
+
var withIssues = (request, response) => {
|
|
1587
|
+
const issues = response.status >= 400 ? recordedIssues(request) : void 0;
|
|
1588
|
+
return issues && issues.length > 0 ? annotateResponse(response, { issues }) : response;
|
|
1589
|
+
};
|
|
1506
1590
|
var defineOperations = (handlers) => handlers;
|
|
1507
1591
|
var OperationRegistryError = class extends Error {
|
|
1508
1592
|
problems;
|
|
@@ -1588,9 +1672,7 @@ var createService = (options) => {
|
|
|
1588
1672
|
now
|
|
1589
1673
|
};
|
|
1590
1674
|
const short = await options.before?.(context);
|
|
1591
|
-
|
|
1592
|
-
return short;
|
|
1593
|
-
return handler(context);
|
|
1675
|
+
return withIssues(request, short ?? await handler(context));
|
|
1594
1676
|
};
|
|
1595
1677
|
app.on(operation.method.toUpperCase(), honoPath(operation.path), route);
|
|
1596
1678
|
}
|
|
@@ -1627,7 +1709,7 @@ var restoreNamespace = (sqlite, namespace, snapshot) => {
|
|
|
1627
1709
|
};
|
|
1628
1710
|
|
|
1629
1711
|
// ../core/dist/version.js
|
|
1630
|
-
var PACKAGE_VERSION = true ? "1.0.
|
|
1712
|
+
var PACKAGE_VERSION = true ? "1.0.1" : UNRELEASED_VERSION;
|
|
1631
1713
|
|
|
1632
1714
|
// ../core/dist/signing.js
|
|
1633
1715
|
var encoder = new TextEncoder();
|
|
@@ -2385,8 +2467,15 @@ var createRuntime = (options) => {
|
|
|
2385
2467
|
const started = monotonicNow();
|
|
2386
2468
|
const url = new URL(request.url);
|
|
2387
2469
|
const operationId = operationIdFor(request, url.pathname);
|
|
2470
|
+
const contentLength = request.headers.get("content-length");
|
|
2471
|
+
const received = {
|
|
2472
|
+
contentType: request.headers.get("content-type"),
|
|
2473
|
+
bodyBytes: contentLength !== null && /^\d+$/.test(contentLength) ? Number(contentLength) : null,
|
|
2474
|
+
transferEncoding: request.headers.get("transfer-encoding")
|
|
2475
|
+
};
|
|
2388
2476
|
const log = (status, faultId, response2) => {
|
|
2389
2477
|
const noted = response2 ? responseNotes(response2) : void 0;
|
|
2478
|
+
const rejected = status >= 400 && faultId === void 0;
|
|
2390
2479
|
const entry = {
|
|
2391
2480
|
service: options.name,
|
|
2392
2481
|
namespace,
|
|
@@ -2398,7 +2487,9 @@ var createRuntime = (options) => {
|
|
|
2398
2487
|
unmatched: options.document !== void 0 && operationId === void 0,
|
|
2399
2488
|
...faultId !== void 0 ? { faultId } : {},
|
|
2400
2489
|
...noted?.ids && Object.keys(noted.ids).length > 0 ? { ids: noted.ids } : {},
|
|
2401
|
-
...noted?.adopted ? { adopted: true } : {}
|
|
2490
|
+
...noted?.adopted ? { adopted: true } : {},
|
|
2491
|
+
...rejected ? { request: { ...received } } : {},
|
|
2492
|
+
...rejected && noted?.issues && noted.issues.length > 0 ? { issues: noted.issues.map((issue) => ({ ...issue })) } : {}
|
|
2402
2493
|
};
|
|
2403
2494
|
metrics.record(entry);
|
|
2404
2495
|
journal.record({ ...entry, at: new Date(clock.now()).toISOString() });
|
|
@@ -2605,25 +2696,6 @@ var presetRoutes = (presets, runtime) => ({
|
|
|
2605
2696
|
}
|
|
2606
2697
|
});
|
|
2607
2698
|
|
|
2608
|
-
// ../core/dist/validation.js
|
|
2609
|
-
var bodyIssues = (context, contentType = "application/json") => {
|
|
2610
|
-
const requestBody = context.operation.operation.requestBody;
|
|
2611
|
-
if (!requestBody)
|
|
2612
|
-
return [];
|
|
2613
|
-
const resolved = deref(context.document, requestBody);
|
|
2614
|
-
const schema = resolved.content?.[contentType]?.schema;
|
|
2615
|
-
if (!schema)
|
|
2616
|
-
return [];
|
|
2617
|
-
const value = context.body.kind === "json" || context.body.kind === "form" ? context.body.value : void 0;
|
|
2618
|
-
if (context.body.kind === "invalid") {
|
|
2619
|
-
return [{ path: "", message: `request body is not valid ${context.body.mediaType}` }];
|
|
2620
|
-
}
|
|
2621
|
-
if (value === void 0) {
|
|
2622
|
-
return resolved.required ? [{ path: "", message: "request body is required" }] : [];
|
|
2623
|
-
}
|
|
2624
|
-
return validateValue(context.document, resolveSchema(context.document, schema), value).map((issue) => ({ path: issue.path.join("."), message: issue.message }));
|
|
2625
|
-
};
|
|
2626
|
-
|
|
2627
2699
|
// src/generated/openapi.ts
|
|
2628
2700
|
var document = JSON.parse(`{"openapi":"3.1.0","info":{"title":"Resend API (Mockingbird subset)","description":"Stateful mock subset of the Resend email API: send (with \`Idempotency-Key\`), retrieve a sent\\nemail, and the received-email (inbound) endpoints our care-chat hydration reads, including\\nthe attachment download URL. Trimmed from Resend's published API reference to what\\n\`resend@4.8.0\` and our backend call.\\n","version":"1","x-mockingbird-upstream":{"note":"Trimmed from https://resend.com/docs/api-reference (emails/send-email, emails/retrieve-email, receiving/retrieve-received-email, receiving/list-attachments); request/response shapes cross-checked against resend@4.8.0 (dist/index.mjs) and our consumer (send-emails.service.ts, notification-dispatcher email.ts, resend-inbound-email.client.ts)."}},"servers":[{"url":"https://api.resend.com"}],"security":[{"bearerAuth":[]}],"paths":{"/emails":{"post":{"operationId":"SendEmail","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"parameters":[{"name":"Idempotency-Key","in":"header","description":"Replays the first response for the same key and payload (1\u2013256 characters).","schema":{"type":"string","pattern":"^[!-~]{1,256}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendEmailBody"}}}},"responses":{"200":{"description":"Accepted","content":{"application/json":{"schema":{"type":"object","required":["id"],"properties":{"id":{"type":"string","x-mockingbird-resource":{"type":"email","identity":true},"x-mockingbird-volatile":{"kind":"id"}}}}}}},"400":{"description":"Invalid idempotency key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Idempotency key reused with another payload, or still in flight","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/emails/{email_id}":{"parameters":[{"name":"email_id","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"email","missing":"00000000-0000-4000-8000-000000000000"}}}],"get":{"operationId":"GetEmail","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"The sent email","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Email"}}}},"401":{"description":"Missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Unknown email","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/emails/receiving/{email_id}":{"parameters":[{"$ref":"#/components/parameters/ReceivedEmailId"}],"get":{"operationId":"GetReceivedEmail","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"The received email, with its content","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReceivedEmail"}}}},"401":{"description":"Missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Unknown email","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/emails/receiving/{email_id}/attachments":{"parameters":[{"$ref":"#/components/parameters/ReceivedEmailId"}],"get":{"operationId":"ListReceivedEmailAttachments","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"Attachment metadata with short-lived download URLs","content":{"application/json":{"schema":{"type":"object","required":["object","data"],"properties":{"object":{"type":"string","enum":["list"]},"has_more":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/ReceivedAttachment"}}}}}}},"401":{"description":"Missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Unknown email","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/downloads/inbound/{attachment_id}":{"get":{"operationId":"DownloadReceivedAttachment","description":"The \`download_url\` of a received attachment: unauthenticated (it stands in for Resend's signed CDN URL), the raw bytes with their content type and length.","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"attachment_id","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"received_attachment","missing":"00000000-0000-4000-8000-000000000000"}}}],"responses":{"200":{"description":"The attachment bytes","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"404":{"description":"Unknown attachment","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"\`Authorization: Bearer re_\u2026\`"}},"parameters":{"ReceivedEmailId":{"name":"email_id","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"received_email","missing":"00000000-0000-4000-8000-000000000000"}}}},"schemas":{"Address":{"type":"string","description":"\`email@example.com\` or \`Name <email@example.com>\`","pattern":"^([^<>@]* <[A-Za-z0-9._+-]+@[A-Za-z0-9-]+(\\\\.[A-Za-z0-9-]+)+>|[A-Za-z0-9._+-]+@[A-Za-z0-9-]+(\\\\.[A-Za-z0-9-]+)+)$","maxLength":120},"Recipients":{"oneOf":[{"$ref":"#/components/schemas/Address"},{"type":"array","minItems":1,"maxItems":50,"items":{"$ref":"#/components/schemas/Address"}}]},"SendEmailBody":{"type":"object","required":["from","to","subject"],"properties":{"from":{"$ref":"#/components/schemas/Address"},"to":{"$ref":"#/components/schemas/Recipients"},"subject":{"type":"string","minLength":1,"maxLength":200},"html":{"type":"string","maxLength":5000},"text":{"type":"string","maxLength":5000},"cc":{"$ref":"#/components/schemas/Recipients"},"bcc":{"$ref":"#/components/schemas/Recipients"},"reply_to":{"$ref":"#/components/schemas/Recipients"},"scheduled_at":{"type":"string","maxLength":60},"headers":{"type":"object","additionalProperties":{"type":"string","maxLength":500}},"tags":{"type":"array","maxItems":10,"items":{"type":"object","required":["name","value"],"properties":{"name":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,256}$"},"value":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,256}$"}}}},"attachments":{"type":"array","maxItems":5,"items":{"type":"object","properties":{"filename":{"type":"string","minLength":1,"maxLength":100},"content":{"description":"Base64 bytes (or a JSON-serialised Node Buffer).","oneOf":[{"type":"string","maxLength":10000},{"type":"object","required":["type","data"],"properties":{"type":{"type":"string","enum":["Buffer"]},"data":{"type":"array","maxItems":10000,"items":{"type":"integer","minimum":0,"maximum":255}}}}]},"path":{"type":"string","maxLength":500},"content_type":{"type":"string","maxLength":100},"inline_content_id":{"type":"string","maxLength":100}}}}}},"Email":{"type":"object","required":["object","id","to","from","created_at","subject","html","text","bcc","cc","reply_to","last_event"],"properties":{"object":{"type":"string","enum":["email"]},"id":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"to":{"type":"array","items":{"type":"string"}},"from":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"subject":{"type":"string"},"html":{"type":["string","null"]},"text":{"type":["string","null"]},"bcc":{"type":["array","null"],"items":{"type":"string"}},"cc":{"type":["array","null"],"items":{"type":"string"}},"reply_to":{"type":["array","null"],"items":{"type":"string"}},"last_event":{"type":"string","enum":["delivered","scheduled"]},"scheduled_at":{"type":["string","null"]},"tags":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"value":{"type":"string"}}}}}},"ReceivedAttachment":{"type":"object","required":["id","filename","content_type","size","download_url"],"properties":{"id":{"type":"string","x-mockingbird-resource":{"type":"received_attachment","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"filename":{"type":"string"},"content_type":{"type":"string"},"content_disposition":{"type":["string","null"]},"content_id":{"type":["string","null"]},"size":{"type":"integer"},"download_url":{"type":"string","x-mockingbird-volatile":{"kind":"url"}},"expires_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"ReceivedEmail":{"type":"object","required":["object","id","to","from","created_at","subject","html","text","message_id","attachments"],"properties":{"object":{"type":"string","enum":["email"]},"id":{"type":"string","x-mockingbird-resource":{"type":"received_email","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"to":{"type":"array","items":{"type":"string"}},"from":{"type":"string"},"cc":{"type":"array","items":{"type":"string"}},"bcc":{"type":"array","items":{"type":"string"}},"reply_to":{"type":"array","items":{"type":"string"}},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"subject":{"type":["string","null"]},"html":{"type":["string","null"]},"text":{"type":["string","null"]},"headers":{"type":"object","additionalProperties":{"type":"string"}},"message_id":{"type":["string","null"]},"attachments":{"type":"array","items":{"type":"object","required":["id","filename","content_type"],"properties":{"id":{"type":"string"},"filename":{"type":"string"},"content_type":{"type":"string"},"content_disposition":{"type":["string","null"]},"content_id":{"type":["string","null"]}}}}}},"Error":{"type":"object","description":"Resend's error body; the SDK returns it verbatim as \`error\`.","required":["statusCode","name","message"],"properties":{"statusCode":{"type":"integer"},"name":{"type":"string"},"message":{"type":"string"}}}}}}`);
|
|
2629
2701
|
var operationIds = ["SendEmail", "GetEmail", "GetReceivedEmail", "ListReceivedEmailAttachments", "DownloadReceivedAttachment"];
|
|
@@ -3230,4 +3302,4 @@ export {
|
|
|
3230
3302
|
bareAddress,
|
|
3231
3303
|
ResendAPI
|
|
3232
3304
|
};
|
|
3233
|
-
//# sourceMappingURL=chunk-
|
|
3305
|
+
//# sourceMappingURL=chunk-C4R6D4IN.js.map
|