@particle-academy/resend-js 0.1.0

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 ADDED
@@ -0,0 +1,58 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@particle-academy/resend-ui`, `@particle-academy/resend-js`,
4
+ `particle-academy/resend-php` and `fancy-resend`.
5
+
6
+ The four packages share one version, because they are generated from one
7
+ `provider/` definition and a version that meant something different in each
8
+ would be a version nobody could reason about.
9
+
10
+ ## [0.1.0] — 2026-08-20
11
+
12
+ First release. Ported from the vendored connector at
13
+ `px-ui-sandbox/resources/flow-nodes/_resend`.
14
+
15
+ ### Added
16
+
17
+ - `email_send` — send an email. `POST /emails`, JSON body.
18
+ - A faker for it, so the node runs on a canvas with no key, no network and no
19
+ Resend account.
20
+ - Webhook signature verification is NOT included; see below.
21
+
22
+ ### The reason this provider exists in the set
23
+
24
+ **Resend has no test estate.** Roughly a third of the providers worth
25
+ connecting do not — and for every one of them "just try it" means writing to
26
+ production. So `mode` here offers `fake` and `live` and nothing else: offering
27
+ a choice the provider cannot honour is an invitation to pick it and read an
28
+ error.
29
+
30
+ Resend's simulator recipients (`delivered@resend.dev`, `bounced@resend.dev`, …)
31
+ look like a sandbox and are not one. Those sends are real, billed, and counted
32
+ against the quota. Modelling them as an estate would put a live send behind a
33
+ control labelled "test".
34
+
35
+ ### Two places this deliberately diverges from what it ported
36
+
37
+ - **The faker returns only `id`.** The predecessor's returned `from`, `to`,
38
+ `subject`, `created_at` and `last_event` too. Those belong to
39
+ `GET /emails/{id}`; `POST /emails` answers with one field. A faker richer
40
+ than the provider lets an author wire a path that works against the fake and
41
+ is `undefined` on the first real send.
42
+ - **An idempotency key is sent.** The predecessor accepted one and did not
43
+ supply it. The generated executor derives it from the run and the node, so a
44
+ resumed durable run mails nobody twice.
45
+
46
+ ### Not here yet
47
+
48
+ Resend signs its webhooks with **Svix**, and three things about that scheme are
49
+ outside the generator's verification vocabulary today: the signed payload is
50
+ `{svix-id}.{svix-timestamp}.{body}` and `{svix-id}` is not a payload slot; the
51
+ signature header carries a space-separated list of `v1,<sig>` pairs, which is
52
+ neither `compound` nor `prefixed`; and the secret is base64 *after* its
53
+ `whsec_` prefix and must be decoded to raw bytes before it is used as the HMAC
54
+ key. Adding it means extending the vocabulary in three directions at once, and
55
+ it is the next thing that will genuinely stress it — Stripe is currently the
56
+ only webhook the vocabulary has ever described.
57
+
58
+ [0.1.0]: https://github.com/Fancy-Friends/resend/releases/tag/v0.1.0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Particle Academy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # Resend
2
+
3
+ Resend for [fancy-flow][flow] — as **four imported, versioned packages**, one
4
+ per runtime. Not vendored source: a copy cannot be upgraded, and third-party APIs
5
+ change.
6
+
7
+ [flow]: https://github.com/Particle-Academy/fancy-flow
8
+
9
+ | Runtime | Package | Install |
10
+ |---|---|---|
11
+ | Authoring surface (every host) | `@particle-academy/resend-ui` | `npm install @particle-academy/resend-ui` |
12
+ | Node | `@particle-academy/resend-js` | `npm install @particle-academy/resend-js` |
13
+ | PHP 8.4+ | `particle-academy/resend-php` | `composer require particle-academy/resend-php` |
14
+ | Python 3.11+ | `fancy-resend` | `pip install fancy-resend` |
15
+
16
+ The `ui` package is the editor surface and is React on every host — a PHP or
17
+ Python project installs it *and* its own runtime package, and never the `js` one.
18
+
19
+ ## What it costs you
20
+
21
+ One dependency: `@particle-academy/fancy-connector-core` (or
22
+ `particle-academy/fancy-connector-core` on Composer), which the `js` and `php`
23
+ packages pull in themselves. The Python package has **zero** runtime
24
+ dependencies.
25
+
26
+ **No Resend SDK.** Plain HTTP, deliberately: a vendor SDK is third-party code
27
+ subject to the kit's full approval bar, and one per provider is hundreds of
28
+ dependencies nobody is tracking.
29
+
30
+ ## Run it before you have credentials
31
+
32
+ Every operation ships a **faker**, whether or not Resend has a sandbox. Set a
33
+ node's mode to `fake` and it returns the shape Resend actually publishes — the
34
+ same field names, deterministically — so you can wire the downstream nodes before
35
+ touching an account, a key, or a network.
36
+
37
+ ## This repository is generated
38
+
39
+ `provider/` is the source. Everything under `packages/` is emitted from it and
40
+ **must not be hand-edited** — CI regenerates and diffs on every push, and the
41
+ next protocol sync destroys anything it finds. See [`AGENTS.md`](AGENTS.md).
42
+
43
+ ## Two namespaces, which do not match on purpose
44
+
45
+ The repo is `github.com/Fancy-Friends/resend`; the packages publish under
46
+ `particle-academy`. Nothing derives one from the other — the names come from
47
+ weaver's `friends.json` and nowhere else.
48
+
49
+ ## Licence
50
+
51
+ MIT.
@@ -0,0 +1,99 @@
1
+ import { callConnector } from '@particle-academy/fancy-connector-core';
2
+
3
+ // src/faker.ts
4
+ function fakeEmailSend({ config, fake }) {
5
+ return {
6
+ "id": `${fake.hex(8)}-${fake.hex(4)}-${fake.hex(4)}-${fake.hex(4)}-${fake.hex(12)}`
7
+ };
8
+ }
9
+ var resendFaker = (operation, request) => {
10
+ switch (operation) {
11
+ case "email_send":
12
+ return fakeEmailSend(request);
13
+ default:
14
+ throw new Error(
15
+ `resend: no fake response is defined for "${operation}". Add a fixture under provider/fixtures/ and regenerate \u2014 a connector without a faker cannot be developed against, tested, or demonstrated.`
16
+ );
17
+ }
18
+ };
19
+
20
+ // src/service.ts
21
+ var CONNECTOR_API_VERSION = 1;
22
+ var RESEND_BASE_URLS = {
23
+ "live": "https://api.resend.com"
24
+ };
25
+ var RESEND_REQUIRES = [
26
+ "apiKey"
27
+ ];
28
+ function resendAuthorize(credentials, request, _mode) {
29
+ request.headers["User-Agent"] = "fancy-flow-connector";
30
+ request.headers.Authorization = `Bearer ${credentials.apiKey ?? ""}`;
31
+ }
32
+ var RESEND = {
33
+ service: "resend",
34
+ title: "Resend",
35
+ sandbox: "none",
36
+ baseUrls: { ...RESEND_BASE_URLS },
37
+ requires: [...RESEND_REQUIRES],
38
+ authorize: resendAuthorize,
39
+ // Resend honours this for 24 hours. It is what makes a retried durable run
40
+ // resend nothing instead of mailing the recipient twice -- the difference
41
+ // between `idempotent` and `unsafe-to-replay` is that this one is merely
42
+ // embarrassing, not financial, but the header costs nothing and removes it
43
+ // entirely.
44
+ idempotencyHeader: "Idempotency-Key",
45
+ faker: resendFaker
46
+ };
47
+ var EMAIL_SEND_OPERATION = "email_send";
48
+ async function resendEmailSend(options) {
49
+ const config = options.config ?? {};
50
+ if (config.from === void 0 || config.from === null || config.from === "") {
51
+ throw new Error(`email_send: "from" is required (From).`);
52
+ }
53
+ if (config.to === void 0 || config.to === null || config.to === "") {
54
+ throw new Error(`email_send: "to" is required (To).`);
55
+ }
56
+ if (!(config.html !== void 0 && config.html !== null && config.html !== "" || config.text !== void 0 && config.text !== null && config.text !== "")) {
57
+ throw new Error(`email_send: needs an "html" or "text" body \u2014 an empty email is never intended.`);
58
+ }
59
+ return callConnector(RESEND, {
60
+ operation: EMAIL_SEND_OPERATION,
61
+ config,
62
+ input: options.input,
63
+ ...options.credentials === void 0 ? {} : { credentials: options.credentials },
64
+ ...options.mode === void 0 ? {} : { mode: options.mode },
65
+ ...options.connectionId === void 0 ? {} : { connectionId: options.connectionId },
66
+ ...options.attempts === void 0 ? {} : { attempts: options.attempts },
67
+ ...options.transport === void 0 ? {} : { transport: options.transport },
68
+ ...options.idempotencyKey === void 0 ? {} : { idempotencyKey: options.idempotencyKey },
69
+ request: {
70
+ method: "POST",
71
+ path: "/emails",
72
+ json: {
73
+ "from": String(config.from),
74
+ "to": toList(config.to),
75
+ "subject": config.subject !== void 0 && config.subject !== null && config.subject !== "" ? String(config.subject) : "",
76
+ ...config.html !== void 0 && config.html !== null && config.html !== "" ? { "html": String(config.html) } : {},
77
+ ...config.text !== void 0 && config.text !== null && config.text !== "" ? { "text": String(config.text) } : {},
78
+ ...config.replyTo !== void 0 && config.replyTo !== null && config.replyTo !== "" ? { "reply_to": String(config.replyTo) } : {},
79
+ "headers": headersMap(config.headers)
80
+ }
81
+ }
82
+ });
83
+ }
84
+ function toList(value) {
85
+ const items = Array.isArray(value) ? value.map(String) : typeof value === "string" ? value.split(",") : [];
86
+ return items.map((item) => item.trim()).filter(Boolean);
87
+ }
88
+ function headersMap(value) {
89
+ if (!value || typeof value !== "object") return {};
90
+ const out = {};
91
+ for (const [key, item] of Object.entries(value)) {
92
+ if (item !== void 0 && item !== null && item !== "") out[key] = String(item);
93
+ }
94
+ return out;
95
+ }
96
+
97
+ export { CONNECTOR_API_VERSION, EMAIL_SEND_OPERATION, RESEND, RESEND_BASE_URLS, RESEND_REQUIRES, resendAuthorize, resendEmailSend, resendFaker };
98
+ //# sourceMappingURL=chunk-QIYVVKWU.js.map
99
+ //# sourceMappingURL=chunk-QIYVVKWU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/faker.ts","../src/service.ts","../src/actions/email-send.ts"],"names":[],"mappings":"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","file":"chunk-QIYVVKWU.js","sourcesContent":["/**\n * GENERATED FILE — do not edit.\n *\n * Emitted from provider/fixtures/ by weaver's generator.\n * A hand-edit here is destroyed by the next protocol sync, which is worse than\n * being rejected, because it works until it silently does not. Fix\n * provider/fixtures/ (or weaver's template/) and regenerate:\n *\n * npm run provider -- resend\n */\n\n/**\n * The Resend faker.\n *\n * Shapes, not behaviour: the goal is that a downstream node sees the field\n * NAMES Resend actually publishes, so an author can wire {{ $json.data.id }}\n * against a fake and have it keep working against the real thing.\n *\n * Deterministic — same inputs, same output. A faker returning a fresh uuid\n * every call cannot be asserted on, so its fixtures degrade to \"it did not\n * throw\", which is the assertion that catches nothing.\n */\n\nimport type { ConnectorFaker, FakeRequest } from \"@particle-academy/fancy-connector-core\";\n\nfunction fakeEmailSend({ config, fake }: FakeRequest): unknown {\n return {\n \"id\": `${fake.hex(8)}-${fake.hex(4)}-${fake.hex(4)}-${fake.hex(4)}-${fake.hex(12)}`,\n };\n}\n\nexport const resendFaker: ConnectorFaker = (operation, request) => {\n switch (operation) {\n case \"email_send\":\n return fakeEmailSend(request);\n\n default:\n // A faker asked for an operation it has no shape for must SAY so. Making\n // something up would produce a green run whose output silently has none\n // of the fields the author is about to reference.\n throw new Error(\n `resend: no fake response is defined for \"${operation}\". ` +\n \"Add a fixture under provider/fixtures/ and regenerate — a connector without a faker \" +\n \"cannot be developed against, tested, or demonstrated.\",\n );\n }\n};\n","/**\n * GENERATED FILE — do not edit.\n *\n * Emitted from provider/manifest.json by weaver's generator.\n * A hand-edit here is destroyed by the next protocol sync, which is worse than\n * being rejected, because it works until it silently does not. Fix\n * provider/manifest.json (or weaver's template/) and regenerate:\n *\n * npm run provider -- resend\n */\n\n/**\n * Resend, as one service descriptor shared by every Resend operation.\n *\n * @particle-academy/fancy-connector-core carries what is true of ALL\n * connectors. This carries what is true of Resend: its base URL, its auth\n * scheme, its idempotency header, and its faker.\n *\n * ## The sandbox trap, written down where it is used\n *\n * Resend has NO test estate, and somebody checked. Its simulator RECIPIENTS --\n * delivered@resend.dev, bounced@resend.dev, complained@resend.dev,\n * suppressed@resend.dev -- are not one: the send is real, it is billed, and it\n * counts against the quota. Modelling them as a sandbox would put a live send\n * behind a control labelled \"test\", so `mode` does not offer sandbox at all\n * and `fake` is the primary development mode rather than the fallback.\n */\n\nimport type { ConnectorMode, PreparedRequest, ServiceDescriptor } from \"@particle-academy/fancy-connector-core\";\n\nimport { resendFaker } from \"./faker.js\";\n\n/**\n * The connector API version this package was GENERATED against.\n *\n * A literal, never imported. An imported constant lets an upgrade rewrite the\n * very claim it exists to detect, after which the copy agrees with itself\n * forever.\n */\nexport const CONNECTOR_API_VERSION = 1;\n\nexport const RESEND_BASE_URLS = {\n \"live\": \"https://api.resend.com\"\n} as const;\n\n/** Credential keys a remote call cannot proceed without. */\nexport const RESEND_REQUIRES = [\n \"apiKey\"\n] as const;\n\n/**\n * Apply Resend's auth scheme to an outgoing request.\n *\n *\n *\n * The mode is passed in because for some providers auth and estate are the\n * same decision expressed in the URL; here it is unused, and saying so is\n * cheaper than wondering later whether it was forgotten.\n */\nexport function resendAuthorize(\n credentials: Record<string, string | undefined>,\n request: PreparedRequest,\n _mode: ConnectorMode,\n): void {\n request.headers[\"User-Agent\"] = \"fancy-flow-connector\";\n\n request.headers.Authorization = `Bearer ${credentials.apiKey ?? \"\"}`;\n}\n\n/** The Resend service, for the TypeScript runtime. */\nexport const RESEND: ServiceDescriptor = {\n service: \"resend\",\n title: \"Resend\",\n sandbox: \"none\",\n baseUrls: { ...RESEND_BASE_URLS },\n requires: [...RESEND_REQUIRES],\n authorize: resendAuthorize,\n // Resend honours this for 24 hours. It is what makes a retried durable run\n // resend nothing instead of mailing the recipient twice -- the difference\n // between `idempotent` and `unsafe-to-replay` is that this one is merely\n // embarrassing, not financial, but the header costs nothing and removes it\n // entirely.\n idempotencyHeader: \"Idempotency-Key\",\n faker: resendFaker,\n};\n","/**\n * GENERATED FILE — do not edit.\n *\n * Emitted from provider/actions/email-send.json by weaver's generator.\n * A hand-edit here is destroyed by the next protocol sync, which is worse than\n * being rejected, because it works until it silently does not. Fix\n * provider/actions/email-send.json (or weaver's template/) and regenerate:\n *\n * npm run provider -- resend\n */\n\n/**\n * Send an email through Resend.\n *\n * POST /emails — https://resend.com/docs/api-reference/emails/send-email\n *\n * Notice what is NOT here: no key, no base URL, no mode check, no retry loop,\n * no fake/real branch. This describes the request; callConnector resolves the\n * connection, picks the estate, and either calls Resend or calls the faker.\n */\n\nimport {\n callConnector,\n type ConnectorResult,\n type RequestedMode,\n type Transport,\n} from \"@particle-academy/fancy-connector-core\";\nimport { RESEND } from \"../service.js\";\n\nexport const EMAIL_SEND_OPERATION = \"email_send\";\n\nexport type EmailSendOptions = {\n /** The node's resolved config. Keys: from, to, subject, html, text, replyTo, headers. */\n config: Record<string, unknown>;\n credentials?: Record<string, string | undefined>;\n mode?: RequestedMode;\n connectionId?: string | null;\n input?: unknown;\n /** Derived from the run and the step, never fresh. See the note above. */\n idempotencyKey?: string;\n attempts?: number;\n /** Override the transport. The only way to exercise this without a network. */\n transport?: Transport;\n};\n\nexport async function resendEmailSend(options: EmailSendOptions): Promise<ConnectorResult> {\n const config = options.config ?? {};\n\n if (config.from === undefined || config.from === null || config.from === \"\") {\n throw new Error(`email_send: \"from\" is required (From).`);\n }\n\n if (config.to === undefined || config.to === null || config.to === \"\") {\n throw new Error(`email_send: \"to\" is required (To).`);\n }\n\n if (!((config.html !== undefined && config.html !== null && config.html !== \"\") || (config.text !== undefined && config.text !== null && config.text !== \"\"))) {\n throw new Error(`email_send: needs an \"html\" or \"text\" body — an empty email is never intended.`);\n }\n\n return callConnector(RESEND, {\n operation: EMAIL_SEND_OPERATION,\n config,\n input: options.input,\n ...(options.credentials === undefined ? {} : { credentials: options.credentials }),\n ...(options.mode === undefined ? {} : { mode: options.mode }),\n ...(options.connectionId === undefined ? {} : { connectionId: options.connectionId }),\n ...(options.attempts === undefined ? {} : { attempts: options.attempts }),\n ...(options.transport === undefined ? {} : { transport: options.transport }),\n ...(options.idempotencyKey === undefined ? {} : { idempotencyKey: options.idempotencyKey }),\n request: {\n method: \"POST\",\n path: \"/emails\",\n json: {\n \"from\": String(config.from),\n \"to\": toList(config.to),\n \"subject\": config.subject !== undefined && config.subject !== null && config.subject !== \"\" ? String(config.subject) : \"\",\n ...(config.html !== undefined && config.html !== null && config.html !== \"\" ? { \"html\": String(config.html) } : {}),\n ...(config.text !== undefined && config.text !== null && config.text !== \"\" ? { \"text\": String(config.text) } : {}),\n ...(config.replyTo !== undefined && config.replyTo !== null && config.replyTo !== \"\" ? { \"reply_to\": String(config.replyTo) } : {}),\n \"headers\": headersMap(config.headers),\n },\n },\n });\n}\n\n/** One value, a \",\"-separated string, or an array — all end up a list. */\nfunction toList(value: unknown): string[] {\n const items = Array.isArray(value)\n ? value.map(String)\n : typeof value === \"string\"\n ? value.split(\",\")\n : [];\n\n return items.map((item) => item.trim()).filter(Boolean);\n}\n\n/** `{ k: \"v\" }` → `{ \"headers\": { \"k\": \"v\" } }` — a JSON body wants the real object. */\nfunction headersMap(value: unknown): Record<string, string> {\n if (!value || typeof value !== \"object\") return {};\n\n const out: Record<string, string> = {};\n for (const [key, item] of Object.entries(value as Record<string, unknown>)) {\n if (item !== undefined && item !== null && item !== \"\") out[key] = String(item);\n }\n\n return out;\n}\n"]}
package/dist/flow.cjs ADDED
@@ -0,0 +1,135 @@
1
+ 'use strict';
2
+
3
+ var fancyConnectorCore = require('@particle-academy/fancy-connector-core');
4
+ var resendUi = require('@particle-academy/resend-ui');
5
+
6
+ // src/flow.ts
7
+
8
+ // src/faker.ts
9
+ function fakeEmailSend({ config, fake }) {
10
+ return {
11
+ "id": `${fake.hex(8)}-${fake.hex(4)}-${fake.hex(4)}-${fake.hex(4)}-${fake.hex(12)}`
12
+ };
13
+ }
14
+ var resendFaker = (operation, request) => {
15
+ switch (operation) {
16
+ case "email_send":
17
+ return fakeEmailSend(request);
18
+ default:
19
+ throw new Error(
20
+ `resend: no fake response is defined for "${operation}". Add a fixture under provider/fixtures/ and regenerate \u2014 a connector without a faker cannot be developed against, tested, or demonstrated.`
21
+ );
22
+ }
23
+ };
24
+
25
+ // src/service.ts
26
+ var RESEND_BASE_URLS = {
27
+ "live": "https://api.resend.com"
28
+ };
29
+ var RESEND_REQUIRES = [
30
+ "apiKey"
31
+ ];
32
+ function resendAuthorize(credentials, request, _mode) {
33
+ request.headers["User-Agent"] = "fancy-flow-connector";
34
+ request.headers.Authorization = `Bearer ${credentials.apiKey ?? ""}`;
35
+ }
36
+ var RESEND = {
37
+ service: "resend",
38
+ title: "Resend",
39
+ sandbox: "none",
40
+ baseUrls: { ...RESEND_BASE_URLS },
41
+ requires: [...RESEND_REQUIRES],
42
+ authorize: resendAuthorize,
43
+ // Resend honours this for 24 hours. It is what makes a retried durable run
44
+ // resend nothing instead of mailing the recipient twice -- the difference
45
+ // between `idempotent` and `unsafe-to-replay` is that this one is merely
46
+ // embarrassing, not financial, but the header costs nothing and removes it
47
+ // entirely.
48
+ idempotencyHeader: "Idempotency-Key",
49
+ faker: resendFaker
50
+ };
51
+ var EMAIL_SEND_OPERATION = "email_send";
52
+ async function resendEmailSend(options) {
53
+ const config = options.config ?? {};
54
+ if (config.from === void 0 || config.from === null || config.from === "") {
55
+ throw new Error(`email_send: "from" is required (From).`);
56
+ }
57
+ if (config.to === void 0 || config.to === null || config.to === "") {
58
+ throw new Error(`email_send: "to" is required (To).`);
59
+ }
60
+ if (!(config.html !== void 0 && config.html !== null && config.html !== "" || config.text !== void 0 && config.text !== null && config.text !== "")) {
61
+ throw new Error(`email_send: needs an "html" or "text" body \u2014 an empty email is never intended.`);
62
+ }
63
+ return fancyConnectorCore.callConnector(RESEND, {
64
+ operation: EMAIL_SEND_OPERATION,
65
+ config,
66
+ input: options.input,
67
+ ...options.credentials === void 0 ? {} : { credentials: options.credentials },
68
+ ...options.mode === void 0 ? {} : { mode: options.mode },
69
+ ...options.connectionId === void 0 ? {} : { connectionId: options.connectionId },
70
+ ...options.attempts === void 0 ? {} : { attempts: options.attempts },
71
+ ...options.transport === void 0 ? {} : { transport: options.transport },
72
+ ...options.idempotencyKey === void 0 ? {} : { idempotencyKey: options.idempotencyKey },
73
+ request: {
74
+ method: "POST",
75
+ path: "/emails",
76
+ json: {
77
+ "from": String(config.from),
78
+ "to": toList(config.to),
79
+ "subject": config.subject !== void 0 && config.subject !== null && config.subject !== "" ? String(config.subject) : "",
80
+ ...config.html !== void 0 && config.html !== null && config.html !== "" ? { "html": String(config.html) } : {},
81
+ ...config.text !== void 0 && config.text !== null && config.text !== "" ? { "text": String(config.text) } : {},
82
+ ...config.replyTo !== void 0 && config.replyTo !== null && config.replyTo !== "" ? { "reply_to": String(config.replyTo) } : {},
83
+ "headers": headersMap(config.headers)
84
+ }
85
+ }
86
+ });
87
+ }
88
+ function toList(value) {
89
+ const items = Array.isArray(value) ? value.map(String) : typeof value === "string" ? value.split(",") : [];
90
+ return items.map((item) => item.trim()).filter(Boolean);
91
+ }
92
+ function headersMap(value) {
93
+ if (!value || typeof value !== "object") return {};
94
+ const out = {};
95
+ for (const [key, item] of Object.entries(value)) {
96
+ if (item !== void 0 && item !== null && item !== "") out[key] = String(item);
97
+ }
98
+ return out;
99
+ }
100
+
101
+ // src/flow.ts
102
+ var resendEmailSendExecutor = async (ctx) => {
103
+ const config = ctx.node.data?.config ?? {};
104
+ const idempotencyKey = fancyConnectorCore.idempotencyKeyFor(ctx, ctx.node.id, {
105
+ context: { service: "resend", operation: "email_send" }
106
+ });
107
+ if (idempotencyKey === null) {
108
+ ctx.emit({
109
+ type: "log",
110
+ level: "warn",
111
+ nodeId: ctx.node.id,
112
+ message: `email_send: ${fancyConnectorCore.NO_IDEMPOTENCY_KEY_WARNING}`
113
+ });
114
+ }
115
+ const result = await resendEmailSend({
116
+ config,
117
+ input: ctx.inputs?.in,
118
+ ...idempotencyKey === null ? {} : { idempotencyKey }
119
+ });
120
+ ctx.emit({
121
+ type: "log",
122
+ level: "info",
123
+ nodeId: ctx.node.id,
124
+ message: `resend email_send ${result.data?.id} (${result.mode})`
125
+ });
126
+ return { __port: "out", value: result };
127
+ };
128
+ var RESEND_RUNNABLE_KINDS = [
129
+ { ...resendUi.resendEmailSendKind, executor: resendEmailSendExecutor }
130
+ ];
131
+
132
+ exports.RESEND_RUNNABLE_KINDS = RESEND_RUNNABLE_KINDS;
133
+ exports.resendEmailSendExecutor = resendEmailSendExecutor;
134
+ //# sourceMappingURL=flow.cjs.map
135
+ //# sourceMappingURL=flow.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/faker.ts","../src/service.ts","../src/actions/email-send.ts","../src/flow.ts"],"names":["callConnector","idempotencyKeyFor","NO_IDEMPOTENCY_KEY_WARNING","resendEmailSendKind"],"mappings":"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","file":"flow.cjs","sourcesContent":["/**\n * GENERATED FILE — do not edit.\n *\n * Emitted from provider/fixtures/ by weaver's generator.\n * A hand-edit here is destroyed by the next protocol sync, which is worse than\n * being rejected, because it works until it silently does not. Fix\n * provider/fixtures/ (or weaver's template/) and regenerate:\n *\n * npm run provider -- resend\n */\n\n/**\n * The Resend faker.\n *\n * Shapes, not behaviour: the goal is that a downstream node sees the field\n * NAMES Resend actually publishes, so an author can wire {{ $json.data.id }}\n * against a fake and have it keep working against the real thing.\n *\n * Deterministic — same inputs, same output. A faker returning a fresh uuid\n * every call cannot be asserted on, so its fixtures degrade to \"it did not\n * throw\", which is the assertion that catches nothing.\n */\n\nimport type { ConnectorFaker, FakeRequest } from \"@particle-academy/fancy-connector-core\";\n\nfunction fakeEmailSend({ config, fake }: FakeRequest): unknown {\n return {\n \"id\": `${fake.hex(8)}-${fake.hex(4)}-${fake.hex(4)}-${fake.hex(4)}-${fake.hex(12)}`,\n };\n}\n\nexport const resendFaker: ConnectorFaker = (operation, request) => {\n switch (operation) {\n case \"email_send\":\n return fakeEmailSend(request);\n\n default:\n // A faker asked for an operation it has no shape for must SAY so. Making\n // something up would produce a green run whose output silently has none\n // of the fields the author is about to reference.\n throw new Error(\n `resend: no fake response is defined for \"${operation}\". ` +\n \"Add a fixture under provider/fixtures/ and regenerate — a connector without a faker \" +\n \"cannot be developed against, tested, or demonstrated.\",\n );\n }\n};\n","/**\n * GENERATED FILE — do not edit.\n *\n * Emitted from provider/manifest.json by weaver's generator.\n * A hand-edit here is destroyed by the next protocol sync, which is worse than\n * being rejected, because it works until it silently does not. Fix\n * provider/manifest.json (or weaver's template/) and regenerate:\n *\n * npm run provider -- resend\n */\n\n/**\n * Resend, as one service descriptor shared by every Resend operation.\n *\n * @particle-academy/fancy-connector-core carries what is true of ALL\n * connectors. This carries what is true of Resend: its base URL, its auth\n * scheme, its idempotency header, and its faker.\n *\n * ## The sandbox trap, written down where it is used\n *\n * Resend has NO test estate, and somebody checked. Its simulator RECIPIENTS --\n * delivered@resend.dev, bounced@resend.dev, complained@resend.dev,\n * suppressed@resend.dev -- are not one: the send is real, it is billed, and it\n * counts against the quota. Modelling them as a sandbox would put a live send\n * behind a control labelled \"test\", so `mode` does not offer sandbox at all\n * and `fake` is the primary development mode rather than the fallback.\n */\n\nimport type { ConnectorMode, PreparedRequest, ServiceDescriptor } from \"@particle-academy/fancy-connector-core\";\n\nimport { resendFaker } from \"./faker.js\";\n\n/**\n * The connector API version this package was GENERATED against.\n *\n * A literal, never imported. An imported constant lets an upgrade rewrite the\n * very claim it exists to detect, after which the copy agrees with itself\n * forever.\n */\nexport const CONNECTOR_API_VERSION = 1;\n\nexport const RESEND_BASE_URLS = {\n \"live\": \"https://api.resend.com\"\n} as const;\n\n/** Credential keys a remote call cannot proceed without. */\nexport const RESEND_REQUIRES = [\n \"apiKey\"\n] as const;\n\n/**\n * Apply Resend's auth scheme to an outgoing request.\n *\n *\n *\n * The mode is passed in because for some providers auth and estate are the\n * same decision expressed in the URL; here it is unused, and saying so is\n * cheaper than wondering later whether it was forgotten.\n */\nexport function resendAuthorize(\n credentials: Record<string, string | undefined>,\n request: PreparedRequest,\n _mode: ConnectorMode,\n): void {\n request.headers[\"User-Agent\"] = \"fancy-flow-connector\";\n\n request.headers.Authorization = `Bearer ${credentials.apiKey ?? \"\"}`;\n}\n\n/** The Resend service, for the TypeScript runtime. */\nexport const RESEND: ServiceDescriptor = {\n service: \"resend\",\n title: \"Resend\",\n sandbox: \"none\",\n baseUrls: { ...RESEND_BASE_URLS },\n requires: [...RESEND_REQUIRES],\n authorize: resendAuthorize,\n // Resend honours this for 24 hours. It is what makes a retried durable run\n // resend nothing instead of mailing the recipient twice -- the difference\n // between `idempotent` and `unsafe-to-replay` is that this one is merely\n // embarrassing, not financial, but the header costs nothing and removes it\n // entirely.\n idempotencyHeader: \"Idempotency-Key\",\n faker: resendFaker,\n};\n","/**\n * GENERATED FILE — do not edit.\n *\n * Emitted from provider/actions/email-send.json by weaver's generator.\n * A hand-edit here is destroyed by the next protocol sync, which is worse than\n * being rejected, because it works until it silently does not. Fix\n * provider/actions/email-send.json (or weaver's template/) and regenerate:\n *\n * npm run provider -- resend\n */\n\n/**\n * Send an email through Resend.\n *\n * POST /emails — https://resend.com/docs/api-reference/emails/send-email\n *\n * Notice what is NOT here: no key, no base URL, no mode check, no retry loop,\n * no fake/real branch. This describes the request; callConnector resolves the\n * connection, picks the estate, and either calls Resend or calls the faker.\n */\n\nimport {\n callConnector,\n type ConnectorResult,\n type RequestedMode,\n type Transport,\n} from \"@particle-academy/fancy-connector-core\";\nimport { RESEND } from \"../service.js\";\n\nexport const EMAIL_SEND_OPERATION = \"email_send\";\n\nexport type EmailSendOptions = {\n /** The node's resolved config. Keys: from, to, subject, html, text, replyTo, headers. */\n config: Record<string, unknown>;\n credentials?: Record<string, string | undefined>;\n mode?: RequestedMode;\n connectionId?: string | null;\n input?: unknown;\n /** Derived from the run and the step, never fresh. See the note above. */\n idempotencyKey?: string;\n attempts?: number;\n /** Override the transport. The only way to exercise this without a network. */\n transport?: Transport;\n};\n\nexport async function resendEmailSend(options: EmailSendOptions): Promise<ConnectorResult> {\n const config = options.config ?? {};\n\n if (config.from === undefined || config.from === null || config.from === \"\") {\n throw new Error(`email_send: \"from\" is required (From).`);\n }\n\n if (config.to === undefined || config.to === null || config.to === \"\") {\n throw new Error(`email_send: \"to\" is required (To).`);\n }\n\n if (!((config.html !== undefined && config.html !== null && config.html !== \"\") || (config.text !== undefined && config.text !== null && config.text !== \"\"))) {\n throw new Error(`email_send: needs an \"html\" or \"text\" body — an empty email is never intended.`);\n }\n\n return callConnector(RESEND, {\n operation: EMAIL_SEND_OPERATION,\n config,\n input: options.input,\n ...(options.credentials === undefined ? {} : { credentials: options.credentials }),\n ...(options.mode === undefined ? {} : { mode: options.mode }),\n ...(options.connectionId === undefined ? {} : { connectionId: options.connectionId }),\n ...(options.attempts === undefined ? {} : { attempts: options.attempts }),\n ...(options.transport === undefined ? {} : { transport: options.transport }),\n ...(options.idempotencyKey === undefined ? {} : { idempotencyKey: options.idempotencyKey }),\n request: {\n method: \"POST\",\n path: \"/emails\",\n json: {\n \"from\": String(config.from),\n \"to\": toList(config.to),\n \"subject\": config.subject !== undefined && config.subject !== null && config.subject !== \"\" ? String(config.subject) : \"\",\n ...(config.html !== undefined && config.html !== null && config.html !== \"\" ? { \"html\": String(config.html) } : {}),\n ...(config.text !== undefined && config.text !== null && config.text !== \"\" ? { \"text\": String(config.text) } : {}),\n ...(config.replyTo !== undefined && config.replyTo !== null && config.replyTo !== \"\" ? { \"reply_to\": String(config.replyTo) } : {}),\n \"headers\": headersMap(config.headers),\n },\n },\n });\n}\n\n/** One value, a \",\"-separated string, or an array — all end up a list. */\nfunction toList(value: unknown): string[] {\n const items = Array.isArray(value)\n ? value.map(String)\n : typeof value === \"string\"\n ? value.split(\",\")\n : [];\n\n return items.map((item) => item.trim()).filter(Boolean);\n}\n\n/** `{ k: \"v\" }` → `{ \"headers\": { \"k\": \"v\" } }` — a JSON body wants the real object. */\nfunction headersMap(value: unknown): Record<string, string> {\n if (!value || typeof value !== \"object\") return {};\n\n const out: Record<string, string> = {};\n for (const [key, item] of Object.entries(value as Record<string, unknown>)) {\n if (item !== undefined && item !== null && item !== \"\") out[key] = String(item);\n }\n\n return out;\n}\n","/**\n * GENERATED FILE — do not edit.\n *\n * Emitted from provider/actions/ + triggers/ by weaver's generator.\n * A hand-edit here is destroyed by the next protocol sync, which is worse than\n * being rejected, because it works until it silently does not. Fix\n * provider/actions/ + triggers/ (or weaver's template/) and regenerate:\n *\n * npm run provider -- resend\n */\n\n/**\n * Resend's node kinds with their TypeScript executors attached — for hosts\n * that EXECUTE on TS.\n *\n * The authoring surface in @particle-academy/resend-ui carries no executor:\n * the editor is React on every host, so a PHP or Python project installs the\n * ui package and never this one.\n */\n\nimport type { NodeExecutor, NodeKindDefinition } from \"@particle-academy/fancy-flow/engine\";\nimport {\n idempotencyKeyFor,\n NO_IDEMPOTENCY_KEY_WARNING,\n resolveConnection,\n triggerEvent,\n type RequestedMode,\n} from \"@particle-academy/fancy-connector-core\";\nimport { RESEND } from \"./service.js\";\n\nimport {\n resendEmailSendKind,\n} from \"@particle-academy/resend-ui\";\n\nimport { resendEmailSend } from \"./actions/email-send.js\";\n\nexport const resendEmailSendExecutor: NodeExecutor = async (ctx) => {\n const config = ((ctx.node.data as { config?: Record<string, unknown> })?.config ?? {});\n\n // Derived from the RUN and the NODE, never fresh. A retried durable run\n // must send the same key or Resend creates a second one — the exact\n // failure \"idempotent\" exists to prevent.\n const idempotencyKey = idempotencyKeyFor(ctx, ctx.node.id, {\n context: { service: \"resend\", operation: \"email_send\" },\n });\n if (idempotencyKey === null) {\n ctx.emit({\n type: \"log\",\n level: \"warn\",\n nodeId: ctx.node.id,\n message: `email_send: ${NO_IDEMPOTENCY_KEY_WARNING}`,\n });\n }\n\n const result = await resendEmailSend({\n config,\n input: ctx.inputs?.in,\n ...(idempotencyKey === null ? {} : { idempotencyKey }),\n });\n\n ctx.emit({\n type: \"log\",\n level: \"info\",\n nodeId: ctx.node.id,\n message: `resend email_send ${(result.data as { id?: string })?.id} (${result.mode})`,\n });\n\n return { __port: \"out\", value: result };\n};\n\n/** The kinds a TypeScript host registers. */\nexport const RESEND_RUNNABLE_KINDS: NodeKindDefinition[] = [\n { ...resendEmailSendKind, executor: resendEmailSendExecutor },\n];\n"]}
@@ -0,0 +1,26 @@
1
+ import { NodeKindDefinition, NodeExecutor } from '@particle-academy/fancy-flow/engine';
2
+
3
+ /**
4
+ * GENERATED FILE — do not edit.
5
+ *
6
+ * Emitted from provider/actions/ + triggers/ by weaver's generator.
7
+ * A hand-edit here is destroyed by the next protocol sync, which is worse than
8
+ * being rejected, because it works until it silently does not. Fix
9
+ * provider/actions/ + triggers/ (or weaver's template/) and regenerate:
10
+ *
11
+ * npm run provider -- resend
12
+ */
13
+ /**
14
+ * Resend's node kinds with their TypeScript executors attached — for hosts
15
+ * that EXECUTE on TS.
16
+ *
17
+ * The authoring surface in @particle-academy/resend-ui carries no executor:
18
+ * the editor is React on every host, so a PHP or Python project installs the
19
+ * ui package and never this one.
20
+ */
21
+
22
+ declare const resendEmailSendExecutor: NodeExecutor;
23
+ /** The kinds a TypeScript host registers. */
24
+ declare const RESEND_RUNNABLE_KINDS: NodeKindDefinition[];
25
+
26
+ export { RESEND_RUNNABLE_KINDS, resendEmailSendExecutor };
package/dist/flow.d.ts ADDED
@@ -0,0 +1,26 @@
1
+ import { NodeKindDefinition, NodeExecutor } from '@particle-academy/fancy-flow/engine';
2
+
3
+ /**
4
+ * GENERATED FILE — do not edit.
5
+ *
6
+ * Emitted from provider/actions/ + triggers/ by weaver's generator.
7
+ * A hand-edit here is destroyed by the next protocol sync, which is worse than
8
+ * being rejected, because it works until it silently does not. Fix
9
+ * provider/actions/ + triggers/ (or weaver's template/) and regenerate:
10
+ *
11
+ * npm run provider -- resend
12
+ */
13
+ /**
14
+ * Resend's node kinds with their TypeScript executors attached — for hosts
15
+ * that EXECUTE on TS.
16
+ *
17
+ * The authoring surface in @particle-academy/resend-ui carries no executor:
18
+ * the editor is React on every host, so a PHP or Python project installs the
19
+ * ui package and never this one.
20
+ */
21
+
22
+ declare const resendEmailSendExecutor: NodeExecutor;
23
+ /** The kinds a TypeScript host registers. */
24
+ declare const RESEND_RUNNABLE_KINDS: NodeKindDefinition[];
25
+
26
+ export { RESEND_RUNNABLE_KINDS, resendEmailSendExecutor };
package/dist/flow.js ADDED
@@ -0,0 +1,37 @@
1
+ import { resendEmailSend } from './chunk-QIYVVKWU.js';
2
+ import { idempotencyKeyFor, NO_IDEMPOTENCY_KEY_WARNING } from '@particle-academy/fancy-connector-core';
3
+ import { resendEmailSendKind } from '@particle-academy/resend-ui';
4
+
5
+ var resendEmailSendExecutor = async (ctx) => {
6
+ const config = ctx.node.data?.config ?? {};
7
+ const idempotencyKey = idempotencyKeyFor(ctx, ctx.node.id, {
8
+ context: { service: "resend", operation: "email_send" }
9
+ });
10
+ if (idempotencyKey === null) {
11
+ ctx.emit({
12
+ type: "log",
13
+ level: "warn",
14
+ nodeId: ctx.node.id,
15
+ message: `email_send: ${NO_IDEMPOTENCY_KEY_WARNING}`
16
+ });
17
+ }
18
+ const result = await resendEmailSend({
19
+ config,
20
+ input: ctx.inputs?.in,
21
+ ...idempotencyKey === null ? {} : { idempotencyKey }
22
+ });
23
+ ctx.emit({
24
+ type: "log",
25
+ level: "info",
26
+ nodeId: ctx.node.id,
27
+ message: `resend email_send ${result.data?.id} (${result.mode})`
28
+ });
29
+ return { __port: "out", value: result };
30
+ };
31
+ var RESEND_RUNNABLE_KINDS = [
32
+ { ...resendEmailSendKind, executor: resendEmailSendExecutor }
33
+ ];
34
+
35
+ export { RESEND_RUNNABLE_KINDS, resendEmailSendExecutor };
36
+ //# sourceMappingURL=flow.js.map
37
+ //# sourceMappingURL=flow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/flow.ts"],"names":[],"mappings":";;;;AAoCO,IAAM,uBAAA,GAAwC,OAAO,GAAA,KAAQ;AAClE,EAAA,MAAM,MAAA,GAAW,GAAA,CAAI,IAAA,CAAK,IAAA,EAA+C,UAAU,EAAC;AAKpF,EAAA,MAAM,cAAA,GAAiB,iBAAA,CAAkB,GAAA,EAAK,GAAA,CAAI,KAAK,EAAA,EAAI;AAAA,IACzD,OAAA,EAAS,EAAE,OAAA,EAAS,QAAA,EAAU,WAAW,YAAA;AAAa,GACvD,CAAA;AACD,EAAA,IAAI,mBAAmB,IAAA,EAAM;AAC3B,IAAA,GAAA,CAAI,IAAA,CAAK;AAAA,MACP,IAAA,EAAM,KAAA;AAAA,MACN,KAAA,EAAO,MAAA;AAAA,MACP,MAAA,EAAQ,IAAI,IAAA,CAAK,EAAA;AAAA,MACjB,OAAA,EAAS,eAAe,0BAA0B,CAAA;AAAA,KACnD,CAAA;AAAA,EACH;AAEA,EAAA,MAAM,MAAA,GAAS,MAAM,eAAA,CAAgB;AAAA,IACnC,MAAA;AAAA,IACA,KAAA,EAAO,IAAI,MAAA,EAAQ,EAAA;AAAA,IACnB,GAAI,cAAA,KAAmB,IAAA,GAAO,EAAC,GAAI,EAAE,cAAA;AAAe,GACrD,CAAA;AAED,EAAA,GAAA,CAAI,IAAA,CAAK;AAAA,IACP,IAAA,EAAM,KAAA;AAAA,IACN,KAAA,EAAO,MAAA;AAAA,IACP,MAAA,EAAQ,IAAI,IAAA,CAAK,EAAA;AAAA,IACjB,SAAS,CAAA,kBAAA,EAAsB,MAAA,CAAO,MAA0B,EAAE,CAAA,EAAA,EAAK,OAAO,IAAI,CAAA,CAAA;AAAA,GACnF,CAAA;AAED,EAAA,OAAO,EAAE,MAAA,EAAQ,KAAA,EAAO,KAAA,EAAO,MAAA,EAAO;AACxC;AAGO,IAAM,qBAAA,GAA8C;AAAA,EACzD,EAAE,GAAG,mBAAA,EAAqB,QAAA,EAAU,uBAAA;AACtC","file":"flow.js","sourcesContent":["/**\n * GENERATED FILE — do not edit.\n *\n * Emitted from provider/actions/ + triggers/ by weaver's generator.\n * A hand-edit here is destroyed by the next protocol sync, which is worse than\n * being rejected, because it works until it silently does not. Fix\n * provider/actions/ + triggers/ (or weaver's template/) and regenerate:\n *\n * npm run provider -- resend\n */\n\n/**\n * Resend's node kinds with their TypeScript executors attached — for hosts\n * that EXECUTE on TS.\n *\n * The authoring surface in @particle-academy/resend-ui carries no executor:\n * the editor is React on every host, so a PHP or Python project installs the\n * ui package and never this one.\n */\n\nimport type { NodeExecutor, NodeKindDefinition } from \"@particle-academy/fancy-flow/engine\";\nimport {\n idempotencyKeyFor,\n NO_IDEMPOTENCY_KEY_WARNING,\n resolveConnection,\n triggerEvent,\n type RequestedMode,\n} from \"@particle-academy/fancy-connector-core\";\nimport { RESEND } from \"./service.js\";\n\nimport {\n resendEmailSendKind,\n} from \"@particle-academy/resend-ui\";\n\nimport { resendEmailSend } from \"./actions/email-send.js\";\n\nexport const resendEmailSendExecutor: NodeExecutor = async (ctx) => {\n const config = ((ctx.node.data as { config?: Record<string, unknown> })?.config ?? {});\n\n // Derived from the RUN and the NODE, never fresh. A retried durable run\n // must send the same key or Resend creates a second one — the exact\n // failure \"idempotent\" exists to prevent.\n const idempotencyKey = idempotencyKeyFor(ctx, ctx.node.id, {\n context: { service: \"resend\", operation: \"email_send\" },\n });\n if (idempotencyKey === null) {\n ctx.emit({\n type: \"log\",\n level: \"warn\",\n nodeId: ctx.node.id,\n message: `email_send: ${NO_IDEMPOTENCY_KEY_WARNING}`,\n });\n }\n\n const result = await resendEmailSend({\n config,\n input: ctx.inputs?.in,\n ...(idempotencyKey === null ? {} : { idempotencyKey }),\n });\n\n ctx.emit({\n type: \"log\",\n level: \"info\",\n nodeId: ctx.node.id,\n message: `resend email_send ${(result.data as { id?: string })?.id} (${result.mode})`,\n });\n\n return { __port: \"out\", value: result };\n};\n\n/** The kinds a TypeScript host registers. */\nexport const RESEND_RUNNABLE_KINDS: NodeKindDefinition[] = [\n { ...resendEmailSendKind, executor: resendEmailSendExecutor },\n];\n"]}
package/dist/index.cjs ADDED
@@ -0,0 +1,108 @@
1
+ 'use strict';
2
+
3
+ var fancyConnectorCore = require('@particle-academy/fancy-connector-core');
4
+
5
+ // src/faker.ts
6
+ function fakeEmailSend({ config, fake }) {
7
+ return {
8
+ "id": `${fake.hex(8)}-${fake.hex(4)}-${fake.hex(4)}-${fake.hex(4)}-${fake.hex(12)}`
9
+ };
10
+ }
11
+ var resendFaker = (operation, request) => {
12
+ switch (operation) {
13
+ case "email_send":
14
+ return fakeEmailSend(request);
15
+ default:
16
+ throw new Error(
17
+ `resend: no fake response is defined for "${operation}". Add a fixture under provider/fixtures/ and regenerate \u2014 a connector without a faker cannot be developed against, tested, or demonstrated.`
18
+ );
19
+ }
20
+ };
21
+
22
+ // src/service.ts
23
+ var CONNECTOR_API_VERSION = 1;
24
+ var RESEND_BASE_URLS = {
25
+ "live": "https://api.resend.com"
26
+ };
27
+ var RESEND_REQUIRES = [
28
+ "apiKey"
29
+ ];
30
+ function resendAuthorize(credentials, request, _mode) {
31
+ request.headers["User-Agent"] = "fancy-flow-connector";
32
+ request.headers.Authorization = `Bearer ${credentials.apiKey ?? ""}`;
33
+ }
34
+ var RESEND = {
35
+ service: "resend",
36
+ title: "Resend",
37
+ sandbox: "none",
38
+ baseUrls: { ...RESEND_BASE_URLS },
39
+ requires: [...RESEND_REQUIRES],
40
+ authorize: resendAuthorize,
41
+ // Resend honours this for 24 hours. It is what makes a retried durable run
42
+ // resend nothing instead of mailing the recipient twice -- the difference
43
+ // between `idempotent` and `unsafe-to-replay` is that this one is merely
44
+ // embarrassing, not financial, but the header costs nothing and removes it
45
+ // entirely.
46
+ idempotencyHeader: "Idempotency-Key",
47
+ faker: resendFaker
48
+ };
49
+ var EMAIL_SEND_OPERATION = "email_send";
50
+ async function resendEmailSend(options) {
51
+ const config = options.config ?? {};
52
+ if (config.from === void 0 || config.from === null || config.from === "") {
53
+ throw new Error(`email_send: "from" is required (From).`);
54
+ }
55
+ if (config.to === void 0 || config.to === null || config.to === "") {
56
+ throw new Error(`email_send: "to" is required (To).`);
57
+ }
58
+ if (!(config.html !== void 0 && config.html !== null && config.html !== "" || config.text !== void 0 && config.text !== null && config.text !== "")) {
59
+ throw new Error(`email_send: needs an "html" or "text" body \u2014 an empty email is never intended.`);
60
+ }
61
+ return fancyConnectorCore.callConnector(RESEND, {
62
+ operation: EMAIL_SEND_OPERATION,
63
+ config,
64
+ input: options.input,
65
+ ...options.credentials === void 0 ? {} : { credentials: options.credentials },
66
+ ...options.mode === void 0 ? {} : { mode: options.mode },
67
+ ...options.connectionId === void 0 ? {} : { connectionId: options.connectionId },
68
+ ...options.attempts === void 0 ? {} : { attempts: options.attempts },
69
+ ...options.transport === void 0 ? {} : { transport: options.transport },
70
+ ...options.idempotencyKey === void 0 ? {} : { idempotencyKey: options.idempotencyKey },
71
+ request: {
72
+ method: "POST",
73
+ path: "/emails",
74
+ json: {
75
+ "from": String(config.from),
76
+ "to": toList(config.to),
77
+ "subject": config.subject !== void 0 && config.subject !== null && config.subject !== "" ? String(config.subject) : "",
78
+ ...config.html !== void 0 && config.html !== null && config.html !== "" ? { "html": String(config.html) } : {},
79
+ ...config.text !== void 0 && config.text !== null && config.text !== "" ? { "text": String(config.text) } : {},
80
+ ...config.replyTo !== void 0 && config.replyTo !== null && config.replyTo !== "" ? { "reply_to": String(config.replyTo) } : {},
81
+ "headers": headersMap(config.headers)
82
+ }
83
+ }
84
+ });
85
+ }
86
+ function toList(value) {
87
+ const items = Array.isArray(value) ? value.map(String) : typeof value === "string" ? value.split(",") : [];
88
+ return items.map((item) => item.trim()).filter(Boolean);
89
+ }
90
+ function headersMap(value) {
91
+ if (!value || typeof value !== "object") return {};
92
+ const out = {};
93
+ for (const [key, item] of Object.entries(value)) {
94
+ if (item !== void 0 && item !== null && item !== "") out[key] = String(item);
95
+ }
96
+ return out;
97
+ }
98
+
99
+ exports.CONNECTOR_API_VERSION = CONNECTOR_API_VERSION;
100
+ exports.EMAIL_SEND_OPERATION = EMAIL_SEND_OPERATION;
101
+ exports.RESEND = RESEND;
102
+ exports.RESEND_BASE_URLS = RESEND_BASE_URLS;
103
+ exports.RESEND_REQUIRES = RESEND_REQUIRES;
104
+ exports.resendAuthorize = resendAuthorize;
105
+ exports.resendEmailSend = resendEmailSend;
106
+ exports.resendFaker = resendFaker;
107
+ //# sourceMappingURL=index.cjs.map
108
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/faker.ts","../src/service.ts","../src/actions/email-send.ts"],"names":["callConnector"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * GENERATED FILE — do not edit.\n *\n * Emitted from provider/fixtures/ by weaver's generator.\n * A hand-edit here is destroyed by the next protocol sync, which is worse than\n * being rejected, because it works until it silently does not. Fix\n * provider/fixtures/ (or weaver's template/) and regenerate:\n *\n * npm run provider -- resend\n */\n\n/**\n * The Resend faker.\n *\n * Shapes, not behaviour: the goal is that a downstream node sees the field\n * NAMES Resend actually publishes, so an author can wire {{ $json.data.id }}\n * against a fake and have it keep working against the real thing.\n *\n * Deterministic — same inputs, same output. A faker returning a fresh uuid\n * every call cannot be asserted on, so its fixtures degrade to \"it did not\n * throw\", which is the assertion that catches nothing.\n */\n\nimport type { ConnectorFaker, FakeRequest } from \"@particle-academy/fancy-connector-core\";\n\nfunction fakeEmailSend({ config, fake }: FakeRequest): unknown {\n return {\n \"id\": `${fake.hex(8)}-${fake.hex(4)}-${fake.hex(4)}-${fake.hex(4)}-${fake.hex(12)}`,\n };\n}\n\nexport const resendFaker: ConnectorFaker = (operation, request) => {\n switch (operation) {\n case \"email_send\":\n return fakeEmailSend(request);\n\n default:\n // A faker asked for an operation it has no shape for must SAY so. Making\n // something up would produce a green run whose output silently has none\n // of the fields the author is about to reference.\n throw new Error(\n `resend: no fake response is defined for \"${operation}\". ` +\n \"Add a fixture under provider/fixtures/ and regenerate — a connector without a faker \" +\n \"cannot be developed against, tested, or demonstrated.\",\n );\n }\n};\n","/**\n * GENERATED FILE — do not edit.\n *\n * Emitted from provider/manifest.json by weaver's generator.\n * A hand-edit here is destroyed by the next protocol sync, which is worse than\n * being rejected, because it works until it silently does not. Fix\n * provider/manifest.json (or weaver's template/) and regenerate:\n *\n * npm run provider -- resend\n */\n\n/**\n * Resend, as one service descriptor shared by every Resend operation.\n *\n * @particle-academy/fancy-connector-core carries what is true of ALL\n * connectors. This carries what is true of Resend: its base URL, its auth\n * scheme, its idempotency header, and its faker.\n *\n * ## The sandbox trap, written down where it is used\n *\n * Resend has NO test estate, and somebody checked. Its simulator RECIPIENTS --\n * delivered@resend.dev, bounced@resend.dev, complained@resend.dev,\n * suppressed@resend.dev -- are not one: the send is real, it is billed, and it\n * counts against the quota. Modelling them as a sandbox would put a live send\n * behind a control labelled \"test\", so `mode` does not offer sandbox at all\n * and `fake` is the primary development mode rather than the fallback.\n */\n\nimport type { ConnectorMode, PreparedRequest, ServiceDescriptor } from \"@particle-academy/fancy-connector-core\";\n\nimport { resendFaker } from \"./faker.js\";\n\n/**\n * The connector API version this package was GENERATED against.\n *\n * A literal, never imported. An imported constant lets an upgrade rewrite the\n * very claim it exists to detect, after which the copy agrees with itself\n * forever.\n */\nexport const CONNECTOR_API_VERSION = 1;\n\nexport const RESEND_BASE_URLS = {\n \"live\": \"https://api.resend.com\"\n} as const;\n\n/** Credential keys a remote call cannot proceed without. */\nexport const RESEND_REQUIRES = [\n \"apiKey\"\n] as const;\n\n/**\n * Apply Resend's auth scheme to an outgoing request.\n *\n *\n *\n * The mode is passed in because for some providers auth and estate are the\n * same decision expressed in the URL; here it is unused, and saying so is\n * cheaper than wondering later whether it was forgotten.\n */\nexport function resendAuthorize(\n credentials: Record<string, string | undefined>,\n request: PreparedRequest,\n _mode: ConnectorMode,\n): void {\n request.headers[\"User-Agent\"] = \"fancy-flow-connector\";\n\n request.headers.Authorization = `Bearer ${credentials.apiKey ?? \"\"}`;\n}\n\n/** The Resend service, for the TypeScript runtime. */\nexport const RESEND: ServiceDescriptor = {\n service: \"resend\",\n title: \"Resend\",\n sandbox: \"none\",\n baseUrls: { ...RESEND_BASE_URLS },\n requires: [...RESEND_REQUIRES],\n authorize: resendAuthorize,\n // Resend honours this for 24 hours. It is what makes a retried durable run\n // resend nothing instead of mailing the recipient twice -- the difference\n // between `idempotent` and `unsafe-to-replay` is that this one is merely\n // embarrassing, not financial, but the header costs nothing and removes it\n // entirely.\n idempotencyHeader: \"Idempotency-Key\",\n faker: resendFaker,\n};\n","/**\n * GENERATED FILE — do not edit.\n *\n * Emitted from provider/actions/email-send.json by weaver's generator.\n * A hand-edit here is destroyed by the next protocol sync, which is worse than\n * being rejected, because it works until it silently does not. Fix\n * provider/actions/email-send.json (or weaver's template/) and regenerate:\n *\n * npm run provider -- resend\n */\n\n/**\n * Send an email through Resend.\n *\n * POST /emails — https://resend.com/docs/api-reference/emails/send-email\n *\n * Notice what is NOT here: no key, no base URL, no mode check, no retry loop,\n * no fake/real branch. This describes the request; callConnector resolves the\n * connection, picks the estate, and either calls Resend or calls the faker.\n */\n\nimport {\n callConnector,\n type ConnectorResult,\n type RequestedMode,\n type Transport,\n} from \"@particle-academy/fancy-connector-core\";\nimport { RESEND } from \"../service.js\";\n\nexport const EMAIL_SEND_OPERATION = \"email_send\";\n\nexport type EmailSendOptions = {\n /** The node's resolved config. Keys: from, to, subject, html, text, replyTo, headers. */\n config: Record<string, unknown>;\n credentials?: Record<string, string | undefined>;\n mode?: RequestedMode;\n connectionId?: string | null;\n input?: unknown;\n /** Derived from the run and the step, never fresh. See the note above. */\n idempotencyKey?: string;\n attempts?: number;\n /** Override the transport. The only way to exercise this without a network. */\n transport?: Transport;\n};\n\nexport async function resendEmailSend(options: EmailSendOptions): Promise<ConnectorResult> {\n const config = options.config ?? {};\n\n if (config.from === undefined || config.from === null || config.from === \"\") {\n throw new Error(`email_send: \"from\" is required (From).`);\n }\n\n if (config.to === undefined || config.to === null || config.to === \"\") {\n throw new Error(`email_send: \"to\" is required (To).`);\n }\n\n if (!((config.html !== undefined && config.html !== null && config.html !== \"\") || (config.text !== undefined && config.text !== null && config.text !== \"\"))) {\n throw new Error(`email_send: needs an \"html\" or \"text\" body — an empty email is never intended.`);\n }\n\n return callConnector(RESEND, {\n operation: EMAIL_SEND_OPERATION,\n config,\n input: options.input,\n ...(options.credentials === undefined ? {} : { credentials: options.credentials }),\n ...(options.mode === undefined ? {} : { mode: options.mode }),\n ...(options.connectionId === undefined ? {} : { connectionId: options.connectionId }),\n ...(options.attempts === undefined ? {} : { attempts: options.attempts }),\n ...(options.transport === undefined ? {} : { transport: options.transport }),\n ...(options.idempotencyKey === undefined ? {} : { idempotencyKey: options.idempotencyKey }),\n request: {\n method: \"POST\",\n path: \"/emails\",\n json: {\n \"from\": String(config.from),\n \"to\": toList(config.to),\n \"subject\": config.subject !== undefined && config.subject !== null && config.subject !== \"\" ? String(config.subject) : \"\",\n ...(config.html !== undefined && config.html !== null && config.html !== \"\" ? { \"html\": String(config.html) } : {}),\n ...(config.text !== undefined && config.text !== null && config.text !== \"\" ? { \"text\": String(config.text) } : {}),\n ...(config.replyTo !== undefined && config.replyTo !== null && config.replyTo !== \"\" ? { \"reply_to\": String(config.replyTo) } : {}),\n \"headers\": headersMap(config.headers),\n },\n },\n });\n}\n\n/** One value, a \",\"-separated string, or an array — all end up a list. */\nfunction toList(value: unknown): string[] {\n const items = Array.isArray(value)\n ? value.map(String)\n : typeof value === \"string\"\n ? value.split(\",\")\n : [];\n\n return items.map((item) => item.trim()).filter(Boolean);\n}\n\n/** `{ k: \"v\" }` → `{ \"headers\": { \"k\": \"v\" } }` — a JSON body wants the real object. */\nfunction headersMap(value: unknown): Record<string, string> {\n if (!value || typeof value !== \"object\") return {};\n\n const out: Record<string, string> = {};\n for (const [key, item] of Object.entries(value as Record<string, unknown>)) {\n if (item !== undefined && item !== null && item !== \"\") out[key] = String(item);\n }\n\n return out;\n}\n"]}
@@ -0,0 +1,116 @@
1
+ import { ServiceDescriptor, PreparedRequest, ConnectorMode, ConnectorFaker, RequestedMode, Transport, ConnectorResult } from '@particle-academy/fancy-connector-core';
2
+
3
+ /**
4
+ * GENERATED FILE — do not edit.
5
+ *
6
+ * Emitted from provider/manifest.json by weaver's generator.
7
+ * A hand-edit here is destroyed by the next protocol sync, which is worse than
8
+ * being rejected, because it works until it silently does not. Fix
9
+ * provider/manifest.json (or weaver's template/) and regenerate:
10
+ *
11
+ * npm run provider -- resend
12
+ */
13
+ /**
14
+ * Resend, as one service descriptor shared by every Resend operation.
15
+ *
16
+ * @particle-academy/fancy-connector-core carries what is true of ALL
17
+ * connectors. This carries what is true of Resend: its base URL, its auth
18
+ * scheme, its idempotency header, and its faker.
19
+ *
20
+ * ## The sandbox trap, written down where it is used
21
+ *
22
+ * Resend has NO test estate, and somebody checked. Its simulator RECIPIENTS --
23
+ * delivered@resend.dev, bounced@resend.dev, complained@resend.dev,
24
+ * suppressed@resend.dev -- are not one: the send is real, it is billed, and it
25
+ * counts against the quota. Modelling them as a sandbox would put a live send
26
+ * behind a control labelled "test", so `mode` does not offer sandbox at all
27
+ * and `fake` is the primary development mode rather than the fallback.
28
+ */
29
+
30
+ /**
31
+ * The connector API version this package was GENERATED against.
32
+ *
33
+ * A literal, never imported. An imported constant lets an upgrade rewrite the
34
+ * very claim it exists to detect, after which the copy agrees with itself
35
+ * forever.
36
+ */
37
+ declare const CONNECTOR_API_VERSION = 1;
38
+ declare const RESEND_BASE_URLS: {
39
+ readonly live: "https://api.resend.com";
40
+ };
41
+ /** Credential keys a remote call cannot proceed without. */
42
+ declare const RESEND_REQUIRES: readonly ["apiKey"];
43
+ /**
44
+ * Apply Resend's auth scheme to an outgoing request.
45
+ *
46
+ *
47
+ *
48
+ * The mode is passed in because for some providers auth and estate are the
49
+ * same decision expressed in the URL; here it is unused, and saying so is
50
+ * cheaper than wondering later whether it was forgotten.
51
+ */
52
+ declare function resendAuthorize(credentials: Record<string, string | undefined>, request: PreparedRequest, _mode: ConnectorMode): void;
53
+ /** The Resend service, for the TypeScript runtime. */
54
+ declare const RESEND: ServiceDescriptor;
55
+
56
+ /**
57
+ * GENERATED FILE — do not edit.
58
+ *
59
+ * Emitted from provider/fixtures/ by weaver's generator.
60
+ * A hand-edit here is destroyed by the next protocol sync, which is worse than
61
+ * being rejected, because it works until it silently does not. Fix
62
+ * provider/fixtures/ (or weaver's template/) and regenerate:
63
+ *
64
+ * npm run provider -- resend
65
+ */
66
+ /**
67
+ * The Resend faker.
68
+ *
69
+ * Shapes, not behaviour: the goal is that a downstream node sees the field
70
+ * NAMES Resend actually publishes, so an author can wire {{ $json.data.id }}
71
+ * against a fake and have it keep working against the real thing.
72
+ *
73
+ * Deterministic — same inputs, same output. A faker returning a fresh uuid
74
+ * every call cannot be asserted on, so its fixtures degrade to "it did not
75
+ * throw", which is the assertion that catches nothing.
76
+ */
77
+
78
+ declare const resendFaker: ConnectorFaker;
79
+
80
+ /**
81
+ * GENERATED FILE — do not edit.
82
+ *
83
+ * Emitted from provider/actions/email-send.json by weaver's generator.
84
+ * A hand-edit here is destroyed by the next protocol sync, which is worse than
85
+ * being rejected, because it works until it silently does not. Fix
86
+ * provider/actions/email-send.json (or weaver's template/) and regenerate:
87
+ *
88
+ * npm run provider -- resend
89
+ */
90
+ /**
91
+ * Send an email through Resend.
92
+ *
93
+ * POST /emails — https://resend.com/docs/api-reference/emails/send-email
94
+ *
95
+ * Notice what is NOT here: no key, no base URL, no mode check, no retry loop,
96
+ * no fake/real branch. This describes the request; callConnector resolves the
97
+ * connection, picks the estate, and either calls Resend or calls the faker.
98
+ */
99
+
100
+ declare const EMAIL_SEND_OPERATION = "email_send";
101
+ type EmailSendOptions = {
102
+ /** The node's resolved config. Keys: from, to, subject, html, text, replyTo, headers. */
103
+ config: Record<string, unknown>;
104
+ credentials?: Record<string, string | undefined>;
105
+ mode?: RequestedMode;
106
+ connectionId?: string | null;
107
+ input?: unknown;
108
+ /** Derived from the run and the step, never fresh. See the note above. */
109
+ idempotencyKey?: string;
110
+ attempts?: number;
111
+ /** Override the transport. The only way to exercise this without a network. */
112
+ transport?: Transport;
113
+ };
114
+ declare function resendEmailSend(options: EmailSendOptions): Promise<ConnectorResult>;
115
+
116
+ export { CONNECTOR_API_VERSION, EMAIL_SEND_OPERATION, type EmailSendOptions, RESEND, RESEND_BASE_URLS, RESEND_REQUIRES, resendAuthorize, resendEmailSend, resendFaker };
@@ -0,0 +1,116 @@
1
+ import { ServiceDescriptor, PreparedRequest, ConnectorMode, ConnectorFaker, RequestedMode, Transport, ConnectorResult } from '@particle-academy/fancy-connector-core';
2
+
3
+ /**
4
+ * GENERATED FILE — do not edit.
5
+ *
6
+ * Emitted from provider/manifest.json by weaver's generator.
7
+ * A hand-edit here is destroyed by the next protocol sync, which is worse than
8
+ * being rejected, because it works until it silently does not. Fix
9
+ * provider/manifest.json (or weaver's template/) and regenerate:
10
+ *
11
+ * npm run provider -- resend
12
+ */
13
+ /**
14
+ * Resend, as one service descriptor shared by every Resend operation.
15
+ *
16
+ * @particle-academy/fancy-connector-core carries what is true of ALL
17
+ * connectors. This carries what is true of Resend: its base URL, its auth
18
+ * scheme, its idempotency header, and its faker.
19
+ *
20
+ * ## The sandbox trap, written down where it is used
21
+ *
22
+ * Resend has NO test estate, and somebody checked. Its simulator RECIPIENTS --
23
+ * delivered@resend.dev, bounced@resend.dev, complained@resend.dev,
24
+ * suppressed@resend.dev -- are not one: the send is real, it is billed, and it
25
+ * counts against the quota. Modelling them as a sandbox would put a live send
26
+ * behind a control labelled "test", so `mode` does not offer sandbox at all
27
+ * and `fake` is the primary development mode rather than the fallback.
28
+ */
29
+
30
+ /**
31
+ * The connector API version this package was GENERATED against.
32
+ *
33
+ * A literal, never imported. An imported constant lets an upgrade rewrite the
34
+ * very claim it exists to detect, after which the copy agrees with itself
35
+ * forever.
36
+ */
37
+ declare const CONNECTOR_API_VERSION = 1;
38
+ declare const RESEND_BASE_URLS: {
39
+ readonly live: "https://api.resend.com";
40
+ };
41
+ /** Credential keys a remote call cannot proceed without. */
42
+ declare const RESEND_REQUIRES: readonly ["apiKey"];
43
+ /**
44
+ * Apply Resend's auth scheme to an outgoing request.
45
+ *
46
+ *
47
+ *
48
+ * The mode is passed in because for some providers auth and estate are the
49
+ * same decision expressed in the URL; here it is unused, and saying so is
50
+ * cheaper than wondering later whether it was forgotten.
51
+ */
52
+ declare function resendAuthorize(credentials: Record<string, string | undefined>, request: PreparedRequest, _mode: ConnectorMode): void;
53
+ /** The Resend service, for the TypeScript runtime. */
54
+ declare const RESEND: ServiceDescriptor;
55
+
56
+ /**
57
+ * GENERATED FILE — do not edit.
58
+ *
59
+ * Emitted from provider/fixtures/ by weaver's generator.
60
+ * A hand-edit here is destroyed by the next protocol sync, which is worse than
61
+ * being rejected, because it works until it silently does not. Fix
62
+ * provider/fixtures/ (or weaver's template/) and regenerate:
63
+ *
64
+ * npm run provider -- resend
65
+ */
66
+ /**
67
+ * The Resend faker.
68
+ *
69
+ * Shapes, not behaviour: the goal is that a downstream node sees the field
70
+ * NAMES Resend actually publishes, so an author can wire {{ $json.data.id }}
71
+ * against a fake and have it keep working against the real thing.
72
+ *
73
+ * Deterministic — same inputs, same output. A faker returning a fresh uuid
74
+ * every call cannot be asserted on, so its fixtures degrade to "it did not
75
+ * throw", which is the assertion that catches nothing.
76
+ */
77
+
78
+ declare const resendFaker: ConnectorFaker;
79
+
80
+ /**
81
+ * GENERATED FILE — do not edit.
82
+ *
83
+ * Emitted from provider/actions/email-send.json by weaver's generator.
84
+ * A hand-edit here is destroyed by the next protocol sync, which is worse than
85
+ * being rejected, because it works until it silently does not. Fix
86
+ * provider/actions/email-send.json (or weaver's template/) and regenerate:
87
+ *
88
+ * npm run provider -- resend
89
+ */
90
+ /**
91
+ * Send an email through Resend.
92
+ *
93
+ * POST /emails — https://resend.com/docs/api-reference/emails/send-email
94
+ *
95
+ * Notice what is NOT here: no key, no base URL, no mode check, no retry loop,
96
+ * no fake/real branch. This describes the request; callConnector resolves the
97
+ * connection, picks the estate, and either calls Resend or calls the faker.
98
+ */
99
+
100
+ declare const EMAIL_SEND_OPERATION = "email_send";
101
+ type EmailSendOptions = {
102
+ /** The node's resolved config. Keys: from, to, subject, html, text, replyTo, headers. */
103
+ config: Record<string, unknown>;
104
+ credentials?: Record<string, string | undefined>;
105
+ mode?: RequestedMode;
106
+ connectionId?: string | null;
107
+ input?: unknown;
108
+ /** Derived from the run and the step, never fresh. See the note above. */
109
+ idempotencyKey?: string;
110
+ attempts?: number;
111
+ /** Override the transport. The only way to exercise this without a network. */
112
+ transport?: Transport;
113
+ };
114
+ declare function resendEmailSend(options: EmailSendOptions): Promise<ConnectorResult>;
115
+
116
+ export { CONNECTOR_API_VERSION, EMAIL_SEND_OPERATION, type EmailSendOptions, RESEND, RESEND_BASE_URLS, RESEND_REQUIRES, resendAuthorize, resendEmailSend, resendFaker };
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { CONNECTOR_API_VERSION, EMAIL_SEND_OPERATION, RESEND, RESEND_BASE_URLS, RESEND_REQUIRES, resendAuthorize, resendEmailSend, resendFaker } from './chunk-QIYVVKWU.js';
2
+ //# sourceMappingURL=index.js.map
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
package/package.json ADDED
@@ -0,0 +1,85 @@
1
+ {
2
+ "name": "@particle-academy/resend-js",
3
+ "version": "0.1.0",
4
+ "description": "Resend for Node — the service descriptor, its faker, its webhook verification, and one function per operation. Plain HTTP on @particle-academy/fancy-connector-core; no vendor SDK.",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/Fancy-Friends/resend.git"
8
+ },
9
+ "homepage": "https://github.com/Fancy-Friends/resend#readme",
10
+ "bugs": "https://github.com/Fancy-Friends/resend/issues",
11
+ "type": "module",
12
+ "main": "./dist/index.cjs",
13
+ "module": "./dist/index.js",
14
+ "types": "./dist/index.d.ts",
15
+ "exports": {
16
+ ".": {
17
+ "import": {
18
+ "types": "./dist/index.d.ts",
19
+ "default": "./dist/index.js"
20
+ },
21
+ "require": {
22
+ "types": "./dist/index.d.cts",
23
+ "default": "./dist/index.cjs"
24
+ }
25
+ },
26
+ "./flow": {
27
+ "import": {
28
+ "types": "./dist/flow.d.ts",
29
+ "default": "./dist/flow.js"
30
+ },
31
+ "require": {
32
+ "types": "./dist/flow.d.cts",
33
+ "default": "./dist/flow.cjs"
34
+ }
35
+ },
36
+ "./package.json": "./package.json"
37
+ },
38
+ "files": [
39
+ "dist",
40
+ "README.md",
41
+ "CHANGELOG.md",
42
+ "LICENSE"
43
+ ],
44
+ "scripts": {
45
+ "build": "tsup",
46
+ "lint": "tsc --noEmit",
47
+ "test": "node --import tsx --test tests/*.test.ts",
48
+ "prepublishOnly": "tsup"
49
+ },
50
+ "dependencies": {
51
+ "@particle-academy/fancy-connector-core": ">=0.3.1 <2.0",
52
+ "@particle-academy/resend-ui": "0.1.0"
53
+ },
54
+ "peerDependencies": {
55
+ "@particle-academy/fancy-flow": ">=0.47.1 <2.0"
56
+ },
57
+ "peerDependenciesMeta": {
58
+ "@particle-academy/fancy-flow": {
59
+ "optional": true
60
+ }
61
+ },
62
+ "devDependencies": {
63
+ "@particle-academy/fancy-flow": ">=0.47.1 <2.0",
64
+ "@types/node": "^26.2.0",
65
+ "tsup": "^8.5.0",
66
+ "tsx": "^4.19.0",
67
+ "typescript": "^5.8.0"
68
+ },
69
+ "publishConfig": {
70
+ "access": "public"
71
+ },
72
+ "keywords": [
73
+ "resend",
74
+ "email",
75
+ "connector",
76
+ "api",
77
+ "fancy-ui",
78
+ "particle-academy"
79
+ ],
80
+ "license": "MIT",
81
+ "sideEffects": false,
82
+ "engines": {
83
+ "node": ">=22"
84
+ }
85
+ }