From 7955826d8b0fce88661c096c4926ccd2c06a3fe8 Mon Sep 17 00:00:00 2001 From: Alex Holovach Date: Sat, 4 Oct 2025 10:22:43 -0500 Subject: [PATCH] resend telemetry package --- packages/otel-resend/CHANGELOG.md | 5 + packages/otel-resend/LICENSE | 21 ++ packages/otel-resend/README.md | 82 +++++ packages/otel-resend/package.json | 53 +++ packages/otel-resend/src/index.test.ts | 190 ++++++++++ packages/otel-resend/src/index.ts | 465 +++++++++++++++++++++++++ packages/otel-resend/tsconfig.json | 21 ++ pnpm-lock.yaml | 449 +++++++++++++++++++++++- 8 files changed, 1284 insertions(+), 2 deletions(-) create mode 100644 packages/otel-resend/CHANGELOG.md create mode 100644 packages/otel-resend/LICENSE create mode 100644 packages/otel-resend/README.md create mode 100644 packages/otel-resend/package.json create mode 100644 packages/otel-resend/src/index.test.ts create mode 100644 packages/otel-resend/src/index.ts create mode 100644 packages/otel-resend/tsconfig.json diff --git a/packages/otel-resend/CHANGELOG.md b/packages/otel-resend/CHANGELOG.md new file mode 100644 index 0000000..03dff5b --- /dev/null +++ b/packages/otel-resend/CHANGELOG.md @@ -0,0 +1,5 @@ +# @kubiks/otel-resend + +## 1.0.0 + +- Initial release. diff --git a/packages/otel-resend/LICENSE b/packages/otel-resend/LICENSE new file mode 100644 index 0000000..55f20b0 --- /dev/null +++ b/packages/otel-resend/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Kubiks + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/otel-resend/README.md b/packages/otel-resend/README.md new file mode 100644 index 0000000..44cd78b --- /dev/null +++ b/packages/otel-resend/README.md @@ -0,0 +1,82 @@ +# @kubiks/otel-resend + +OpenTelemetry instrumentation for the [Resend](https://resend.com) Node.js SDK. +Capture spans for every Resend API call, enrich them with operation metadata, +and keep an eye on message delivery from your traces. + +## Installation + +```bash +npm install @kubiks/otel-resend +# or +pnpm add @kubiks/otel-resend +``` + +**Peer Dependencies:** `@opentelemetry/api` >= 1.9.0, `resend` >= 3.0.0 + +## Quick Start + +```ts +import { Resend } from "resend"; +import { instrumentResend } from "@kubiks/otel-resend"; + +const resend = instrumentResend(new Resend(process.env.RESEND_API_KEY!)); + +await resend.emails.send({ + from: "hello@example.com", + to: ["user@example.com"], + subject: "Welcome", + html: "

Hello world

", +}); +``` + +`instrumentResend` wraps the instance you already use—no configuration changes +needed. Every SDK call creates a client span with useful attributes. + +## What Gets Traced + +- All top-level Resend client methods (e.g. `resend.ping`) +- Nested resource methods such as `resend.emails.send`, `resend.emails.batch`, + `resend.domains.create`, `resend.apiKeys.create`, and custom resources +- Both async and sync methods defined on resource instances or their prototypes + +## Span Attributes + +Each span includes: + +| Attribute | Description | Example | +| --- | --- | --- | +| `messaging.system` | Constant value `resend` | `resend` | +| `messaging.operation` | Operation derived from the method name | `send`, `create`, `list` | +| `resend.resource` | Top-level resource name | `emails`, `domains` | +| `resend.target` | Fully-qualified target (resource + method) | `emails.send` | +| `resend.recipient_count` | Total recipients detected in the request payload | `3` | +| `resend.template_id` | Template referenced in the request (when present) | `tmpl_123` | +| `resend.message_id` | Message ID returned by email operations | `email_123` | +| `resend.message_count` | How many message IDs were returned | `2` | +| `resend.resource_id` | Identifier returned by non-email resources | `domain_456` | + +Sensitive request payloads are never recorded—only counts and identifiers that +Resend already exposes. + +## Configuration + +```ts +instrumentResend(resend, { + tracerName: "my-service", + captureRequestMetadata: true, + captureResponseMetadata: true, + shouldInstrument: (path, method) => !(path[0] === "emails" && method === "list"), +}); +``` + +- `tracerName` / `tracer`: reuse an existing tracer if you have one. +- `captureRequestMetadata`: toggle attributes derived from the request payload + (recipient counts, template IDs). Enabled by default. +- `captureResponseMetadata`: toggle attributes derived from the response + (message IDs, resource IDs). Enabled by default. +- `shouldInstrument`: skip specific methods programmatically. + +## License + +MIT diff --git a/packages/otel-resend/package.json b/packages/otel-resend/package.json new file mode 100644 index 0000000..7d4d95e --- /dev/null +++ b/packages/otel-resend/package.json @@ -0,0 +1,53 @@ +{ + "name": "@kubiks/otel-resend", + "version": "1.0.0", + "private": false, + "publishConfig": { + "access": "public" + }, + "description": "OpenTelemetry instrumentation for the Resend Node.js SDK", + "author": "Kubiks", + "license": "MIT", + "repository": "kubiks-inc/otel", + "sideEffects": false, + "type": "module", + "exports": { + ".": { + "types": "./dist/types/index.d.ts", + "import": "./dist/index.js", + "default": "./dist/index.js" + } + }, + "main": "./dist/index.js", + "types": "./dist/types/index.d.ts", + "files": [ + "dist", + "LICENSE", + "README.md" + ], + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "scripts": { + "build": "pnpm clean && tsc", + "clean": "rimraf dist", + "prepublishOnly": "pnpm build", + "type-check": "tsc --noEmit", + "unit-test": "vitest --run", + "unit-test-watch": "vitest" + }, + "dependencies": {}, + "devDependencies": { + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/sdk-trace-base": "^2.1.0", + "@types/node": "18.15.11", + "resend": "^3.0.0", + "rimraf": "3.0.2", + "typescript": "^5", + "vitest": "0.33.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <2.0.0", + "resend": ">=3.0.0" + } +} diff --git a/packages/otel-resend/src/index.test.ts b/packages/otel-resend/src/index.test.ts new file mode 100644 index 0000000..547ccb8 --- /dev/null +++ b/packages/otel-resend/src/index.test.ts @@ -0,0 +1,190 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { SpanStatusCode, trace } from "@opentelemetry/api"; +import { + BasicTracerProvider, + InMemorySpanExporter, + SimpleSpanProcessor, +} from "@opentelemetry/sdk-trace-base"; +import { + instrumentResend, + SEMATTRS_MESSAGING_OPERATION, + SEMATTRS_MESSAGING_SYSTEM, + SEMATTRS_RESEND_MESSAGE_COUNT, + SEMATTRS_RESEND_MESSAGE_ID, + SEMATTRS_RESEND_RECIPIENT_COUNT, + SEMATTRS_RESEND_RESOURCE, + SEMATTRS_RESEND_RESOURCE_ID, + SEMATTRS_RESEND_TARGET, + SEMATTRS_RESEND_TEMPLATE_ID, +} from "./index"; + +describe("instrumentResend", () => { + let provider: BasicTracerProvider; + let exporter: InMemorySpanExporter; + + beforeEach(() => { + exporter = new InMemorySpanExporter(); + provider = new BasicTracerProvider({ + spanProcessors: [new SimpleSpanProcessor(exporter)], + }); + trace.setGlobalTracerProvider(provider); + }); + + afterEach(async () => { + await provider.shutdown(); + exporter.reset(); + trace.disable(); + }); + + const createMockResend = () => { + class EmailsResource { + async send(payload: Record) { + return { id: "email_123", payload }; + } + + async list() { + return { data: [{ id: "email_1" }, { id: "email_2" }] }; + } + + fail() { + throw new Error("boom"); + } + } + + class DomainsResource { + async create() { + return { id: "domain_123" }; + } + } + + return { + emails: new EmailsResource(), + domains: new DomainsResource(), + apiKeys: { + create: vi.fn(async () => ({ id: "key_abc" })), + }, + ping: vi.fn(() => "pong"), + }; + }; + + it("wraps methods and records spans", async () => { + const resend = createMockResend(); + instrumentResend(resend); + + const payload = { + to: ["user@example.com", { email: "second@example.com" }], + template_id: "tmpl_123", + }; + + const response = await resend.emails.send(payload); + expect(response.id).toBe("email_123"); + + const spans = exporter.getFinishedSpans(); + expect(spans).toHaveLength(1); + + const span = spans[0]; + if (!span) { + throw new Error("Expected a span to be recorded"); + } + + expect(span.name).toBe("resend.emails.send"); + expect(span.attributes[SEMATTRS_MESSAGING_SYSTEM]).toBe("resend"); + expect(span.attributes[SEMATTRS_MESSAGING_OPERATION]).toBe("send"); + expect(span.attributes[SEMATTRS_RESEND_RESOURCE]).toBe("emails"); + expect(span.attributes[SEMATTRS_RESEND_TARGET]).toBe("emails.send"); + expect(span.attributes[SEMATTRS_RESEND_MESSAGE_ID]).toBe("email_123"); + expect(span.attributes[SEMATTRS_RESEND_MESSAGE_COUNT]).toBe(1); + expect(span.attributes[SEMATTRS_RESEND_RECIPIENT_COUNT]).toBe(2); + expect(span.attributes[SEMATTRS_RESEND_TEMPLATE_ID]).toBe("tmpl_123"); + expect(span.status.code).toBe(SpanStatusCode.OK); + }); + + it("records spans for prototype methods", async () => { + const resend = createMockResend(); + instrumentResend(resend); + + await resend.domains.create(); + + const spans = exporter.getFinishedSpans(); + expect(spans).toHaveLength(1); + + const span = spans[0]; + if (!span) { + throw new Error("Expected a span to be recorded"); + } + + expect(span.name).toBe("resend.domains.create"); + expect(span.attributes[SEMATTRS_RESEND_RESOURCE]).toBe("domains"); + expect(span.attributes[SEMATTRS_RESEND_MESSAGE_ID]).toBeUndefined(); + expect(span.attributes[SEMATTRS_RESEND_RESOURCE_ID]).toBe("domain_123"); + expect(span.status.code).toBe(SpanStatusCode.OK); + }); + + it("handles synchronous functions", () => { + const resend = createMockResend(); + instrumentResend(resend); + + const result = resend.ping(); + expect(result).toBe("pong"); + + const spans = exporter.getFinishedSpans(); + expect(spans).toHaveLength(1); + + const span = spans[0]; + if (!span) { + throw new Error("Expected a span to be recorded"); + } + + expect(span.name).toBe("resend.ping"); + expect(span.status.code).toBe(SpanStatusCode.OK); + }); + + it("captures errors and marks span status", async () => { + const resend = createMockResend(); + instrumentResend(resend); + + await expect(async () => resend.emails.fail()).rejects.toThrowError("boom"); + + const spans = exporter.getFinishedSpans(); + expect(spans).toHaveLength(1); + + const span = spans[0]; + if (!span) { + throw new Error("Expected a span to be recorded"); + } + + expect(span.status.code).toBe(SpanStatusCode.ERROR); + const hasException = span.events.some((event) => event.name === "exception"); + expect(hasException).toBe(true); + }); + + it("is idempotent", async () => { + const resend = createMockResend(); + const first = instrumentResend(resend); + const second = instrumentResend(first); + + expect(first).toBe(second); + expect(first.emails.send).toBe(second.emails.send); + + await second.emails.send({}); + + expect(exporter.getFinishedSpans()).toHaveLength(1); + }); + + it("respects shouldInstrument filter", async () => { + const resend = createMockResend(); + instrumentResend(resend, { + shouldInstrument: (path, methodName) => { + if (path[0] === "emails" && methodName === "list") { + return false; + } + return true; + }, + }); + + await resend.emails.list(); + + const spans = exporter.getFinishedSpans(); + expect(spans).toHaveLength(0); + }); +}); diff --git a/packages/otel-resend/src/index.ts b/packages/otel-resend/src/index.ts new file mode 100644 index 0000000..ace5492 --- /dev/null +++ b/packages/otel-resend/src/index.ts @@ -0,0 +1,465 @@ +import { + context, + SpanKind, + SpanStatusCode, + trace, + type Span, + type Tracer, +} from "@opentelemetry/api"; + +const DEFAULT_TRACER_NAME = "@kubiks/otel-resend"; +const INSTRUMENTED_FLAG = "__kubiksOtelResendInstrumented" as const; +const INSTRUMENTED_METHOD_FLAG = Symbol("kubiksOtelResendInstrumentedMethod"); + +export const SEMATTRS_MESSAGING_SYSTEM = "messaging.system" as const; +export const SEMATTRS_MESSAGING_OPERATION = "messaging.operation" as const; +export const SEMATTRS_RESEND_RESOURCE = "resend.resource" as const; +export const SEMATTRS_RESEND_TARGET = "resend.target" as const; +export const SEMATTRS_RESEND_MESSAGE_ID = "resend.message_id" as const; +export const SEMATTRS_RESEND_MESSAGE_COUNT = "resend.message_count" as const; +export const SEMATTRS_RESEND_TEMPLATE_ID = "resend.template_id" as const; +export const SEMATTRS_RESEND_SEGMENT_ID = "resend.segment_id" as const; +export const SEMATTRS_RESEND_AUDIENCE_ID = "resend.audience_id" as const; +export const SEMATTRS_RESEND_RECIPIENT_COUNT = "resend.recipient_count" as const; +export const SEMATTRS_RESEND_RESOURCE_ID = "resend.resource_id" as const; + +export interface InstrumentResendConfig { + tracerName?: string; + tracer?: Tracer; + captureRequestMetadata?: boolean; + captureResponseMetadata?: boolean; + shouldInstrument?( + path: readonly string[], + methodName: string, + original: AnyFunction, + ): boolean; +} + +type AnyFunction = (...args: unknown[]) => unknown; +type ResendLike = Record; + +interface InstrumentedResendLike extends ResendLike { + [INSTRUMENTED_FLAG]?: true; +} + +interface NormalizedConfig { + tracer: Tracer; + tracerName: string; + captureRequestMetadata: boolean; + captureResponseMetadata: boolean; + shouldInstrument( + path: readonly string[], + methodName: string, + original: AnyFunction, + ): boolean; +} + +const instrumentedObjects = new WeakSet(); +const defaultShouldInstrument: NormalizedConfig["shouldInstrument"] = () => true; + +function toSnakeCase(input: string): string { + return input + .replace(/([a-z0-9])([A-Z])/g, "$1_$2") + .replace(/[\s./-]+/g, "_") + .toLowerCase(); +} + +function buildSpanName(path: readonly string[], methodName: string): string { + const parts = [...path, methodName].filter(Boolean); + return parts.length ? `resend.${parts.join(".")}` : "resend.call"; +} + +function buildBaseAttributes( + path: readonly string[], + methodName: string, +): Record { + const attributes: Record = { + [SEMATTRS_MESSAGING_SYSTEM]: "resend", + [SEMATTRS_MESSAGING_OPERATION]: toSnakeCase(methodName), + [SEMATTRS_RESEND_TARGET]: [...path, methodName].join("."), + }; + + if (path[0]) { + attributes[SEMATTRS_RESEND_RESOURCE] = path[0]; + } + + return attributes; +} + +function countRecipients(value: unknown): number { + if (!value) { + return 0; + } + if (typeof value === "string") { + return value.trim() ? 1 : 0; + } + if (Array.isArray(value)) { + return value.reduce((count, item) => count + countRecipients(item), 0); + } + if (typeof value === "object") { + // Array-like or iterable structures + if (typeof (value as { length?: number }).length === "number") { + return (value as { length: number }).length; + } + if (Symbol.iterator in (value as object)) { + let count = 0; + for (const item of value as Iterable) { + count += countRecipients(item); + } + return count; + } + if ( + typeof (value as { email?: unknown }).email === "string" || + typeof (value as { address?: unknown }).address === "string" + ) { + return 1; + } + } + return 0; +} + +function annotateRequest( + span: Span, + path: readonly string[], + args: unknown[], + capture: boolean, +): void { + if (!capture || !args.length) { + return; + } + + const payload = args[0]; + if (!payload || typeof payload !== "object") { + return; + } + + const data = payload as Record; + + const recipientCount = + countRecipients(data.to) + countRecipients(data.cc) + countRecipients(data.bcc); + if (recipientCount > 0) { + span.setAttribute(SEMATTRS_RESEND_RECIPIENT_COUNT, recipientCount); + } + + const templateId = + (typeof data.template_id === "string" && data.template_id) || + (typeof data.templateId === "string" && data.templateId) || + (typeof data.template === "string" && data.template); + if (templateId) { + span.setAttribute(SEMATTRS_RESEND_TEMPLATE_ID, templateId); + } + + const segmentId = + (typeof data.segment_id === "string" && data.segment_id) || + (typeof data.segmentId === "string" && data.segmentId); + if (segmentId) { + span.setAttribute(SEMATTRS_RESEND_SEGMENT_ID, segmentId); + } + + const audienceId = + (typeof data.audience_id === "string" && data.audience_id) || + (typeof data.audienceId === "string" && data.audienceId); + if (audienceId) { + span.setAttribute(SEMATTRS_RESEND_AUDIENCE_ID, audienceId); + } +} + +function collectIdentifiers(value: unknown, depth = 0): string[] { + if (!value || depth > 3) { + return []; + } + + if (typeof value === "string") { + return value ? [value] : []; + } + + if (Array.isArray(value)) { + return value.flatMap((item) => collectIdentifiers(item, depth + 1)); + } + + if (typeof value === "object") { + const record = value as Record; + const ids: string[] = []; + + const directId = + (typeof record.id === "string" && record.id) || + (typeof record.messageId === "string" && record.messageId) || + (typeof record.message_id === "string" && record.message_id); + if (directId) { + ids.push(directId); + } + + const nestedKeys = ["data", "items", "messages", "results", "entries"]; + for (const key of nestedKeys) { + if (key in record) { + ids.push(...collectIdentifiers(record[key], depth + 1)); + } + } + + return ids; + } + + return []; +} + +function annotateResponse( + span: Span, + resource: string | undefined, + result: unknown, + capture: boolean, +): void { + if (!capture) { + return; + } + + const identifiers = collectIdentifiers(result); + if (!identifiers.length) { + return; + } + + const uniqueIds = Array.from(new Set(identifiers)); + span.setAttribute(SEMATTRS_RESEND_MESSAGE_COUNT, uniqueIds.length); + + if (resource === "emails") { + if (uniqueIds.length === 1) { + span.setAttribute(SEMATTRS_RESEND_MESSAGE_ID, uniqueIds[0]!); + } + } else if (uniqueIds.length === 1) { + span.setAttribute(SEMATTRS_RESEND_RESOURCE_ID, uniqueIds[0]!); + } +} + +function finalizeSpan(span: Span, error?: unknown): void { + if (error) { + if (error instanceof Error) { + span.recordException(error); + } else { + span.recordException(new Error(String(error))); + } + span.setStatus({ code: SpanStatusCode.ERROR }); + } else { + span.setStatus({ code: SpanStatusCode.OK }); + } + span.end(); +} + +function wrapMethod( + original: AnyFunction, + path: readonly string[], + methodName: string, + tracer: Tracer, + config: NormalizedConfig, +): AnyFunction { + const spanName = buildSpanName(path, methodName); + const baseAttributes = buildBaseAttributes(path, methodName); + const resource = path[0]; + + const instrumented = function instrumentedResendMethod( + this: unknown, + ...args: unknown[] + ) { + const span = tracer.startSpan(spanName, { + kind: SpanKind.CLIENT, + attributes: baseAttributes, + }); + + annotateRequest(span, path, args, config.captureRequestMetadata); + + const activeContext = trace.setSpan(context.active(), span); + + const invokeOriginal = () => original.apply(this, args); + + try { + const result = context.with(activeContext, invokeOriginal); + + if (result && typeof (result as Promise).then === "function") { + return (result as Promise) + .then((value) => { + annotateResponse(span, resource, value, config.captureResponseMetadata); + finalizeSpan(span); + return value; + }) + .catch((error: unknown) => { + finalizeSpan(span, error); + throw error; + }); + } + + annotateResponse(span, resource, result, config.captureResponseMetadata); + finalizeSpan(span); + return result; + } catch (error) { + finalizeSpan(span, error); + throw error; + } + }; + + (instrumented as { [INSTRUMENTED_METHOD_FLAG]?: true })[INSTRUMENTED_METHOD_FLAG] = true; + return instrumented; +} + +function instrumentObject( + target: ResendLike, + path: readonly string[], + tracer: Tracer, + config: NormalizedConfig, +): void { + if (!target || typeof target !== "object") { + return; + } + + if (instrumentedObjects.has(target)) { + return; + } + instrumentedObjects.add(target); + + const processedKeys = new Set(); + + for (const key of Reflect.ownKeys(target)) { + if (typeof key === "symbol") { + continue; + } + if (key === INSTRUMENTED_FLAG) { + continue; + } + + processedKeys.add(key); + + const descriptor = Object.getOwnPropertyDescriptor(target, key); + let value: unknown; + + if (!descriptor || "value" in descriptor) { + value = (target as ResendLike)[key]; + } + + if (typeof value === "function") { + const original = value as AnyFunction; + + if ((original as { [INSTRUMENTED_METHOD_FLAG]?: true })[INSTRUMENTED_METHOD_FLAG]) { + continue; + } + + if (!config.shouldInstrument(path, key, original)) { + continue; + } + + const wrapped = wrapMethod(original, path, key, tracer, config); + + let replaced = false; + try { + replaced = Reflect.set(target, key, wrapped); + } catch { + replaced = false; + } + + if (!replaced) { + Object.defineProperty(target, key, { + configurable: descriptor?.configurable ?? true, + enumerable: descriptor?.enumerable ?? true, + writable: descriptor?.writable ?? true, + value: wrapped, + }); + } + + continue; + } + + if (value && typeof value === "object") { + instrumentObject(value as ResendLike, [...path, key], tracer, config); + continue; + } + + if (descriptor && (descriptor.get || descriptor.set)) { + try { + const resolved = (target as ResendLike)[key]; + if (resolved && typeof resolved === "object") { + instrumentObject(resolved as ResendLike, [...path, key], tracer, config); + } + } catch { + // Ignore accessor errors. + } + } + } + + let prototype = Object.getPrototypeOf(target); + while ( + prototype && + prototype !== Object.prototype && + prototype !== Function.prototype + ) { + for (const key of Reflect.ownKeys(prototype)) { + if (typeof key === "symbol" || key === "constructor") { + continue; + } + if (processedKeys.has(key)) { + continue; + } + + const descriptor = Object.getOwnPropertyDescriptor(prototype, key); + if (!descriptor || typeof descriptor.value !== "function") { + continue; + } + + const original = descriptor.value as AnyFunction; + if ((original as { [INSTRUMENTED_METHOD_FLAG]?: true })[INSTRUMENTED_METHOD_FLAG]) { + continue; + } + + if (!config.shouldInstrument(path, key, original)) { + continue; + } + + const wrapped = wrapMethod(original, path, key, tracer, config); + + let replaced = false; + try { + replaced = Reflect.set(target, key, wrapped); + } catch { + replaced = false; + } + + if (!replaced) { + Object.defineProperty(target, key, { + configurable: true, + enumerable: descriptor.enumerable ?? true, + writable: true, + value: wrapped, + }); + } + + processedKeys.add(key); + } + + prototype = Object.getPrototypeOf(prototype); + } +} + +export function instrumentResend( + client: TClient, + config?: InstrumentResendConfig, +): TClient { + if (!client || typeof client !== "object") { + return client; + } + + if ((client as InstrumentedResendLike)[INSTRUMENTED_FLAG]) { + return client; + } + + const tracerName = config?.tracerName ?? DEFAULT_TRACER_NAME; + const tracer = config?.tracer ?? trace.getTracer(tracerName); + + const normalizedConfig: NormalizedConfig = { + tracer, + tracerName, + captureRequestMetadata: config?.captureRequestMetadata ?? true, + captureResponseMetadata: config?.captureResponseMetadata ?? true, + shouldInstrument: config?.shouldInstrument ?? defaultShouldInstrument, + }; + + instrumentObject(client, [], tracer, normalizedConfig); + + (client as InstrumentedResendLike)[INSTRUMENTED_FLAG] = true; + + return client; +} diff --git a/packages/otel-resend/tsconfig.json b/packages/otel-resend/tsconfig.json new file mode 100644 index 0000000..47fac92 --- /dev/null +++ b/packages/otel-resend/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "lib": ["ES2020", "DOM"], + "outDir": "dist", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "strict": false, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "declarationDir": "dist/types", + "stripInternal": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "**/*.test.ts"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9dfeebe..119f4df 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -78,6 +78,30 @@ importers: specifier: 0.33.0 version: 0.33.0(less@4.2.0)(sass@1.69.7)(stylus@0.59.0) + packages/otel-resend: + devDependencies: + '@opentelemetry/api': + specifier: ^1.9.0 + version: 1.9.0 + '@opentelemetry/sdk-trace-base': + specifier: ^2.1.0 + version: 2.1.0(@opentelemetry/api@1.9.0) + '@types/node': + specifier: 18.15.11 + version: 18.15.11 + resend: + specifier: ^3.0.0 + version: 3.5.0(react-dom@18.3.1(react@18.2.0))(react@18.2.0) + rimraf: + specifier: 3.0.2 + version: 3.0.2 + typescript: + specifier: ^5 + version: 5.3.3 + vitest: + specifier: 0.33.0 + version: 0.33.0(less@4.2.0)(sass@1.69.7)(stylus@0.59.0) + packages: '@adobe/css-tools@4.3.2': @@ -304,6 +328,10 @@ packages: '@hexagon/base64@1.1.28': resolution: {integrity: sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw==} + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + '@jest/schemas@29.6.3': resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -340,6 +368,9 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@one-ini/wasm@0.1.1': + resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} + '@opentelemetry/api@1.9.0': resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} engines: {node: '>=8.0.0'} @@ -448,6 +479,20 @@ packages: '@peculiar/x509@1.14.0': resolution: {integrity: sha512-Yc4PDxN3OrxUPiXgU63c+ZRXKGE8YKF2McTciYhUHFtHVB0KMnjeFSU0qpztGhsp4P0uKix4+J2xEpIEDu8oXg==} + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@react-email/render@0.0.16': + resolution: {integrity: sha512-wDaMy27xAq1cJHtSFptp0DTKPuV2GYhloqia95ub/DH9Dea1aWYsbdM918MOc/b/HvVS3w1z8DWzfAk13bGStQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.2.0 + react-dom: ^18.2.0 + + '@selderee/plugin-htmlparser2@0.11.0': + resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==} + '@simplewebauthn/browser@13.2.0': resolution: {integrity: sha512-N3fuA1AAnTo5gCStYoIoiasPccC+xPLx2YU88Dv0GeAmPQTWHETlZQq5xZ0DgUq1H9loXMWQH5qqUjcI7BHJ1A==} @@ -506,6 +551,10 @@ packages: '@vitest/utils@0.33.0': resolution: {integrity: sha512-pF1w22ic965sv+EN6uoePkAOTkAPWM03Ri/jXNyMIKBb/XHLDPfhLvf/Fa9g0YECevAIz56oVYXhodLvLQ/awA==} + abbrev@2.0.0: + resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + acorn-walk@8.3.1: resolution: {integrity: sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw==} engines: {node: '>=0.4.0'} @@ -523,6 +572,10 @@ packages: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + ansi-styles@3.2.1: resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} engines: {node: '>=4'} @@ -535,6 +588,10 @@ packages: resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} engines: {node: '>=10'} + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} @@ -618,6 +675,9 @@ packages: brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + braces@3.0.2: resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} engines: {node: '>=8'} @@ -690,15 +750,26 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + commander@10.0.1: + resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} + engines: {node: '>=14'} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + config-chain@1.1.13: + resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} + copy-anything@2.0.6: resolution: {integrity: sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==} cross-spawn@5.1.0: resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==} + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} @@ -739,6 +810,10 @@ packages: resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==} engines: {node: '>=6'} + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + defaults@1.0.4: resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} @@ -765,6 +840,19 @@ packages: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + domutils@3.2.2: + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + dotenv@8.6.0: resolution: {integrity: sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==} engines: {node: '>=10'} @@ -861,13 +949,28 @@ packages: sqlite3: optional: true + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + editorconfig@1.0.4: + resolution: {integrity: sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==} + engines: {node: '>=14'} + hasBin: true + emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + enquirer@2.4.1: resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} engines: {node: '>=8.6'} + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + errno@0.1.8: resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==} hasBin: true @@ -915,6 +1018,9 @@ packages: resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} engines: {node: '>=4'} + fast-deep-equal@2.0.1: + resolution: {integrity: sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==} + fast-glob@3.3.2: resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} engines: {node: '>=8.6.0'} @@ -940,6 +1046,10 @@ packages: for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + fs-extra@7.0.1: resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} engines: {node: '>=6 <7 || >=8'} @@ -984,6 +1094,10 @@ packages: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Glob versions prior to v9 are no longer supported @@ -1042,6 +1156,13 @@ packages: hosted-git-info@2.8.9: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} + html-to-text@9.0.5: + resolution: {integrity: sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==} + engines: {node: '>=14'} + + htmlparser2@8.0.2: + resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==} + human-id@1.0.2: resolution: {integrity: sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw==} @@ -1076,6 +1197,9 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + internal-slot@1.0.6: resolution: {integrity: sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==} engines: {node: '>= 0.4'} @@ -1175,9 +1299,21 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + jose@6.1.0: resolution: {integrity: sha512-TTQJyoEoKcC1lscpVDCSsVgYzUDg/0Bt3WE//WiTPK6uOCQC2KZS4MpugbMWt/zyjkopgZoXhZuCi00gLudfUA==} + js-beautify@1.15.4: + resolution: {integrity: sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==} + engines: {node: '>=14'} + hasBin: true + + js-cookie@3.0.5: + resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} + engines: {node: '>=14'} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -1206,6 +1342,9 @@ packages: resolution: {integrity: sha512-u/cAuTL4DRIiO2/g4vNGRgklEKNIj5Q3CG7RoUB5DV5SfEC2hMvPxKi0GWPmnzwL2ryIeud2VTcEEmqzTzEPNw==} engines: {node: '>=20.0.0'} + leac@0.6.0: + resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==} + less@4.2.0: resolution: {integrity: sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==} engines: {node: '>=6'} @@ -1240,6 +1379,9 @@ packages: loupe@2.3.7: resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@4.1.5: resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} @@ -1287,10 +1429,22 @@ packages: minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@9.0.1: + resolution: {integrity: sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==} + engines: {node: '>=16 || 14 >=14.17'} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + minimist-options@4.1.0: resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} engines: {node: '>= 6'} + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + mixme@0.5.10: resolution: {integrity: sha512-5H76ANWinB1H3twpJ6JY8uvAtpmFvHNArpilJAjXRKXSDDLPIMoZArw5SH0q9z+lLs8IrMw7Q2VWpWimFKFT1Q==} engines: {node: '>= 8.0.0'} @@ -1324,6 +1478,11 @@ packages: encoding: optional: true + nopt@7.2.1: + resolution: {integrity: sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + hasBin: true + normalize-package-data@2.5.0: resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} @@ -1384,6 +1543,9 @@ packages: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + parse-json@5.2.0: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} @@ -1392,6 +1554,9 @@ packages: resolution: {integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==} engines: {node: '>= 0.10'} + parseley@0.12.1: + resolution: {integrity: sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -1400,9 +1565,17 @@ packages: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -1413,6 +1586,9 @@ packages: pathval@1.1.1: resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + peberminta@0.9.0: + resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==} + pg-int8@1.0.1: resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} engines: {node: '>=4.0.0'} @@ -1484,6 +1660,9 @@ packages: resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + proto-list@1.2.4: + resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + prr@1.0.1: resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==} @@ -1504,9 +1683,17 @@ packages: resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} engines: {node: '>=8'} + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + react-is@18.2.0: resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} + react-promise-suspense@0.3.4: + resolution: {integrity: sha512-I42jl7L3Ze6kZaq+7zXWSunBa3b1on5yfvUW6Eo/3fFOj6dZ5Bqmcd264nJbTK/gn1HjjILAjSwnZbV4RpSaNQ==} + react@18.2.0: resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} engines: {node: '>=0.10.0'} @@ -1548,6 +1735,10 @@ packages: require-main-filename@2.0.0: resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} + resend@3.5.0: + resolution: {integrity: sha512-bKu4LhXSecP6krvhfDzyDESApYdNfjirD5kykkT1xO0Cj9TKSiGh5Void4pGTs3Am+inSnp4dg0B5XzdwHBJOQ==} + engines: {node: '>=18'} + resolve-from@5.0.0: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} @@ -1597,6 +1788,12 @@ packages: sax@1.3.0: resolution: {integrity: sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==} + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + + selderee@0.11.0: + resolution: {integrity: sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==} + semver@5.7.2: resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} hasBin: true @@ -1624,10 +1821,18 @@ packages: resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} engines: {node: '>=0.10.0'} + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + shebang-regex@1.0.0: resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==} engines: {node: '>=0.10.0'} + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + side-channel@1.0.4: resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} @@ -1637,6 +1842,10 @@ packages: signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} @@ -1689,6 +1898,10 @@ packages: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + string.prototype.trim@1.2.8: resolution: {integrity: sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==} engines: {node: '>= 0.4'} @@ -1703,6 +1916,10 @@ packages: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} + strip-ansi@7.1.2: + resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} + engines: {node: '>=12'} + strip-bom@3.0.0: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} @@ -1952,6 +2169,11 @@ packages: resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} hasBin: true + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + why-is-node-running@2.2.2: resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==} engines: {node: '>=8'} @@ -1965,6 +2187,10 @@ packages: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -2276,6 +2502,15 @@ snapshots: '@hexagon/base64@1.1.28': {} + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.2 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + '@jest/schemas@29.6.3': dependencies: '@sinclair/typebox': 0.27.8 @@ -2316,6 +2551,8 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.16.0 + '@one-ini/wasm@0.1.1': {} + '@opentelemetry/api@1.9.0': {} '@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0)': @@ -2478,6 +2715,22 @@ snapshots: tslib: 2.8.1 tsyringe: 4.10.0 + '@pkgjs/parseargs@0.11.0': + optional: true + + '@react-email/render@0.0.16(react-dom@18.3.1(react@18.2.0))(react@18.2.0)': + dependencies: + html-to-text: 9.0.5 + js-beautify: 1.15.4 + react: 18.2.0 + react-dom: 18.3.1(react@18.2.0) + react-promise-suspense: 0.3.4 + + '@selderee/plugin-htmlparser2@0.11.0': + dependencies: + domhandler: 5.0.3 + selderee: 0.11.0 + '@simplewebauthn/browser@13.2.0': {} '@simplewebauthn/server@13.2.1': @@ -2556,6 +2809,8 @@ snapshots: loupe: 2.3.7 pretty-format: 29.7.0 + abbrev@2.0.0: {} + acorn-walk@8.3.1: {} acorn@8.15.0: {} @@ -2564,6 +2819,8 @@ snapshots: ansi-regex@5.0.1: {} + ansi-regex@6.2.2: {} + ansi-styles@3.2.1: dependencies: color-convert: 1.9.3 @@ -2574,6 +2831,8 @@ snapshots: ansi-styles@5.2.0: {} + ansi-styles@6.2.3: {} + anymatch@3.1.3: dependencies: normalize-path: 3.0.0 @@ -2658,6 +2917,10 @@ snapshots: balanced-match: 1.0.2 concat-map: 0.0.1 + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + braces@3.0.2: dependencies: fill-range: 7.0.1 @@ -2750,8 +3013,15 @@ snapshots: color-name@1.1.4: {} + commander@10.0.1: {} + concat-map@0.0.1: {} + config-chain@1.1.13: + dependencies: + ini: 1.3.8 + proto-list: 1.2.4 + copy-anything@2.0.6: dependencies: is-what: 3.14.1 @@ -2763,6 +3033,12 @@ snapshots: shebang-command: 1.2.0 which: 1.3.1 + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + csstype@3.1.3: optional: true @@ -2796,6 +3072,8 @@ snapshots: dependencies: type-detect: 4.0.8 + deepmerge@4.3.1: {} + defaults@1.0.4: dependencies: clone: 1.0.4 @@ -2822,6 +3100,24 @@ snapshots: dependencies: path-type: 4.0.0 + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + domelementtype@2.3.0: {} + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + + domutils@3.2.2: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + dotenv@8.6.0: {} drizzle-orm@0.36.4(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(@types/react@18.2.46)(kysely@0.28.7)(postgres@3.4.7)(react@18.2.0): @@ -2833,13 +3129,26 @@ snapshots: postgres: 3.4.7 react: 18.2.0 + eastasianwidth@0.2.0: {} + + editorconfig@1.0.4: + dependencies: + '@one-ini/wasm': 0.1.1 + commander: 10.0.1 + minimatch: 9.0.1 + semver: 7.5.4 + emoji-regex@8.0.0: {} + emoji-regex@9.2.2: {} + enquirer@2.4.1: dependencies: ansi-colors: 4.1.3 strip-ansi: 6.0.1 + entities@4.5.0: {} + errno@0.1.8: dependencies: prr: 1.0.1 @@ -2946,6 +3255,8 @@ snapshots: iconv-lite: 0.4.24 tmp: 0.0.33 + fast-deep-equal@2.0.1: {} + fast-glob@3.3.2: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -2981,6 +3292,11 @@ snapshots: dependencies: is-callable: 1.2.7 + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + fs-extra@7.0.1: dependencies: graceful-fs: 4.2.11 @@ -3029,6 +3345,15 @@ snapshots: dependencies: is-glob: 4.0.3 + glob@10.4.5: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + glob@7.2.3: dependencies: fs.realpath: 1.0.0 @@ -3085,6 +3410,21 @@ snapshots: hosted-git-info@2.8.9: {} + html-to-text@9.0.5: + dependencies: + '@selderee/plugin-htmlparser2': 0.11.0 + deepmerge: 4.3.1 + dom-serializer: 2.0.0 + htmlparser2: 8.0.2 + selderee: 0.11.0 + + htmlparser2@8.0.2: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.2.2 + entities: 4.5.0 + human-id@1.0.2: {} iconv-lite@0.4.24: @@ -3113,6 +3453,8 @@ snapshots: inherits@2.0.4: {} + ini@1.3.8: {} + internal-slot@1.0.6: dependencies: get-intrinsic: 1.2.2 @@ -3207,8 +3549,24 @@ snapshots: isexe@2.0.0: {} + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + jose@6.1.0: {} + js-beautify@1.15.4: + dependencies: + config-chain: 1.1.13 + editorconfig: 1.0.4 + glob: 10.4.5 + js-cookie: 3.0.5 + nopt: 7.2.1 + + js-cookie@3.0.5: {} + js-tokens@4.0.0: {} js-yaml@3.14.1: @@ -3230,6 +3588,8 @@ snapshots: kysely@0.28.7: {} + leac@0.6.0: {} + less@4.2.0: dependencies: copy-anything: 2.0.6 @@ -3269,12 +3629,13 @@ snapshots: loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 - optional: true loupe@2.3.7: dependencies: get-func-name: 2.0.2 + lru-cache@10.4.3: {} + lru-cache@4.1.5: dependencies: pseudomap: 1.0.2 @@ -3328,12 +3689,22 @@ snapshots: dependencies: brace-expansion: 1.1.11 + minimatch@9.0.1: + dependencies: + brace-expansion: 2.0.2 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + minimist-options@4.1.0: dependencies: arrify: 1.0.1 is-plain-obj: 1.1.0 kind-of: 6.0.3 + minipass@7.1.2: {} + mixme@0.5.10: {} mlly@1.4.2: @@ -3359,6 +3730,10 @@ snapshots: dependencies: whatwg-url: 5.0.0 + nopt@7.2.1: + dependencies: + abbrev: 2.0.0 + normalize-package-data@2.5.0: dependencies: hosted-git-info: 2.8.9 @@ -3416,6 +3791,8 @@ snapshots: p-try@2.2.0: {} + package-json-from-dist@1.0.1: {} + parse-json@5.2.0: dependencies: '@babel/code-frame': 7.23.5 @@ -3426,18 +3803,32 @@ snapshots: parse-node-version@1.0.1: optional: true + parseley@0.12.1: + dependencies: + leac: 0.6.0 + peberminta: 0.9.0 + path-exists@4.0.0: {} path-is-absolute@1.0.1: {} + path-key@3.1.1: {} + path-parse@1.0.7: {} + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + path-type@4.0.0: {} pathe@1.1.1: {} pathval@1.1.1: {} + peberminta@0.9.0: {} + pg-int8@1.0.1: {} pg-protocol@1.10.3: {} @@ -3501,6 +3892,8 @@ snapshots: ansi-styles: 5.2.0 react-is: 18.2.0 + proto-list@1.2.4: {} + prr@1.0.1: optional: true @@ -3516,12 +3909,21 @@ snapshots: quick-lru@4.0.1: {} + react-dom@18.3.1(react@18.2.0): + dependencies: + loose-envify: 1.4.0 + react: 18.2.0 + scheduler: 0.23.2 + react-is@18.2.0: {} + react-promise-suspense@0.3.4: + dependencies: + fast-deep-equal: 2.0.1 + react@18.2.0: dependencies: loose-envify: 1.4.0 - optional: true read-pkg-up@7.0.1: dependencies: @@ -3567,6 +3969,13 @@ snapshots: require-main-filename@2.0.0: {} + resend@3.5.0(react-dom@18.3.1(react@18.2.0))(react@18.2.0): + dependencies: + '@react-email/render': 0.0.16(react-dom@18.3.1(react@18.2.0))(react@18.2.0) + transitivePeerDependencies: + - react + - react-dom + resolve-from@5.0.0: {} resolve@1.22.8: @@ -3619,6 +4028,14 @@ snapshots: sax@1.3.0: optional: true + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + + selderee@0.11.0: + dependencies: + parseley: 0.12.1 + semver@5.7.2: {} semver@7.5.4: @@ -3646,8 +4063,14 @@ snapshots: dependencies: shebang-regex: 1.0.0 + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + shebang-regex@1.0.0: {} + shebang-regex@3.0.0: {} + side-channel@1.0.4: dependencies: call-bind: 1.0.5 @@ -3658,6 +4081,8 @@ snapshots: signal-exit@3.0.7: {} + signal-exit@4.1.0: {} + slash@3.0.0: {} smartwrap@2.0.2: @@ -3712,6 +4137,12 @@ snapshots: is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.2 + string.prototype.trim@1.2.8: dependencies: call-bind: 1.0.5 @@ -3734,6 +4165,10 @@ snapshots: dependencies: ansi-regex: 5.0.1 + strip-ansi@7.1.2: + dependencies: + ansi-regex: 6.2.2 + strip-bom@3.0.0: {} strip-indent@3.0.0: @@ -3988,6 +4423,10 @@ snapshots: dependencies: isexe: 2.0.0 + which@2.0.2: + dependencies: + isexe: 2.0.0 + why-is-node-running@2.2.2: dependencies: siginfo: 2.0.0 @@ -4005,6 +4444,12 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.1.2 + wrappy@1.0.2: {} xtend@4.0.2: {}